Imported Upstream version 1.0.15
Sophie Brun
8 years ago
8 | 8 | |
9 | 9 | New features in the latest update |
10 | 10 | ===================================== |
11 | ||
12 | TBA: | |
13 | --- | |
14 | * Continuous Scanning Tool cscan added to ./scripts/cscan | |
15 | * Fix for saving objects without parent | |
16 | * Hosts and Services views now have pagination and search | |
17 | * Updates version number on Faraday Start | |
18 | * Visual fixes on Firefox | |
19 | * Migrate graphs from D3.js to Chart.js | |
20 | * Added Services columns to Status Report | |
21 | * Added sections of Commercial versions | |
22 | * Converted references to links in Status Report. Support for CVE, CWE, Exploit Database and Open Source Vulnerability Database | |
23 | * Added Pippingtom, SSHdefaultscan and pasteAnalyzer plugins | |
24 | * Fixed Debian install | |
11 | 25 | |
12 | 26 | Sep 10, 2015: |
13 | 27 | --- |
0 | #!/usr/bin/env python2.7 | |
1 | ''' | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | ||
6 | ''' | |
7 | import argparse | |
8 | from bs4 import BeautifulSoup | |
9 | ||
10 | def main(): | |
11 | parser = argparse.ArgumentParser(prog='cleanXML', epilog="Example: ./%(prog)s.py") | |
12 | ||
13 | parser.add_argument('-i', '--input', action='store', type=str, | |
14 | dest='infile', help='XML File to read from', | |
15 | required=True) | |
16 | parser.add_argument('-o', '--output', action='store', type=str, | |
17 | dest='outfile', help='Filename to write output', | |
18 | default="clean.xml") | |
19 | ||
20 | args = parser.parse_args() | |
21 | ||
22 | xml = open(args.infile, 'r') | |
23 | soup = BeautifulSoup(xml.read(), 'xml') | |
24 | ||
25 | out = open(args.outfile, 'w') | |
26 | out.write(soup.encode('utf-8')) | |
27 | out.flush() | |
28 | out.close() | |
29 | ||
30 | xml.close() | |
31 | ||
32 | if __name__ == "__main__": | |
33 | main() |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>1.0.14</version> | |
4 | <version>1.0.15</version> | |
5 | 5 | <debug_status>0</debug_status> |
6 | 6 | <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font> |
7 | 7 | <home_path>~/</home_path> |
5 | 5 | ''' |
6 | 6 | |
7 | 7 | CONST_REQUIREMENTS_FILE = 'requirements.txt' |
8 | CONST_CONFIG = 'views/reports/_attachments/scripts/config/config.json' | |
8 | 9 | CONST_FARADAY_HOME_PATH = '~/.faraday' |
9 | 10 | CONST_FARADAY_PLUGINS_PATH = 'plugins' |
10 | 11 | CONST_FARADAY_PLUGINS_REPO_PATH = 'plugins/repo' |
17 | 17 | import platform |
18 | 18 | import subprocess |
19 | 19 | import pip |
20 | import json | |
20 | 21 | |
21 | 22 | from utils.logs import getLogger, setUpLogger |
22 | 23 | sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/external_libs/lib/python2.7/dist-packages') |
23 | 24 | from config.configuration import getInstanceConfiguration |
24 | 25 | from config.globals import * |
25 | 26 | from utils.profilehooks import profile |
27 | from utils.user_input import query_yes_no | |
26 | 28 | |
27 | 29 | |
28 | 30 | |
539 | 541 | # Non fatal error |
540 | 542 | pass |
541 | 543 | |
544 | def checkVersion(): | |
545 | try: | |
546 | f = open(CONST_VERSION_FILE) | |
547 | f_version = f.read().strip() | |
548 | if not args.update: | |
549 | if getInstanceConfiguration().getVersion() != None and getInstanceConfiguration().getVersion() != f_version: | |
550 | logger.warning("You have different version of Faraday since your last run.\nRun ./faraday.py --update to update configuration!") | |
551 | if query_yes_no('Do you want to close Faraday?', 'yes'): | |
552 | exit(-1) | |
553 | ||
554 | getInstanceConfiguration().setVersion(f_version) | |
555 | f.close() | |
556 | ||
557 | doc = {"ver": getInstanceConfiguration().getVersion()} | |
558 | ||
559 | if os.path.isfile(CONST_CONFIG): | |
560 | os.remove(CONST_CONFIG) | |
561 | with open(CONST_CONFIG, "w") as doc_file: | |
562 | json.dump(doc, doc_file) | |
563 | except Exception as e: | |
564 | getLogger("launcher").error("It seems that something's wrong with your version\nPlease contact customer support") | |
565 | exit(-1) | |
566 | ||
542 | 567 | |
543 | 568 | def init(): |
544 | 569 | """Initializes what is needed before starting. |
569 | 594 | checkConfiguration() |
570 | 595 | setConf() |
571 | 596 | checkCouchUrl() |
597 | checkVersion() | |
572 | 598 | setUpLogger() |
573 | 599 | update() |
574 | 600 | checkUpdates() |
0 | #!/usr/bin/env python2.7 | |
1 | ''' | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | ||
6 | ''' | |
7 | ''' | |
8 | This script fixes invalid XMLs. | |
9 | ''' | |
10 | ||
11 | import argparse | |
12 | from bs4 import BeautifulSoup | |
13 | ||
14 | def main(): | |
15 | parser = argparse.ArgumentParser(prog='cleanXML', epilog="Example: ./%(prog)s.py") | |
16 | ||
17 | parser.add_argument('-i', '--input', action='store', type=str, | |
18 | dest='infile', help='XML File to read from', | |
19 | required=True) | |
20 | parser.add_argument('-o', '--output', action='store', type=str, | |
21 | dest='outfile', help='Filename to write output', | |
22 | default="clean.xml") | |
23 | ||
24 | args = parser.parse_args() | |
25 | ||
26 | xml = open(args.infile, 'r') | |
27 | soup = BeautifulSoup(xml.read(), 'xml') | |
28 | ||
29 | out = open(args.outfile, 'w') | |
30 | out.write(soup.encode('utf-8')) | |
31 | out.flush() | |
32 | out.close() | |
33 | ||
34 | xml.close() | |
35 | ||
36 | if __name__ == "__main__": | |
37 | main() |
0 | #!/usr/bin/env python2.7 | |
1 | ''' | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | ||
6 | ''' | |
7 | ''' | |
8 | This script either updates or removes Interfaces, Services and Vulnerabilities in case their parent property is null. | |
9 | If the property is null but a parent is found in Couch, the document is updated. | |
10 | If the parent is not found in Couch the document is deleted, since it is an invalid one. | |
11 | ''' | |
12 | ||
13 | import argparse | |
14 | import json | |
15 | import requests | |
16 | import os | |
17 | from pprint import pprint | |
18 | ||
19 | def main(): | |
20 | #arguments parser | |
21 | parser = argparse.ArgumentParser(prog='fixBrokenChildren', epilog="Example: ./%(prog)s.py") | |
22 | parser.add_argument('-c', '--couchdburi', action='store', type=str, | |
23 | dest='couchdb',default="http://127.0.0.1:5984", | |
24 | help='Couchdb URL (default http://127.0.0.1:5984)') | |
25 | parser.add_argument('-d', '--db', action='store', type=str, | |
26 | dest='db', help='DB to process') | |
27 | ||
28 | #arguments put in variables | |
29 | args = parser.parse_args() | |
30 | dbs = list() | |
31 | ||
32 | #default value from ENV COUCHDB | |
33 | couchdb = os.environ.get('COUCHDB') | |
34 | #Else from argument | |
35 | if not couchdb: | |
36 | couchdb = args.couchdb | |
37 | ||
38 | if args.db: | |
39 | dbs.append(args.db) | |
40 | ||
41 | if len(dbs) == 0: | |
42 | dbs = requests.get(couchdb + '/_all_dbs') | |
43 | dbs = dbs.json() | |
44 | dbs = filter(lambda x: not x.startswith('_') and x != 'cwe' and x != 'reports', dbs) | |
45 | ||
46 | for db in dbs: | |
47 | fixDb(couchdb, db) | |
48 | ||
49 | def fixDb(couchdb, db): | |
50 | couchdb = str(couchdb) | |
51 | db = str(db) | |
52 | ||
53 | #get all broken elements from CouchDB | |
54 | headers = {'Content-Type': 'application/json'} | |
55 | payload = { "map" : """function(doc) { if(doc.type == \"Interface\" || | |
56 | doc.type == \"Service\" || | |
57 | doc.type == \"Vulnerability\" || | |
58 | doc.type == \"VulnerabilityWeb\"){ if(doc.parent == null) emit(doc.parent, 1); }}""" } | |
59 | ||
60 | r = requests.post(couchdb + '/' + db + '/_temp_view', headers=headers, data=json.dumps(payload)) | |
61 | response_code = r.status_code | |
62 | ||
63 | if response_code == 200: | |
64 | response = r.json() | |
65 | rows = response['rows'] | |
66 | rows = sorted(rows, key=lambda x: x['id']) | |
67 | ||
68 | if len(rows) > 0: | |
69 | print " [*[ Processing " + str(len(rows)) + " documents for " + db + " ]*]" | |
70 | ||
71 | for row in rows: | |
72 | id = str(row['id']) | |
73 | parent = str(id[:id.rfind('.')]) | |
74 | ||
75 | parent_response = requests.get(couchdb + '/' + db + '/' + parent) | |
76 | parent_code = parent_response.status_code | |
77 | ||
78 | child_response = requests.get(couchdb + '/' + db + '/' + id) | |
79 | child = child_response.json() | |
80 | ||
81 | #object parent exists in Couch | |
82 | #update parent field in obj | |
83 | if parent_code == 200: | |
84 | print " - Updating " + child['type'] + " \"" + child['name'] + "\" with ID " + id | |
85 | child['parent'] = parent | |
86 | #print doc['parent'] | |
87 | update = requests.put(couchdb + '/' + db + '/' + id, headers=headers, data=json.dumps(child)) | |
88 | print " -- " + update.reason + " (" + str(update.status_code) + ")" | |
89 | ||
90 | #object has no valid parent | |
91 | #delete obj | |
92 | elif parent_code == 404: | |
93 | # delete vuln | |
94 | print " - Deleting " + child['type'] + " \"" + child['name'] + "\" with ID " + id | |
95 | delete = requests.delete(couchdb + '/' + db + '/' + id + '?rev=' + child['_rev']) | |
96 | print " -- " + delete.reason + " (" + str(delete.status_code) + ")" | |
97 | elif parent_code == 401: | |
98 | print " Autorization required, make sure to add user:pwd to Couch URI using --couchdburi" | |
99 | else: | |
100 | print " Fail" | |
101 | else: | |
102 | print "Congratz, " + db + " is just fine!" | |
103 | elif response_code == 401: | |
104 | print " Autorization required to access " + db + ", make sure to add user:pwd to Couch URI using --couchdburi" | |
105 | ||
106 | if __name__ == "__main__": | |
107 | main() |
0 | #!/usr/bin/env python2.7 | |
1 | ''' | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | ||
6 | ''' | |
7 | ''' | |
8 | This script upload a Vulnerability database to Couch. | |
9 | It takes the content of the DB from data/cwe.csv | |
10 | ''' | |
11 | import argparse | |
12 | import os | |
13 | from couchdbkit import Server, designer | |
14 | import json | |
15 | import csv | |
16 | ||
17 | ||
18 | def main(): | |
19 | ||
20 | #arguments parser | |
21 | parser = argparse.ArgumentParser(prog='pushExecutiveReports', epilog="Example: ./%(prog)s.py") | |
22 | parser.add_argument('-c', '--couchdburi', action='store', type=str, | |
23 | dest='couchdb',default="http://127.0.0.1:5984", | |
24 | help='Couchdb URL (default http://127.0.0.1:5984)') | |
25 | ||
26 | #arguments put in variables | |
27 | args = parser.parse_args() | |
28 | ||
29 | #default value from ENV COUCHDB | |
30 | couchdb = os.environ.get('COUCHDB') | |
31 | #Else from argument | |
32 | if not couchdb: | |
33 | couchdb = args.couchdb | |
34 | __serv = Server(uri = couchdb) | |
35 | ||
36 | # reports = os.path.join(os.getcwd(), "views", "reports") | |
37 | workspace = __serv.get_or_create_db("cwe") | |
38 | # designer.push(reports, workspace, atomic = False) | |
39 | ||
40 | with open('data/cwe.csv', 'r') as csvfile: | |
41 | cwereader = csv.reader(csvfile, delimiter=',') | |
42 | header = cwereader.next() | |
43 | for cwe in cwereader: | |
44 | cwe_doc = dict(zip(header, cwe)) | |
45 | workspace.save_doc(cwe_doc) | |
46 | ||
47 | if __name__ == "__main__": | |
48 | main() |
65 | 65 | # Bug: https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1306991 |
66 | 66 | wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py |
67 | 67 | python get-pip.py |
68 | elif [[ "$os" =~ "Debian 7".*|"Debian 8".* ]]; then | |
68 | elif [[ "$os" =~ "Debian 7".*|"Debian 8".*|"stretch/sid".* ]]; then | |
69 | 69 | version="ubuntu13-10-$arch" |
70 | 70 | down=1 |
71 | 71 | wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py |
447 | 447 | old_obj = dataMapper.find(obj.getID()) |
448 | 448 | if old_obj: |
449 | 449 | if not old_obj.needs_merge(obj): |
450 | #the object is exactly the same, | |
450 | # the object is exactly the same, | |
451 | 451 | # so return and do nothing |
452 | 452 | return True |
453 | 453 | if not self.addUpdate(old_obj, obj): |
458 | 458 | object_parent = self.mappers_manager.find(parent_id) |
459 | 459 | if object_parent: |
460 | 460 | object_parent.addChild(obj) |
461 | # we have to make sure that certain objects have to have a parent | |
462 | if (obj.class_signature in | |
463 | [model.hosts.Interface.class_signature, | |
464 | model.hosts.Service.class_signature, | |
465 | model.common.ModelObjectNote.class_signature, | |
466 | model.common.ModelObjectVuln.class_signature, | |
467 | model.common.ModelObjectVulnWeb.class_signature, | |
468 | model.common.ModelObjectCred.class_signature] and object_parent is None): | |
469 | # TODO: refactor log module. We need to log twice to see it in | |
470 | # qt and in the terminal. Ugly. | |
471 | msg = "A parent is needed for %s objects" % obj.class_signature | |
472 | getLogger(self).error(msg) | |
473 | model.api.log(msg) | |
474 | return False | |
461 | 475 | dataMapper.save(obj) |
462 | 476 | self.treeWordsTries.addWord(obj.getName()) |
463 | 477 | if obj.class_signature == model.hosts.Host.class_signature: |
878 | 892 | hosts = self.mappers_manager.getMapper( |
879 | 893 | model.hosts.Host.__name__).getAll() |
880 | 894 | return hosts |
881 | ||
895 | ||
882 | 896 | def getWebVulns(self): |
883 | 897 | return self.mappers_manager.getMapper( |
884 | 898 | model.common.ModelObjectVulnWeb.class_signature).getAll() |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' | |
6 |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | #Author: @EzequielTBH | |
4 | ||
5 | from plugins import core | |
6 | import json | |
7 | import re | |
8 | ||
9 | __author__ = "@EzequielTBH" | |
10 | __copyright__ = "Copyright 2015, @EzequielTBH" | |
11 | __credits__ = "@EzequielTBH" | |
12 | __license__ = "GPL v3" | |
13 | __version__ = "1.0.0" | |
14 | ||
15 | class pasteAnalyzerPlugin(core.PluginBase): | |
16 | ||
17 | def __init__(self): | |
18 | core.PluginBase.__init__(self) | |
19 | self.id = "pasteAnalyzer" | |
20 | self.name = "pasteAnalyzer JSON Output Plugin" | |
21 | self.plugin_version = "1.0.0" | |
22 | self.command_string = "" | |
23 | self.current_path = "" | |
24 | self._command_regex = re.compile( | |
25 | r'^(pasteAnalyzer|python pasteAnalyzer.py|\./pasteAnalyzer.py|sudo python pasteAnalyzer.py|sudo \./pasteAnalyzer.py).*?') | |
26 | ||
27 | ||
28 | def parseOutputString(self, output, debug = False): | |
29 | ||
30 | print("[*]Parsing Output...") | |
31 | ||
32 | #Generating file name with full path. | |
33 | indexStart = self.command_string.find("-j") + 3 | |
34 | ||
35 | fileJson = self.command_string [ indexStart : | |
36 | self.command_string.find(" ", indexStart) ] | |
37 | ||
38 | fileJson = self.current_path + "/" + fileJson | |
39 | ||
40 | try: | |
41 | with open(fileJson,"r") as fileJ: | |
42 | results = json.loads( fileJ.read() ) | |
43 | ||
44 | except Exception as e: | |
45 | print("\n[!]Exception opening file\n" + str(e) ) | |
46 | return | |
47 | ||
48 | if results == []: | |
49 | return | |
50 | ||
51 | print("[*]Results loaded...") | |
52 | ||
53 | #Configuration initial. | |
54 | hostId = self.createAndAddHost("pasteAnalyzer") | |
55 | interfaceId = self.createAndAddInterface(hostId, "Results") | |
56 | serviceId = self.createAndAddServiceToInterface( | |
57 | hostId, | |
58 | interfaceId, | |
59 | "Web", | |
60 | "TcpHTTP", | |
61 | ['80'] | |
62 | ) | |
63 | print("[*]Initial Configuration ready....") | |
64 | ||
65 | #Loading results. | |
66 | for i in range(0, len(results), 2 ): | |
67 | ||
68 | data = results[i + 1] | |
69 | description = "" | |
70 | ||
71 | for element in data: | |
72 | ||
73 | #Is Category | |
74 | if type(element) == str or type(element) == unicode: | |
75 | description += element +": " | |
76 | ||
77 | #Is a list with results! | |
78 | else: | |
79 | for element2 in element: | |
80 | description += "\n" + element2 | |
81 | ||
82 | self.createAndAddVulnWebToService( | |
83 | hostId, | |
84 | serviceId, | |
85 | results[i], | |
86 | description | |
87 | ) | |
88 | ||
89 | print("[*]Parse finished, API faraday called...") | |
90 | ||
91 | def processCommandString(self, username, current_path, command_string): | |
92 | ||
93 | print("[*]pasteAnalyzer Plugin running...") | |
94 | ||
95 | if command_string.find("-j") < 0: | |
96 | command_string += " -j JSON_OUTPUT " | |
97 | ||
98 | self.command_string = command_string | |
99 | self.current_path = current_path | |
100 | ||
101 | return command_string | |
102 | ||
103 | def createPlugin(): | |
104 | return pasteAnalyzerPlugin() | |
105 | ||
106 | ||
107 |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' | |
6 |
0 | # -*- coding: utf-8 -*- | |
1 | """ | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | """ | |
6 | ||
7 | import re | |
8 | import socket | |
9 | from os import path | |
10 | from plugins import core | |
11 | from urlparse import urlparse | |
12 | ||
13 | __author__ = "Andres Tarantini" | |
14 | __copyright__ = "Copyright (c) 2015 Andres Tarantini" | |
15 | __credits__ = ["Andres Tarantini"] | |
16 | __license__ = "MIT" | |
17 | __version__ = "0.0.1" | |
18 | __maintainer__ = "Andres Tarantini" | |
19 | __email__ = "[email protected]" | |
20 | __status__ = "Development" | |
21 | ||
22 | ||
23 | class PeepingTomPlugin(core.PluginBase): | |
24 | """ | |
25 | Handle PeepingTom (https://bitbucket.org/LaNMaSteR53/peepingtom) output | |
26 | """ | |
27 | def __init__(self): | |
28 | core.PluginBase.__init__(self) | |
29 | self.id = "peepingtom" | |
30 | self.name = "PeepingTom" | |
31 | self.plugin_version = "0.0.1" | |
32 | self.version = "02.19.15" | |
33 | self._command_regex = re.compile(r'^(python peepingtom.py|\./peepingtom.py).*?') | |
34 | self._path = None | |
35 | ||
36 | def parseOutputString(self, output): | |
37 | # Find data path | |
38 | data_path_search = re.search(r"in '(.*)\/'", output) | |
39 | print data_path_search | |
40 | if not data_path_search: | |
41 | # No data path found | |
42 | return True | |
43 | ||
44 | # Parse "peepingtom.html" report and extract results | |
45 | data_path = data_path_search.groups()[0] | |
46 | html = open(path.join(self._path, data_path, "peepingtom.html")).read() | |
47 | for url in re.findall(r'href=[\'"]?([^\'" >]+)', html): | |
48 | if "://" in url: | |
49 | url_parsed = urlparse(url) | |
50 | address = socket.gethostbyname(url_parsed.netloc) | |
51 | host = self.createAndAddHost(address) | |
52 | iface = self.createAndAddInterface(host, address, ipv4_address=address) | |
53 | service = self.createAndAddServiceToInterface( | |
54 | host, iface, "http", protocol="tcp", ports=80 | |
55 | ) | |
56 | note = self.createAndAddNoteToService( | |
57 | host, | |
58 | service, | |
59 | 'screenshot', | |
60 | path.join( | |
61 | self._path, | |
62 | data_path_search.groups()[0], | |
63 | "{}.png".format(url.replace("://", "").replace("/", "").replace(".", "")) | |
64 | ) | |
65 | ) | |
66 | ||
67 | return True | |
68 | ||
69 | def processCommandString(self, username, current_path, command_string): | |
70 | self._path = current_path | |
71 | return None | |
72 | ||
73 | ||
74 | def createPlugin(): | |
75 | return PeepingTomPlugin() | |
76 |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' | |
6 |
0 | # -*- coding: utf-8 -*- | |
1 | """ | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | """ | |
6 | ||
7 | from plugins import core | |
8 | import re | |
9 | ||
10 | __author__ = "Andres Tarantini" | |
11 | __copyright__ = "Copyright (c) 2015 Andres Tarantini" | |
12 | __credits__ = ["Andres Tarantini"] | |
13 | __license__ = "MIT" | |
14 | __version__ = "0.0.1" | |
15 | __maintainer__ = "Andres Tarantini" | |
16 | __email__ = "[email protected]" | |
17 | __status__ = "Development" | |
18 | ||
19 | ||
20 | class SSHDefaultScanPlugin(core.PluginBase): | |
21 | """ | |
22 | Handle sshdefaultscan (https://github.com/atarantini/sshdefaultscan) output | |
23 | using --batch and --batch-template; supports --username and --password | |
24 | """ | |
25 | def __init__(self): | |
26 | core.PluginBase.__init__(self) | |
27 | self.id = "sshdefaultscan" | |
28 | self.name = "sshdefaultscan" | |
29 | self.plugin_version = "0.0.1" | |
30 | self.version = "1.0.0" | |
31 | self._command_regex = re.compile(r'^(python sshdefaultscan.py|\./sshdefaultscan.py).*?') | |
32 | self._completition = {"--fast": "Fast scan mode"} | |
33 | ||
34 | def parseOutputString(self, output, debug=False): | |
35 | for line in [l.strip() for l in output.split("\n")]: | |
36 | output_rexeg_match = re.match(r".*:.*@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", line) | |
37 | if output_rexeg_match: | |
38 | credentials, address = line.split("@") | |
39 | host = self.createAndAddHost(address) | |
40 | iface = self.createAndAddInterface(host, address, ipv4_address=address) | |
41 | service = self.createAndAddServiceToInterface( | |
42 | host, iface, "ssh", protocol="tcp", ports=22 | |
43 | ) | |
44 | username, password = credentials.split(":") | |
45 | cred = self.createAndAddCredToService(host, service, username, password) | |
46 | vuln = self.createAndAddVulnToService( | |
47 | host, | |
48 | service, | |
49 | "Default credentials", | |
50 | desc="The SSH server have default credentials ({username}:{password})".format( | |
51 | username=username, | |
52 | password=password | |
53 | ), | |
54 | severity=3 | |
55 | ) | |
56 | ||
57 | return True | |
58 | ||
59 | def processCommandString(self, username, current_path, command_string): | |
60 | if "--batch" not in command_string: | |
61 | return "{command} --batch --batch-template {template}".format( | |
62 | command=command_string, | |
63 | template="{username}:{password}@{host}" | |
64 | ) | |
65 | ||
66 | return None | |
67 | ||
68 | ||
69 | def createPlugin(): | |
70 | return SSHDefaultScanPlugin() |
0 | #!/usr/bin/env python2.7 | |
1 | ''' | |
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | ||
6 | ''' | |
7 | import argparse | |
8 | import os | |
9 | from couchdbkit import Server, designer | |
10 | import json | |
11 | import csv | |
12 | ||
13 | ||
14 | def main(): | |
15 | ||
16 | #arguments parser | |
17 | parser = argparse.ArgumentParser(prog='pushExecutiveReports', epilog="Example: ./%(prog)s.py") | |
18 | parser.add_argument('-c', '--couchdburi', action='store', type=str, | |
19 | dest='couchdb',default="http://127.0.0.1:5984", | |
20 | help='Couchdb URL (default http://127.0.0.1:5984)') | |
21 | ||
22 | #arguments put in variables | |
23 | args = parser.parse_args() | |
24 | ||
25 | #default value from ENV COUCHDB | |
26 | couchdb = os.environ.get('COUCHDB') | |
27 | #Else from argument | |
28 | if not couchdb: | |
29 | couchdb = args.couchdb | |
30 | __serv = Server(uri = couchdb) | |
31 | ||
32 | # reports = os.path.join(os.getcwd(), "views", "reports") | |
33 | workspace = __serv.get_or_create_db("cwe") | |
34 | # designer.push(reports, workspace, atomic = False) | |
35 | ||
36 | with open('data/cwe.csv', 'r') as csvfile: | |
37 | cwereader = csv.reader(csvfile, delimiter=',') | |
38 | header = cwereader.next() | |
39 | for cwe in cwereader: | |
40 | cwe_doc = dict(zip(header, cwe)) | |
41 | workspace.save_doc(cwe_doc) | |
42 | ||
43 | if __name__ == "__main__": | |
44 | main()⏎ |
0 | # cscan | |
1 | Faraday Continuous Scanning | |
2 | ||
3 | More information: | |
4 | [http://blog.infobytesec.com/2015/09/faraday-continuous-scanning.html] (http://blog.infobytesec.com/2015/09/faraday-continuous-scanning.html) |
0 | #!/usr/bin/env python | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | config = { | |
8 | #NMAP | |
9 | 'CS_NMAP' : "nmap", | |
10 | #OPENVAS | |
11 | 'CS_OPENVAS_USER' : 'admin', | |
12 | 'CS_OPENVAS_PASSWORD' : 'openvas', | |
13 | 'CS_OPENVAS_SCAN_CONFIG' : "Full and fast", | |
14 | 'CS_OPENVAS_ALIVE_TEST' : "ICMP, TCP-ACK Service & ARP Ping", | |
15 | 'CS_OPENVAS' : 'omp', | |
16 | #BURP | |
17 | 'CS_BURP' : '/root/tools/burpsuite_pro_v1.6.26.jar', | |
18 | #NIKTO | |
19 | 'CS_NIKTO' : "nikto", | |
20 | #W3AF | |
21 | 'CS_W3AF' : "/root/tools/w3af/w3af_api", | |
22 | 'CS_W3AF_PROFILE' : "/root/tools/w3af/profiles/fast_scan.pw3af", | |
23 | #ZAP | |
24 | 'CS_ZAP' : "/root/tools/zap/ZAP_D-2015-08-24/zap.sh", | |
25 | #NESSUS | |
26 | 'CS_NESSUS_URL' : "https://127.0.0.1:8834", | |
27 | 'CS_NESSUS_USER' : "nessus", | |
28 | 'CS_NESSUS_PASS' : "nessus", | |
29 | 'CS_NESSUS_PROFILE' : "Basic Network Scan", | |
30 | } | |
31 |
0 | #!/usr/bin/env python | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | import subprocess | |
8 | import os | |
9 | import argparse | |
10 | import time | |
11 | from pprint import pprint | |
12 | from config import config | |
13 | ||
14 | def lockFile(lockfile): | |
15 | ||
16 | if os.path.isfile(lockfile): | |
17 | return False | |
18 | else: | |
19 | f = open(lockfile, 'w') | |
20 | f.close() | |
21 | return True | |
22 | ||
23 | def main(): | |
24 | ||
25 | lockf = ".lock.pod" | |
26 | if not lockFile(lockf): | |
27 | print "You can run only one instance of cscan (%s)" % lockf | |
28 | exit(0) | |
29 | ||
30 | my_env = os.environ | |
31 | env = config.copy() | |
32 | env.update(my_env) | |
33 | #Parser argument in command line | |
34 | parser = argparse.ArgumentParser(description='continues scanning on Faraday') | |
35 | parser.add_argument('-p','--plugin', help='Scan only the following plugin ej: ./cscan.py -p nmap.sh', required=False) | |
36 | args = parser.parse_args() | |
37 | ||
38 | for dirpath, dnames, fnames in os.walk("./scripts/web/"): | |
39 | for f in fnames: | |
40 | if args.plugin and args.plugin != f: | |
41 | continue | |
42 | script = os.path.join(dirpath, f) | |
43 | cmd = "%s websites.txt output/" % (script) | |
44 | print "Running: %s" % cmd | |
45 | proc = subprocess.call(cmd, shell=True, stdin=None, stderr=subprocess.PIPE, env=dict(env)) | |
46 | ||
47 | for dirpath, dnames, fnames in os.walk("./scripts/network/"): | |
48 | for f in fnames: | |
49 | if args.plugin and args.plugin != f: | |
50 | continue | |
51 | script = os.path.join(dirpath, f) | |
52 | cmd = "%s ips.txt output/" % (script) | |
53 | print "Running: %s" % cmd | |
54 | proc = subprocess.call(cmd, shell=True, stdin=None, stderr=subprocess.PIPE, env=dict(env)) | |
55 | ||
56 | #Remove lockfile | |
57 | os.remove(lockf) | |
58 | ||
59 | if __name__ == "__main__": | |
60 | main()⏎ |
0 | 127.0.0.1 |
0 | <?xml version="1.0" encoding="UTF-8"?> | |
1 | <!DOCTYPE nmaprun> | |
2 | <?xml-stylesheet href="file:///usr/bin/../share/nmap/nmap.xsl" type="text/xsl"?> | |
3 | <!-- Nmap 6.49BETA4 scan initiated Wed Sep 23 02:45:41 2015 as: nmap -iL ips.txt -oX output/nmap_1442987141.xml --> | |
4 | <nmaprun scanner="nmap" args="nmap -iL ips.txt -oX output/nmap_1442987141.xml" start="1442987141" startstr="Wed Sep 23 02:45:41 2015" version="6.49BETA4" xmloutputversion="1.04"> | |
5 | <scaninfo type="syn" protocol="tcp" numservices="1000" services="1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687-1688,1700,1717-1721,1723,1755,1761,1782-1783,1801,1805,1812,1839-1840,1862-1864,1875,1900,1914,1935,1947,1971-1972,1974,1984,1998-2010,2013,2020-2022,2030,2033-2035,2038,2040-2043,2045-2049,2065,2068,2099-2100,2103,2105-2107,2111,2119,2121,2126,2135,2144,2160-2161,2170,2179,2190-2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381-2383,2393-2394,2399,2401,2492,2500,2522,2525,2557,2601-2602,2604-2605,2607-2608,2638,2701-2702,2710,2717-2718,2725,2800,2809,2811,2869,2875,2909-2910,2920,2967-2968,2998,3000-3001,3003,3005-3007,3011,3013,3017,3030-3031,3052,3071,3077,3128,3168,3211,3221,3260-3261,3268-3269,3283,3300-3301,3306,3322-3325,3333,3351,3367,3369-3372,3389-3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689-3690,3703,3737,3766,3784,3800-3801,3809,3814,3826-3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000-4006,4045,4111,4125-4126,4129,4224,4242,4279,4321,4343,4443-4446,4449,4550,4567,4662,4848,4899-4900,4998,5000-5004,5009,5030,5033,5050-5051,5054,5060-5061,5080,5087,5100-5102,5120,5190,5200,5214,5221-5222,5225-5226,5269,5280,5298,5357,5405,5414,5431-5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678-5679,5718,5730,5800-5802,5810-5811,5815,5822,5825,5850,5859,5862,5877,5900-5904,5906-5907,5910-5911,5915,5922,5925,5950,5952,5959-5963,5987-5989,5998-6007,6009,6025,6059,6100-6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565-6567,6580,6646,6666-6669,6689,6692,6699,6779,6788-6789,6792,6839,6881,6901,6969,7000-7002,7004,7007,7019,7025,7070,7100,7103,7106,7200-7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777-7778,7800,7911,7920-7921,7937-7938,7999-8002,8007-8011,8021-8022,8031,8042,8045,8080-8090,8093,8099-8100,8180-8181,8192-8194,8200,8222,8254,8290-8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651-8652,8654,8701,8800,8873,8888,8899,8994,9000-9003,9009-9011,9040,9050,9071,9080-9081,9090-9091,9099-9103,9110-9111,9200,9207,9220,9290,9415,9418,9485,9500,9502-9503,9535,9575,9593-9595,9618,9666,9876-9878,9898,9900,9917,9929,9943-9944,9968,9998-10004,10009-10010,10012,10024-10025,10082,10180,10215,10243,10566,10616-10617,10621,10626,10628-10629,10778,11110-11111,11967,12000,12174,12265,12345,13456,13722,13782-13783,14000,14238,14441-14442,15000,15002-15004,15660,15742,16000-16001,16012,16016,16018,16080,16113,16992-16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221-20222,20828,21571,22939,23502,24444,24800,25734-25735,26214,27000,27352-27353,27355-27356,27715,28201,30000,30718,30951,31038,31337,32768-32785,33354,33899,34571-34573,35500,38292,40193,40911,41511,42510,44176,44442-44443,44501,45100,48080,49152-49161,49163,49165,49167,49175-49176,49400,49999-50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055-55056,55555,55600,56737-56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389"/> | |
6 | <verbose level="0"/> | |
7 | <debugging level="0"/> | |
8 | <host starttime="1442987141" endtime="1442987142"><status state="up" reason="localhost-response" reason_ttl="0"/> | |
9 | <address addr="127.0.0.1" addrtype="ipv4"/> | |
10 | <hostnames> | |
11 | <hostname name="localhost" type="PTR"/> | |
12 | </hostnames> | |
13 | <ports><extraports state="closed" count="998"> | |
14 | <extrareasons reason="resets" count="998"/> | |
15 | </extraports> | |
16 | <port protocol="tcp" portid="22"><state state="open" reason="syn-ack" reason_ttl="64"/><service name="ssh" method="table" conf="3"/></port> | |
17 | <port protocol="tcp" portid="5432"><state state="open" reason="syn-ack" reason_ttl="64"/><service name="postgresql" method="table" conf="3"/></port> | |
18 | </ports> | |
19 | <times srtt="3" rttvar="1" to="100000"/> | |
20 | </host> | |
21 | <runstats><finished time="1442987142" timestr="Wed Sep 23 02:45:42 2015" elapsed="1.69" summary="Nmap done at Wed Sep 23 02:45:42 2015; 1 IP address (1 host up) scanned in 1.69 seconds" exit="success"/><hosts up="1" down="0" total="1"/> | |
22 | </runstats> | |
23 | </nmaprun> |
0 | # Created by Blake Cornell, CTO, Integris Security LLC | |
1 | # Integris Security Carbonator - Beta Version - v1.2 | |
2 | # Released under GPL Version 2 license. | |
3 | # | |
4 | # See the INSTALL file for installation instructions. | |
5 | # | |
6 | # For more information contact us at carbonator at integrissecurity dot com | |
7 | # Or visit us at https://www.integrissecurity.com/ | |
8 | from burp import IBurpExtender | |
9 | from burp import IHttpListener | |
10 | from burp import IScannerListener | |
11 | from java.net import URL | |
12 | from java.io import File | |
13 | ||
14 | import time | |
15 | ||
16 | class BurpExtender(IBurpExtender, IHttpListener, IScannerListener): | |
17 | def registerExtenderCallbacks(self, callbacks): | |
18 | self._callbacks = callbacks | |
19 | self._callbacks.setExtensionName("Carbonator") | |
20 | self._helpers = self._callbacks.getHelpers() | |
21 | self.clivars = None | |
22 | ||
23 | self.spider_results=[] | |
24 | self.scanner_results=[] | |
25 | self.packet_timeout=5 | |
26 | ||
27 | self.last_packet_seen= int(time.time()) #initialize the start of the spider/scan | |
28 | ||
29 | if not self.processCLI(): | |
30 | return None | |
31 | else: | |
32 | self.clivars = True | |
33 | ||
34 | print "Initiating Carbonator Against: ", str(self.url) | |
35 | #add to scope if not already in there. | |
36 | if self._callbacks.isInScope(self.url) == 0: | |
37 | self._callbacks.includeInScope(self.url) | |
38 | ||
39 | #added to ensure that the root directory is scanned | |
40 | base_request = str.encode(str("GET "+self.path+" HTTP/1.1\nHost: "+self.fqdn+"\n\n")) | |
41 | if(self.scheme == 'HTTPS'): | |
42 | print self._callbacks.doActiveScan(self.fqdn,self.port,1,base_request) | |
43 | else: | |
44 | print self._callbacks.doActiveScan(self.fqdn,self.port,0,base_request) | |
45 | ||
46 | self._callbacks.sendToSpider(self.url) | |
47 | self._callbacks.registerHttpListener(self) | |
48 | self._callbacks.registerScannerListener(self) | |
49 | ||
50 | while int(time.time())-self.last_packet_seen <= self.packet_timeout: | |
51 | time.sleep(1) | |
52 | print "No packets seen in the last", self.packet_timeout, "seconds." | |
53 | print "Removing Listeners" | |
54 | self._callbacks.removeHttpListener(self) | |
55 | self._callbacks.removeScannerListener(self) | |
56 | self._callbacks.excludeFromScope(self.url) | |
57 | ||
58 | print "Generating Report" | |
59 | self.generateReport(self.rtype) | |
60 | print "Report Generated" | |
61 | print "Closing Burp in", self.packet_timeout, "seconds." | |
62 | time.sleep(self.packet_timeout) | |
63 | ||
64 | if self.clivars: | |
65 | self._callbacks.exitSuite(False) | |
66 | ||
67 | return | |
68 | ||
69 | def processHttpMessage(self, tool_flag, isRequest, current): | |
70 | self.last_packet_seen = int(time.time()) | |
71 | if tool_flag == self._callbacks.TOOL_SPIDER and isRequest: #if is a spider request then send to scanner | |
72 | self.spider_results.append(current) | |
73 | print "Sending new URL to Vulnerability Scanner: URL #",len(self.spider_results) | |
74 | if self.scheme == 'https': | |
75 | self._callbacks.doActiveScan(self.fqdn,self.port,1,current.getRequest()) #returns scan queue, push to array | |
76 | else: | |
77 | self._callbacks.doActiveScan(self.fqdn,self.port,0,current.getRequest()) #returns scan queue, push to array | |
78 | return | |
79 | ||
80 | def newScanIssue(self, issue): | |
81 | self.scanner_results.append(issue) | |
82 | print "New issue identified: Issue #",len(self.scanner_results); | |
83 | return | |
84 | ||
85 | def generateReport(self, format): | |
86 | if format != 'XML': | |
87 | format = 'HTML' | |
88 | ||
89 | file_name = self.output | |
90 | self._callbacks.generateScanReport(format,self.scanner_results,File(file_name)) | |
91 | ||
92 | time.sleep(5) | |
93 | return | |
94 | ||
95 | def processCLI(self): | |
96 | cli = self._callbacks.getCommandLineArguments() | |
97 | if len(cli) < 0: | |
98 | print "Incomplete target information provided." | |
99 | return False | |
100 | elif not cli: | |
101 | print "Integris Security Carbonator is now loaded." | |
102 | print "If Carbonator was loaded through the BApp store then you can run in headless mode simply adding the `-Djava.awt.headless=true` flag from within your shell. Note: If burp doesn't close at the conclusion of a scan then disable Automatic Backup on Exit." | |
103 | print "For questions or feature requests contact us at carbonator at integris security dot com." | |
104 | print "Visit carbonator at https://www.integrissecurity.com/Carbonator" | |
105 | return False | |
106 | else: | |
107 | self.url = URL(cli[0]) | |
108 | self.rtype = cli[1] | |
109 | self.output = cli[2] | |
110 | ||
111 | self.scheme = self.url.getProtocol() | |
112 | self.fqdn = self.url.getHost() | |
113 | self.port1 = self.url.getPort() | |
114 | if self.port1 == -1 and self.scheme == 'http': | |
115 | self.port = 80 | |
116 | elif self.port1 == -1 and self.scheme == 'https': | |
117 | self.port = 443 | |
118 | else: | |
119 | self.port = self.port1 | |
120 | self.path = self.url.getFile() | |
121 | print "self.url: " + str(self.url) + "\n" | |
122 | print "Scheme: " + self.scheme + "\n" | |
123 | print "FQDN: " + self.fqdn + "\n" | |
124 | print "Port: " + str(self.port1) + "\n" | |
125 | print "Path: " + self.path + "\n" | |
126 | return True |
0 | #!/usr/bin/env python | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | import requests | |
7 | import json | |
8 | import time | |
9 | import sys | |
10 | import argparse | |
11 | import os | |
12 | ||
13 | my_env = os.environ | |
14 | ||
15 | url = my_env["CS_NESSUS_URL"] if 'CS_NESSUS_URL' in my_env else "https://127.0.0.1:8834" | |
16 | username = my_env["CS_NESSUS_USER"] if 'CS_NESSUS_USER' in my_env else "nessus" | |
17 | password = my_env["CS_NESSUS_PASS"] if 'CS_NESSUS_PASS' in my_env else "nessus" | |
18 | profile = my_env["CS_NESSUS_PROFILE"] if 'CS_NESSUS_PROFILE' in my_env else "'Basic Network Scan'" | |
19 | ||
20 | verify = False | |
21 | token = '' | |
22 | ||
23 | def build_url(resource): | |
24 | return '{0}{1}'.format(url, resource) | |
25 | ||
26 | ||
27 | def connect(method, resource, data=None): | |
28 | """ | |
29 | Send a request | |
30 | ||
31 | Send a request to Nessus based on the specified data. If the session token | |
32 | is available add it to the request. Specify the content type as JSON and | |
33 | convert the data to JSON format. | |
34 | """ | |
35 | headers = {'X-Cookie': 'token={0}'.format(token), | |
36 | 'content-type': 'application/json'} | |
37 | ||
38 | data = json.dumps(data) | |
39 | ||
40 | if method == 'POST': | |
41 | r = requests.post(build_url(resource), data=data, headers=headers, verify=verify) | |
42 | elif method == 'PUT': | |
43 | r = requests.put(build_url(resource), data=data, headers=headers, verify=verify) | |
44 | elif method == 'DELETE': | |
45 | r = requests.delete(build_url(resource), data=data, headers=headers, verify=verify) | |
46 | else: | |
47 | r = requests.get(build_url(resource), params=data, headers=headers, verify=verify) | |
48 | ||
49 | # Exit if there is an error. | |
50 | if r.status_code != 200: | |
51 | e = r.json() | |
52 | print e['error'] | |
53 | sys.exit() | |
54 | ||
55 | # When downloading a scan we need the raw contents not the JSON data. | |
56 | if 'download' in resource: | |
57 | return r.content | |
58 | else: | |
59 | try: | |
60 | return r.json() | |
61 | except: | |
62 | pass | |
63 | ||
64 | ||
65 | def login(usr, pwd): | |
66 | """ | |
67 | Login to nessus. | |
68 | """ | |
69 | ||
70 | login = {'username': usr, 'password': pwd} | |
71 | data = connect('POST', '/session', data=login) | |
72 | ||
73 | return data['token'] | |
74 | ||
75 | ||
76 | def logout(): | |
77 | """ | |
78 | Logout of nessus. | |
79 | """ | |
80 | ||
81 | connect('DELETE', '/session') | |
82 | ||
83 | ||
84 | def get_policies(): | |
85 | """ | |
86 | Get scan policies | |
87 | ||
88 | Get all of the scan policies but return only the title and the uuid of | |
89 | each policy. | |
90 | """ | |
91 | ||
92 | data = connect('GET', '/editor/policy/templates') | |
93 | ||
94 | return dict((p['title'], p['uuid']) for p in data['templates']) | |
95 | ||
96 | ||
97 | def get_history_ids(sid): | |
98 | """ | |
99 | Get history ids | |
100 | ||
101 | Create a dictionary of scan uuids and history ids so we can lookup the | |
102 | history id by uuid. | |
103 | """ | |
104 | data = connect('GET', '/scans/{0}'.format(sid)) | |
105 | ||
106 | return dict((h['uuid'], h['history_id']) for h in data['history']) | |
107 | ||
108 | ||
109 | def get_scan_history(sid, hid): | |
110 | """ | |
111 | Scan history details | |
112 | ||
113 | Get the details of a particular run of a scan. | |
114 | """ | |
115 | params = {'history_id': hid} | |
116 | data = connect('GET', '/scans/{0}'.format(sid), params) | |
117 | ||
118 | return data['info'] | |
119 | ||
120 | ||
121 | def add(name, desc, targets, pid): | |
122 | """ | |
123 | Add a new scan | |
124 | ||
125 | Create a new scan using the policy_id, name, description and targets. The | |
126 | scan will be created in the default folder for the user. Return the id of | |
127 | the newly created scan. | |
128 | """ | |
129 | ||
130 | scan = {'uuid': pid, | |
131 | 'settings': { | |
132 | 'name': name, | |
133 | 'description': desc, | |
134 | 'text_targets': targets} | |
135 | } | |
136 | ||
137 | data = connect('POST', '/scans', data=scan) | |
138 | ||
139 | return data['scan'] | |
140 | ||
141 | ||
142 | def update(scan_id, name, desc, targets, pid=None): | |
143 | """ | |
144 | Update a scan | |
145 | ||
146 | Update the name, description, targets, or policy of the specified scan. If | |
147 | the name and description are not set, then the policy name and description | |
148 | will be set to None after the update. In addition the targets value must | |
149 | be set or you will get an "Invalid 'targets' field" error. | |
150 | """ | |
151 | ||
152 | scan = {} | |
153 | scan['settings'] = {} | |
154 | scan['settings']['name'] = name | |
155 | scan['settings']['desc'] = desc | |
156 | scan['settings']['text_targets'] = targets | |
157 | ||
158 | if pid is not None: | |
159 | scan['uuid'] = pid | |
160 | ||
161 | data = connect('PUT', '/scans/{0}'.format(scan_id), data=scan) | |
162 | ||
163 | return data | |
164 | ||
165 | ||
166 | def launch(sid): | |
167 | """ | |
168 | Launch a scan | |
169 | ||
170 | Launch the scan specified by the sid. | |
171 | """ | |
172 | ||
173 | data = connect('POST', '/scans/{0}/launch'.format(sid)) | |
174 | ||
175 | return data['scan_uuid'] | |
176 | ||
177 | ||
178 | def status(sid, hid): | |
179 | """ | |
180 | Check the status of a scan run | |
181 | ||
182 | Get the historical information for the particular scan and hid. Return | |
183 | the status if available. If not return unknown. | |
184 | """ | |
185 | ||
186 | d = get_scan_history(sid, hid) | |
187 | return d['status'] | |
188 | ||
189 | ||
190 | def export_status(sid, fid): | |
191 | """ | |
192 | Check export status | |
193 | ||
194 | Check to see if the export is ready for download. | |
195 | """ | |
196 | ||
197 | data = connect('GET', '/scans/{0}/export/{1}/status'.format(sid, fid)) | |
198 | ||
199 | return data['status'] == 'ready' | |
200 | ||
201 | ||
202 | def export(sid, hid): | |
203 | """ | |
204 | Make an export request | |
205 | ||
206 | Request an export of the scan results for the specified scan and | |
207 | historical run. In this case the format is hard coded as nessus but the | |
208 | format can be any one of nessus, html, pdf, csv, or db. Once the request | |
209 | is made, we have to wait for the export to be ready. | |
210 | """ | |
211 | ||
212 | data = {'history_id': hid, | |
213 | 'format': 'nessus'} | |
214 | ||
215 | data = connect('POST', '/scans/{0}/export'.format(sid), data=data) | |
216 | ||
217 | fid = data['file'] | |
218 | ||
219 | while export_status(sid, fid) is False: | |
220 | time.sleep(5) | |
221 | ||
222 | return fid | |
223 | ||
224 | ||
225 | def download(sid, fid, output): | |
226 | """ | |
227 | Download the scan results | |
228 | ||
229 | Download the scan results stored in the export file specified by fid for | |
230 | the scan specified by sid. | |
231 | """ | |
232 | ||
233 | data = connect('GET', '/scans/{0}/export/{1}/download'.format(sid, fid)) | |
234 | ||
235 | print('Saving scan results to {0}.'.format(output)) | |
236 | with open(output, 'w') as f: | |
237 | f.write(data) | |
238 | ||
239 | ||
240 | def delete(sid): | |
241 | """ | |
242 | Delete a scan | |
243 | ||
244 | This deletes a scan and all of its associated history. The scan is not | |
245 | moved to the trash folder, it is deleted. | |
246 | """ | |
247 | ||
248 | connect('DELETE', '/scans/{0}'.format(scan_id)) | |
249 | ||
250 | ||
251 | def history_delete(sid, hid): | |
252 | """ | |
253 | Delete a historical scan. | |
254 | ||
255 | This deletes a particular run of the scan and not the scan itself. the | |
256 | scan run is defined by the history id. | |
257 | """ | |
258 | ||
259 | connect('DELETE', '/scans/{0}/history/{1}'.format(sid, hid)) | |
260 | ||
261 | if __name__ == "__main__": | |
262 | parser = argparse.ArgumentParser(description='nessus_client is develop for automating security testing') | |
263 | parser.add_argument('-t','--target', help='Network or Host for scan', required=False) | |
264 | parser.add_argument('-o','--output', help='Output file', required=False) | |
265 | args = parser.parse_args() | |
266 | ||
267 | # Review de Command input | |
268 | if args.target == None or args.output == None: | |
269 | print "Argument errors check -h" | |
270 | exit(0) | |
271 | ||
272 | print('Login') | |
273 | try: | |
274 | token = login(username, password) | |
275 | except: | |
276 | print "Unexpected error:", sys.exc_info()[0] | |
277 | raise | |
278 | ||
279 | ||
280 | print('Adding new scan.' + token) | |
281 | print args.target | |
282 | ||
283 | policies = get_policies() | |
284 | policy_id = policies[profile] | |
285 | scan_data = add('CScan nessus', 'Create a new scan with API', args.target, policy_id) | |
286 | scan_id = scan_data['id'] | |
287 | ||
288 | print('Launching new scan.') | |
289 | scan_uuid = launch(scan_id) | |
290 | history_ids = get_history_ids(scan_id) | |
291 | history_id = history_ids[scan_uuid] | |
292 | while status(scan_id, history_id) not in ('completed','canceled'): | |
293 | time.sleep(5) | |
294 | ||
295 | ||
296 | print('Exporting the completed scan.') | |
297 | file_id = export(scan_id, history_id) | |
298 | download(scan_id, file_id, args.output) | |
299 | ||
300 | print('Deleting the scan.') | |
301 | history_delete(scan_id, history_id) | |
302 | delete(scan_id) | |
303 | ||
304 | print('Logout') | |
305 | logout() |
0 | #!/usr/bin/env python | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | from w3af_api_client import Connection, Scan | |
8 | import subprocess | |
9 | import os | |
10 | import argparse | |
11 | import time | |
12 | import signal | |
13 | from pprint import pprint | |
14 | import atexit | |
15 | child_pid = None | |
16 | ||
17 | def kill_child(): | |
18 | global child_pid | |
19 | if child_pid is None: | |
20 | pass | |
21 | else: | |
22 | os.kill(child_pid, signal.SIGTERM) | |
23 | ||
24 | def main(): | |
25 | atexit.register(kill_child) | |
26 | ||
27 | my_env = os.environ | |
28 | cmd = my_env["CS_W3AF"] if 'CS_W3AF' in my_env else "/root/tools/w3af/w3af_api" | |
29 | profile = my_env["CS_W3AF_PROFILE"] if 'CS_W3AF_PROFILE' in my_env else "/root/tools/w3af/profiles/fast_scan.pw3af" | |
30 | ||
31 | #Parser argument in command line | |
32 | parser = argparse.ArgumentParser(description='w3af_client is develop for automating security testing') | |
33 | parser.add_argument('-t','--target', help='Network or Host for scan', required=False) | |
34 | parser.add_argument('-o','--output', help='Output file', required=False) | |
35 | args = parser.parse_args() | |
36 | ||
37 | if args.target == None or args.output == None: | |
38 | print "Argument errors check -h" | |
39 | exit(0) | |
40 | ||
41 | print 'Starting w3af api ...' | |
42 | global child_pid | |
43 | proc = subprocess.Popen([cmd]) | |
44 | child_pid = proc.pid | |
45 | ||
46 | print 'Waiting for W3af to load, 5 seconds ...' | |
47 | time.sleep(5) | |
48 | ||
49 | # Connect to the REST API and get it's version | |
50 | conn = Connection('http://127.0.0.1:5000/') | |
51 | print conn.get_version() | |
52 | ||
53 | # Define the target and configuration | |
54 | #scan_profile = file('/root/tools/w3af/profiles/fast_scan_xml.pw3af').read() | |
55 | scan_profile = file(profile).read() | |
56 | scan_profile = "[output.xml_file]\noutput_file = %s\n%s\n" % (args.output, scan_profile ) | |
57 | # scan_profile = file('/root/tools/w3af/profiles/fast_scan.pw3af').read() | |
58 | ||
59 | target_urls = [args.target] | |
60 | ||
61 | scan = Scan(conn) | |
62 | s = scan.start(scan_profile, target_urls) | |
63 | time.sleep(2) | |
64 | ||
65 | # Wait some time for the scan to start and then | |
66 | scan.get_urls() | |
67 | scan.get_log() | |
68 | scan.get_findings() | |
69 | ||
70 | while(scan.get_status()['status'] == "Running"): | |
71 | print 'Scan progress: %s' + str(scan.get_status()['rpm']) | |
72 | time.sleep(2) | |
73 | ||
74 | if __name__ == "__main__": | |
75 | main()⏎ |
0 | #!/usr/bin/env python | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | ''' | |
8 | By tartamar | |
9 | ''' | |
10 | import argparse | |
11 | import time | |
12 | import re | |
13 | from pprint import pprint | |
14 | from zapv2 import ZAPv2 | |
15 | import subprocess | |
16 | import os | |
17 | import signal | |
18 | import atexit | |
19 | child_pid = None | |
20 | ||
21 | def kill_child(): | |
22 | global child_pid | |
23 | if child_pid is None: | |
24 | pass | |
25 | else: | |
26 | os.kill(child_pid, signal.SIGTERM) | |
27 | ||
28 | def is_http_url(page): | |
29 | """ | |
30 | Returns true if s is valid http url, else false | |
31 | Arguments: | |
32 | - `page`: | |
33 | """ | |
34 | if re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', page): | |
35 | return True | |
36 | else: | |
37 | return False | |
38 | ||
39 | def exportfile(filename,zap): | |
40 | #Output for XML Report | |
41 | print 'Generating XML Report...' | |
42 | filex=open(filename, 'w') | |
43 | filex.write(zap.core.xmlreport) | |
44 | filex.close() | |
45 | ||
46 | def main(): | |
47 | ||
48 | atexit.register(kill_child) | |
49 | ||
50 | my_env = os.environ | |
51 | cmd = my_env["CS_ZAP"] if 'CS_ZAP' in my_env else "/usr/share/zaproxy/zap.sh" | |
52 | ||
53 | #Parser argument in command line | |
54 | parser = argparse.ArgumentParser(description='PyZap is develop for automating security testing') | |
55 | parser.add_argument('-t','--target', help='Network or Host for scan', required=False) | |
56 | parser.add_argument('-o','--output', help='Output file', required=False) | |
57 | args = parser.parse_args() | |
58 | ||
59 | # Review de Command input | |
60 | if args.target == None: | |
61 | # Do nothing | |
62 | # Input data for test | |
63 | target = raw_input('[+] Enter your target: ') | |
64 | if is_http_url(target) == True: | |
65 | print '[-] Target selected: ', target | |
66 | else: | |
67 | print '[w] Please type a correct URL address' | |
68 | quit() | |
69 | else: | |
70 | # Check for valid URL addres | |
71 | if is_http_url(args.target) == True: | |
72 | target = args.target | |
73 | print '[-] Target selected: ', target | |
74 | else: | |
75 | print '[w] Please type a correct URL Address' | |
76 | quit() | |
77 | print 'Starting ZAP ...' | |
78 | ||
79 | global child_pid | |
80 | proc = subprocess.Popen([cmd,'-daemon']) | |
81 | child_pid = proc.pid | |
82 | ||
83 | print 'Waiting for ZAP to load, 10 seconds ...' | |
84 | time.sleep(10) | |
85 | zap = ZAPv2() | |
86 | # Use the line below if ZAP is not listening on 8090 | |
87 | zap = ZAPv2(proxies={'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'}) | |
88 | ||
89 | # do stuff | |
90 | print 'Accessing target %s' % target | |
91 | # try have a unique enough session... | |
92 | zap.urlopen(target) | |
93 | # Give the sites tree a chance to get updated | |
94 | time.sleep(2) | |
95 | ||
96 | print 'Spidering target %s' % target | |
97 | print target | |
98 | zap.spider.scan(target) | |
99 | # Give the Spider a chance to start | |
100 | time.sleep(2) | |
101 | #print 'Status %s' % zap.spider.status | |
102 | while(int(zap.spider.status) < 100): | |
103 | print 'Spider progress %: ' + zap.spider.status | |
104 | time.sleep(2) | |
105 | ||
106 | print 'Spider completed' | |
107 | # Give the passive scanner a chance to finish | |
108 | time.sleep(5) | |
109 | ||
110 | print 'Scanning target %s' % target | |
111 | zap.ascan.scan(target) | |
112 | while(int(zap.ascan.status) < 100): | |
113 | print 'Scan progress %: ' + zap.ascan.status | |
114 | time.sleep(5) | |
115 | ||
116 | print 'Scan completed' | |
117 | ||
118 | # Report the results | |
119 | ||
120 | print 'Hosts: ' + ', '.join(zap.core.hosts) | |
121 | # print 'Alerts: ' | |
122 | # pprint (zap.core.alerts()) | |
123 | #pprint (zap.core.xmlreport()) | |
124 | exportfile(args.output,zap) | |
125 | ||
126 | print 'Shutting down ZAP ...' | |
127 | zap.core.shutdown | |
128 | #EOF | |
129 | ||
130 | if __name__ == "__main__": | |
131 | main()⏎ |
0 | #!/bin/bash | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | while read h; do | |
8 | NAME="nessus_$(date +%s).xml" | |
9 | echo plugin/nessus.py --target $h --output $2$NAME | |
10 | ./plugin/nessus.py --target $h --output $2$NAME | |
11 | done <$1 |
0 | #!/bin/bash | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | NAME="nmap_$(date +%s).xml" | |
8 | ${CS_NMAP:=nmap} -iL $1 -oX $2$NAME |
0 | #!/bin/bash | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | # ------------- | |
8 | # CONFIG PARAMS | |
9 | # ------------- | |
10 | # Your Dashboard login data | |
11 | USER_NAME=${CS_OPENVAS_USER:=admin} | |
12 | # If you set this to None, you will be asked on startup | |
13 | USER_PASSWORD=${CS_OPENVAS_PASSWORD:=openvas} | |
14 | ||
15 | # Your targets, seperated by space | |
16 | #TARGET_SRVS="localhost" | |
17 | # The name of the OpenVAS preset for the scan | |
18 | # The following configs are available by default: | |
19 | # Discovery | |
20 | # empty | |
21 | # Full and fast | |
22 | # Full and fast ultimate | |
23 | # Full and very deep | |
24 | # Full and very deep ultimate | |
25 | # Host Discovery | |
26 | # System Discovery | |
27 | SCAN_CONFIG=${CS_OPENVAS_SCAN_CONFIG:='Full and fast'} | |
28 | # A valid "alive_test" parameter | |
29 | # Defines how it is determined if the targets are alive | |
30 | # Currently, valid values are the following: | |
31 | # Scan Config Default | |
32 | # ICMP, TCP-ACK Service & ARP Ping | |
33 | # TCP-ACK Service & ARP Ping | |
34 | # ICMP & ARP Ping | |
35 | # ICMP & TCP-ACK Service Ping | |
36 | # ARP Ping | |
37 | # TCP-ACK Service Ping | |
38 | # TCP-SYN Service Ping | |
39 | # ICMP Ping | |
40 | # Consider Alive | |
41 | ALIVE_TEST=${CS_OPENVAS_ALIVE_TEST:='ICMP, TCP-ACK Service & ARP Ping'} | |
42 | ||
43 | CS_OMP=${CS_OPENVAS:=omp} | |
44 | ||
45 | ||
46 | function fail { | |
47 | echo "There was an error during execution! Current action: $1" | |
48 | exit 1 | |
49 | } | |
50 | ||
51 | function sindex { | |
52 | x="${1%%$2*}" | |
53 | [[ $x = $1 ]] && echo -1 || echo ${#x} | |
54 | } | |
55 | ||
56 | ||
57 | THREAT=0 | |
58 | ADDRS="" | |
59 | SRV=$1 | |
60 | ||
61 | while read h; do | |
62 | ||
63 | echo "Processing target $h..." | |
64 | ||
65 | #echo $CS_OMP -u $USER_NAME -w $USER_PASSWORD --xml=\ | |
66 | "<create_target>\ | |
67 | <name>TARG$(date +%s)</name><hosts>$h</hosts>\ | |
68 | <alive_tests>$ALIVE_TEST</alive_tests>\ | |
69 | </create_target>" | |
70 | ||
71 | TARGET_RETURN=$($CS_OMP -u $USER_NAME -w $USER_PASSWORD --xml=\ | |
72 | "<create_target>\ | |
73 | <name>TARG$(date +%s)</name><hosts>$h</hosts>\ | |
74 | <alive_tests>$ALIVE_TEST</alive_tests>\ | |
75 | </create_target>") | |
76 | echo "$TARGET_RETURN" | grep -m1 'resource created' || fail 'creating target' | |
77 | ||
78 | T_ID_INDEX=$(sindex "$TARGET_RETURN" "id=") | |
79 | T_ID_INDEX=$((T_ID_INDEX + 4)) | |
80 | T_ID=${TARGET_RETURN:T_ID_INDEX:36} | |
81 | echo "> Target has ID $T_ID" | |
82 | ||
83 | C_ID=$($CS_OMP -u $USER_NAME -w $USER_PASSWORD -g | grep -i "$TEST_CONFIG") | |
84 | if [ $? -ne 0 ]; then fail 'getting configs'; fi | |
85 | ||
86 | C_ID=${C_ID:0:36} | |
87 | echo "> Config $TEST_CONFIG has ID $C_ID" | |
88 | ||
89 | J_ID=$($CS_OMP -u $USER_NAME -w $USER_PASSWORD -C -n "CScan openvas" \ | |
90 | --target="$T_ID" --config="$C_ID") | |
91 | if [ $? -ne 0 ]; then fail 'creating job'; fi | |
92 | echo "> Created job with ID $J_ID" | |
93 | ||
94 | R_ID=$($CS_OMP -u $USER_NAME -w $USER_PASSWORD -S "$J_ID") | |
95 | if [ $? -ne 0 ]; then fail 'starting job'; fi | |
96 | echo "> Started job, report gets ID $R_ID" | |
97 | ||
98 | while true; do | |
99 | RET=$($CS_OMP -u $USER_NAME -w $USER_PASSWORD -G) | |
100 | if [ $? -ne 0 ]; then fail 'querying jobs'; fi | |
101 | RET=$(echo "$RET" | grep -m1 "$J_ID") | |
102 | echo $RET | |
103 | echo "$RET" | grep -m1 -i "fail" && fail 'running job' | |
104 | echo "$RET" | grep -m1 -i -E "done|Stopped" && break | |
105 | sleep 1 | |
106 | done | |
107 | ||
108 | echo "> Job done, generating report..." | |
109 | ||
110 | FILENAME=${h// /_} | |
111 | FILENAME="openvas_${FILENAME//[^a-zA-Z0-9_\.\-]/}_$(date +%s)" | |
112 | $CS_OMP -u $USER_NAME -w $USER_PASSWORD -R "$R_ID" > $2$FILENAME.xml | |
113 | if [ $? -ne 0 ]; then fail 'getting report'; fi | |
114 | ||
115 | echo "Scan done" | |
116 | ||
117 | echo "Remove task" | |
118 | $CS_OMP -u $USER_NAME -w $USER_PASSWORD -D "$J_ID" | |
119 | ||
120 | ||
121 | done <$1 |
0 | #!/bin/bash | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | CMD=${CS_BURP:=/root/tools/burpsuite_pro_v1.6.26.jar} | |
7 | while read h; do | |
8 | NAME="burp_$(date +%s).xml" | |
9 | echo java -jar -Xmx1g -Djava.awt.headless=true $CMD $h XML $2$NAME | |
10 | java -jar -Xmx1g -Djava.awt.headless=true $CMD $h XML $2$NAME | |
11 | done <$1 |
0 | #!/bin/bash | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | while read h; do | |
7 | NAME="nikto_$(date +%s).xml" | |
8 | ${CS_NIKTO:=nikto} -host $h -output $2$NAME -Format XML | |
9 | done <$1⏎ |
0 | #!/bin/bash | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | ||
8 | while read h; do | |
9 | NAME="w3af_$(date +%s).xml" | |
10 | echo plugin/w3af.py --target $h --output $2$NAME | |
11 | ./plugin/w3af.py --target $h --output $2$NAME | |
12 | done <$1 | |
13 | #fix zombie w3af | |
14 | kill -9 $(ps aux | grep w3af_api | awk -F" " {'print $2'}) 2> /dev/null |
0 | #!/bin/bash | |
1 | ### | |
2 | ## Faraday Penetration Test IDE | |
3 | ## Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
4 | ## See the file 'doc/LICENSE' for the license information | |
5 | ### | |
6 | ||
7 | while read h; do | |
8 | NAME="zap_$(date +%s).xml" | |
9 | echo ./plugin/zap.py --target $h --output $2$NAME | |
10 | ./plugin/zap.py --target $h --output $2$NAME | |
11 | done <$1⏎ |
0 | http://localhost:80 |
318 | 318 | /* Home Menu grande > Community */ |
319 | 319 | .home-list.community .item:nth-child(4) {clear:left;} |
320 | 320 | /* Home Menu grande > Professional */ |
321 | .home-list.professional .item:nth-child(4) {clear:left;} | |
321 | .home-list.professional .item:nth-child(5) {clear:left;} | |
322 | 322 | /* Home Menu grande > Corporate */ |
323 | .home-list.corporate .item:nth-child(5) {clear:left;} | |
323 | .home-list.corporate .item:nth-child(5), .home-list.corporate .item:nth-child(9) {clear:left;} | |
324 | 324 | |
325 | 325 | |
326 | 326 | .item:nth-child(0) {-webkit-animation-delay: 0s;} |
331 | 331 | .item:nth-child(5) {-webkit-animation-delay: 0.5s;} |
332 | 332 | .item:nth-child(6) {-webkit-animation-delay: 0.6s;} |
333 | 333 | .item:nth-child(7) {-webkit-animation-delay: 0.7s;} |
334 | ||
334 | .item:nth-child(8) {-webkit-animation-delay: 0.8s;} | |
335 | .item:nth-child(9) {-webkit-animation-delay: 0.9s;} | |
335 | 336 | /* Icons on Home */ |
336 | 337 | .icons-color-home{color: #B3B4B5;} |
337 | 338 | .fa.host{font-size: 2.6em;} |
1004 | 1005 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); |
1005 | 1006 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075); |
1006 | 1007 | } |
1008 | #bar .chart-container, #doughnut .chart-container{height: 130px} | |
1009 | div.form-group.input-accordion{margin: 0px!important} | |
1010 | /* Firefox */ | |
1011 | @-moz-document url-prefix() { | |
1012 | .severities > button > span{margin-top: -10px;} | |
1013 | } | |
1014 | span.bold{font-weight: bold} | |
1015 | div.jumbotron.jumbotron-font h1{font-size: 54px} | |
1016 | /* Normalize datepicker */ | |
1017 | div.datepicker ul.dropdown-menu{width: 288px;} | |
1018 | div.datepicker > p > ul > li > div > table{width: 97%} | |
1019 | div.datepicker .btn-sm, .btn-group-sm > .btn{padding: 5px 7px}⏎ |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="32px" height="32px" viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve"> | |
5 | <g id="xdEhD1.tif_9_"> | |
6 | <g id="xIXYAg_6_"> | |
7 | <g> | |
8 | <path style="fill:#FFFFFF;" d="M31.989,29.6c-1.376-0.59-2.654-1.138-3.932-1.686c-0.536-0.23-1.062-0.611-1.61-0.645 | |
9 | c-0.538-0.033-1.094,0.302-1.65,0.448c-4.203,1.098-8.183,0.686-11.798-1.864c-1.645-1.161-2.821-2.709-3.243-4.721 | |
10 | c-0.545-2.6,0.325-4.781,2.147-6.611c1.731-1.739,3.882-2.698,6.27-3.121c3.21-0.568,6.298-0.185,9.183,1.393 | |
11 | c1.84,1.006,3.321,2.39,4.113,4.377c1.026,2.574,0.526,4.929-1.177,7.052c-0.248,0.309-0.256,0.534-0.118,0.868 | |
12 | C30.777,26.553,31.358,28.026,31.989,29.6z"/> | |
13 | <path style="fill:#FFFFFF;" d="M26.982,10.704c-1.219-0.322-2.303-0.705-3.42-0.886c-4.469-0.724-8.604,0.071-12.15,3.02 | |
14 | c-2.238,1.861-3.504,4.258-3.397,7.24c0.027,0.765,0.223,1.524,0.348,2.328c-0.347-0.125-0.74-0.244-1.112-0.411 | |
15 | c-0.253-0.113-0.457-0.114-0.715-0.001c-2.021,0.881-4.05,1.744-6.077,2.612c-0.123,0.053-0.25,0.097-0.442,0.171 | |
16 | c0.147-0.379,0.267-0.694,0.392-1.008c0.629-1.577,1.254-3.156,1.896-4.727c0.096-0.235,0.063-0.377-0.098-0.567 | |
17 | c-1.64-1.94-2.467-4.149-2.134-6.713c0.296-2.283,1.436-4.119,3.114-5.639c2.184-1.978,4.798-3.055,7.676-3.5 | |
18 | c3.027-0.468,6.001-0.209,8.868,0.909c2.619,1.022,4.829,2.586,6.276,5.06C26.391,9.248,26.651,9.977,26.982,10.704z"/> | |
19 | </g> | |
20 | </g> | |
21 | </g> | |
22 | </svg> |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="48px" height="48px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve"> | |
5 | <g id="xdEhD1.tif_9_"> | |
6 | <g id="xIXYAg_6_"> | |
7 | <g> | |
8 | <path style="fill:#B3B4B5;" d="M47.983,44.4c-2.063-0.885-3.98-1.707-5.898-2.529c-0.804-0.344-1.594-0.916-2.416-0.967 | |
9 | c-0.807-0.05-1.641,0.454-2.475,0.671c-6.305,1.647-12.275,1.03-17.698-2.796c-2.468-1.741-4.231-4.064-4.864-7.081 | |
10 | c-0.818-3.9,0.488-7.171,3.221-9.917c2.597-2.608,5.823-4.047,9.405-4.681c4.815-0.852,9.447-0.278,13.775,2.089 | |
11 | c2.76,1.509,4.981,3.586,6.17,6.565c1.54,3.861,0.79,7.393-1.765,10.578c-0.372,0.464-0.384,0.801-0.177,1.302 | |
12 | C46.166,39.83,47.037,42.039,47.983,44.4z"/> | |
13 | <path style="fill:#B3B4B5;" d="M40.473,16.056c-1.828-0.483-3.455-1.058-5.131-1.33c-6.703-1.086-12.906,0.106-18.225,4.529 | |
14 | c-3.357,2.792-5.256,6.387-5.095,10.86c0.041,1.147,0.334,2.286,0.521,3.492c-0.52-0.188-1.11-0.366-1.668-0.616 | |
15 | c-0.379-0.17-0.686-0.171-1.072-0.002c-3.032,1.321-6.075,2.617-9.116,3.919c-0.184,0.079-0.374,0.145-0.664,0.256 | |
16 | c0.221-0.569,0.401-1.042,0.588-1.511c0.944-2.365,1.881-4.733,2.845-7.091c0.144-0.353,0.095-0.565-0.147-0.851 | |
17 | c-2.459-2.91-3.7-6.224-3.201-10.07c0.445-3.425,2.154-6.179,4.671-8.459c3.276-2.967,7.197-4.583,11.515-5.25 | |
18 | c4.541-0.702,9.002-0.314,13.302,1.364c3.929,1.533,7.243,3.878,9.415,7.589C39.587,13.871,39.977,14.966,40.473,16.056z"/> | |
19 | </g> | |
20 | </g> | |
21 | </g> | |
22 | </svg> |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="32px" height="32px" viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve"> | |
5 | <g id="_x38_ELw6c_1_"> | |
6 | <g> | |
7 | <path style="fill:#FFFFFF;" d="M0,9.499c0.041-0.104,0.083-0.207,0.124-0.311c0.306-0.765,0.87-1.26,1.685-1.292 | |
8 | C3.19,7.842,4.575,7.881,5.98,7.881c0,6.992,0,13.972,0,20.979c-0.098,0.005-0.198,0.014-0.298,0.014 | |
9 | c-1.177,0.001-2.353,0.001-3.53,0.001c-1.09-0.001-1.769-0.504-2.1-1.552C0.044,27.294,0.018,27.273,0,27.249 | |
10 | C0,21.332,0,15.416,0,9.499z"/> | |
11 | <path style="fill:#FFFFFF;" d="M32,27.249c-0.044,0.114-0.086,0.228-0.133,0.34c-0.316,0.743-0.875,1.233-1.676,1.264 | |
12 | c-1.381,0.054-2.766,0.015-4.17,0.015c0-6.994,0-13.973,0-20.996c0.12,0,0.241,0,0.362,0c1.145,0,2.291,0.002,3.436,0.001 | |
13 | c1.116-0.001,1.794,0.492,2.13,1.553C31.956,9.454,31.982,9.475,32,9.499C32,15.416,32,21.332,32,27.249z"/> | |
14 | <path style="fill:#FFFFFF;" d="M20.749,7.874c1.431,0,2.822,0,4.233,0c0,7.003,0,13.984,0,20.983c-5.989,0-11.97,0-17.967,0 | |
15 | c0-6.986,0-13.959,0-20.968c1.394,0,2.792,0,4.235,0c0-0.145,0-0.254,0-0.364c0-0.719-0.008-1.437,0.001-2.155 | |
16 | c0.016-1.256,0.991-2.236,2.249-2.244c1.676-0.011,3.353-0.01,5.029,0c1.215,0.007,2.201,0.992,2.218,2.208 | |
17 | C20.759,6.166,20.749,6.998,20.749,7.874z M19.245,7.868c0-0.879,0.016-1.73-0.007-2.58c-0.01-0.391-0.353-0.662-0.772-0.663 | |
18 | c-1.634-0.004-3.268-0.004-4.902,0c-0.509,0.001-0.809,0.308-0.813,0.821c-0.004,0.645-0.001,1.291-0.001,1.936 | |
19 | c0,0.161,0,0.322,0,0.486C14.934,7.868,17.067,7.868,19.245,7.868z"/> | |
20 | </g> | |
21 | </g> | |
22 | </svg> |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="48px" height="48px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve"> | |
5 | <g id="_x38_ELw6c_1_"> | |
6 | <g> | |
7 | <path style="fill:#B4B4B4;" d="M0,14.248c0.062-0.155,0.124-0.311,0.186-0.466c0.459-1.147,1.305-1.891,2.527-1.939 | |
8 | c2.072-0.081,4.149-0.022,6.256-0.022c0,10.488,0,20.958,0,31.468c-0.147,0.007-0.297,0.02-0.447,0.02 | |
9 | c-1.765,0.001-3.53,0.002-5.294,0.001c-1.635-0.001-2.653-0.755-3.149-2.328C0.066,40.942,0.027,40.909,0,40.873 | |
10 | C0,31.998,0,23.123,0,14.248z"/> | |
11 | <path style="fill:#B4B4B4;" d="M48,40.873c-0.066,0.17-0.129,0.342-0.2,0.511c-0.474,1.115-1.313,1.85-2.514,1.896 | |
12 | c-2.072,0.081-4.149,0.022-6.256,0.022c0-10.49,0-20.96,0-31.493c0.18,0,0.361,0,0.542,0c1.718,0.001,3.436,0.004,5.154,0.001 | |
13 | c1.675-0.002,2.691,0.738,3.195,2.33c0.013,0.04,0.052,0.072,0.079,0.108C48,23.123,48,31.998,48,40.873z"/> | |
14 | <path style="fill:#B4B4B4;" d="M31.124,11.811c2.146,0,4.233,0,6.349,0c0,10.505,0,20.976,0,31.474c-8.983,0-17.955,0-26.95,0 | |
15 | c0-10.479,0-20.939,0-31.452c2.09,0,4.189,0,6.353,0c0-0.217,0-0.382,0-0.547c0-1.078-0.013-2.156,0.001-3.233 | |
16 | c0.025-1.884,1.487-3.353,3.373-3.365c2.515-0.016,5.029-0.015,7.544,0c1.822,0.01,3.302,1.489,3.327,3.312 | |
17 | C31.138,9.248,31.124,10.497,31.124,11.811z M28.868,11.802c0-1.318,0.024-2.595-0.011-3.87c-0.016-0.587-0.53-0.993-1.158-0.994 | |
18 | c-2.451-0.006-4.902-0.006-7.353,0C19.583,6.939,19.132,7.4,19.126,8.17c-0.006,0.968-0.001,1.936-0.001,2.904 | |
19 | c0,0.241,0,0.483,0,0.729C22.402,11.802,25.601,11.802,28.868,11.802z"/> | |
20 | </g> | |
21 | </g> | |
22 | </svg> |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="32px" height="32px" viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve"> | |
5 | <g id="eP0hGU_2_"> | |
6 | <g> | |
7 | <path style="fill:#FFFFFF;" d="M15.993,28.598c-2.067,0-4.134,0.001-6.201-0.001c-0.759-0.001-1.482-0.149-2.133-0.559 | |
8 | c-0.967-0.609-1.476-1.513-1.535-2.631c-0.093-1.778,0.093-3.533,0.687-5.223c0.297-0.845,0.724-1.622,1.387-2.243 | |
9 | c0.77-0.722,1.703-1.031,2.746-1.021c0.131,0.001,0.272,0.08,0.39,0.152c0.358,0.219,0.709,0.45,1.059,0.682 | |
10 | c2.199,1.464,4.992,1.467,7.199,0.006c0.322-0.213,0.654-0.412,0.967-0.637c0.292-0.209,0.607-0.232,0.946-0.195 | |
11 | c1.487,0.16,2.522,0.958,3.223,2.247c0.499,0.916,0.766,1.904,0.942,2.923c0.189,1.092,0.267,2.192,0.2,3.297 | |
12 | c-0.101,1.665-1.277,2.919-2.928,3.144c-0.274,0.037-0.553,0.056-0.83,0.057C20.073,28.6,18.033,28.598,15.993,28.598z"/> | |
13 | <path style="fill:#FFFFFF;" d="M21.391,12.527c-0.02,2.896-2.537,5.39-5.604,5.275c-2.856-0.107-5.215-2.52-5.195-5.429 | |
14 | c0.021-3.078,2.604-5.457,5.553-5.378C19.122,7.076,21.501,9.626,21.391,12.527z"/> | |
15 | <path style="fill:#FFFFFF;" d="M10.801,15.963c-0.431,0.078-0.847,0.129-1.25,0.232c-0.94,0.242-1.705,0.773-2.353,1.486 | |
16 | c-0.049,0.054-0.131,0.11-0.197,0.11c-0.765-0.003-1.532,0.011-2.295-0.036c-0.636-0.04-1.208-0.289-1.672-0.744 | |
17 | c-0.335-0.329-0.513-0.744-0.524-1.201c-0.032-1.316,0.003-2.632,0.408-3.9c0.105-0.327,0.296-0.638,0.501-0.917 | |
18 | c0.114-0.156,0.33-0.247,0.515-0.337c0.179-0.087,0.36-0.041,0.545,0.043c0.664,0.303,1.319,0.632,2.006,0.873 | |
19 | c0.974,0.342,1.972,0.283,2.959,0.003c0.084-0.024,0.169-0.047,0.254-0.068c0.01-0.003,0.024,0.009,0.053,0.021 | |
20 | C9.537,13.136,9.895,14.621,10.801,15.963z"/> | |
21 | <path style="fill:#FFFFFF;" d="M21.182,15.976c0.922-1.36,1.27-2.848,1.058-4.486c0.243,0.064,0.45,0.123,0.659,0.172 | |
22 | c1.112,0.257,2.193,0.143,3.237-0.304c0.447-0.191,0.889-0.398,1.316-0.63c0.359-0.195,0.666-0.119,0.96,0.102 | |
23 | c0.358,0.268,0.53,0.661,0.667,1.068c0.228,0.679,0.316,1.384,0.365,2.094c0.032,0.459,0.027,0.92,0.051,1.38 | |
24 | c0.075,1.41-0.762,2.1-1.936,2.341c-0.38,0.078-0.78,0.07-1.171,0.081c-0.44,0.013-0.881,0.01-1.32-0.002 | |
25 | c-0.094-0.003-0.213-0.05-0.276-0.117c-0.716-0.779-1.567-1.327-2.61-1.546C21.865,16.061,21.542,16.03,21.182,15.976z"/> | |
26 | <path style="fill:#FFFFFF;" d="M20.507,6.975c0.016-2.002,1.608-3.579,3.606-3.574c2.028,0.005,3.607,1.625,3.587,3.678 | |
27 | c-0.019,1.922-1.656,3.528-3.589,3.519C22.092,10.589,20.49,8.979,20.507,6.975z"/> | |
28 | <path style="fill:#FFFFFF;" d="M7.899,3.402c2.02,0.018,3.608,1.642,3.594,3.632c-0.014,1.907-1.593,3.57-3.608,3.563 | |
29 | C5.855,10.59,4.283,8.93,4.294,7.021C4.305,4.981,5.907,3.385,7.899,3.402z"/> | |
30 | </g> | |
31 | </g> | |
32 | </svg> |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="48px" height="48px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve"> | |
5 | <g id="eP0hGU_2_"> | |
6 | <g> | |
7 | <path style="fill:#B4B4B4;" d="M23.989,43.596c-3.215,0-6.431,0.002-9.647-0.001c-1.181-0.001-2.305-0.231-3.318-0.869 | |
8 | c-1.504-0.947-2.296-2.353-2.387-4.093c-0.145-2.766,0.144-5.495,1.07-8.125c0.462-1.314,1.127-2.524,2.157-3.49 | |
9 | c1.197-1.123,2.65-1.603,4.271-1.589c0.203,0.002,0.423,0.124,0.607,0.237c0.556,0.342,1.103,0.7,1.647,1.061 | |
10 | c3.421,2.277,7.766,2.281,11.198,0.008c0.5-0.331,1.016-0.641,1.505-0.99c0.455-0.325,0.945-0.36,1.472-0.304 | |
11 | c2.313,0.248,3.923,1.491,5.014,3.494c0.777,1.425,1.191,2.962,1.465,4.547c0.294,1.699,0.415,3.409,0.312,5.129 | |
12 | c-0.157,2.589-1.986,4.542-4.555,4.891c-0.427,0.058-0.861,0.087-1.291,0.088C30.335,43.6,27.162,43.596,23.989,43.596z"/> | |
13 | <path style="fill:#B4B4B4;" d="M32.386,18.598c-0.031,4.505-3.946,8.385-8.718,8.206c-4.443-0.166-8.112-3.92-8.081-8.446 | |
14 | c0.034-4.788,4.051-8.489,8.637-8.365C28.856,10.118,32.558,14.085,32.386,18.598z"/> | |
15 | <path style="fill:#B4B4B4;" d="M15.913,23.943c-0.671,0.122-1.318,0.201-1.944,0.361c-1.462,0.376-2.652,1.203-3.66,2.311 | |
16 | c-0.076,0.083-0.203,0.172-0.306,0.171c-1.19-0.005-2.384,0.018-3.57-0.056c-0.989-0.062-1.88-0.45-2.601-1.157 | |
17 | c-0.522-0.511-0.798-1.158-0.816-1.869C2.966,21.657,3.02,19.61,3.65,17.638c0.162-0.509,0.46-0.992,0.779-1.426 | |
18 | c0.177-0.242,0.513-0.385,0.802-0.525c0.278-0.135,0.561-0.064,0.847,0.067c1.032,0.471,2.052,0.984,3.12,1.358 | |
19 | c1.516,0.531,3.068,0.441,4.603,0.004c0.132-0.037,0.263-0.073,0.396-0.105c0.016-0.004,0.037,0.014,0.083,0.033 | |
20 | C13.946,19.545,14.504,21.855,15.913,23.943z"/> | |
21 | <path style="fill:#B4B4B4;" d="M32.06,23.963c1.435-2.115,1.975-4.43,1.645-6.977c0.379,0.099,0.7,0.192,1.026,0.267 | |
22 | c1.73,0.399,3.411,0.223,5.036-0.473c0.695-0.298,1.383-0.619,2.048-0.98c0.558-0.302,1.035-0.185,1.494,0.159 | |
23 | c0.556,0.416,0.824,1.028,1.037,1.661c0.355,1.056,0.491,2.153,0.567,3.257c0.049,0.714,0.042,1.431,0.08,2.146 | |
24 | c0.117,2.193-1.185,3.267-3.012,3.641c-0.592,0.121-1.213,0.109-1.822,0.127c-0.684,0.02-1.37,0.015-2.054-0.004 | |
25 | c-0.147-0.004-0.331-0.077-0.428-0.183c-1.113-1.211-2.438-2.064-4.061-2.405C33.124,24.095,32.621,24.047,32.06,23.963z"/> | |
26 | <path style="fill:#B4B4B4;" d="M31.01,9.962c0.025-3.114,2.5-5.567,5.608-5.559c3.155,0.008,5.611,2.527,5.579,5.721 | |
27 | c-0.03,2.99-2.577,5.487-5.582,5.474C33.476,15.583,30.984,13.078,31.01,9.962z"/> | |
28 | <path style="fill:#B4B4B4;" d="M11.399,4.404c3.142,0.028,5.612,2.554,5.59,5.649c-0.021,2.966-2.478,5.553-5.613,5.543 | |
29 | c-3.157-0.011-5.602-2.594-5.586-5.564C5.808,6.86,8.301,4.376,11.399,4.404z"/> | |
30 | </g> | |
31 | </g> | |
32 | </svg> |
0 | <?xml version="1.0" encoding="utf-8"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> | |
5 | <g> | |
6 | <g id="isbxUX_1_"> | |
7 | <g> | |
8 | <g> | |
9 | <path fill="#FFFFFF" d="M11.346,16.465c-0.09-0.059-0.156-0.104-0.224-0.147c-1.024-0.644-2.049-1.287-3.073-1.93 | |
10 | c-0.243-0.153-0.348-0.353-0.302-0.574c0.075-0.36,0.465-0.524,0.792-0.324c0.476,0.292,0.946,0.592,1.418,0.889 | |
11 | c0.861,0.541,1.721,1.081,2.581,1.624c0.425,0.268,0.417,0.701-0.017,0.958c-1.321,0.783-2.642,1.565-3.965,2.345 | |
12 | c-0.338,0.199-0.694,0.071-0.797-0.279c-0.069-0.237,0.034-0.454,0.288-0.605c0.86-0.509,1.721-1.018,2.581-1.527 | |
13 | C10.858,16.757,11.089,16.619,11.346,16.465z"/> | |
14 | </g> | |
15 | </g> | |
16 | <path fill="#FFFFFF" d="M18.495,18.806c0,0.326-0.267,0.593-0.593,0.593h-4.213c-0.326,0-0.593-0.267-0.593-0.593V18.76 | |
17 | c0-0.326,0.267-0.593,0.593-0.593h4.213c0.326,0,0.593,0.267,0.593,0.593V18.806z"/> | |
18 | </g> | |
19 | <path fill="#FFFFFF" d="M30.259,5.651H1.741C1.333,5.651,1,5.984,1,6.392v19.956h30V6.392C31,5.984,30.667,5.651,30.259,5.651z | |
20 | M2.308,6.959h27.384v0.044v0.899H2.308V7.003V6.959z M2.308,24.081V9.21h27.384v14.872H2.308z"/> | |
21 | </g> | |
22 | </svg> |
0 | <?xml version="1.0" encoding="utf-8"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve"> | |
5 | <g> | |
6 | <g id="isbxUX_1_"> | |
7 | <g> | |
8 | <g> | |
9 | <path fill="#B3B4B5" d="M16.891,24.711c-0.137-0.09-0.239-0.159-0.343-0.225c-1.565-0.983-3.13-1.965-4.694-2.948 | |
10 | c-0.372-0.234-0.532-0.539-0.462-0.877c0.114-0.551,0.71-0.801,1.209-0.494c0.726,0.446,1.445,0.904,2.167,1.357 | |
11 | c1.314,0.826,2.63,1.651,3.942,2.48c0.649,0.41,0.636,1.07-0.026,1.463c-2.018,1.195-4.036,2.39-6.056,3.582 | |
12 | c-0.516,0.304-1.061,0.108-1.217-0.427c-0.106-0.362,0.051-0.694,0.44-0.923c1.314-0.778,2.629-1.554,3.943-2.332 | |
13 | C16.146,25.157,16.498,24.945,16.891,24.711z"/> | |
14 | </g> | |
15 | </g> | |
16 | <path fill="#B3B4B5" d="M27.81,28.286c0,0.498-0.408,0.906-0.906,0.906h-6.435c-0.498,0-0.906-0.408-0.906-0.906v-0.069 | |
17 | c0-0.498,0.408-0.906,0.906-0.906h6.435c0.498,0,0.906,0.408,0.906,0.906V28.286z"/> | |
18 | </g> | |
19 | <path fill="#B3B4B5" d="M45.78,8.193H2.22c-0.624,0-1.132,0.508-1.132,1.132v30.483h45.824V9.325 | |
20 | C46.912,8.7,46.404,8.193,45.78,8.193z M3.086,10.191h41.828v0.067v1.373H3.086v-1.373V10.191z M3.086,36.344V13.628h41.828v22.716 | |
21 | H3.086z"/> | |
22 | </g> | |
23 | </svg> |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="32px" height="32px" viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve"> | |
5 | <g id="_x38_lzLR4.tif_1_"> | |
6 | <g> | |
7 | <path style="fill:#FFFFFF;" d="M23.983,19.968c-2.237,0-4.43,0-6.68,0c0,0.35-0.011,0.693,0.004,1.036 | |
8 | c0.01,0.223-0.067,0.295-0.289,0.291c-0.206-0.003-0.315-0.043-0.304-0.279c0.015-0.335,0.004-0.672,0.004-1.034 | |
9 | c-2.019,0-4.017,0-6.062,0c0,0.557-0.017,1.112,0.007,1.666c0.012,0.282-0.067,0.387-0.355,0.418 | |
10 | c-0.296,0.031-0.583,0.146-0.934,0.239c0-0.173,0-0.315,0-0.457c0.001-0.96-0.005-1.921,0.009-2.881 | |
11 | c0.004-0.255-0.061-0.343-0.332-0.343c-2.901,0.008-5.803-0.001-8.705,0.009c-0.293,0.001-0.344-0.095-0.344-0.362 | |
12 | C0.009,12.532,0.01,6.792,0,1.052C-0.001,0.746,0.098,0.69,0.377,0.69C7.489,0.697,14.6,0.694,21.711,0.694 | |
13 | c0.19,0,0.382,0.021,0.569-0.001c0.291-0.033,0.351,0.094,0.35,0.361c-0.01,2.216-0.009,4.431-0.008,6.647 | |
14 | c0,0.453,0.023,0.907,0.015,1.36c-0.005,0.255,0.099,0.314,0.339,0.313c2.881-0.007,5.761,0.001,8.641-0.011 | |
15 | c0.311-0.001,0.38,0.091,0.38,0.389C31.99,15.472,31.989,21.192,32,26.91c0.001,0.328-0.087,0.399-0.405,0.398 | |
16 | c-2.838-0.012-5.677-0.009-8.515,0.001c-0.272,0.001-0.385-0.061-0.363-0.349c0.015-0.186-0.024-0.377-0.052-0.565 | |
17 | c-0.041-0.273,0.057-0.395,0.342-0.373c0.263,0.02,0.527,0.016,0.791,0.022c0.181,0.004,0.263-0.072,0.262-0.265 | |
18 | c-0.006-1.203,0.002-2.405-0.007-3.608c-0.004-0.61-0.033-1.219-0.051-1.829C23.999,20.228,23.991,20.115,23.983,19.968z | |
19 | M7.953,7.326c2.249,0,4.477,0,6.722,0c0-0.919,0-1.818,0-2.734c-2.248,0-4.475,0-6.722,0C7.953,5.502,7.953,6.394,7.953,7.326z | |
20 | M17.323,15.999c2.227,0,4.433,0,6.659,0c0-0.919,0-1.817,0-2.735c-2.227,0-4.433,0-6.659,0 | |
21 | C17.323,14.175,17.323,15.067,17.323,15.999z M23.983,16.694c-2.233,0-4.447,0-6.66,0c0,0.9,0,1.771,0,2.651 | |
22 | c2.233,0,4.447,0,6.66,0C23.983,18.444,23.983,17.574,23.983,16.694z M1.31,4.591c0,0.933,0,1.825,0,2.73c2.02,0,4.016,0,6.016,0 | |
23 | c0-0.924,0-1.823,0-2.73C5.312,4.591,3.323,4.591,1.31,4.591z M21.323,7.326c0-0.934,0-1.826,0-2.729c-2.017,0-4.013,0-6.017,0 | |
24 | c0,0.925,0,1.825,0,2.729C17.323,7.326,19.312,7.326,21.323,7.326z M10.675,15.994c2.015,0,4.011,0,6.017,0 | |
25 | c0-0.923,0-1.822,0-2.72c-2.025,0-4.021,0-6.017,0C10.675,14.196,10.675,15.088,10.675,15.994z M30.694,13.265 | |
26 | c-2.031,0-4.019,0-6.025,0c0,0.919,0,1.819,0,2.728c2.017,0,4.014,0,6.025,0C30.694,15.076,30.694,14.185,30.694,13.265z | |
27 | M24.667,23.33c0,0.911,0,1.783,0,2.667c2.016,0,4.011,0,6.017,0c0-0.903,0-1.78,0-2.667C28.666,23.33,26.678,23.33,24.667,23.33z | |
28 | M24.667,19.976c0,0.911,0,1.781,0,2.661c2.023,0,4.025,0,6.027,0c0-0.901,0-1.771,0-2.661 | |
29 | C28.679,19.976,26.683,19.976,24.667,19.976z M24.672,16.683c0,0.912,0,1.782,0,2.667c2.02,0,4.016,0,6.016,0 | |
30 | c0-0.903,0-1.781,0-2.667C28.674,16.683,26.685,16.683,24.672,16.683z M10.676,19.354c2.031,0,4.019,0,6.025,0 | |
31 | c0-0.898,0-1.777,0-2.664c-2.017,0-4.013,0-6.025,0C10.676,17.586,10.676,18.456,10.676,19.354z M1.307,17.328 | |
32 | c2.031,0,4.019,0,6.023,0c0-0.899,0-1.777,0-2.663c-2.018,0-4.014,0-6.023,0C1.307,15.562,1.307,16.432,1.307,17.328z | |
33 | M7.331,11.303c-2.031,0-4.019,0-6.024,0c0,0.899,0,1.777,0,2.663c2.018,0,4.013,0,6.024,0C7.331,13.07,7.331,12.2,7.331,11.303z | |
34 | M7.333,8.02c-2.022,0-4.025,0-6.027,0c0,0.901,0,1.771,0,2.651c2.023,0,4.025,0,6.027,0C7.333,9.77,7.333,8.9,7.333,8.02z | |
35 | M7.954,10.675c0.491,0,0.939,0,1.423,0c0-0.339,0.015-0.654-0.005-0.967C9.357,9.452,9.431,9.36,9.703,9.365 | |
36 | c1.001,0.015,2.003,0.006,3.005,0.006c0.65,0,1.3,0,1.965,0c0-0.477,0-0.912,0-1.353c-2.251,0-4.478,0-6.719,0 | |
37 | C7.954,8.914,7.954,9.784,7.954,10.675z M15.298,8.01c0,0.469,0,0.897,0,1.331c2.023,0,4.025,0,6.025,0c0-0.458,0-0.886,0-1.331 | |
38 | C19.308,8.01,17.313,8.01,15.298,8.01z M9.359,14.658c-0.49,0-0.939,0-1.405,0c0,0.897,0,1.774,0,2.67c0.475,0,0.931,0,1.405,0 | |
39 | C9.359,16.432,9.359,15.555,9.359,14.658z M9.348,13.974c0-0.904,0-1.789,0-2.671c-0.479,0-0.927,0-1.384,0 | |
40 | c0,0.904,0,1.789,0,2.671C8.442,13.974,8.891,13.974,9.348,13.974z"/> | |
41 | <path style="fill:#FFFFFF;" d="M2.021,24.64c-0.555,0-1.067-0.001-1.579,0c-0.43,0.001-0.43,0.003-0.431-0.436 | |
42 | c-0.001-0.837-0.001-0.837,0.848-0.837c0.781,0,1.561,0.006,2.341-0.005c0.127-0.002,0.264-0.057,0.379-0.119 | |
43 | c1.738-0.942,3.867-0.602,5.212,0.839c0.106,0.113,0.191,0.223,0.358,0.067c1.135-1.07,3.335-1.013,4.441,0.058 | |
44 | c0.014,0.013,0.037,0.017,0.094,0.042c0.077-0.073,0.167-0.155,0.253-0.241c1.355-1.362,3.319-1.675,5.037-0.817 | |
45 | c0.207,0.103,0.456,0.163,0.687,0.168c0.865,0.019,1.73,0.021,2.595,0.001c0.305-0.007,0.393,0.103,0.368,0.384 | |
46 | c-0.017,0.189-0.018,0.381-0.003,0.569c0.02,0.247-0.069,0.337-0.322,0.33c-0.535-0.016-1.072-0.005-1.672-0.005 | |
47 | c0.061,0.137,0.095,0.228,0.141,0.313c1.363,2.515-0.033,5.582-2.851,6.256c-1.87,0.447-4.002-0.598-4.801-2.381 | |
48 | c-0.532-1.189-0.545-2.387-0.022-3.581c0.091-0.209,0.059-0.343-0.081-0.507c-0.908-1.062-2.535-1.048-3.401,0.029 | |
49 | c-0.12,0.149-0.143,0.274-0.061,0.456c1.202,2.668-0.487,5.669-3.401,6.054c-2.435,0.321-4.728-1.661-4.774-4.115 | |
50 | c-0.015-0.787,0.13-1.528,0.516-2.218C1.936,24.865,1.964,24.776,2.021,24.64z M13.947,26.977 | |
51 | c0.017,1.697,1.292,3.057,3.065,3.085c1.648,0.026,3.067-1.387,3.044-3.043c-0.025-1.747-1.327-3.033-3.05-3.067 | |
52 | C15.355,23.918,13.949,25.33,13.947,26.977z M8.685,27.029c0.067-1.582-1.239-3.061-3.047-3.081 | |
53 | c-1.638-0.017-3.011,1.377-3.03,3.016c-0.019,1.668,1.291,3.1,3.045,3.09C7.521,30.043,8.712,28.556,8.685,27.029z"/> | |
54 | </g> | |
55 | </g> | |
56 | </svg> |
0 | <?xml version="1.0" encoding="iso-8859-1"?> | |
1 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |
4 | width="48px" height="48px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve"> | |
5 | <g id="_x38_lzLR4.tif_1_"> | |
6 | <g> | |
7 | <path style="fill:#B3B4B5;" d="M35.975,29.953c-3.356,0-6.645,0-10.02,0c0,0.525-0.017,1.04,0.006,1.554 | |
8 | c0.015,0.334-0.101,0.442-0.434,0.437c-0.309-0.005-0.472-0.065-0.456-0.418c0.023-0.503,0.006-1.008,0.006-1.551 | |
9 | c-3.029,0-6.025,0-9.093,0c0,0.835-0.025,1.668,0.01,2.499c0.018,0.423-0.1,0.58-0.533,0.627 | |
10 | c-0.444,0.047-0.875,0.219-1.401,0.359c0-0.26,0-0.473,0-0.686c0.002-1.44-0.008-2.881,0.013-4.321 | |
11 | c0.006-0.383-0.091-0.515-0.498-0.514c-4.352,0.012-8.705-0.002-13.057,0.013c-0.439,0.002-0.516-0.142-0.516-0.543 | |
12 | C0.014,18.798,0.015,10.188,0,1.578C-0.001,1.12,0.147,1.036,0.566,1.036C11.233,1.046,21.9,1.042,32.567,1.041 | |
13 | c0.285,0,0.573,0.031,0.854-0.001c0.437-0.05,0.527,0.141,0.525,0.542c-0.015,3.324-0.013,6.647-0.012,9.971 | |
14 | c0,0.68,0.035,1.36,0.023,2.04c-0.007,0.382,0.149,0.471,0.509,0.47c4.321-0.011,8.641,0.001,12.962-0.017 | |
15 | c0.466-0.002,0.57,0.137,0.57,0.584C47.985,23.209,47.983,31.788,48,40.366c0.001,0.492-0.13,0.599-0.607,0.597 | |
16 | c-4.257-0.018-8.515-0.014-12.772,0.002c-0.408,0.001-0.578-0.092-0.544-0.524c0.022-0.279-0.036-0.566-0.078-0.847 | |
17 | c-0.061-0.409,0.085-0.592,0.513-0.559c0.394,0.03,0.79,0.024,1.186,0.033c0.271,0.006,0.394-0.108,0.393-0.398 | |
18 | c-0.009-1.804,0.003-3.608-0.01-5.412c-0.006-0.915-0.049-1.829-0.077-2.744C35.998,30.343,35.986,30.173,35.975,29.953z | |
19 | M11.929,10.989c3.374,0,6.715,0,10.083,0c0-1.378,0-2.727,0-4.101c-3.372,0-6.713,0-10.083,0 | |
20 | C11.929,8.254,11.929,9.592,11.929,10.989z M25.985,23.999c3.341,0,6.65,0,9.988,0c0-1.378,0-2.726,0-4.102 | |
21 | c-3.34,0-6.649,0-9.988,0C25.985,21.263,25.985,22.601,25.985,23.999z M35.975,25.041c-3.349,0-6.67,0-9.99,0 | |
22 | c0,1.35,0,2.656,0,3.977c3.35,0,6.67,0,9.99,0C35.975,27.667,35.975,26.362,35.975,25.041z M1.965,6.887c0,1.4,0,2.738,0,4.095 | |
23 | c3.03,0,6.024,0,9.024,0c0-1.386,0-2.735,0-4.095C7.968,6.887,4.985,6.887,1.965,6.887z M31.984,10.99c0-1.401,0-2.739,0-4.093 | |
24 | c-3.026,0-6.02,0-9.025,0c0,1.388,0,2.737,0,4.093C25.985,10.99,28.968,10.99,31.984,10.99z M16.013,23.992 | |
25 | c3.023,0,6.017,0,9.026,0c0-1.385,0-2.733,0-4.08c-3.038,0-6.031,0-9.026,0C16.013,21.294,16.013,22.632,16.013,23.992z | |
26 | M46.041,19.898c-3.046,0-6.029,0-9.037,0c0,1.379,0,2.728,0,4.092c3.026,0,6.021,0,9.037,0 | |
27 | C46.041,22.615,46.041,21.278,46.041,19.898z M37.001,34.995c0,1.367,0,2.674,0,4.001c3.024,0,6.017,0,9.025,0 | |
28 | c0-1.354,0-2.67,0-4.001C42.999,34.995,40.017,34.995,37.001,34.995z M37.001,29.964c0,1.366,0,2.672,0,3.991 | |
29 | c3.034,0,6.038,0,9.04,0c0-1.351,0-2.657,0-3.991C43.018,29.964,40.025,29.964,37.001,29.964z M37.008,25.025c0,1.368,0,2.673,0,4 | |
30 | c3.03,0,6.024,0,9.024,0c0-1.354,0-2.672,0-4C43.011,25.025,40.028,25.025,37.008,25.025z M16.014,29.032c3.046,0,6.029,0,9.037,0 | |
31 | c0-1.347,0-2.665,0-3.996c-3.026,0-6.02,0-9.037,0C16.014,26.38,16.014,27.685,16.014,29.032z M1.96,25.992 | |
32 | c3.047,0,6.029,0,9.035,0c0-1.348,0-2.666,0-3.995c-3.027,0-6.021,0-9.035,0C1.96,23.343,1.96,24.648,1.96,25.992z M10.997,16.955 | |
33 | c-3.047,0-6.029,0-9.036,0c0,1.348,0,2.666,0,3.995c3.027,0,6.02,0,9.036,0C10.997,19.605,10.997,18.3,10.997,16.955z | |
34 | M10.999,12.031c-3.033,0-6.038,0-9.04,0c0,1.351,0,2.656,0,3.976c3.034,0,6.038,0,9.04,0 | |
35 | C10.999,14.656,10.999,13.35,10.999,12.031z M11.931,16.013c0.737,0,1.409,0,2.135,0c0-0.508,0.022-0.981-0.007-1.45 | |
36 | c-0.024-0.384,0.088-0.522,0.496-0.515c1.502,0.023,3.005,0.009,4.507,0.009c0.975,0,1.95,0,2.947,0c0-0.715,0-1.368,0-2.03 | |
37 | c-3.376,0-6.717,0-10.078,0C11.931,13.372,11.931,14.677,11.931,16.013z M22.947,12.015c0,0.704,0,1.345,0,1.997 | |
38 | c3.034,0,6.038,0,9.038,0c0-0.687,0-1.329,0-1.997C28.962,12.015,25.97,12.015,22.947,12.015z M14.038,21.987 | |
39 | c-0.735,0-1.408,0-2.108,0c0,1.345,0,2.661,0,4.005c0.713,0,1.396,0,2.108,0C14.038,24.648,14.038,23.333,14.038,21.987z | |
40 | M14.022,20.961c0-1.356,0-2.683,0-4.007c-0.718,0-1.39,0-2.076,0c0,1.356,0,2.683,0,4.007 | |
41 | C12.663,20.961,13.336,20.961,14.022,20.961z"/> | |
42 | <path style="fill:#B3B4B5;" d="M3.032,36.96c-0.833,0-1.601-0.002-2.369,0c-0.645,0.002-0.645,0.004-0.646-0.654 | |
43 | C0.016,35.05,0.016,35.05,1.289,35.05c1.171,0,2.342,0.009,3.512-0.007c0.191-0.003,0.396-0.085,0.568-0.178 | |
44 | c2.607-1.413,5.801-0.903,7.818,1.258c0.159,0.17,0.287,0.335,0.537,0.1c1.703-1.605,5.003-1.52,6.662,0.087 | |
45 | c0.021,0.02,0.056,0.026,0.141,0.063c0.115-0.109,0.251-0.232,0.38-0.361c2.032-2.043,4.979-2.512,7.555-1.225 | |
46 | c0.31,0.155,0.684,0.244,1.031,0.252c1.297,0.029,2.595,0.032,3.892,0.001c0.458-0.011,0.59,0.155,0.552,0.576 | |
47 | c-0.026,0.283-0.027,0.571-0.004,0.854c0.03,0.37-0.103,0.506-0.483,0.495c-0.803-0.024-1.608-0.007-2.508-0.007 | |
48 | c0.091,0.205,0.142,0.342,0.211,0.469c2.044,3.772-0.049,8.373-4.277,9.384c-2.805,0.671-6.003-0.897-7.201-3.572 | |
49 | c-0.798-1.783-0.818-3.581-0.033-5.371c0.137-0.313,0.089-0.515-0.121-0.76c-1.362-1.593-3.802-1.572-5.101,0.044 | |
50 | c-0.18,0.224-0.214,0.411-0.091,0.684c1.803,4.002-0.731,8.504-5.102,9.081c-3.652,0.482-7.092-2.492-7.161-6.172 | |
51 | c-0.022-1.181,0.195-2.292,0.774-3.327C2.904,37.298,2.946,37.165,3.032,36.96z M20.92,40.466 | |
52 | c0.025,2.546,1.938,4.585,4.597,4.627c2.472,0.039,4.601-2.08,4.566-4.565c-0.037-2.621-1.991-4.549-4.575-4.6 | |
53 | C23.032,35.878,20.924,37.995,20.92,40.466z M13.028,40.544c0.1-2.373-1.858-4.592-4.57-4.621 | |
54 | c-2.457-0.026-4.517,2.066-4.545,4.524c-0.028,2.502,1.936,4.65,4.568,4.635C11.281,45.065,13.068,42.834,13.028,40.544z"/> | |
55 | </g> | |
56 | </g> | |
57 | </svg> |
24 | 24 | <link rel="stylesheet" href="script/bootstrap-theme.min.css"> |
25 | 25 | <link rel="stylesheet" type="text/css" href="styles/font-awesome.css" /> |
26 | 26 | <link rel="stylesheet" type="text/css" href="styles/angular-hotkeys.css" /> |
27 | <link rel="stylesheet" type="text/css" href="script/angular-chart.css" /> | |
27 | 28 | |
28 | 29 | <!-- Icons --> |
29 | 30 | <link href="favicon.ico" rel="shortcut icon"> |
47 | 48 | <script type="text/javascript" src="script/jquery.qtip.js"></script> |
48 | 49 | <script type="text/javascript" src="script/cryptojs-sha1.js"></script> |
49 | 50 | <script type="text/javascript" src="script/ZeroClipboard.min.js"></script> |
51 | <!-- angular chart --> | |
52 | <script type="text/javascript" src="script/Chart.js"></script> | |
53 | <script type="text/javascript" src="script/angular-chart.min.js"></script> | |
50 | 54 | </head> |
51 | 55 | |
52 | 56 | <body> |
53 | 57 | <div id="cont"> |
54 | <div class="wrapper"> | |
58 | <div class="wrapper" ng-controller="indexCtrl"> | |
55 | 59 | <header class="head"> |
56 | <a href="#" class="ws-dashboard"><img class="logo animated fadeInDown" src="images/logo-faraday.svg" alt="Faraday home | WS Dashboard"/></a> | |
60 | <a href="#" class="ws-dashboard"><img class="logo animated fadeInDown" title="{{version}}" src="images/logo-faraday.svg" alt="Faraday home | WS Dashboard"/></a> | |
57 | 61 | </header> |
58 | 62 | |
59 | 63 | <div ng-controller="navigationCtrl" ng-include="'scripts/navigation/partials/leftBar.html'"></div> |
66 | 70 | <script type="text/javascript" src="scripts/attachments/providers/attachments.js"></script> |
67 | 71 | <script type="text/javascript" src="scripts/commons/directives/contenteditable.js"></script> |
68 | 72 | <script type="text/javascript" src="scripts/commons/controllers/modal.js"></script> |
73 | <script type="text/javascript" src="scripts/commons/controllers/commercialCtrl.js"></script> | |
69 | 74 | <script type="text/javascript" src="scripts/commons/providers/commons.js"></script> |
70 | 75 | <script type="text/javascript" src="scripts/commons/filters/decodeURIComponent.js"></script> |
71 | 76 | <script type="text/javascript" src="scripts/commons/filters/encodeURIComponent.js"></script> |
73 | 78 | <script type="text/javascript" src="scripts/commons/filters/orderObjectBy.js"></script> |
74 | 79 | <script type="text/javascript" src="scripts/commons/filters/startFrom.js"></script> |
75 | 80 | <script type="text/javascript" src="scripts/commons/filters/integer.js"></script> |
81 | <script type="text/javascript" src="scripts/config/services/config.js"></script> | |
76 | 82 | <script type="text/javascript" src="scripts/csv/providers/csv.js"></script> |
77 | 83 | <script type="text/javascript" src="scripts/fileExporter/directives/download.js"></script> |
78 | 84 | <script type="text/javascript" src="scripts/fileExporter/providers/blob.js"></script> |
84 | 90 | <script type="text/javascript" src="scripts/hosts/controllers/hostsModalNew.js"></script> |
85 | 91 | <script type="text/javascript" src="scripts/hosts/providers/host.js"></script> |
86 | 92 | <script type="text/javascript" src="scripts/hosts/providers/hosts.js"></script> |
93 | <script type="text/javascript" src="scripts/index/controllers/indexCtrl.js"></script> | |
94 | <script type="text/javascript" src="scripts/index/providers/index.js"></script> | |
87 | 95 | <script type="text/javascript" src="scripts/navigation/controllers/navigationCtrl.js"></script> |
88 | 96 | <script type="text/javascript" src="scripts/notes/providers/notes.js"></script> |
89 | 97 | <script type="text/javascript" src="scripts/services/controllers/serviceModalNew.js"></script> |
0 | /*! normalize.css v1.1.3 | MIT License | git.io/normalize */ | |
1 | ||
2 | /* ========================================================================== | |
3 | HTML5 display definitions | |
4 | ========================================================================== */ | |
5 | ||
6 | /** | |
7 | * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. | |
0 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ | |
1 | ||
2 | /** | |
3 | * 1. Set default font family to sans-serif. | |
4 | * 2. Prevent iOS text size adjust after orientation change, without disabling | |
5 | * user zoom. | |
6 | */ | |
7 | ||
8 | html { | |
9 | font-family: sans-serif; /* 1 */ | |
10 | -ms-text-size-adjust: 100%; /* 2 */ | |
11 | -webkit-text-size-adjust: 100%; /* 2 */ | |
12 | } | |
13 | ||
14 | /** | |
15 | * Remove default margin. | |
16 | */ | |
17 | ||
18 | body { | |
19 | margin: 0; | |
20 | } | |
21 | ||
22 | /* HTML5 display definitions | |
23 | ========================================================================== */ | |
24 | ||
25 | /** | |
26 | * Correct `block` display not defined for any HTML5 element in IE 8/9. | |
27 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 | |
28 | * and Firefox. | |
29 | * Correct `block` display not defined for `main` in IE 11. | |
8 | 30 | */ |
9 | 31 | |
10 | 32 | article, |
16 | 38 | header, |
17 | 39 | hgroup, |
18 | 40 | main, |
41 | menu, | |
19 | 42 | nav, |
20 | 43 | section, |
21 | 44 | summary { |
22 | display: block; | |
23 | } | |
24 | ||
25 | /** | |
26 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. | |
45 | display: block; | |
46 | } | |
47 | ||
48 | /** | |
49 | * 1. Correct `inline-block` display not defined in IE 8/9. | |
50 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. | |
27 | 51 | */ |
28 | 52 | |
29 | 53 | audio, |
30 | 54 | canvas, |
55 | progress, | |
31 | 56 | video { |
32 | display: inline-block; | |
33 | *display: inline; | |
34 | *zoom: 1; | |
57 | display: inline-block; /* 1 */ | |
58 | vertical-align: baseline; /* 2 */ | |
35 | 59 | } |
36 | 60 | |
37 | 61 | /** |
40 | 64 | */ |
41 | 65 | |
42 | 66 | audio:not([controls]) { |
43 | display: none; | |
44 | height: 0; | |
45 | } | |
46 | ||
47 | /** | |
48 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. | |
49 | * Known issue: no IE 6 support. | |
50 | */ | |
51 | ||
52 | [hidden] { | |
53 | display: none; | |
54 | } | |
55 | ||
56 | /* ========================================================================== | |
57 | Base | |
58 | ========================================================================== */ | |
59 | ||
60 | /** | |
61 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using | |
62 | * `em` units. | |
63 | * 2. Prevent iOS text size adjust after orientation change, without disabling | |
64 | * user zoom. | |
65 | */ | |
66 | ||
67 | html { | |
68 | font-size: 100%; /* 1 */ | |
69 | -ms-text-size-adjust: 100%; /* 2 */ | |
70 | -webkit-text-size-adjust: 100%; /* 2 */ | |
71 | } | |
72 | ||
73 | /** | |
74 | * Address `font-family` inconsistency between `textarea` and other form | |
75 | * elements. | |
76 | */ | |
77 | ||
78 | html, | |
79 | button, | |
80 | input, | |
81 | select, | |
82 | textarea { | |
83 | font-family: sans-serif; | |
84 | } | |
85 | ||
86 | /** | |
87 | * Address margins handled incorrectly in IE 6/7. | |
88 | */ | |
89 | ||
90 | body { | |
91 | margin: 0; | |
92 | } | |
93 | ||
94 | /* ========================================================================== | |
95 | Links | |
96 | ========================================================================== */ | |
97 | ||
98 | /** | |
99 | * Address `outline` inconsistency between Chrome and other browsers. | |
100 | */ | |
101 | ||
102 | a:focus { | |
103 | outline: thin dotted; | |
67 | display: none; | |
68 | height: 0; | |
69 | } | |
70 | ||
71 | /** | |
72 | * Address `[hidden]` styling not present in IE 8/9/10. | |
73 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. | |
74 | */ | |
75 | ||
76 | [hidden], | |
77 | template { | |
78 | display: none; | |
79 | } | |
80 | ||
81 | /* Links | |
82 | ========================================================================== */ | |
83 | ||
84 | /** | |
85 | * Remove the gray background color from active links in IE 10. | |
86 | */ | |
87 | ||
88 | a { | |
89 | background-color: transparent; | |
104 | 90 | } |
105 | 91 | |
106 | 92 | /** |
109 | 95 | |
110 | 96 | a:active, |
111 | 97 | a:hover { |
112 | outline: 0; | |
113 | } | |
114 | ||
115 | /* ========================================================================== | |
116 | Typography | |
117 | ========================================================================== */ | |
118 | ||
119 | /** | |
120 | * Address font sizes and margins set differently in IE 6/7. | |
121 | * Address font sizes within `section` and `article` in Firefox 4+, Safari 5, | |
122 | * and Chrome. | |
123 | */ | |
124 | ||
125 | h1 { | |
126 | font-size: 2em; | |
127 | margin: 0.67em 0; | |
128 | } | |
129 | ||
130 | h2 { | |
131 | font-size: 1.5em; | |
132 | margin: 0.83em 0; | |
133 | } | |
134 | ||
135 | h3 { | |
136 | font-size: 1.17em; | |
137 | margin: 1em 0; | |
138 | } | |
139 | ||
140 | h4 { | |
141 | font-size: 1em; | |
142 | margin: 1.33em 0; | |
143 | } | |
144 | ||
145 | h5 { | |
146 | font-size: 0.83em; | |
147 | margin: 1.67em 0; | |
148 | } | |
149 | ||
150 | h6 { | |
151 | font-size: 0.67em; | |
152 | margin: 2.33em 0; | |
153 | } | |
154 | ||
155 | /** | |
156 | * Address styling not present in IE 7/8/9, Safari 5, and Chrome. | |
98 | outline: 0; | |
99 | } | |
100 | ||
101 | /* Text-level semantics | |
102 | ========================================================================== */ | |
103 | ||
104 | /** | |
105 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. | |
157 | 106 | */ |
158 | 107 | |
159 | 108 | abbr[title] { |
160 | border-bottom: 1px dotted; | |
161 | } | |
162 | ||
163 | /** | |
164 | * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. | |
109 | border-bottom: 1px dotted; | |
110 | } | |
111 | ||
112 | /** | |
113 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. | |
165 | 114 | */ |
166 | 115 | |
167 | 116 | b, |
168 | 117 | strong { |
169 | font-weight: bold; | |
170 | } | |
171 | ||
172 | blockquote { | |
173 | margin: 1em 40px; | |
174 | } | |
175 | ||
176 | /** | |
177 | * Address styling not present in Safari 5 and Chrome. | |
118 | font-weight: bold; | |
119 | } | |
120 | ||
121 | /** | |
122 | * Address styling not present in Safari and Chrome. | |
178 | 123 | */ |
179 | 124 | |
180 | 125 | dfn { |
181 | font-style: italic; | |
126 | font-style: italic; | |
127 | } | |
128 | ||
129 | /** | |
130 | * Address variable `h1` font-size and margin within `section` and `article` | |
131 | * contexts in Firefox 4+, Safari, and Chrome. | |
132 | */ | |
133 | ||
134 | h1 { | |
135 | font-size: 2em; | |
136 | margin: 0.67em 0; | |
137 | } | |
138 | ||
139 | /** | |
140 | * Address styling not present in IE 8/9. | |
141 | */ | |
142 | ||
143 | mark { | |
144 | background: #ff0; | |
145 | color: #000; | |
146 | } | |
147 | ||
148 | /** | |
149 | * Address inconsistent and variable font size in all browsers. | |
150 | */ | |
151 | ||
152 | small { | |
153 | font-size: 80%; | |
154 | } | |
155 | ||
156 | /** | |
157 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. | |
158 | */ | |
159 | ||
160 | sub, | |
161 | sup { | |
162 | font-size: 75%; | |
163 | line-height: 0; | |
164 | position: relative; | |
165 | vertical-align: baseline; | |
166 | } | |
167 | ||
168 | sup { | |
169 | top: -0.5em; | |
170 | } | |
171 | ||
172 | sub { | |
173 | bottom: -0.25em; | |
174 | } | |
175 | ||
176 | /* Embedded content | |
177 | ========================================================================== */ | |
178 | ||
179 | /** | |
180 | * Remove border when inside `a` element in IE 8/9/10. | |
181 | */ | |
182 | ||
183 | img { | |
184 | border: 0; | |
185 | } | |
186 | ||
187 | /** | |
188 | * Correct overflow not hidden in IE 9/10/11. | |
189 | */ | |
190 | ||
191 | svg:not(:root) { | |
192 | overflow: hidden; | |
193 | } | |
194 | ||
195 | /* Grouping content | |
196 | ========================================================================== */ | |
197 | ||
198 | /** | |
199 | * Address margin not present in IE 8/9 and Safari. | |
200 | */ | |
201 | ||
202 | figure { | |
203 | margin: 1em 40px; | |
182 | 204 | } |
183 | 205 | |
184 | 206 | /** |
185 | 207 | * Address differences between Firefox and other browsers. |
186 | * Known issue: no IE 6/7 normalization. | |
187 | 208 | */ |
188 | 209 | |
189 | 210 | hr { |
190 | -moz-box-sizing: content-box; | |
191 | box-sizing: content-box; | |
192 | height: 0; | |
193 | } | |
194 | ||
195 | /** | |
196 | * Address styling not present in IE 6/7/8/9. | |
197 | */ | |
198 | ||
199 | mark { | |
200 | background: #ff0; | |
201 | color: #000; | |
202 | } | |
203 | ||
204 | /** | |
205 | * Address margins set differently in IE 6/7. | |
206 | */ | |
207 | ||
208 | p, | |
211 | -moz-box-sizing: content-box; | |
212 | box-sizing: content-box; | |
213 | height: 0; | |
214 | } | |
215 | ||
216 | /** | |
217 | * Contain overflow in all browsers. | |
218 | */ | |
219 | ||
209 | 220 | pre { |
210 | margin: 1em 0; | |
211 | } | |
212 | ||
213 | /** | |
214 | * Correct font family set oddly in IE 6, Safari 4/5, and Chrome. | |
221 | overflow: auto; | |
222 | } | |
223 | ||
224 | /** | |
225 | * Address odd `em`-unit font size rendering in all browsers. | |
215 | 226 | */ |
216 | 227 | |
217 | 228 | code, |
218 | 229 | kbd, |
219 | 230 | pre, |
220 | 231 | samp { |
221 | font-family: monospace, serif; | |
222 | _font-family: 'courier new', monospace; | |
223 | font-size: 1em; | |
224 | } | |
225 | ||
226 | /** | |
227 | * Improve readability of pre-formatted text in all browsers. | |
228 | */ | |
229 | ||
230 | pre { | |
231 | white-space: pre; | |
232 | white-space: pre-wrap; | |
233 | word-wrap: break-word; | |
234 | } | |
235 | ||
236 | /** | |
237 | * Address CSS quotes not supported in IE 6/7. | |
238 | */ | |
239 | ||
240 | q { | |
241 | quotes: none; | |
242 | } | |
243 | ||
244 | /** | |
245 | * Address `quotes` property not supported in Safari 4. | |
246 | */ | |
247 | ||
248 | q:before, | |
249 | q:after { | |
250 | content: ''; | |
251 | content: none; | |
252 | } | |
253 | ||
254 | /** | |
255 | * Address inconsistent and variable font size in all browsers. | |
256 | */ | |
257 | ||
258 | small { | |
259 | font-size: 80%; | |
260 | } | |
261 | ||
262 | /** | |
263 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. | |
264 | */ | |
265 | ||
266 | sub, | |
267 | sup { | |
268 | font-size: 75%; | |
269 | line-height: 0; | |
270 | position: relative; | |
271 | vertical-align: baseline; | |
272 | } | |
273 | ||
274 | sup { | |
275 | top: -0.5em; | |
276 | } | |
277 | ||
278 | sub { | |
279 | bottom: -0.25em; | |
280 | } | |
281 | ||
282 | /* ========================================================================== | |
283 | Lists | |
284 | ========================================================================== */ | |
285 | ||
286 | /** | |
287 | * Address margins set differently in IE 6/7. | |
288 | */ | |
289 | ||
290 | dl, | |
291 | menu, | |
292 | ol, | |
293 | ul { | |
294 | margin: 1em 0; | |
295 | } | |
296 | ||
297 | dd { | |
298 | margin: 0 0 0 40px; | |
299 | } | |
300 | ||
301 | /** | |
302 | * Address paddings set differently in IE 6/7. | |
303 | */ | |
304 | ||
305 | menu, | |
306 | ol, | |
307 | ul { | |
308 | padding: 0 0 0 40px; | |
309 | } | |
310 | ||
311 | /** | |
312 | * Correct list images handled incorrectly in IE 7. | |
313 | */ | |
314 | ||
315 | nav ul, | |
316 | nav ol { | |
317 | list-style: none; | |
318 | list-style-image: none; | |
319 | } | |
320 | ||
321 | /* ========================================================================== | |
322 | Embedded content | |
323 | ========================================================================== */ | |
324 | ||
325 | /** | |
326 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. | |
327 | * 2. Improve image quality when scaled in IE 7. | |
328 | */ | |
329 | ||
330 | img { | |
331 | border: 0; /* 1 */ | |
332 | -ms-interpolation-mode: bicubic; /* 2 */ | |
333 | } | |
334 | ||
335 | /** | |
336 | * Correct overflow displayed oddly in IE 9. | |
337 | */ | |
338 | ||
339 | svg:not(:root) { | |
340 | overflow: hidden; | |
341 | } | |
342 | ||
343 | /* ========================================================================== | |
344 | Figures | |
345 | ========================================================================== */ | |
346 | ||
347 | /** | |
348 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. | |
349 | */ | |
350 | ||
351 | figure { | |
352 | margin: 0; | |
353 | } | |
354 | ||
355 | /* ========================================================================== | |
356 | Forms | |
357 | ========================================================================== */ | |
358 | ||
359 | /** | |
360 | * Correct margin displayed oddly in IE 6/7. | |
361 | */ | |
362 | ||
363 | form { | |
364 | margin: 0; | |
365 | } | |
366 | ||
367 | /** | |
368 | * Define consistent border, margin, and padding. | |
369 | */ | |
370 | ||
371 | fieldset { | |
372 | border: 1px solid #c0c0c0; | |
373 | margin: 0 2px; | |
374 | padding: 0.35em 0.625em 0.75em; | |
375 | } | |
376 | ||
377 | /** | |
378 | * 1. Correct color not being inherited in IE 6/7/8/9. | |
379 | * 2. Correct text not wrapping in Firefox 3. | |
380 | * 3. Correct alignment displayed oddly in IE 6/7. | |
381 | */ | |
382 | ||
383 | legend { | |
384 | border: 0; /* 1 */ | |
385 | padding: 0; | |
386 | white-space: normal; /* 2 */ | |
387 | *margin-left: -7px; /* 3 */ | |
388 | } | |
389 | ||
390 | /** | |
391 | * 1. Correct font size not being inherited in all browsers. | |
392 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, | |
393 | * and Chrome. | |
394 | * 3. Improve appearance and consistency in all browsers. | |
232 | font-family: monospace, monospace; | |
233 | font-size: 1em; | |
234 | } | |
235 | ||
236 | /* Forms | |
237 | ========================================================================== */ | |
238 | ||
239 | /** | |
240 | * Known limitation: by default, Chrome and Safari on OS X allow very limited | |
241 | * styling of `select`, unless a `border` property is set. | |
242 | */ | |
243 | ||
244 | /** | |
245 | * 1. Correct color not being inherited. | |
246 | * Known issue: affects color of disabled elements. | |
247 | * 2. Correct font properties not being inherited. | |
248 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. | |
395 | 249 | */ |
396 | 250 | |
397 | 251 | button, |
398 | 252 | input, |
253 | optgroup, | |
399 | 254 | select, |
400 | 255 | textarea { |
401 | font-size: 100%; /* 1 */ | |
402 | margin: 0; /* 2 */ | |
403 | vertical-align: baseline; /* 3 */ | |
404 | *vertical-align: middle; /* 3 */ | |
405 | } | |
406 | ||
407 | /** | |
408 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in | |
409 | * the UA stylesheet. | |
410 | */ | |
411 | ||
412 | button, | |
413 | input { | |
414 | line-height: normal; | |
256 | color: inherit; /* 1 */ | |
257 | font: inherit; /* 2 */ | |
258 | margin: 0; /* 3 */ | |
259 | } | |
260 | ||
261 | /** | |
262 | * Address `overflow` set to `hidden` in IE 8/9/10/11. | |
263 | */ | |
264 | ||
265 | button { | |
266 | overflow: visible; | |
415 | 267 | } |
416 | 268 | |
417 | 269 | /** |
418 | 270 | * Address inconsistent `text-transform` inheritance for `button` and `select`. |
419 | 271 | * All other form control elements do not inherit `text-transform` values. |
420 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. | |
421 | * Correct `select` style inheritance in Firefox 4+ and Opera. | |
272 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. | |
273 | * Correct `select` style inheritance in Firefox. | |
422 | 274 | */ |
423 | 275 | |
424 | 276 | button, |
425 | 277 | select { |
426 | text-transform: none; | |
278 | text-transform: none; | |
427 | 279 | } |
428 | 280 | |
429 | 281 | /** |
432 | 284 | * 2. Correct inability to style clickable `input` types in iOS. |
433 | 285 | * 3. Improve usability and consistency of cursor style between image-type |
434 | 286 | * `input` and others. |
435 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs. | |
436 | * Known issue: inner spacing remains in IE 6. | |
437 | 287 | */ |
438 | 288 | |
439 | 289 | button, |
440 | 290 | html input[type="button"], /* 1 */ |
441 | 291 | input[type="reset"], |
442 | 292 | input[type="submit"] { |
443 | -webkit-appearance: button; /* 2 */ | |
444 | cursor: pointer; /* 3 */ | |
445 | *overflow: visible; /* 4 */ | |
293 | -webkit-appearance: button; /* 2 */ | |
294 | cursor: pointer; /* 3 */ | |
446 | 295 | } |
447 | 296 | |
448 | 297 | /** |
451 | 300 | |
452 | 301 | button[disabled], |
453 | 302 | html input[disabled] { |
454 | cursor: default; | |
455 | } | |
456 | ||
457 | /** | |
458 | * 1. Address box sizing set to content-box in IE 8/9. | |
459 | * 2. Remove excess padding in IE 8/9. | |
460 | * 3. Remove excess padding in IE 7. | |
461 | * Known issue: excess padding remains in IE 6. | |
303 | cursor: default; | |
304 | } | |
305 | ||
306 | /** | |
307 | * Remove inner padding and border in Firefox 4+. | |
308 | */ | |
309 | ||
310 | button::-moz-focus-inner, | |
311 | input::-moz-focus-inner { | |
312 | border: 0; | |
313 | padding: 0; | |
314 | } | |
315 | ||
316 | /** | |
317 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in | |
318 | * the UA stylesheet. | |
319 | */ | |
320 | ||
321 | input { | |
322 | line-height: normal; | |
323 | } | |
324 | ||
325 | /** | |
326 | * It's recommended that you don't attempt to style these elements. | |
327 | * Firefox's implementation doesn't respect box-sizing, padding, or width. | |
328 | * | |
329 | * 1. Address box sizing set to `content-box` in IE 8/9/10. | |
330 | * 2. Remove excess padding in IE 8/9/10. | |
462 | 331 | */ |
463 | 332 | |
464 | 333 | input[type="checkbox"], |
465 | 334 | input[type="radio"] { |
466 | box-sizing: border-box; /* 1 */ | |
467 | padding: 0; /* 2 */ | |
468 | *height: 13px; /* 3 */ | |
469 | *width: 13px; /* 3 */ | |
470 | } | |
471 | ||
472 | /** | |
473 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. | |
474 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome | |
335 | box-sizing: border-box; /* 1 */ | |
336 | padding: 0; /* 2 */ | |
337 | } | |
338 | ||
339 | /** | |
340 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain | |
341 | * `font-size` values of the `input`, it causes the cursor style of the | |
342 | * decrement button to change from `default` to `text`. | |
343 | */ | |
344 | ||
345 | input[type="number"]::-webkit-inner-spin-button, | |
346 | input[type="number"]::-webkit-outer-spin-button { | |
347 | height: auto; | |
348 | } | |
349 | ||
350 | /** | |
351 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. | |
352 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome | |
475 | 353 | * (include `-moz` to future-proof). |
476 | 354 | */ |
477 | 355 | |
478 | 356 | input[type="search"] { |
479 | -webkit-appearance: textfield; /* 1 */ | |
480 | -moz-box-sizing: content-box; | |
481 | -webkit-box-sizing: content-box; /* 2 */ | |
482 | box-sizing: content-box; | |
483 | } | |
484 | ||
485 | /** | |
486 | * Remove inner padding and search cancel button in Safari 5 and Chrome | |
487 | * on OS X. | |
357 | -webkit-appearance: textfield; /* 1 */ | |
358 | -moz-box-sizing: content-box; | |
359 | -webkit-box-sizing: content-box; /* 2 */ | |
360 | box-sizing: content-box; | |
361 | } | |
362 | ||
363 | /** | |
364 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. | |
365 | * Safari (but not Chrome) clips the cancel button when the search input has | |
366 | * padding (and `textfield` appearance). | |
488 | 367 | */ |
489 | 368 | |
490 | 369 | input[type="search"]::-webkit-search-cancel-button, |
491 | 370 | input[type="search"]::-webkit-search-decoration { |
492 | -webkit-appearance: none; | |
493 | } | |
494 | ||
495 | /** | |
496 | * Remove inner padding and border in Firefox 3+. | |
497 | */ | |
498 | ||
499 | button::-moz-focus-inner, | |
500 | input::-moz-focus-inner { | |
501 | border: 0; | |
502 | padding: 0; | |
503 | } | |
504 | ||
505 | /** | |
506 | * 1. Remove default vertical scrollbar in IE 6/7/8/9. | |
507 | * 2. Improve readability and alignment in all browsers. | |
371 | -webkit-appearance: none; | |
372 | } | |
373 | ||
374 | /** | |
375 | * Define consistent border, margin, and padding. | |
376 | */ | |
377 | ||
378 | fieldset { | |
379 | border: 1px solid #c0c0c0; | |
380 | margin: 0 2px; | |
381 | padding: 0.35em 0.625em 0.75em; | |
382 | } | |
383 | ||
384 | /** | |
385 | * 1. Correct `color` not being inherited in IE 8/9/10/11. | |
386 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. | |
387 | */ | |
388 | ||
389 | legend { | |
390 | border: 0; /* 1 */ | |
391 | padding: 0; /* 2 */ | |
392 | } | |
393 | ||
394 | /** | |
395 | * Remove default vertical scrollbar in IE 8/9/10/11. | |
508 | 396 | */ |
509 | 397 | |
510 | 398 | textarea { |
511 | overflow: auto; /* 1 */ | |
512 | vertical-align: top; /* 2 */ | |
513 | } | |
514 | ||
515 | /* ========================================================================== | |
516 | Tables | |
399 | overflow: auto; | |
400 | } | |
401 | ||
402 | /** | |
403 | * Don't inherit the `font-weight` (applied by a rule above). | |
404 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. | |
405 | */ | |
406 | ||
407 | optgroup { | |
408 | font-weight: bold; | |
409 | } | |
410 | ||
411 | /* Tables | |
517 | 412 | ========================================================================== */ |
518 | 413 | |
519 | 414 | /** |
521 | 416 | */ |
522 | 417 | |
523 | 418 | table { |
524 | border-collapse: collapse; | |
525 | border-spacing: 0; | |
526 | } | |
419 | border-collapse: collapse; | |
420 | border-spacing: 0; | |
421 | } | |
422 | ||
423 | td, | |
424 | th { | |
425 | padding: 0; | |
426 | }⏎ |
0 | /*! | |
1 | * Chart.js | |
2 | * http://chartjs.org/ | |
3 | * Version: 1.0.1-beta.3 | |
4 | * | |
5 | * Copyright 2014 Nick Downie | |
6 | * Released under the MIT license | |
7 | * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md | |
8 | */ | |
9 | ||
10 | ||
11 | (function(){ | |
12 | ||
13 | "use strict"; | |
14 | ||
15 | //Declare root variable - window in the browser, global on the server | |
16 | var root = this, | |
17 | previous = root.Chart; | |
18 | ||
19 | //Occupy the global variable of Chart, and create a simple base class | |
20 | var Chart = function(context){ | |
21 | var chart = this; | |
22 | this.canvas = context.canvas; | |
23 | ||
24 | this.ctx = context; | |
25 | ||
26 | //Variables global to the chart | |
27 | var width = this.width = context.canvas.width; | |
28 | var height = this.height = context.canvas.height; | |
29 | this.aspectRatio = this.width / this.height; | |
30 | //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. | |
31 | helpers.retinaScale(this); | |
32 | ||
33 | return this; | |
34 | }; | |
35 | //Globally expose the defaults to allow for user updating/changing | |
36 | Chart.defaults = { | |
37 | global: { | |
38 | // Boolean - Whether to animate the chart | |
39 | animation: true, | |
40 | ||
41 | // Number - Number of animation steps | |
42 | animationSteps: 60, | |
43 | ||
44 | // String - Animation easing effect | |
45 | animationEasing: "easeOutQuart", | |
46 | ||
47 | // Boolean - If we should show the scale at all | |
48 | showScale: true, | |
49 | ||
50 | // Boolean - If we want to override with a hard coded scale | |
51 | scaleOverride: false, | |
52 | ||
53 | // ** Required if scaleOverride is true ** | |
54 | // Number - The number of steps in a hard coded scale | |
55 | scaleSteps: null, | |
56 | // Number - The value jump in the hard coded scale | |
57 | scaleStepWidth: null, | |
58 | // Number - The scale starting value | |
59 | scaleStartValue: null, | |
60 | ||
61 | // String - Colour of the scale line | |
62 | scaleLineColor: "rgba(0,0,0,.1)", | |
63 | ||
64 | // Number - Pixel width of the scale line | |
65 | scaleLineWidth: 1, | |
66 | ||
67 | // Boolean - Whether to show labels on the scale | |
68 | scaleShowLabels: true, | |
69 | ||
70 | // Boolean or a positive integer denoting number of labels to be shown on x axis | |
71 | showXLabels: true, | |
72 | ||
73 | // Interpolated JS string - can access value | |
74 | scaleLabel: "<%=value%>", | |
75 | ||
76 | // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there | |
77 | scaleIntegersOnly: true, | |
78 | ||
79 | // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value | |
80 | scaleBeginAtZero: false, | |
81 | ||
82 | // String - Scale label font declaration for the scale label | |
83 | scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", | |
84 | ||
85 | // Number - Scale label font size in pixels | |
86 | scaleFontSize: 12, | |
87 | ||
88 | // String - Scale label font weight style | |
89 | scaleFontStyle: "normal", | |
90 | ||
91 | // String - Scale label font colour | |
92 | scaleFontColor: "#666", | |
93 | ||
94 | // Boolean - whether or not the chart should be responsive and resize when the browser does. | |
95 | responsive: false, | |
96 | ||
97 | // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container | |
98 | maintainAspectRatio: true, | |
99 | ||
100 | // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove | |
101 | showTooltips: true, | |
102 | ||
103 | // Array - Array of string names to attach tooltip events | |
104 | tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"], | |
105 | ||
106 | // String - Tooltip background colour | |
107 | tooltipFillColor: "rgba(0,0,0,0.8)", | |
108 | ||
109 | // String - Tooltip label font declaration for the scale label | |
110 | tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", | |
111 | ||
112 | // Number - Tooltip label font size in pixels | |
113 | tooltipFontSize: 14, | |
114 | ||
115 | // String - Tooltip font weight style | |
116 | tooltipFontStyle: "normal", | |
117 | ||
118 | // String - Tooltip label font colour | |
119 | tooltipFontColor: "#fff", | |
120 | ||
121 | // String - Tooltip title font declaration for the scale label | |
122 | tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", | |
123 | ||
124 | // Number - Tooltip title font size in pixels | |
125 | tooltipTitleFontSize: 14, | |
126 | ||
127 | // String - Tooltip title font weight style | |
128 | tooltipTitleFontStyle: "bold", | |
129 | ||
130 | // String - Tooltip title font colour | |
131 | tooltipTitleFontColor: "#fff", | |
132 | ||
133 | // Number - pixel width of padding around tooltip text | |
134 | tooltipYPadding: 6, | |
135 | ||
136 | // Number - pixel width of padding around tooltip text | |
137 | tooltipXPadding: 6, | |
138 | ||
139 | // Number - Size of the caret on the tooltip | |
140 | tooltipCaretSize: 8, | |
141 | ||
142 | // Number - Pixel radius of the tooltip border | |
143 | tooltipCornerRadius: 6, | |
144 | ||
145 | // Number - Pixel offset from point x to tooltip edge | |
146 | tooltipXOffset: 10, | |
147 | ||
148 | // String - Template string for single tooltips | |
149 | tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>", | |
150 | ||
151 | // String - Template string for single tooltips | |
152 | multiTooltipTemplate: "<%= value %>", | |
153 | ||
154 | // String - Colour behind the legend colour block | |
155 | multiTooltipKeyBackground: '#fff', | |
156 | ||
157 | // Function - Will fire on animation progression. | |
158 | onAnimationProgress: function(){}, | |
159 | ||
160 | // Function - Will fire on animation completion. | |
161 | onAnimationComplete: function(){} | |
162 | ||
163 | } | |
164 | }; | |
165 | ||
166 | //Create a dictionary of chart types, to allow for extension of existing types | |
167 | Chart.types = {}; | |
168 | ||
169 | //Global Chart helpers object for utility methods and classes | |
170 | var helpers = Chart.helpers = {}; | |
171 | ||
172 | //-- Basic js utility methods | |
173 | var each = helpers.each = function(loopable,callback,self){ | |
174 | var additionalArgs = Array.prototype.slice.call(arguments, 3); | |
175 | // Check to see if null or undefined firstly. | |
176 | if (loopable){ | |
177 | if (loopable.length === +loopable.length){ | |
178 | var i; | |
179 | for (i=0; i<loopable.length; i++){ | |
180 | callback.apply(self,[loopable[i], i].concat(additionalArgs)); | |
181 | } | |
182 | } | |
183 | else{ | |
184 | for (var item in loopable){ | |
185 | callback.apply(self,[loopable[item],item].concat(additionalArgs)); | |
186 | } | |
187 | } | |
188 | } | |
189 | }, | |
190 | clone = helpers.clone = function(obj){ | |
191 | var objClone = {}; | |
192 | each(obj,function(value,key){ | |
193 | if (obj.hasOwnProperty(key)) objClone[key] = value; | |
194 | }); | |
195 | return objClone; | |
196 | }, | |
197 | extend = helpers.extend = function(base){ | |
198 | each(Array.prototype.slice.call(arguments,1), function(extensionObject) { | |
199 | each(extensionObject,function(value,key){ | |
200 | if (extensionObject.hasOwnProperty(key)) base[key] = value; | |
201 | }); | |
202 | }); | |
203 | return base; | |
204 | }, | |
205 | merge = helpers.merge = function(base,master){ | |
206 | //Merge properties in left object over to a shallow clone of object right. | |
207 | var args = Array.prototype.slice.call(arguments,0); | |
208 | args.unshift({}); | |
209 | return extend.apply(null, args); | |
210 | }, | |
211 | indexOf = helpers.indexOf = function(arrayToSearch, item){ | |
212 | if (Array.prototype.indexOf) { | |
213 | return arrayToSearch.indexOf(item); | |
214 | } | |
215 | else{ | |
216 | for (var i = 0; i < arrayToSearch.length; i++) { | |
217 | if (arrayToSearch[i] === item) return i; | |
218 | } | |
219 | return -1; | |
220 | } | |
221 | }, | |
222 | inherits = helpers.inherits = function(extensions){ | |
223 | //Basic javascript inheritance based on the model created in Backbone.js | |
224 | var parent = this; | |
225 | var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); }; | |
226 | ||
227 | var Surrogate = function(){ this.constructor = ChartElement;}; | |
228 | Surrogate.prototype = parent.prototype; | |
229 | ChartElement.prototype = new Surrogate(); | |
230 | ||
231 | ChartElement.extend = inherits; | |
232 | ||
233 | if (extensions) extend(ChartElement.prototype, extensions); | |
234 | ||
235 | ChartElement.__super__ = parent.prototype; | |
236 | ||
237 | return ChartElement; | |
238 | }, | |
239 | noop = helpers.noop = function(){}, | |
240 | uid = helpers.uid = (function(){ | |
241 | var id=0; | |
242 | return function(){ | |
243 | return "chart-" + id++; | |
244 | }; | |
245 | })(), | |
246 | warn = helpers.warn = function(str){ | |
247 | //Method for warning of errors | |
248 | if (window.console && typeof window.console.warn == "function") console.warn(str); | |
249 | }, | |
250 | amd = helpers.amd = (typeof root.define == 'function' && root.define.amd), | |
251 | //-- Math methods | |
252 | isNumber = helpers.isNumber = function(n){ | |
253 | return !isNaN(parseFloat(n)) && isFinite(n); | |
254 | }, | |
255 | max = helpers.max = function(array){ | |
256 | return Math.max.apply( Math, array ); | |
257 | }, | |
258 | min = helpers.min = function(array){ | |
259 | return Math.min.apply( Math, array ); | |
260 | }, | |
261 | cap = helpers.cap = function(valueToCap,maxValue,minValue){ | |
262 | if(isNumber(maxValue)) { | |
263 | if( valueToCap > maxValue ) { | |
264 | return maxValue; | |
265 | } | |
266 | } | |
267 | else if(isNumber(minValue)){ | |
268 | if ( valueToCap < minValue ){ | |
269 | return minValue; | |
270 | } | |
271 | } | |
272 | return valueToCap; | |
273 | }, | |
274 | getDecimalPlaces = helpers.getDecimalPlaces = function(num){ | |
275 | if (num%1!==0 && isNumber(num)){ | |
276 | return num.toString().split(".")[1].length; | |
277 | } | |
278 | else { | |
279 | return 0; | |
280 | } | |
281 | }, | |
282 | toRadians = helpers.radians = function(degrees){ | |
283 | return degrees * (Math.PI/180); | |
284 | }, | |
285 | // Gets the angle from vertical upright to the point about a centre. | |
286 | getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){ | |
287 | var distanceFromXCenter = anglePoint.x - centrePoint.x, | |
288 | distanceFromYCenter = anglePoint.y - centrePoint.y, | |
289 | radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); | |
290 | ||
291 | ||
292 | var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter); | |
293 | ||
294 | //If the segment is in the top left quadrant, we need to add another rotation to the angle | |
295 | if (distanceFromXCenter < 0 && distanceFromYCenter < 0){ | |
296 | angle += Math.PI*2; | |
297 | } | |
298 | ||
299 | return { | |
300 | angle: angle, | |
301 | distance: radialDistanceFromCenter | |
302 | }; | |
303 | }, | |
304 | aliasPixel = helpers.aliasPixel = function(pixelWidth){ | |
305 | return (pixelWidth % 2 === 0) ? 0 : 0.5; | |
306 | }, | |
307 | splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){ | |
308 | //Props to Rob Spencer at scaled innovation for his post on splining between points | |
309 | //http://scaledinnovation.com/analytics/splines/aboutSplines.html | |
310 | var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)), | |
311 | d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)), | |
312 | fa=t*d01/(d01+d12),// scaling factor for triangle Ta | |
313 | fb=t*d12/(d01+d12); | |
314 | return { | |
315 | inner : { | |
316 | x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x), | |
317 | y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y) | |
318 | }, | |
319 | outer : { | |
320 | x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x), | |
321 | y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y) | |
322 | } | |
323 | }; | |
324 | }, | |
325 | calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){ | |
326 | return Math.floor(Math.log(val) / Math.LN10); | |
327 | }, | |
328 | calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){ | |
329 | ||
330 | //Set a minimum step of two - a point at the top of the graph, and a point at the base | |
331 | var minSteps = 2, | |
332 | maxSteps = Math.floor(drawingSize/(textSize * 1.5)), | |
333 | skipFitting = (minSteps >= maxSteps); | |
334 | ||
335 | var maxValue = max(valuesArray), | |
336 | minValue = min(valuesArray); | |
337 | ||
338 | // We need some degree of seperation here to calculate the scales if all the values are the same | |
339 | // Adding/minusing 0.5 will give us a range of 1. | |
340 | if (maxValue === minValue){ | |
341 | maxValue += 0.5; | |
342 | // So we don't end up with a graph with a negative start value if we've said always start from zero | |
343 | if (minValue >= 0.5 && !startFromZero){ | |
344 | minValue -= 0.5; | |
345 | } | |
346 | else{ | |
347 | // Make up a whole number above the values | |
348 | maxValue += 0.5; | |
349 | } | |
350 | } | |
351 | ||
352 | var valueRange = Math.abs(maxValue - minValue), | |
353 | rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange), | |
354 | graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), | |
355 | graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), | |
356 | graphRange = graphMax - graphMin, | |
357 | stepValue = Math.pow(10, rangeOrderOfMagnitude), | |
358 | numberOfSteps = Math.round(graphRange / stepValue); | |
359 | ||
360 | //If we have more space on the graph we'll use it to give more definition to the data | |
361 | while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) { | |
362 | if(numberOfSteps > maxSteps){ | |
363 | stepValue *=2; | |
364 | numberOfSteps = Math.round(graphRange/stepValue); | |
365 | // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps. | |
366 | if (numberOfSteps % 1 !== 0){ | |
367 | skipFitting = true; | |
368 | } | |
369 | } | |
370 | //We can fit in double the amount of scale points on the scale | |
371 | else{ | |
372 | //If user has declared ints only, and the step value isn't a decimal | |
373 | if (integersOnly && rangeOrderOfMagnitude >= 0){ | |
374 | //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float | |
375 | if(stepValue/2 % 1 === 0){ | |
376 | stepValue /=2; | |
377 | numberOfSteps = Math.round(graphRange/stepValue); | |
378 | } | |
379 | //If it would make it a float break out of the loop | |
380 | else{ | |
381 | break; | |
382 | } | |
383 | } | |
384 | //If the scale doesn't have to be an int, make the scale more granular anyway. | |
385 | else{ | |
386 | stepValue /=2; | |
387 | numberOfSteps = Math.round(graphRange/stepValue); | |
388 | } | |
389 | ||
390 | } | |
391 | } | |
392 | ||
393 | if (skipFitting){ | |
394 | numberOfSteps = minSteps; | |
395 | stepValue = graphRange / numberOfSteps; | |
396 | } | |
397 | ||
398 | return { | |
399 | steps : numberOfSteps, | |
400 | stepValue : stepValue, | |
401 | min : graphMin, | |
402 | max : graphMin + (numberOfSteps * stepValue) | |
403 | }; | |
404 | ||
405 | }, | |
406 | /* jshint ignore:start */ | |
407 | // Blows up jshint errors based on the new Function constructor | |
408 | //Templating methods | |
409 | //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ | |
410 | template = helpers.template = function(templateString, valuesObject){ | |
411 | // If templateString is function rather than string-template - call the function for valuesObject | |
412 | if(templateString instanceof Function) | |
413 | { | |
414 | return templateString(valuesObject); | |
415 | } | |
416 | ||
417 | var cache = {}; | |
418 | function tmpl(str, data){ | |
419 | // Figure out if we're getting a template, or if we need to | |
420 | // load the template - and be sure to cache the result. | |
421 | var fn = !/\W/.test(str) ? | |
422 | cache[str] = cache[str] : | |
423 | ||
424 | // Generate a reusable function that will serve as a template | |
425 | // generator (and which will be cached). | |
426 | new Function("obj", | |
427 | "var p=[],print=function(){p.push.apply(p,arguments);};" + | |
428 | ||
429 | // Introduce the data as local variables using with(){} | |
430 | "with(obj){p.push('" + | |
431 | ||
432 | // Convert the template into pure JavaScript | |
433 | str | |
434 | .replace(/[\r\t\n]/g, " ") | |
435 | .split("<%").join("\t") | |
436 | .replace(/((^|%>)[^\t]*)'/g, "$1\r") | |
437 | .replace(/\t=(.*?)%>/g, "',$1,'") | |
438 | .split("\t").join("');") | |
439 | .split("%>").join("p.push('") | |
440 | .split("\r").join("\\'") + | |
441 | "');}return p.join('');" | |
442 | ); | |
443 | ||
444 | // Provide some basic currying to the user | |
445 | return data ? fn( data ) : fn; | |
446 | } | |
447 | return tmpl(templateString,valuesObject); | |
448 | }, | |
449 | /* jshint ignore:end */ | |
450 | generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){ | |
451 | var labelsArray = new Array(numberOfSteps); | |
452 | if (labelTemplateString){ | |
453 | each(labelsArray,function(val,index){ | |
454 | labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))}); | |
455 | }); | |
456 | } | |
457 | return labelsArray; | |
458 | }, | |
459 | //--Animation methods | |
460 | //Easing functions adapted from Robert Penner's easing equations | |
461 | //http://www.robertpenner.com/easing/ | |
462 | easingEffects = helpers.easingEffects = { | |
463 | linear: function (t) { | |
464 | return t; | |
465 | }, | |
466 | easeInQuad: function (t) { | |
467 | return t * t; | |
468 | }, | |
469 | easeOutQuad: function (t) { | |
470 | return -1 * t * (t - 2); | |
471 | }, | |
472 | easeInOutQuad: function (t) { | |
473 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t; | |
474 | return -1 / 2 * ((--t) * (t - 2) - 1); | |
475 | }, | |
476 | easeInCubic: function (t) { | |
477 | return t * t * t; | |
478 | }, | |
479 | easeOutCubic: function (t) { | |
480 | return 1 * ((t = t / 1 - 1) * t * t + 1); | |
481 | }, | |
482 | easeInOutCubic: function (t) { | |
483 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t; | |
484 | return 1 / 2 * ((t -= 2) * t * t + 2); | |
485 | }, | |
486 | easeInQuart: function (t) { | |
487 | return t * t * t * t; | |
488 | }, | |
489 | easeOutQuart: function (t) { | |
490 | return -1 * ((t = t / 1 - 1) * t * t * t - 1); | |
491 | }, | |
492 | easeInOutQuart: function (t) { | |
493 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t; | |
494 | return -1 / 2 * ((t -= 2) * t * t * t - 2); | |
495 | }, | |
496 | easeInQuint: function (t) { | |
497 | return 1 * (t /= 1) * t * t * t * t; | |
498 | }, | |
499 | easeOutQuint: function (t) { | |
500 | return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); | |
501 | }, | |
502 | easeInOutQuint: function (t) { | |
503 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t; | |
504 | return 1 / 2 * ((t -= 2) * t * t * t * t + 2); | |
505 | }, | |
506 | easeInSine: function (t) { | |
507 | return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; | |
508 | }, | |
509 | easeOutSine: function (t) { | |
510 | return 1 * Math.sin(t / 1 * (Math.PI / 2)); | |
511 | }, | |
512 | easeInOutSine: function (t) { | |
513 | return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); | |
514 | }, | |
515 | easeInExpo: function (t) { | |
516 | return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); | |
517 | }, | |
518 | easeOutExpo: function (t) { | |
519 | return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); | |
520 | }, | |
521 | easeInOutExpo: function (t) { | |
522 | if (t === 0) return 0; | |
523 | if (t === 1) return 1; | |
524 | if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1)); | |
525 | return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); | |
526 | }, | |
527 | easeInCirc: function (t) { | |
528 | if (t >= 1) return t; | |
529 | return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); | |
530 | }, | |
531 | easeOutCirc: function (t) { | |
532 | return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); | |
533 | }, | |
534 | easeInOutCirc: function (t) { | |
535 | if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1); | |
536 | return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); | |
537 | }, | |
538 | easeInElastic: function (t) { | |
539 | var s = 1.70158; | |
540 | var p = 0; | |
541 | var a = 1; | |
542 | if (t === 0) return 0; | |
543 | if ((t /= 1) == 1) return 1; | |
544 | if (!p) p = 1 * 0.3; | |
545 | if (a < Math.abs(1)) { | |
546 | a = 1; | |
547 | s = p / 4; | |
548 | } else s = p / (2 * Math.PI) * Math.asin(1 / a); | |
549 | return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); | |
550 | }, | |
551 | easeOutElastic: function (t) { | |
552 | var s = 1.70158; | |
553 | var p = 0; | |
554 | var a = 1; | |
555 | if (t === 0) return 0; | |
556 | if ((t /= 1) == 1) return 1; | |
557 | if (!p) p = 1 * 0.3; | |
558 | if (a < Math.abs(1)) { | |
559 | a = 1; | |
560 | s = p / 4; | |
561 | } else s = p / (2 * Math.PI) * Math.asin(1 / a); | |
562 | return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; | |
563 | }, | |
564 | easeInOutElastic: function (t) { | |
565 | var s = 1.70158; | |
566 | var p = 0; | |
567 | var a = 1; | |
568 | if (t === 0) return 0; | |
569 | if ((t /= 1 / 2) == 2) return 1; | |
570 | if (!p) p = 1 * (0.3 * 1.5); | |
571 | if (a < Math.abs(1)) { | |
572 | a = 1; | |
573 | s = p / 4; | |
574 | } else s = p / (2 * Math.PI) * Math.asin(1 / a); | |
575 | if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); | |
576 | return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; | |
577 | }, | |
578 | easeInBack: function (t) { | |
579 | var s = 1.70158; | |
580 | return 1 * (t /= 1) * t * ((s + 1) * t - s); | |
581 | }, | |
582 | easeOutBack: function (t) { | |
583 | var s = 1.70158; | |
584 | return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); | |
585 | }, | |
586 | easeInOutBack: function (t) { | |
587 | var s = 1.70158; | |
588 | if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); | |
589 | return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); | |
590 | }, | |
591 | easeInBounce: function (t) { | |
592 | return 1 - easingEffects.easeOutBounce(1 - t); | |
593 | }, | |
594 | easeOutBounce: function (t) { | |
595 | if ((t /= 1) < (1 / 2.75)) { | |
596 | return 1 * (7.5625 * t * t); | |
597 | } else if (t < (2 / 2.75)) { | |
598 | return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); | |
599 | } else if (t < (2.5 / 2.75)) { | |
600 | return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); | |
601 | } else { | |
602 | return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); | |
603 | } | |
604 | }, | |
605 | easeInOutBounce: function (t) { | |
606 | if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5; | |
607 | return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; | |
608 | } | |
609 | }, | |
610 | //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ | |
611 | requestAnimFrame = helpers.requestAnimFrame = (function(){ | |
612 | return window.requestAnimationFrame || | |
613 | window.webkitRequestAnimationFrame || | |
614 | window.mozRequestAnimationFrame || | |
615 | window.oRequestAnimationFrame || | |
616 | window.msRequestAnimationFrame || | |
617 | function(callback) { | |
618 | return window.setTimeout(callback, 1000 / 60); | |
619 | }; | |
620 | })(), | |
621 | cancelAnimFrame = helpers.cancelAnimFrame = (function(){ | |
622 | return window.cancelAnimationFrame || | |
623 | window.webkitCancelAnimationFrame || | |
624 | window.mozCancelAnimationFrame || | |
625 | window.oCancelAnimationFrame || | |
626 | window.msCancelAnimationFrame || | |
627 | function(callback) { | |
628 | return window.clearTimeout(callback, 1000 / 60); | |
629 | }; | |
630 | })(), | |
631 | animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){ | |
632 | ||
633 | var currentStep = 0, | |
634 | easingFunction = easingEffects[easingString] || easingEffects.linear; | |
635 | ||
636 | var animationFrame = function(){ | |
637 | currentStep++; | |
638 | var stepDecimal = currentStep/totalSteps; | |
639 | var easeDecimal = easingFunction(stepDecimal); | |
640 | ||
641 | callback.call(chartInstance,easeDecimal,stepDecimal, currentStep); | |
642 | onProgress.call(chartInstance,easeDecimal,stepDecimal); | |
643 | if (currentStep < totalSteps){ | |
644 | chartInstance.animationFrame = requestAnimFrame(animationFrame); | |
645 | } else{ | |
646 | onComplete.apply(chartInstance); | |
647 | } | |
648 | }; | |
649 | requestAnimFrame(animationFrame); | |
650 | }, | |
651 | //-- DOM methods | |
652 | getRelativePosition = helpers.getRelativePosition = function(evt){ | |
653 | var mouseX, mouseY; | |
654 | var e = evt.originalEvent || evt, | |
655 | canvas = evt.currentTarget || evt.srcElement, | |
656 | boundingRect = canvas.getBoundingClientRect(); | |
657 | ||
658 | if (e.touches){ | |
659 | mouseX = e.touches[0].clientX - boundingRect.left; | |
660 | mouseY = e.touches[0].clientY - boundingRect.top; | |
661 | ||
662 | } | |
663 | else{ | |
664 | mouseX = e.clientX - boundingRect.left; | |
665 | mouseY = e.clientY - boundingRect.top; | |
666 | } | |
667 | ||
668 | return { | |
669 | x : mouseX, | |
670 | y : mouseY | |
671 | }; | |
672 | ||
673 | }, | |
674 | addEvent = helpers.addEvent = function(node,eventType,method){ | |
675 | if (node.addEventListener){ | |
676 | node.addEventListener(eventType,method); | |
677 | } else if (node.attachEvent){ | |
678 | node.attachEvent("on"+eventType, method); | |
679 | } else { | |
680 | node["on"+eventType] = method; | |
681 | } | |
682 | }, | |
683 | removeEvent = helpers.removeEvent = function(node, eventType, handler){ | |
684 | if (node.removeEventListener){ | |
685 | node.removeEventListener(eventType, handler, false); | |
686 | } else if (node.detachEvent){ | |
687 | node.detachEvent("on"+eventType,handler); | |
688 | } else{ | |
689 | node["on" + eventType] = noop; | |
690 | } | |
691 | }, | |
692 | bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){ | |
693 | // Create the events object if it's not already present | |
694 | if (!chartInstance.events) chartInstance.events = {}; | |
695 | ||
696 | each(arrayOfEvents,function(eventName){ | |
697 | chartInstance.events[eventName] = function(){ | |
698 | handler.apply(chartInstance, arguments); | |
699 | }; | |
700 | addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]); | |
701 | }); | |
702 | }, | |
703 | unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) { | |
704 | each(arrayOfEvents, function(handler,eventName){ | |
705 | removeEvent(chartInstance.chart.canvas, eventName, handler); | |
706 | }); | |
707 | }, | |
708 | getMaximumWidth = helpers.getMaximumWidth = function(domNode){ | |
709 | var container = domNode.parentNode; | |
710 | // TODO = check cross browser stuff with this. | |
711 | return container.clientWidth; | |
712 | }, | |
713 | getMaximumHeight = helpers.getMaximumHeight = function(domNode){ | |
714 | var container = domNode.parentNode; | |
715 | // TODO = check cross browser stuff with this. | |
716 | return container.clientHeight; | |
717 | }, | |
718 | getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support | |
719 | retinaScale = helpers.retinaScale = function(chart){ | |
720 | var ctx = chart.ctx, | |
721 | width = chart.canvas.width, | |
722 | height = chart.canvas.height; | |
723 | //console.log(width + " x " + height); | |
724 | if (window.devicePixelRatio) { | |
725 | ctx.canvas.style.width = width + "px"; | |
726 | ctx.canvas.style.height = height + "px"; | |
727 | ctx.canvas.height = height * window.devicePixelRatio; | |
728 | ctx.canvas.width = width * window.devicePixelRatio; | |
729 | ctx.scale(window.devicePixelRatio, window.devicePixelRatio); | |
730 | } | |
731 | }, | |
732 | //-- Canvas methods | |
733 | clear = helpers.clear = function(chart){ | |
734 | chart.ctx.clearRect(0,0,chart.width,chart.height); | |
735 | }, | |
736 | fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){ | |
737 | return fontStyle + " " + pixelSize+"px " + fontFamily; | |
738 | }, | |
739 | longestText = helpers.longestText = function(ctx,font,arrayOfStrings){ | |
740 | ctx.font = font; | |
741 | var longest = 0; | |
742 | each(arrayOfStrings,function(string){ | |
743 | var textWidth = ctx.measureText(string).width; | |
744 | longest = (textWidth > longest) ? textWidth : longest; | |
745 | }); | |
746 | return longest; | |
747 | }, | |
748 | drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){ | |
749 | ctx.beginPath(); | |
750 | ctx.moveTo(x + radius, y); | |
751 | ctx.lineTo(x + width - radius, y); | |
752 | ctx.quadraticCurveTo(x + width, y, x + width, y + radius); | |
753 | ctx.lineTo(x + width, y + height - radius); | |
754 | ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); | |
755 | ctx.lineTo(x + radius, y + height); | |
756 | ctx.quadraticCurveTo(x, y + height, x, y + height - radius); | |
757 | ctx.lineTo(x, y + radius); | |
758 | ctx.quadraticCurveTo(x, y, x + radius, y); | |
759 | ctx.closePath(); | |
760 | }; | |
761 | ||
762 | ||
763 | //Store a reference to each instance - allowing us to globally resize chart instances on window resize. | |
764 | //Destroy method on the chart will remove the instance of the chart from this reference. | |
765 | Chart.instances = {}; | |
766 | ||
767 | Chart.Type = function(data,options,chart){ | |
768 | this.options = options; | |
769 | this.chart = chart; | |
770 | this.id = uid(); | |
771 | //Add the chart instance to the global namespace | |
772 | Chart.instances[this.id] = this; | |
773 | ||
774 | // Initialize is always called when a chart type is created | |
775 | // By default it is a no op, but it should be extended | |
776 | if (options.responsive){ | |
777 | this.resize(); | |
778 | } | |
779 | this.initialize.call(this,data); | |
780 | }; | |
781 | ||
782 | //Core methods that'll be a part of every chart type | |
783 | extend(Chart.Type.prototype,{ | |
784 | initialize : function(){return this;}, | |
785 | clear : function(){ | |
786 | clear(this.chart); | |
787 | return this; | |
788 | }, | |
789 | stop : function(){ | |
790 | // Stops any current animation loop occuring | |
791 | helpers.cancelAnimFrame.call(root, this.animationFrame); | |
792 | return this; | |
793 | }, | |
794 | resize : function(callback){ | |
795 | this.stop(); | |
796 | var canvas = this.chart.canvas, | |
797 | newWidth = getMaximumWidth(this.chart.canvas), | |
798 | newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas); | |
799 | ||
800 | canvas.width = this.chart.width = newWidth; | |
801 | canvas.height = this.chart.height = newHeight; | |
802 | ||
803 | retinaScale(this.chart); | |
804 | ||
805 | if (typeof callback === "function"){ | |
806 | callback.apply(this, Array.prototype.slice.call(arguments, 1)); | |
807 | } | |
808 | return this; | |
809 | }, | |
810 | reflow : noop, | |
811 | render : function(reflow){ | |
812 | if (reflow){ | |
813 | this.reflow(); | |
814 | } | |
815 | if (this.options.animation && !reflow){ | |
816 | helpers.animationLoop( | |
817 | this.draw, | |
818 | this.options.animationSteps, | |
819 | this.options.animationEasing, | |
820 | this.options.onAnimationProgress, | |
821 | this.options.onAnimationComplete, | |
822 | this | |
823 | ); | |
824 | } | |
825 | else{ | |
826 | this.draw(); | |
827 | this.options.onAnimationComplete.call(this); | |
828 | } | |
829 | return this; | |
830 | }, | |
831 | generateLegend : function(){ | |
832 | return template(this.options.legendTemplate,this); | |
833 | }, | |
834 | destroy : function(){ | |
835 | this.clear(); | |
836 | unbindEvents(this, this.events); | |
837 | delete Chart.instances[this.id]; | |
838 | }, | |
839 | showTooltip : function(ChartElements, forceRedraw){ | |
840 | // Only redraw the chart if we've actually changed what we're hovering on. | |
841 | if (typeof this.activeElements === 'undefined') this.activeElements = []; | |
842 | ||
843 | var isChanged = (function(Elements){ | |
844 | var changed = false; | |
845 | ||
846 | if (Elements.length !== this.activeElements.length){ | |
847 | changed = true; | |
848 | return changed; | |
849 | } | |
850 | ||
851 | each(Elements, function(element, index){ | |
852 | if (element !== this.activeElements[index]){ | |
853 | changed = true; | |
854 | } | |
855 | }, this); | |
856 | return changed; | |
857 | }).call(this, ChartElements); | |
858 | ||
859 | if (!isChanged && !forceRedraw){ | |
860 | return; | |
861 | } | |
862 | else{ | |
863 | this.activeElements = ChartElements; | |
864 | } | |
865 | this.draw(); | |
866 | if (ChartElements.length > 0){ | |
867 | // If we have multiple datasets, show a MultiTooltip for all of the data points at that index | |
868 | if (this.datasets && this.datasets.length > 1) { | |
869 | var dataArray, | |
870 | dataIndex; | |
871 | ||
872 | for (var i = this.datasets.length - 1; i >= 0; i--) { | |
873 | dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments; | |
874 | dataIndex = indexOf(dataArray, ChartElements[0]); | |
875 | if (dataIndex !== -1){ | |
876 | break; | |
877 | } | |
878 | } | |
879 | var tooltipLabels = [], | |
880 | tooltipColors = [], | |
881 | medianPosition = (function(index) { | |
882 | ||
883 | // Get all the points at that particular index | |
884 | var Elements = [], | |
885 | dataCollection, | |
886 | xPositions = [], | |
887 | yPositions = [], | |
888 | xMax, | |
889 | yMax, | |
890 | xMin, | |
891 | yMin; | |
892 | helpers.each(this.datasets, function(dataset){ | |
893 | dataCollection = dataset.points || dataset.bars || dataset.segments; | |
894 | if (dataCollection[dataIndex]){ | |
895 | Elements.push(dataCollection[dataIndex]); | |
896 | } | |
897 | }); | |
898 | ||
899 | helpers.each(Elements, function(element) { | |
900 | xPositions.push(element.x); | |
901 | yPositions.push(element.y); | |
902 | ||
903 | ||
904 | //Include any colour information about the element | |
905 | tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element)); | |
906 | tooltipColors.push({ | |
907 | fill: element._saved.fillColor || element.fillColor, | |
908 | stroke: element._saved.strokeColor || element.strokeColor | |
909 | }); | |
910 | ||
911 | }, this); | |
912 | ||
913 | yMin = min(yPositions); | |
914 | yMax = max(yPositions); | |
915 | ||
916 | xMin = min(xPositions); | |
917 | xMax = max(xPositions); | |
918 | ||
919 | return { | |
920 | x: (xMin > this.chart.width/2) ? xMin : xMax, | |
921 | y: (yMin + yMax)/2 | |
922 | }; | |
923 | }).call(this, dataIndex); | |
924 | ||
925 | new Chart.MultiTooltip({ | |
926 | x: medianPosition.x, | |
927 | y: medianPosition.y, | |
928 | xPadding: this.options.tooltipXPadding, | |
929 | yPadding: this.options.tooltipYPadding, | |
930 | xOffset: this.options.tooltipXOffset, | |
931 | fillColor: this.options.tooltipFillColor, | |
932 | textColor: this.options.tooltipFontColor, | |
933 | fontFamily: this.options.tooltipFontFamily, | |
934 | fontStyle: this.options.tooltipFontStyle, | |
935 | fontSize: this.options.tooltipFontSize, | |
936 | titleTextColor: this.options.tooltipTitleFontColor, | |
937 | titleFontFamily: this.options.tooltipTitleFontFamily, | |
938 | titleFontStyle: this.options.tooltipTitleFontStyle, | |
939 | titleFontSize: this.options.tooltipTitleFontSize, | |
940 | cornerRadius: this.options.tooltipCornerRadius, | |
941 | labels: tooltipLabels, | |
942 | legendColors: tooltipColors, | |
943 | legendColorBackground : this.options.multiTooltipKeyBackground, | |
944 | title: ChartElements[0].label, | |
945 | chart: this.chart, | |
946 | ctx: this.chart.ctx | |
947 | }).draw(); | |
948 | ||
949 | } else { | |
950 | each(ChartElements, function(Element) { | |
951 | var tooltipPosition = Element.tooltipPosition(); | |
952 | new Chart.Tooltip({ | |
953 | x: Math.round(tooltipPosition.x), | |
954 | y: Math.round(tooltipPosition.y), | |
955 | xPadding: this.options.tooltipXPadding, | |
956 | yPadding: this.options.tooltipYPadding, | |
957 | fillColor: this.options.tooltipFillColor, | |
958 | textColor: this.options.tooltipFontColor, | |
959 | fontFamily: this.options.tooltipFontFamily, | |
960 | fontStyle: this.options.tooltipFontStyle, | |
961 | fontSize: this.options.tooltipFontSize, | |
962 | caretHeight: this.options.tooltipCaretSize, | |
963 | cornerRadius: this.options.tooltipCornerRadius, | |
964 | text: template(this.options.tooltipTemplate, Element), | |
965 | chart: this.chart | |
966 | }).draw(); | |
967 | }, this); | |
968 | } | |
969 | } | |
970 | return this; | |
971 | }, | |
972 | toBase64Image : function(){ | |
973 | return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); | |
974 | } | |
975 | }); | |
976 | ||
977 | Chart.Type.extend = function(extensions){ | |
978 | ||
979 | var parent = this; | |
980 | ||
981 | var ChartType = function(){ | |
982 | return parent.apply(this,arguments); | |
983 | }; | |
984 | ||
985 | //Copy the prototype object of the this class | |
986 | ChartType.prototype = clone(parent.prototype); | |
987 | //Now overwrite some of the properties in the base class with the new extensions | |
988 | extend(ChartType.prototype, extensions); | |
989 | ||
990 | ChartType.extend = Chart.Type.extend; | |
991 | ||
992 | if (extensions.name || parent.prototype.name){ | |
993 | ||
994 | var chartName = extensions.name || parent.prototype.name; | |
995 | //Assign any potential default values of the new chart type | |
996 | ||
997 | //If none are defined, we'll use a clone of the chart type this is being extended from. | |
998 | //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart | |
999 | //doesn't define some defaults of their own. | |
1000 | ||
1001 | var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; | |
1002 | ||
1003 | Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults); | |
1004 | ||
1005 | Chart.types[chartName] = ChartType; | |
1006 | ||
1007 | //Register this new chart type in the Chart prototype | |
1008 | Chart.prototype[chartName] = function(data,options){ | |
1009 | var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {}); | |
1010 | return new ChartType(data,config,this); | |
1011 | }; | |
1012 | } else{ | |
1013 | warn("Name not provided for this chart, so it hasn't been registered"); | |
1014 | } | |
1015 | return parent; | |
1016 | }; | |
1017 | ||
1018 | Chart.Element = function(configuration){ | |
1019 | extend(this,configuration); | |
1020 | this.initialize.apply(this,arguments); | |
1021 | this.save(); | |
1022 | }; | |
1023 | extend(Chart.Element.prototype,{ | |
1024 | initialize : function(){}, | |
1025 | restore : function(props){ | |
1026 | if (!props){ | |
1027 | extend(this,this._saved); | |
1028 | } else { | |
1029 | each(props,function(key){ | |
1030 | this[key] = this._saved[key]; | |
1031 | },this); | |
1032 | } | |
1033 | return this; | |
1034 | }, | |
1035 | save : function(){ | |
1036 | this._saved = clone(this); | |
1037 | delete this._saved._saved; | |
1038 | return this; | |
1039 | }, | |
1040 | update : function(newProps){ | |
1041 | each(newProps,function(value,key){ | |
1042 | this._saved[key] = this[key]; | |
1043 | this[key] = value; | |
1044 | },this); | |
1045 | return this; | |
1046 | }, | |
1047 | transition : function(props,ease){ | |
1048 | each(props,function(value,key){ | |
1049 | this[key] = ((value - this._saved[key]) * ease) + this._saved[key]; | |
1050 | },this); | |
1051 | return this; | |
1052 | }, | |
1053 | tooltipPosition : function(){ | |
1054 | return { | |
1055 | x : this.x, | |
1056 | y : this.y | |
1057 | }; | |
1058 | } | |
1059 | }); | |
1060 | ||
1061 | Chart.Element.extend = inherits; | |
1062 | ||
1063 | ||
1064 | Chart.Point = Chart.Element.extend({ | |
1065 | display: true, | |
1066 | inRange: function(chartX,chartY){ | |
1067 | var hitDetectionRange = this.hitDetectionRadius + this.radius; | |
1068 | return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2)); | |
1069 | }, | |
1070 | draw : function(){ | |
1071 | if (this.display){ | |
1072 | var ctx = this.ctx; | |
1073 | ctx.beginPath(); | |
1074 | ||
1075 | ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2); | |
1076 | ctx.closePath(); | |
1077 | ||
1078 | ctx.strokeStyle = this.strokeColor; | |
1079 | ctx.lineWidth = this.strokeWidth; | |
1080 | ||
1081 | ctx.fillStyle = this.fillColor; | |
1082 | ||
1083 | ctx.fill(); | |
1084 | ctx.stroke(); | |
1085 | } | |
1086 | ||
1087 | ||
1088 | //Quick debug for bezier curve splining | |
1089 | //Highlights control points and the line between them. | |
1090 | //Handy for dev - stripped in the min version. | |
1091 | ||
1092 | // ctx.save(); | |
1093 | // ctx.fillStyle = "black"; | |
1094 | // ctx.strokeStyle = "black" | |
1095 | // ctx.beginPath(); | |
1096 | // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2); | |
1097 | // ctx.fill(); | |
1098 | ||
1099 | // ctx.beginPath(); | |
1100 | // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2); | |
1101 | // ctx.fill(); | |
1102 | ||
1103 | // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y); | |
1104 | // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y); | |
1105 | // ctx.stroke(); | |
1106 | ||
1107 | // ctx.restore(); | |
1108 | ||
1109 | ||
1110 | ||
1111 | } | |
1112 | }); | |
1113 | ||
1114 | Chart.Arc = Chart.Element.extend({ | |
1115 | inRange : function(chartX,chartY){ | |
1116 | ||
1117 | var pointRelativePosition = helpers.getAngleFromPoint(this, { | |
1118 | x: chartX, | |
1119 | y: chartY | |
1120 | }); | |
1121 | ||
1122 | //Check if within the range of the open/close angle | |
1123 | var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle), | |
1124 | withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius); | |
1125 | ||
1126 | return (betweenAngles && withinRadius); | |
1127 | //Ensure within the outside of the arc centre, but inside arc outer | |
1128 | }, | |
1129 | tooltipPosition : function(){ | |
1130 | var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2), | |
1131 | rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius; | |
1132 | return { | |
1133 | x : this.x + (Math.cos(centreAngle) * rangeFromCentre), | |
1134 | y : this.y + (Math.sin(centreAngle) * rangeFromCentre) | |
1135 | }; | |
1136 | }, | |
1137 | draw : function(animationPercent){ | |
1138 | ||
1139 | var easingDecimal = animationPercent || 1; | |
1140 | ||
1141 | var ctx = this.ctx; | |
1142 | ||
1143 | ctx.beginPath(); | |
1144 | ||
1145 | ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle); | |
1146 | ||
1147 | ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true); | |
1148 | ||
1149 | ctx.closePath(); | |
1150 | ctx.strokeStyle = this.strokeColor; | |
1151 | ctx.lineWidth = this.strokeWidth; | |
1152 | ||
1153 | ctx.fillStyle = this.fillColor; | |
1154 | ||
1155 | ctx.fill(); | |
1156 | ctx.lineJoin = 'bevel'; | |
1157 | ||
1158 | if (this.showStroke){ | |
1159 | ctx.stroke(); | |
1160 | } | |
1161 | } | |
1162 | }); | |
1163 | ||
1164 | Chart.Rectangle = Chart.Element.extend({ | |
1165 | draw : function(){ | |
1166 | var ctx = this.ctx, | |
1167 | halfWidth = this.width/2, | |
1168 | leftX = this.x - halfWidth, | |
1169 | rightX = this.x + halfWidth, | |
1170 | top = this.base - (this.base - this.y), | |
1171 | halfStroke = this.strokeWidth / 2; | |
1172 | ||
1173 | // Canvas doesn't allow us to stroke inside the width so we can | |
1174 | // adjust the sizes to fit if we're setting a stroke on the line | |
1175 | if (this.showStroke){ | |
1176 | leftX += halfStroke; | |
1177 | rightX -= halfStroke; | |
1178 | top += halfStroke; | |
1179 | } | |
1180 | ||
1181 | ctx.beginPath(); | |
1182 | ||
1183 | ctx.fillStyle = this.fillColor; | |
1184 | ctx.strokeStyle = this.strokeColor; | |
1185 | ctx.lineWidth = this.strokeWidth; | |
1186 | ||
1187 | // It'd be nice to keep this class totally generic to any rectangle | |
1188 | // and simply specify which border to miss out. | |
1189 | ctx.moveTo(leftX, this.base); | |
1190 | ctx.lineTo(leftX, top); | |
1191 | ctx.lineTo(rightX, top); | |
1192 | ctx.lineTo(rightX, this.base); | |
1193 | ctx.fill(); | |
1194 | if (this.showStroke){ | |
1195 | ctx.stroke(); | |
1196 | } | |
1197 | }, | |
1198 | height : function(){ | |
1199 | return this.base - this.y; | |
1200 | }, | |
1201 | inRange : function(chartX,chartY){ | |
1202 | return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base); | |
1203 | } | |
1204 | }); | |
1205 | ||
1206 | Chart.Tooltip = Chart.Element.extend({ | |
1207 | draw : function(){ | |
1208 | ||
1209 | var ctx = this.chart.ctx; | |
1210 | ||
1211 | ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); | |
1212 | ||
1213 | this.xAlign = "center"; | |
1214 | this.yAlign = "above"; | |
1215 | ||
1216 | //Distance between the actual element.y position and the start of the tooltip caret | |
1217 | var caretPadding = 2; | |
1218 | ||
1219 | var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding, | |
1220 | tooltipRectHeight = this.fontSize + 2*this.yPadding, | |
1221 | tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding; | |
1222 | ||
1223 | if (this.x + tooltipWidth/2 >this.chart.width){ | |
1224 | this.xAlign = "left"; | |
1225 | } else if (this.x - tooltipWidth/2 < 0){ | |
1226 | this.xAlign = "right"; | |
1227 | } | |
1228 | ||
1229 | if (this.y - tooltipHeight < 0){ | |
1230 | this.yAlign = "below"; | |
1231 | } | |
1232 | ||
1233 | ||
1234 | var tooltipX = this.x - tooltipWidth/2, | |
1235 | tooltipY = this.y - tooltipHeight; | |
1236 | ||
1237 | ctx.fillStyle = this.fillColor; | |
1238 | ||
1239 | switch(this.yAlign) | |
1240 | { | |
1241 | case "above": | |
1242 | //Draw a caret above the x/y | |
1243 | ctx.beginPath(); | |
1244 | ctx.moveTo(this.x,this.y - caretPadding); | |
1245 | ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight)); | |
1246 | ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight)); | |
1247 | ctx.closePath(); | |
1248 | ctx.fill(); | |
1249 | break; | |
1250 | case "below": | |
1251 | tooltipY = this.y + caretPadding + this.caretHeight; | |
1252 | //Draw a caret below the x/y | |
1253 | ctx.beginPath(); | |
1254 | ctx.moveTo(this.x, this.y + caretPadding); | |
1255 | ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight); | |
1256 | ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight); | |
1257 | ctx.closePath(); | |
1258 | ctx.fill(); | |
1259 | break; | |
1260 | } | |
1261 | ||
1262 | switch(this.xAlign) | |
1263 | { | |
1264 | case "left": | |
1265 | tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight); | |
1266 | break; | |
1267 | case "right": | |
1268 | tooltipX = this.x - (this.cornerRadius + this.caretHeight); | |
1269 | break; | |
1270 | } | |
1271 | ||
1272 | drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius); | |
1273 | ||
1274 | ctx.fill(); | |
1275 | ||
1276 | ctx.fillStyle = this.textColor; | |
1277 | ctx.textAlign = "center"; | |
1278 | ctx.textBaseline = "middle"; | |
1279 | ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2); | |
1280 | } | |
1281 | }); | |
1282 | ||
1283 | Chart.MultiTooltip = Chart.Element.extend({ | |
1284 | initialize : function(){ | |
1285 | this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); | |
1286 | ||
1287 | this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily); | |
1288 | ||
1289 | this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5; | |
1290 | ||
1291 | this.ctx.font = this.titleFont; | |
1292 | ||
1293 | var titleWidth = this.ctx.measureText(this.title).width, | |
1294 | //Label has a legend square as well so account for this. | |
1295 | labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3, | |
1296 | longestTextWidth = max([labelWidth,titleWidth]); | |
1297 | ||
1298 | this.width = longestTextWidth + (this.xPadding*2); | |
1299 | ||
1300 | ||
1301 | var halfHeight = this.height/2; | |
1302 | ||
1303 | //Check to ensure the height will fit on the canvas | |
1304 | //The three is to buffer form the very | |
1305 | if (this.y - halfHeight < 0 ){ | |
1306 | this.y = halfHeight; | |
1307 | } else if (this.y + halfHeight > this.chart.height){ | |
1308 | this.y = this.chart.height - halfHeight; | |
1309 | } | |
1310 | ||
1311 | //Decide whether to align left or right based on position on canvas | |
1312 | if (this.x > this.chart.width/2){ | |
1313 | this.x -= this.xOffset + this.width; | |
1314 | } else { | |
1315 | this.x += this.xOffset; | |
1316 | } | |
1317 | ||
1318 | ||
1319 | }, | |
1320 | getLineHeight : function(index){ | |
1321 | var baseLineHeight = this.y - (this.height/2) + this.yPadding, | |
1322 | afterTitleIndex = index-1; | |
1323 | ||
1324 | //If the index is zero, we're getting the title | |
1325 | if (index === 0){ | |
1326 | return baseLineHeight + this.titleFontSize/2; | |
1327 | } else{ | |
1328 | return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5; | |
1329 | } | |
1330 | ||
1331 | }, | |
1332 | draw : function(){ | |
1333 | drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius); | |
1334 | var ctx = this.ctx; | |
1335 | ctx.fillStyle = this.fillColor; | |
1336 | ctx.fill(); | |
1337 | ctx.closePath(); | |
1338 | ||
1339 | ctx.textAlign = "left"; | |
1340 | ctx.textBaseline = "middle"; | |
1341 | ctx.fillStyle = this.titleTextColor; | |
1342 | ctx.font = this.titleFont; | |
1343 | ||
1344 | ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0)); | |
1345 | ||
1346 | ctx.font = this.font; | |
1347 | helpers.each(this.labels,function(label,index){ | |
1348 | ctx.fillStyle = this.textColor; | |
1349 | ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1)); | |
1350 | ||
1351 | //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) | |
1352 | //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); | |
1353 | //Instead we'll make a white filled block to put the legendColour palette over. | |
1354 | ||
1355 | ctx.fillStyle = this.legendColorBackground; | |
1356 | ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); | |
1357 | ||
1358 | ctx.fillStyle = this.legendColors[index].fill; | |
1359 | ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); | |
1360 | ||
1361 | ||
1362 | },this); | |
1363 | } | |
1364 | }); | |
1365 | ||
1366 | Chart.Scale = Chart.Element.extend({ | |
1367 | initialize : function(){ | |
1368 | this.fit(); | |
1369 | }, | |
1370 | buildYLabels : function(){ | |
1371 | this.yLabels = []; | |
1372 | ||
1373 | var stepDecimalPlaces = getDecimalPlaces(this.stepValue); | |
1374 | ||
1375 | for (var i=0; i<=this.steps; i++){ | |
1376 | this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); | |
1377 | } | |
1378 | this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0; | |
1379 | }, | |
1380 | addXLabel : function(label){ | |
1381 | this.xLabels.push(label); | |
1382 | this.valuesCount++; | |
1383 | this.fit(); | |
1384 | }, | |
1385 | removeXLabel : function(){ | |
1386 | this.xLabels.shift(); | |
1387 | this.valuesCount--; | |
1388 | this.fit(); | |
1389 | }, | |
1390 | // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use | |
1391 | fit: function(){ | |
1392 | // First we need the width of the yLabels, assuming the xLabels aren't rotated | |
1393 | ||
1394 | // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation | |
1395 | this.startPoint = (this.display) ? this.fontSize : 0; | |
1396 | this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels | |
1397 | ||
1398 | // Apply padding settings to the start and end point. | |
1399 | this.startPoint += this.padding; | |
1400 | this.endPoint -= this.padding; | |
1401 | ||
1402 | // Cache the starting height, so can determine if we need to recalculate the scale yAxis | |
1403 | var cachedHeight = this.endPoint - this.startPoint, | |
1404 | cachedYLabelWidth; | |
1405 | ||
1406 | // Build the current yLabels so we have an idea of what size they'll be to start | |
1407 | /* | |
1408 | * This sets what is returned from calculateScaleRange as static properties of this class: | |
1409 | * | |
1410 | this.steps; | |
1411 | this.stepValue; | |
1412 | this.min; | |
1413 | this.max; | |
1414 | * | |
1415 | */ | |
1416 | this.calculateYRange(cachedHeight); | |
1417 | ||
1418 | // With these properties set we can now build the array of yLabels | |
1419 | // and also the width of the largest yLabel | |
1420 | this.buildYLabels(); | |
1421 | ||
1422 | this.calculateXLabelRotation(); | |
1423 | ||
1424 | while((cachedHeight > this.endPoint - this.startPoint)){ | |
1425 | cachedHeight = this.endPoint - this.startPoint; | |
1426 | cachedYLabelWidth = this.yLabelWidth; | |
1427 | ||
1428 | this.calculateYRange(cachedHeight); | |
1429 | this.buildYLabels(); | |
1430 | ||
1431 | // Only go through the xLabel loop again if the yLabel width has changed | |
1432 | if (cachedYLabelWidth < this.yLabelWidth){ | |
1433 | this.calculateXLabelRotation(); | |
1434 | } | |
1435 | } | |
1436 | ||
1437 | }, | |
1438 | calculateXLabelRotation : function(){ | |
1439 | //Get the width of each grid by calculating the difference | |
1440 | //between x offsets between 0 and 1. | |
1441 | ||
1442 | this.ctx.font = this.font; | |
1443 | ||
1444 | var firstWidth = this.ctx.measureText(this.xLabels[0]).width, | |
1445 | lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width, | |
1446 | firstRotated, | |
1447 | lastRotated; | |
1448 | ||
1449 | ||
1450 | this.xScalePaddingRight = lastWidth/2 + 3; | |
1451 | this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10; | |
1452 | ||
1453 | this.xLabelRotation = 0; | |
1454 | if (this.display){ | |
1455 | var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels), | |
1456 | cosRotation, | |
1457 | firstRotatedWidth; | |
1458 | this.xLabelWidth = originalLabelWidth; | |
1459 | //Allow 3 pixels x2 padding either side for label readability | |
1460 | var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6; | |
1461 | ||
1462 | //Max label rotate should be 90 - also act as a loop counter | |
1463 | while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){ | |
1464 | cosRotation = Math.cos(toRadians(this.xLabelRotation)); | |
1465 | ||
1466 | firstRotated = cosRotation * firstWidth; | |
1467 | lastRotated = cosRotation * lastWidth; | |
1468 | ||
1469 | // We're right aligning the text now. | |
1470 | if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){ | |
1471 | this.xScalePaddingLeft = firstRotated + this.fontSize / 2; | |
1472 | } | |
1473 | this.xScalePaddingRight = this.fontSize/2; | |
1474 | ||
1475 | ||
1476 | this.xLabelRotation++; | |
1477 | this.xLabelWidth = cosRotation * originalLabelWidth; | |
1478 | ||
1479 | } | |
1480 | if (this.xLabelRotation > 0){ | |
1481 | this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3; | |
1482 | } | |
1483 | } | |
1484 | else{ | |
1485 | this.xLabelWidth = 0; | |
1486 | this.xScalePaddingRight = this.padding; | |
1487 | this.xScalePaddingLeft = this.padding; | |
1488 | } | |
1489 | ||
1490 | }, | |
1491 | // Needs to be overidden in each Chart type | |
1492 | // Otherwise we need to pass all the data into the scale class | |
1493 | calculateYRange: noop, | |
1494 | drawingArea: function(){ | |
1495 | return this.startPoint - this.endPoint; | |
1496 | }, | |
1497 | calculateY : function(value){ | |
1498 | var scalingFactor = this.drawingArea() / (this.min - this.max); | |
1499 | return this.endPoint - (scalingFactor * (value - this.min)); | |
1500 | }, | |
1501 | calculateX : function(index){ | |
1502 | var isRotated = (this.xLabelRotation > 0), | |
1503 | // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding, | |
1504 | innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight), | |
1505 | valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), | |
1506 | valueOffset = (valueWidth * index) + this.xScalePaddingLeft; | |
1507 | ||
1508 | if (this.offsetGridLines){ | |
1509 | valueOffset += (valueWidth/2); | |
1510 | } | |
1511 | ||
1512 | return Math.round(valueOffset); | |
1513 | }, | |
1514 | update : function(newProps){ | |
1515 | helpers.extend(this, newProps); | |
1516 | this.fit(); | |
1517 | }, | |
1518 | draw : function(){ | |
1519 | var ctx = this.ctx, | |
1520 | yLabelGap = (this.endPoint - this.startPoint) / this.steps, | |
1521 | xStart = Math.round(this.xScalePaddingLeft); | |
1522 | if (this.display){ | |
1523 | ctx.fillStyle = this.textColor; | |
1524 | ctx.font = this.font; | |
1525 | each(this.yLabels,function(labelString,index){ | |
1526 | var yLabelCenter = this.endPoint - (yLabelGap * index), | |
1527 | linePositionY = Math.round(yLabelCenter); | |
1528 | ||
1529 | ctx.textAlign = "right"; | |
1530 | ctx.textBaseline = "middle"; | |
1531 | if (this.showLabels){ | |
1532 | ctx.fillText(labelString,xStart - 10,yLabelCenter); | |
1533 | } | |
1534 | ctx.beginPath(); | |
1535 | if (index > 0){ | |
1536 | // This is a grid line in the centre, so drop that | |
1537 | ctx.lineWidth = this.gridLineWidth; | |
1538 | ctx.strokeStyle = this.gridLineColor; | |
1539 | } else { | |
1540 | // This is the first line on the scale | |
1541 | ctx.lineWidth = this.lineWidth; | |
1542 | ctx.strokeStyle = this.lineColor; | |
1543 | } | |
1544 | ||
1545 | linePositionY += helpers.aliasPixel(ctx.lineWidth); | |
1546 | ||
1547 | ctx.moveTo(xStart, linePositionY); | |
1548 | ctx.lineTo(this.width, linePositionY); | |
1549 | ctx.stroke(); | |
1550 | ctx.closePath(); | |
1551 | ||
1552 | ctx.lineWidth = this.lineWidth; | |
1553 | ctx.strokeStyle = this.lineColor; | |
1554 | ctx.beginPath(); | |
1555 | ctx.moveTo(xStart - 5, linePositionY); | |
1556 | ctx.lineTo(xStart, linePositionY); | |
1557 | ctx.stroke(); | |
1558 | ctx.closePath(); | |
1559 | ||
1560 | },this); | |
1561 | ||
1562 | // xLabelsSkipper is a number which if gives 0 as remainder [ indexof(xLabel)/xLabelsSkipper ], we print xLabels, otherwise, we skip it | |
1563 | // if number then divide and determine | else, if true, print all labels, else we never print | |
1564 | this.xLabelsSkipper = isNumber(this.showXLabels) ? Math.ceil(this.xLabels.length/this.showXLabels) : (this.showXLabels === true) ? 1 : this.xLabels.length+1; | |
1565 | each(this.xLabels,function(label,index){ | |
1566 | var xPos = this.calculateX(index) + aliasPixel(this.lineWidth), | |
1567 | // Check to see if line/bar here and decide where to place the line | |
1568 | linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth), | |
1569 | isRotated = (this.xLabelRotation > 0); | |
1570 | ||
1571 | ctx.beginPath(); | |
1572 | ||
1573 | if (index > 0){ | |
1574 | // This is a grid line in the centre, so drop that | |
1575 | ctx.lineWidth = this.gridLineWidth; | |
1576 | ctx.strokeStyle = this.gridLineColor; | |
1577 | } else { | |
1578 | // This is the first line on the scale | |
1579 | ctx.lineWidth = this.lineWidth; | |
1580 | ctx.strokeStyle = this.lineColor; | |
1581 | } | |
1582 | ctx.moveTo(linePos,this.endPoint); | |
1583 | ctx.lineTo(linePos,this.startPoint - 3); | |
1584 | ctx.stroke(); | |
1585 | ctx.closePath(); | |
1586 | ||
1587 | ||
1588 | ctx.lineWidth = this.lineWidth; | |
1589 | ctx.strokeStyle = this.lineColor; | |
1590 | ||
1591 | ||
1592 | // Small lines at the bottom of the base grid line | |
1593 | ctx.beginPath(); | |
1594 | ctx.moveTo(linePos,this.endPoint); | |
1595 | ctx.lineTo(linePos,this.endPoint + 5); | |
1596 | ctx.stroke(); | |
1597 | ctx.closePath(); | |
1598 | ||
1599 | ctx.save(); | |
1600 | ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8); | |
1601 | ctx.rotate(toRadians(this.xLabelRotation)*-1); | |
1602 | ctx.font = this.font; | |
1603 | ctx.textAlign = (isRotated) ? "right" : "center"; | |
1604 | ctx.textBaseline = (isRotated) ? "middle" : "top"; | |
1605 | if(index % this.xLabelsSkipper === 0) { | |
1606 | ctx.fillText(label, 0, 0); | |
1607 | } | |
1608 | ctx.restore(); | |
1609 | },this); | |
1610 | ||
1611 | } | |
1612 | } | |
1613 | ||
1614 | }); | |
1615 | ||
1616 | Chart.RadialScale = Chart.Element.extend({ | |
1617 | initialize: function(){ | |
1618 | this.size = min([this.height, this.width]); | |
1619 | this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); | |
1620 | }, | |
1621 | calculateCenterOffset: function(value){ | |
1622 | // Take into account half font size + the yPadding of the top value | |
1623 | var scalingFactor = this.drawingArea / (this.max - this.min); | |
1624 | ||
1625 | return (value - this.min) * scalingFactor; | |
1626 | }, | |
1627 | update : function(){ | |
1628 | if (!this.lineArc){ | |
1629 | this.setScaleSize(); | |
1630 | } else { | |
1631 | this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); | |
1632 | } | |
1633 | this.buildYLabels(); | |
1634 | }, | |
1635 | buildYLabels: function(){ | |
1636 | this.yLabels = []; | |
1637 | ||
1638 | var stepDecimalPlaces = getDecimalPlaces(this.stepValue); | |
1639 | ||
1640 | for (var i=0; i<=this.steps; i++){ | |
1641 | this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); | |
1642 | } | |
1643 | }, | |
1644 | getCircumference : function(){ | |
1645 | return ((Math.PI*2) / this.valuesCount); | |
1646 | }, | |
1647 | setScaleSize: function(){ | |
1648 | /* | |
1649 | * Right, this is really confusing and there is a lot of maths going on here | |
1650 | * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 | |
1651 | * | |
1652 | * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif | |
1653 | * | |
1654 | * Solution: | |
1655 | * | |
1656 | * We assume the radius of the polygon is half the size of the canvas at first | |
1657 | * at each index we check if the text overlaps. | |
1658 | * | |
1659 | * Where it does, we store that angle and that index. | |
1660 | * | |
1661 | * After finding the largest index and angle we calculate how much we need to remove | |
1662 | * from the shape radius to move the point inwards by that x. | |
1663 | * | |
1664 | * We average the left and right distances to get the maximum shape radius that can fit in the box | |
1665 | * along with labels. | |
1666 | * | |
1667 | * Once we have that, we can find the centre point for the chart, by taking the x text protrusion | |
1668 | * on each side, removing that from the size, halving it and adding the left x protrusion width. | |
1669 | * | |
1670 | * This will mean we have a shape fitted to the canvas, as large as it can be with the labels | |
1671 | * and position it in the most space efficient manner | |
1672 | * | |
1673 | * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif | |
1674 | */ | |
1675 | ||
1676 | ||
1677 | // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. | |
1678 | // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points | |
1679 | var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]), | |
1680 | pointPosition, | |
1681 | i, | |
1682 | textWidth, | |
1683 | halfTextWidth, | |
1684 | furthestRight = this.width, | |
1685 | furthestRightIndex, | |
1686 | furthestRightAngle, | |
1687 | furthestLeft = 0, | |
1688 | furthestLeftIndex, | |
1689 | furthestLeftAngle, | |
1690 | xProtrusionLeft, | |
1691 | xProtrusionRight, | |
1692 | radiusReductionRight, | |
1693 | radiusReductionLeft, | |
1694 | maxWidthRadius; | |
1695 | this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); | |
1696 | for (i=0;i<this.valuesCount;i++){ | |
1697 | // 5px to space the text slightly out - similar to what we do in the draw function. | |
1698 | pointPosition = this.getPointPosition(i, largestPossibleRadius); | |
1699 | textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5; | |
1700 | if (i === 0 || i === this.valuesCount/2){ | |
1701 | // If we're at index zero, or exactly the middle, we're at exactly the top/bottom | |
1702 | // of the radar chart, so text will be aligned centrally, so we'll half it and compare | |
1703 | // w/left and right text sizes | |
1704 | halfTextWidth = textWidth/2; | |
1705 | if (pointPosition.x + halfTextWidth > furthestRight) { | |
1706 | furthestRight = pointPosition.x + halfTextWidth; | |
1707 | furthestRightIndex = i; | |
1708 | } | |
1709 | if (pointPosition.x - halfTextWidth < furthestLeft) { | |
1710 | furthestLeft = pointPosition.x - halfTextWidth; | |
1711 | furthestLeftIndex = i; | |
1712 | } | |
1713 | } | |
1714 | else if (i < this.valuesCount/2) { | |
1715 | // Less than half the values means we'll left align the text | |
1716 | if (pointPosition.x + textWidth > furthestRight) { | |
1717 | furthestRight = pointPosition.x + textWidth; | |
1718 | furthestRightIndex = i; | |
1719 | } | |
1720 | } | |
1721 | else if (i > this.valuesCount/2){ | |
1722 | // More than half the values means we'll right align the text | |
1723 | if (pointPosition.x - textWidth < furthestLeft) { | |
1724 | furthestLeft = pointPosition.x - textWidth; | |
1725 | furthestLeftIndex = i; | |
1726 | } | |
1727 | } | |
1728 | } | |
1729 | ||
1730 | xProtrusionLeft = furthestLeft; | |
1731 | ||
1732 | xProtrusionRight = Math.ceil(furthestRight - this.width); | |
1733 | ||
1734 | furthestRightAngle = this.getIndexAngle(furthestRightIndex); | |
1735 | ||
1736 | furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); | |
1737 | ||
1738 | radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2); | |
1739 | ||
1740 | radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2); | |
1741 | ||
1742 | // Ensure we actually need to reduce the size of the chart | |
1743 | radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0; | |
1744 | radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; | |
1745 | ||
1746 | this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2; | |
1747 | ||
1748 | //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) | |
1749 | this.setCenterPoint(radiusReductionLeft, radiusReductionRight); | |
1750 | ||
1751 | }, | |
1752 | setCenterPoint: function(leftMovement, rightMovement){ | |
1753 | ||
1754 | var maxRight = this.width - rightMovement - this.drawingArea, | |
1755 | maxLeft = leftMovement + this.drawingArea; | |
1756 | ||
1757 | this.xCenter = (maxLeft + maxRight)/2; | |
1758 | // Always vertically in the centre as the text height doesn't change | |
1759 | this.yCenter = (this.height/2); | |
1760 | }, | |
1761 | ||
1762 | getIndexAngle : function(index){ | |
1763 | var angleMultiplier = (Math.PI * 2) / this.valuesCount; | |
1764 | // Start from the top instead of right, so remove a quarter of the circle | |
1765 | ||
1766 | return index * angleMultiplier - (Math.PI/2); | |
1767 | }, | |
1768 | getPointPosition : function(index, distanceFromCenter){ | |
1769 | var thisAngle = this.getIndexAngle(index); | |
1770 | return { | |
1771 | x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, | |
1772 | y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter | |
1773 | }; | |
1774 | }, | |
1775 | draw: function(){ | |
1776 | if (this.display){ | |
1777 | var ctx = this.ctx; | |
1778 | each(this.yLabels, function(label, index){ | |
1779 | // Don't draw a centre value | |
1780 | if (index > 0){ | |
1781 | var yCenterOffset = index * (this.drawingArea/this.steps), | |
1782 | yHeight = this.yCenter - yCenterOffset, | |
1783 | pointPosition; | |
1784 | ||
1785 | // Draw circular lines around the scale | |
1786 | if (this.lineWidth > 0){ | |
1787 | ctx.strokeStyle = this.lineColor; | |
1788 | ctx.lineWidth = this.lineWidth; | |
1789 | ||
1790 | if(this.lineArc){ | |
1791 | ctx.beginPath(); | |
1792 | ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2); | |
1793 | ctx.closePath(); | |
1794 | ctx.stroke(); | |
1795 | } else{ | |
1796 | ctx.beginPath(); | |
1797 | for (var i=0;i<this.valuesCount;i++) | |
1798 | { | |
1799 | pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue))); | |
1800 | if (i === 0){ | |
1801 | ctx.moveTo(pointPosition.x, pointPosition.y); | |
1802 | } else { | |
1803 | ctx.lineTo(pointPosition.x, pointPosition.y); | |
1804 | } | |
1805 | } | |
1806 | ctx.closePath(); | |
1807 | ctx.stroke(); | |
1808 | } | |
1809 | } | |
1810 | if(this.showLabels){ | |
1811 | ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); | |
1812 | if (this.showLabelBackdrop){ | |
1813 | var labelWidth = ctx.measureText(label).width; | |
1814 | ctx.fillStyle = this.backdropColor; | |
1815 | ctx.fillRect( | |
1816 | this.xCenter - labelWidth/2 - this.backdropPaddingX, | |
1817 | yHeight - this.fontSize/2 - this.backdropPaddingY, | |
1818 | labelWidth + this.backdropPaddingX*2, | |
1819 | this.fontSize + this.backdropPaddingY*2 | |
1820 | ); | |
1821 | } | |
1822 | ctx.textAlign = 'center'; | |
1823 | ctx.textBaseline = "middle"; | |
1824 | ctx.fillStyle = this.fontColor; | |
1825 | ctx.fillText(label, this.xCenter, yHeight); | |
1826 | } | |
1827 | } | |
1828 | }, this); | |
1829 | ||
1830 | if (!this.lineArc){ | |
1831 | ctx.lineWidth = this.angleLineWidth; | |
1832 | ctx.strokeStyle = this.angleLineColor; | |
1833 | for (var i = this.valuesCount - 1; i >= 0; i--) { | |
1834 | if (this.angleLineWidth > 0){ | |
1835 | var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max)); | |
1836 | ctx.beginPath(); | |
1837 | ctx.moveTo(this.xCenter, this.yCenter); | |
1838 | ctx.lineTo(outerPosition.x, outerPosition.y); | |
1839 | ctx.stroke(); | |
1840 | ctx.closePath(); | |
1841 | } | |
1842 | // Extra 3px out for some label spacing | |
1843 | var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5); | |
1844 | ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); | |
1845 | ctx.fillStyle = this.pointLabelFontColor; | |
1846 | ||
1847 | var labelsCount = this.labels.length, | |
1848 | halfLabelsCount = this.labels.length/2, | |
1849 | quarterLabelsCount = halfLabelsCount/2, | |
1850 | upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), | |
1851 | exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); | |
1852 | if (i === 0){ | |
1853 | ctx.textAlign = 'center'; | |
1854 | } else if(i === halfLabelsCount){ | |
1855 | ctx.textAlign = 'center'; | |
1856 | } else if (i < halfLabelsCount){ | |
1857 | ctx.textAlign = 'left'; | |
1858 | } else { | |
1859 | ctx.textAlign = 'right'; | |
1860 | } | |
1861 | ||
1862 | // Set the correct text baseline based on outer positioning | |
1863 | if (exactQuarter){ | |
1864 | ctx.textBaseline = 'middle'; | |
1865 | } else if (upperHalf){ | |
1866 | ctx.textBaseline = 'bottom'; | |
1867 | } else { | |
1868 | ctx.textBaseline = 'top'; | |
1869 | } | |
1870 | ||
1871 | ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); | |
1872 | } | |
1873 | } | |
1874 | } | |
1875 | } | |
1876 | }); | |
1877 | ||
1878 | // Attach global event to resize each chart instance when the browser resizes | |
1879 | helpers.addEvent(window, "resize", (function(){ | |
1880 | // Basic debounce of resize function so it doesn't hurt performance when resizing browser. | |
1881 | var timeout; | |
1882 | return function(){ | |
1883 | clearTimeout(timeout); | |
1884 | timeout = setTimeout(function(){ | |
1885 | each(Chart.instances,function(instance){ | |
1886 | // If the responsive flag is set in the chart instance config | |
1887 | // Cascade the resize event down to the chart. | |
1888 | if (instance.options.responsive){ | |
1889 | instance.resize(instance.render, true); | |
1890 | } | |
1891 | }); | |
1892 | }, 50); | |
1893 | }; | |
1894 | })()); | |
1895 | ||
1896 | ||
1897 | if (amd) { | |
1898 | define(function(){ | |
1899 | return Chart; | |
1900 | }); | |
1901 | } else if (typeof module === 'object' && module.exports) { | |
1902 | module.exports = Chart; | |
1903 | } | |
1904 | ||
1905 | root.Chart = Chart; | |
1906 | ||
1907 | Chart.noConflict = function(){ | |
1908 | root.Chart = previous; | |
1909 | return Chart; | |
1910 | }; | |
1911 | ||
1912 | }).call(this); | |
1913 | ||
1914 | (function(){ | |
1915 | "use strict"; | |
1916 | ||
1917 | var root = this, | |
1918 | Chart = root.Chart, | |
1919 | helpers = Chart.helpers; | |
1920 | ||
1921 | ||
1922 | var defaultConfig = { | |
1923 | //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value | |
1924 | scaleBeginAtZero : true, | |
1925 | ||
1926 | //Boolean - Whether grid lines are shown across the chart | |
1927 | scaleShowGridLines : true, | |
1928 | ||
1929 | //String - Colour of the grid lines | |
1930 | scaleGridLineColor : "rgba(0,0,0,.05)", | |
1931 | ||
1932 | //Number - Width of the grid lines | |
1933 | scaleGridLineWidth : 1, | |
1934 | ||
1935 | //Boolean - If there is a stroke on each bar | |
1936 | barShowStroke : true, | |
1937 | ||
1938 | //Number - Pixel width of the bar stroke | |
1939 | barStrokeWidth : 2, | |
1940 | ||
1941 | //Number - Spacing between each of the X value sets | |
1942 | barValueSpacing : 5, | |
1943 | ||
1944 | //Number - Spacing between data sets within X values | |
1945 | barDatasetSpacing : 1, | |
1946 | ||
1947 | //String - A legend template | |
1948 | legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>" | |
1949 | ||
1950 | }; | |
1951 | ||
1952 | ||
1953 | Chart.Type.extend({ | |
1954 | name: "Bar", | |
1955 | defaults : defaultConfig, | |
1956 | initialize: function(data){ | |
1957 | ||
1958 | //Expose options as a scope variable here so we can access it in the ScaleClass | |
1959 | var options = this.options; | |
1960 | ||
1961 | this.ScaleClass = Chart.Scale.extend({ | |
1962 | offsetGridLines : true, | |
1963 | calculateBarX : function(datasetCount, datasetIndex, barIndex){ | |
1964 | //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar | |
1965 | var xWidth = this.calculateBaseWidth(), | |
1966 | xAbsolute = this.calculateX(barIndex) - (xWidth/2), | |
1967 | barWidth = this.calculateBarWidth(datasetCount); | |
1968 | ||
1969 | return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2; | |
1970 | }, | |
1971 | calculateBaseWidth : function(){ | |
1972 | return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing); | |
1973 | }, | |
1974 | calculateBarWidth : function(datasetCount){ | |
1975 | //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset | |
1976 | var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing); | |
1977 | ||
1978 | return (baseWidth / datasetCount); | |
1979 | } | |
1980 | }); | |
1981 | ||
1982 | this.datasets = []; | |
1983 | ||
1984 | //Set up tooltip events on the chart | |
1985 | if (this.options.showTooltips){ | |
1986 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ | |
1987 | var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : []; | |
1988 | ||
1989 | this.eachBars(function(bar){ | |
1990 | bar.restore(['fillColor', 'strokeColor']); | |
1991 | }); | |
1992 | helpers.each(activeBars, function(activeBar){ | |
1993 | activeBar.fillColor = activeBar.highlightFill; | |
1994 | activeBar.strokeColor = activeBar.highlightStroke; | |
1995 | }); | |
1996 | this.showTooltip(activeBars); | |
1997 | }); | |
1998 | } | |
1999 | ||
2000 | //Declare the extension of the default point, to cater for the options passed in to the constructor | |
2001 | this.BarClass = Chart.Rectangle.extend({ | |
2002 | strokeWidth : this.options.barStrokeWidth, | |
2003 | showStroke : this.options.barShowStroke, | |
2004 | ctx : this.chart.ctx | |
2005 | }); | |
2006 | ||
2007 | //Iterate through each of the datasets, and build this into a property of the chart | |
2008 | helpers.each(data.datasets,function(dataset,datasetIndex){ | |
2009 | ||
2010 | var datasetObject = { | |
2011 | label : dataset.label || null, | |
2012 | fillColor : dataset.fillColor, | |
2013 | strokeColor : dataset.strokeColor, | |
2014 | bars : [] | |
2015 | }; | |
2016 | ||
2017 | this.datasets.push(datasetObject); | |
2018 | ||
2019 | helpers.each(dataset.data,function(dataPoint,index){ | |
2020 | if (helpers.isNumber(dataPoint)){ | |
2021 | //Add a new point for each piece of data, passing any required data to draw. | |
2022 | datasetObject.bars.push(new this.BarClass({ | |
2023 | value : dataPoint, | |
2024 | label : data.labels[index], | |
2025 | datasetLabel: dataset.label, | |
2026 | strokeColor : dataset.strokeColor, | |
2027 | fillColor : dataset.fillColor, | |
2028 | highlightFill : dataset.highlightFill || dataset.fillColor, | |
2029 | highlightStroke : dataset.highlightStroke || dataset.strokeColor | |
2030 | })); | |
2031 | } | |
2032 | },this); | |
2033 | ||
2034 | },this); | |
2035 | ||
2036 | this.buildScale(data.labels); | |
2037 | ||
2038 | this.BarClass.prototype.base = this.scale.endPoint; | |
2039 | ||
2040 | this.eachBars(function(bar, index, datasetIndex){ | |
2041 | helpers.extend(bar, { | |
2042 | width : this.scale.calculateBarWidth(this.datasets.length), | |
2043 | x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index), | |
2044 | y: this.scale.endPoint | |
2045 | }); | |
2046 | bar.save(); | |
2047 | }, this); | |
2048 | ||
2049 | this.render(); | |
2050 | }, | |
2051 | update : function(){ | |
2052 | this.scale.update(); | |
2053 | // Reset any highlight colours before updating. | |
2054 | helpers.each(this.activeElements, function(activeElement){ | |
2055 | activeElement.restore(['fillColor', 'strokeColor']); | |
2056 | }); | |
2057 | ||
2058 | this.eachBars(function(bar){ | |
2059 | bar.save(); | |
2060 | }); | |
2061 | this.render(); | |
2062 | }, | |
2063 | eachBars : function(callback){ | |
2064 | helpers.each(this.datasets,function(dataset, datasetIndex){ | |
2065 | helpers.each(dataset.bars, callback, this, datasetIndex); | |
2066 | },this); | |
2067 | }, | |
2068 | getBarsAtEvent : function(e){ | |
2069 | var barsArray = [], | |
2070 | eventPosition = helpers.getRelativePosition(e), | |
2071 | datasetIterator = function(dataset){ | |
2072 | barsArray.push(dataset.bars[barIndex]); | |
2073 | }, | |
2074 | barIndex; | |
2075 | ||
2076 | for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) { | |
2077 | for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) { | |
2078 | if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){ | |
2079 | helpers.each(this.datasets, datasetIterator); | |
2080 | return barsArray; | |
2081 | } | |
2082 | } | |
2083 | } | |
2084 | ||
2085 | return barsArray; | |
2086 | }, | |
2087 | buildScale : function(labels){ | |
2088 | var self = this; | |
2089 | ||
2090 | var dataTotal = function(){ | |
2091 | var values = []; | |
2092 | self.eachBars(function(bar){ | |
2093 | values.push(bar.value); | |
2094 | }); | |
2095 | return values; | |
2096 | }; | |
2097 | ||
2098 | var scaleOptions = { | |
2099 | templateString : this.options.scaleLabel, | |
2100 | height : this.chart.height, | |
2101 | width : this.chart.width, | |
2102 | ctx : this.chart.ctx, | |
2103 | textColor : this.options.scaleFontColor, | |
2104 | fontSize : this.options.scaleFontSize, | |
2105 | fontStyle : this.options.scaleFontStyle, | |
2106 | fontFamily : this.options.scaleFontFamily, | |
2107 | valuesCount : labels.length, | |
2108 | beginAtZero : this.options.scaleBeginAtZero, | |
2109 | integersOnly : this.options.scaleIntegersOnly, | |
2110 | calculateYRange: function(currentHeight){ | |
2111 | var updatedRanges = helpers.calculateScaleRange( | |
2112 | dataTotal(), | |
2113 | currentHeight, | |
2114 | this.fontSize, | |
2115 | this.beginAtZero, | |
2116 | this.integersOnly | |
2117 | ); | |
2118 | helpers.extend(this, updatedRanges); | |
2119 | }, | |
2120 | xLabels : labels, | |
2121 | showXLabels: (this.options.showXLabels) ? this.options.showXLabels : true, | |
2122 | font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), | |
2123 | lineWidth : this.options.scaleLineWidth, | |
2124 | lineColor : this.options.scaleLineColor, | |
2125 | gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, | |
2126 | gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", | |
2127 | padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0, | |
2128 | showLabels : this.options.scaleShowLabels, | |
2129 | display : this.options.showScale | |
2130 | }; | |
2131 | ||
2132 | if (this.options.scaleOverride){ | |
2133 | helpers.extend(scaleOptions, { | |
2134 | calculateYRange: helpers.noop, | |
2135 | steps: this.options.scaleSteps, | |
2136 | stepValue: this.options.scaleStepWidth, | |
2137 | min: this.options.scaleStartValue, | |
2138 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) | |
2139 | }); | |
2140 | } | |
2141 | ||
2142 | this.scale = new this.ScaleClass(scaleOptions); | |
2143 | }, | |
2144 | addData : function(valuesArray,label){ | |
2145 | //Map the values array for each of the datasets | |
2146 | helpers.each(valuesArray,function(value,datasetIndex){ | |
2147 | if (helpers.isNumber(value)){ | |
2148 | //Add a new point for each piece of data, passing any required data to draw. | |
2149 | this.datasets[datasetIndex].bars.push(new this.BarClass({ | |
2150 | value : value, | |
2151 | label : label, | |
2152 | x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1), | |
2153 | y: this.scale.endPoint, | |
2154 | width : this.scale.calculateBarWidth(this.datasets.length), | |
2155 | base : this.scale.endPoint, | |
2156 | strokeColor : this.datasets[datasetIndex].strokeColor, | |
2157 | fillColor : this.datasets[datasetIndex].fillColor | |
2158 | })); | |
2159 | } | |
2160 | },this); | |
2161 | ||
2162 | this.scale.addXLabel(label); | |
2163 | //Then re-render the chart. | |
2164 | this.update(); | |
2165 | }, | |
2166 | removeData : function(){ | |
2167 | this.scale.removeXLabel(); | |
2168 | //Then re-render the chart. | |
2169 | helpers.each(this.datasets,function(dataset){ | |
2170 | dataset.bars.shift(); | |
2171 | },this); | |
2172 | this.update(); | |
2173 | }, | |
2174 | reflow : function(){ | |
2175 | helpers.extend(this.BarClass.prototype,{ | |
2176 | y: this.scale.endPoint, | |
2177 | base : this.scale.endPoint | |
2178 | }); | |
2179 | var newScaleProps = helpers.extend({ | |
2180 | height : this.chart.height, | |
2181 | width : this.chart.width | |
2182 | }); | |
2183 | this.scale.update(newScaleProps); | |
2184 | }, | |
2185 | draw : function(ease){ | |
2186 | var easingDecimal = ease || 1; | |
2187 | this.clear(); | |
2188 | ||
2189 | var ctx = this.chart.ctx; | |
2190 | ||
2191 | this.scale.draw(easingDecimal); | |
2192 | ||
2193 | //Draw all the bars for each dataset | |
2194 | helpers.each(this.datasets,function(dataset,datasetIndex){ | |
2195 | helpers.each(dataset.bars,function(bar,index){ | |
2196 | bar.base = this.scale.endPoint; | |
2197 | //Transition then draw | |
2198 | bar.transition({ | |
2199 | x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index), | |
2200 | y : this.scale.calculateY(bar.value), | |
2201 | width : this.scale.calculateBarWidth(this.datasets.length) | |
2202 | }, easingDecimal).draw(); | |
2203 | },this); | |
2204 | ||
2205 | },this); | |
2206 | } | |
2207 | }); | |
2208 | ||
2209 | ||
2210 | }).call(this); | |
2211 | (function(){ | |
2212 | "use strict"; | |
2213 | ||
2214 | var root = this, | |
2215 | Chart = root.Chart, | |
2216 | //Cache a local reference to Chart.helpers | |
2217 | helpers = Chart.helpers; | |
2218 | ||
2219 | var defaultConfig = { | |
2220 | //Boolean - Whether we should show a stroke on each segment | |
2221 | segmentShowStroke : true, | |
2222 | ||
2223 | //String - The colour of each segment stroke | |
2224 | segmentStrokeColor : "#fff", | |
2225 | ||
2226 | //Number - The width of each segment stroke | |
2227 | segmentStrokeWidth : 2, | |
2228 | ||
2229 | //The percentage of the chart that we cut out of the middle. | |
2230 | percentageInnerCutout : 50, | |
2231 | ||
2232 | //Number - Amount of animation steps | |
2233 | animationSteps : 100, | |
2234 | ||
2235 | //String - Animation easing effect | |
2236 | animationEasing : "easeOutBounce", | |
2237 | ||
2238 | //Boolean - Whether we animate the rotation of the Doughnut | |
2239 | animateRotate : true, | |
2240 | ||
2241 | //Boolean - Whether we animate scaling the Doughnut from the centre | |
2242 | animateScale : false, | |
2243 | ||
2244 | //String - A legend template | |
2245 | legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>" | |
2246 | ||
2247 | }; | |
2248 | ||
2249 | ||
2250 | Chart.Type.extend({ | |
2251 | //Passing in a name registers this chart in the Chart namespace | |
2252 | name: "Doughnut", | |
2253 | //Providing a defaults will also register the deafults in the chart namespace | |
2254 | defaults : defaultConfig, | |
2255 | //Initialize is fired when the chart is initialized - Data is passed in as a parameter | |
2256 | //Config is automatically merged by the core of Chart.js, and is available at this.options | |
2257 | initialize: function(data){ | |
2258 | ||
2259 | //Declare segments as a static property to prevent inheriting across the Chart type prototype | |
2260 | this.segments = []; | |
2261 | this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; | |
2262 | ||
2263 | this.SegmentArc = Chart.Arc.extend({ | |
2264 | ctx : this.chart.ctx, | |
2265 | x : this.chart.width/2, | |
2266 | y : this.chart.height/2 | |
2267 | }); | |
2268 | ||
2269 | //Set up tooltip events on the chart | |
2270 | if (this.options.showTooltips){ | |
2271 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ | |
2272 | var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; | |
2273 | ||
2274 | helpers.each(this.segments,function(segment){ | |
2275 | segment.restore(["fillColor"]); | |
2276 | }); | |
2277 | helpers.each(activeSegments,function(activeSegment){ | |
2278 | activeSegment.fillColor = activeSegment.highlightColor; | |
2279 | }); | |
2280 | this.showTooltip(activeSegments); | |
2281 | }); | |
2282 | } | |
2283 | this.calculateTotal(data); | |
2284 | ||
2285 | helpers.each(data,function(datapoint, index){ | |
2286 | this.addData(datapoint, index, true); | |
2287 | },this); | |
2288 | ||
2289 | this.render(); | |
2290 | }, | |
2291 | getSegmentsAtEvent : function(e){ | |
2292 | var segmentsArray = []; | |
2293 | ||
2294 | var location = helpers.getRelativePosition(e); | |
2295 | ||
2296 | helpers.each(this.segments,function(segment){ | |
2297 | if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); | |
2298 | },this); | |
2299 | return segmentsArray; | |
2300 | }, | |
2301 | addData : function(segment, atIndex, silent){ | |
2302 | var index = atIndex || this.segments.length; | |
2303 | this.segments.splice(index, 0, new this.SegmentArc({ | |
2304 | value : segment.value, | |
2305 | outerRadius : (this.options.animateScale) ? 0 : this.outerRadius, | |
2306 | innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout, | |
2307 | fillColor : segment.color, | |
2308 | highlightColor : segment.highlight || segment.color, | |
2309 | showStroke : this.options.segmentShowStroke, | |
2310 | strokeWidth : this.options.segmentStrokeWidth, | |
2311 | strokeColor : this.options.segmentStrokeColor, | |
2312 | startAngle : Math.PI * 1.5, | |
2313 | circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value), | |
2314 | label : segment.label | |
2315 | })); | |
2316 | if (!silent){ | |
2317 | this.reflow(); | |
2318 | this.update(); | |
2319 | } | |
2320 | }, | |
2321 | calculateCircumference : function(value){ | |
2322 | return (Math.PI*2)*(value / this.total); | |
2323 | }, | |
2324 | calculateTotal : function(data){ | |
2325 | this.total = 0; | |
2326 | helpers.each(data,function(segment){ | |
2327 | this.total += segment.value; | |
2328 | },this); | |
2329 | }, | |
2330 | update : function(){ | |
2331 | this.calculateTotal(this.segments); | |
2332 | ||
2333 | // Reset any highlight colours before updating. | |
2334 | helpers.each(this.activeElements, function(activeElement){ | |
2335 | activeElement.restore(['fillColor']); | |
2336 | }); | |
2337 | ||
2338 | helpers.each(this.segments,function(segment){ | |
2339 | segment.save(); | |
2340 | }); | |
2341 | this.render(); | |
2342 | }, | |
2343 | ||
2344 | removeData: function(atIndex){ | |
2345 | var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; | |
2346 | this.segments.splice(indexToDelete, 1); | |
2347 | this.reflow(); | |
2348 | this.update(); | |
2349 | }, | |
2350 | ||
2351 | reflow : function(){ | |
2352 | helpers.extend(this.SegmentArc.prototype,{ | |
2353 | x : this.chart.width/2, | |
2354 | y : this.chart.height/2 | |
2355 | }); | |
2356 | this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; | |
2357 | helpers.each(this.segments, function(segment){ | |
2358 | segment.update({ | |
2359 | outerRadius : this.outerRadius, | |
2360 | innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout | |
2361 | }); | |
2362 | }, this); | |
2363 | }, | |
2364 | draw : function(easeDecimal){ | |
2365 | var animDecimal = (easeDecimal) ? easeDecimal : 1; | |
2366 | this.clear(); | |
2367 | helpers.each(this.segments,function(segment,index){ | |
2368 | segment.transition({ | |
2369 | circumference : this.calculateCircumference(segment.value), | |
2370 | outerRadius : this.outerRadius, | |
2371 | innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout | |
2372 | },animDecimal); | |
2373 | ||
2374 | segment.endAngle = segment.startAngle + segment.circumference; | |
2375 | ||
2376 | segment.draw(); | |
2377 | if (index === 0){ | |
2378 | segment.startAngle = Math.PI * 1.5; | |
2379 | } | |
2380 | //Check to see if it's the last segment, if not get the next and update the start angle | |
2381 | if (index < this.segments.length-1){ | |
2382 | this.segments[index+1].startAngle = segment.endAngle; | |
2383 | } | |
2384 | },this); | |
2385 | ||
2386 | } | |
2387 | }); | |
2388 | ||
2389 | Chart.types.Doughnut.extend({ | |
2390 | name : "Pie", | |
2391 | defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0}) | |
2392 | }); | |
2393 | ||
2394 | }).call(this); | |
2395 | (function(){ | |
2396 | "use strict"; | |
2397 | ||
2398 | var root = this, | |
2399 | Chart = root.Chart, | |
2400 | helpers = Chart.helpers; | |
2401 | ||
2402 | var defaultConfig = { | |
2403 | ||
2404 | ///Boolean - Whether grid lines are shown across the chart | |
2405 | scaleShowGridLines : true, | |
2406 | ||
2407 | //String - Colour of the grid lines | |
2408 | scaleGridLineColor : "rgba(0,0,0,.05)", | |
2409 | ||
2410 | //Number - Width of the grid lines | |
2411 | scaleGridLineWidth : 1, | |
2412 | ||
2413 | //Boolean - Whether the line is curved between points | |
2414 | bezierCurve : true, | |
2415 | ||
2416 | //Number - Tension of the bezier curve between points | |
2417 | bezierCurveTension : 0.4, | |
2418 | ||
2419 | //Boolean - Whether to show a dot for each point | |
2420 | pointDot : true, | |
2421 | ||
2422 | //Number - Radius of each point dot in pixels | |
2423 | pointDotRadius : 4, | |
2424 | ||
2425 | //Number - Pixel width of point dot stroke | |
2426 | pointDotStrokeWidth : 1, | |
2427 | ||
2428 | //Number - amount extra to add to the radius to cater for hit detection outside the drawn point | |
2429 | pointHitDetectionRadius : 20, | |
2430 | ||
2431 | //Boolean - Whether to show a stroke for datasets | |
2432 | datasetStroke : true, | |
2433 | ||
2434 | //Number - Pixel width of dataset stroke | |
2435 | datasetStrokeWidth : 2, | |
2436 | ||
2437 | //Boolean - Whether to fill the dataset with a colour | |
2438 | datasetFill : true, | |
2439 | ||
2440 | //String - A legend template | |
2441 | legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>" | |
2442 | ||
2443 | }; | |
2444 | ||
2445 | ||
2446 | Chart.Type.extend({ | |
2447 | name: "Line", | |
2448 | defaults : defaultConfig, | |
2449 | initialize: function(data){ | |
2450 | //Declare the extension of the default point, to cater for the options passed in to the constructor | |
2451 | this.PointClass = Chart.Point.extend({ | |
2452 | strokeWidth : this.options.pointDotStrokeWidth, | |
2453 | radius : this.options.pointDotRadius, | |
2454 | display: this.options.pointDot, | |
2455 | hitDetectionRadius : this.options.pointHitDetectionRadius, | |
2456 | ctx : this.chart.ctx, | |
2457 | inRange : function(mouseX){ | |
2458 | return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2)); | |
2459 | } | |
2460 | }); | |
2461 | ||
2462 | this.datasets = []; | |
2463 | ||
2464 | //Set up tooltip events on the chart | |
2465 | if (this.options.showTooltips){ | |
2466 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ | |
2467 | var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; | |
2468 | this.eachPoints(function(point){ | |
2469 | point.restore(['fillColor', 'strokeColor']); | |
2470 | }); | |
2471 | helpers.each(activePoints, function(activePoint){ | |
2472 | activePoint.fillColor = activePoint.highlightFill; | |
2473 | activePoint.strokeColor = activePoint.highlightStroke; | |
2474 | }); | |
2475 | this.showTooltip(activePoints); | |
2476 | }); | |
2477 | } | |
2478 | ||
2479 | //Iterate through each of the datasets, and build this into a property of the chart | |
2480 | helpers.each(data.datasets,function(dataset){ | |
2481 | ||
2482 | var datasetObject = { | |
2483 | label : dataset.label || null, | |
2484 | fillColor : dataset.fillColor, | |
2485 | strokeColor : dataset.strokeColor, | |
2486 | pointColor : dataset.pointColor, | |
2487 | pointStrokeColor : dataset.pointStrokeColor, | |
2488 | points : [] | |
2489 | }; | |
2490 | ||
2491 | this.datasets.push(datasetObject); | |
2492 | ||
2493 | ||
2494 | helpers.each(dataset.data,function(dataPoint,index){ | |
2495 | //Best way to do this? or in draw sequence...? | |
2496 | if (helpers.isNumber(dataPoint)){ | |
2497 | //Add a new point for each piece of data, passing any required data to draw. | |
2498 | datasetObject.points.push(new this.PointClass({ | |
2499 | value : dataPoint, | |
2500 | label : data.labels[index], | |
2501 | datasetLabel: dataset.label, | |
2502 | strokeColor : dataset.pointStrokeColor, | |
2503 | fillColor : dataset.pointColor, | |
2504 | highlightFill : dataset.pointHighlightFill || dataset.pointColor, | |
2505 | highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor | |
2506 | })); | |
2507 | } | |
2508 | },this); | |
2509 | ||
2510 | this.buildScale(data.labels); | |
2511 | ||
2512 | ||
2513 | this.eachPoints(function(point, index){ | |
2514 | helpers.extend(point, { | |
2515 | x: this.scale.calculateX(index), | |
2516 | y: this.scale.endPoint | |
2517 | }); | |
2518 | point.save(); | |
2519 | }, this); | |
2520 | ||
2521 | },this); | |
2522 | ||
2523 | ||
2524 | this.render(); | |
2525 | }, | |
2526 | update : function(){ | |
2527 | this.scale.update(); | |
2528 | // Reset any highlight colours before updating. | |
2529 | helpers.each(this.activeElements, function(activeElement){ | |
2530 | activeElement.restore(['fillColor', 'strokeColor']); | |
2531 | }); | |
2532 | this.eachPoints(function(point){ | |
2533 | point.save(); | |
2534 | }); | |
2535 | this.render(); | |
2536 | }, | |
2537 | eachPoints : function(callback){ | |
2538 | helpers.each(this.datasets,function(dataset){ | |
2539 | helpers.each(dataset.points,callback,this); | |
2540 | },this); | |
2541 | }, | |
2542 | getPointsAtEvent : function(e){ | |
2543 | var pointsArray = [], | |
2544 | eventPosition = helpers.getRelativePosition(e); | |
2545 | helpers.each(this.datasets,function(dataset){ | |
2546 | helpers.each(dataset.points,function(point){ | |
2547 | if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point); | |
2548 | }); | |
2549 | },this); | |
2550 | return pointsArray; | |
2551 | }, | |
2552 | buildScale : function(labels){ | |
2553 | var self = this; | |
2554 | ||
2555 | var dataTotal = function(){ | |
2556 | var values = []; | |
2557 | self.eachPoints(function(point){ | |
2558 | values.push(point.value); | |
2559 | }); | |
2560 | ||
2561 | return values; | |
2562 | }; | |
2563 | ||
2564 | var scaleOptions = { | |
2565 | templateString : this.options.scaleLabel, | |
2566 | height : this.chart.height, | |
2567 | width : this.chart.width, | |
2568 | ctx : this.chart.ctx, | |
2569 | textColor : this.options.scaleFontColor, | |
2570 | fontSize : this.options.scaleFontSize, | |
2571 | fontStyle : this.options.scaleFontStyle, | |
2572 | fontFamily : this.options.scaleFontFamily, | |
2573 | valuesCount : labels.length, | |
2574 | beginAtZero : this.options.scaleBeginAtZero, | |
2575 | integersOnly : this.options.scaleIntegersOnly, | |
2576 | calculateYRange : function(currentHeight){ | |
2577 | var updatedRanges = helpers.calculateScaleRange( | |
2578 | dataTotal(), | |
2579 | currentHeight, | |
2580 | this.fontSize, | |
2581 | this.beginAtZero, | |
2582 | this.integersOnly | |
2583 | ); | |
2584 | helpers.extend(this, updatedRanges); | |
2585 | }, | |
2586 | xLabels : labels, | |
2587 | showXLabels: (this.options.showXLabels) ? this.options.showXLabels : true, | |
2588 | font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), | |
2589 | lineWidth : this.options.scaleLineWidth, | |
2590 | lineColor : this.options.scaleLineColor, | |
2591 | gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, | |
2592 | gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", | |
2593 | padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth, | |
2594 | showLabels : this.options.scaleShowLabels, | |
2595 | display : this.options.showScale | |
2596 | }; | |
2597 | ||
2598 | if (this.options.scaleOverride){ | |
2599 | helpers.extend(scaleOptions, { | |
2600 | calculateYRange: helpers.noop, | |
2601 | steps: this.options.scaleSteps, | |
2602 | stepValue: this.options.scaleStepWidth, | |
2603 | min: this.options.scaleStartValue, | |
2604 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) | |
2605 | }); | |
2606 | } | |
2607 | ||
2608 | ||
2609 | this.scale = new Chart.Scale(scaleOptions); | |
2610 | }, | |
2611 | addData : function(valuesArray,label){ | |
2612 | //Map the values array for each of the datasets | |
2613 | ||
2614 | helpers.each(valuesArray,function(value,datasetIndex){ | |
2615 | if (helpers.isNumber(value)){ | |
2616 | //Add a new point for each piece of data, passing any required data to draw. | |
2617 | this.datasets[datasetIndex].points.push(new this.PointClass({ | |
2618 | value : value, | |
2619 | label : label, | |
2620 | x: this.scale.calculateX(this.scale.valuesCount+1), | |
2621 | y: this.scale.endPoint, | |
2622 | strokeColor : this.datasets[datasetIndex].pointStrokeColor, | |
2623 | fillColor : this.datasets[datasetIndex].pointColor | |
2624 | })); | |
2625 | } | |
2626 | },this); | |
2627 | ||
2628 | this.scale.addXLabel(label); | |
2629 | //Then re-render the chart. | |
2630 | this.update(); | |
2631 | }, | |
2632 | removeData : function(){ | |
2633 | this.scale.removeXLabel(); | |
2634 | //Then re-render the chart. | |
2635 | helpers.each(this.datasets,function(dataset){ | |
2636 | dataset.points.shift(); | |
2637 | },this); | |
2638 | this.update(); | |
2639 | }, | |
2640 | reflow : function(){ | |
2641 | var newScaleProps = helpers.extend({ | |
2642 | height : this.chart.height, | |
2643 | width : this.chart.width | |
2644 | }); | |
2645 | this.scale.update(newScaleProps); | |
2646 | }, | |
2647 | draw : function(ease){ | |
2648 | var easingDecimal = ease || 1; | |
2649 | this.clear(); | |
2650 | ||
2651 | var ctx = this.chart.ctx; | |
2652 | ||
2653 | this.scale.draw(easingDecimal); | |
2654 | ||
2655 | ||
2656 | helpers.each(this.datasets,function(dataset){ | |
2657 | ||
2658 | //Transition each point first so that the line and point drawing isn't out of sync | |
2659 | //We can use this extra loop to calculate the control points of this dataset also in this loop | |
2660 | ||
2661 | helpers.each(dataset.points,function(point,index){ | |
2662 | point.transition({ | |
2663 | y : this.scale.calculateY(point.value), | |
2664 | x : this.scale.calculateX(index) | |
2665 | }, easingDecimal); | |
2666 | ||
2667 | },this); | |
2668 | ||
2669 | ||
2670 | // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point | |
2671 | // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed | |
2672 | if (this.options.bezierCurve){ | |
2673 | helpers.each(dataset.points,function(point,index){ | |
2674 | //If we're at the start or end, we don't have a previous/next point | |
2675 | //By setting the tension to 0 here, the curve will transition to straight at the end | |
2676 | if (index === 0){ | |
2677 | point.controlPoints = helpers.splineCurve(point,point,dataset.points[index+1],0); | |
2678 | } | |
2679 | else if (index >= dataset.points.length-1){ | |
2680 | point.controlPoints = helpers.splineCurve(dataset.points[index-1],point,point,0); | |
2681 | } | |
2682 | else{ | |
2683 | point.controlPoints = helpers.splineCurve(dataset.points[index-1],point,dataset.points[index+1],this.options.bezierCurveTension); | |
2684 | } | |
2685 | },this); | |
2686 | } | |
2687 | ||
2688 | ||
2689 | //Draw the line between all the points | |
2690 | ctx.lineWidth = this.options.datasetStrokeWidth; | |
2691 | ctx.strokeStyle = dataset.strokeColor; | |
2692 | ctx.beginPath(); | |
2693 | helpers.each(dataset.points,function(point,index){ | |
2694 | if (index>0){ | |
2695 | if(this.options.bezierCurve){ | |
2696 | ctx.bezierCurveTo( | |
2697 | dataset.points[index-1].controlPoints.outer.x, | |
2698 | dataset.points[index-1].controlPoints.outer.y, | |
2699 | point.controlPoints.inner.x, | |
2700 | point.controlPoints.inner.y, | |
2701 | point.x, | |
2702 | point.y | |
2703 | ); | |
2704 | } | |
2705 | else{ | |
2706 | ctx.lineTo(point.x,point.y); | |
2707 | } | |
2708 | ||
2709 | } | |
2710 | else{ | |
2711 | ctx.moveTo(point.x,point.y); | |
2712 | } | |
2713 | },this); | |
2714 | ctx.stroke(); | |
2715 | ||
2716 | ||
2717 | if (this.options.datasetFill){ | |
2718 | //Round off the line by going to the base of the chart, back to the start, then fill. | |
2719 | ctx.lineTo(dataset.points[dataset.points.length-1].x, this.scale.endPoint); | |
2720 | ctx.lineTo(this.scale.calculateX(0), this.scale.endPoint); | |
2721 | ctx.fillStyle = dataset.fillColor; | |
2722 | ctx.closePath(); | |
2723 | ctx.fill(); | |
2724 | } | |
2725 | ||
2726 | //Now draw the points over the line | |
2727 | //A little inefficient double looping, but better than the line | |
2728 | //lagging behind the point positions | |
2729 | helpers.each(dataset.points,function(point){ | |
2730 | point.draw(); | |
2731 | }); | |
2732 | ||
2733 | },this); | |
2734 | } | |
2735 | }); | |
2736 | ||
2737 | ||
2738 | }).call(this); | |
2739 | (function(){ | |
2740 | "use strict"; | |
2741 | ||
2742 | var root = this, | |
2743 | Chart = root.Chart, | |
2744 | //Cache a local reference to Chart.helpers | |
2745 | helpers = Chart.helpers; | |
2746 | ||
2747 | var defaultConfig = { | |
2748 | //Boolean - Show a backdrop to the scale label | |
2749 | scaleShowLabelBackdrop : true, | |
2750 | ||
2751 | //String - The colour of the label backdrop | |
2752 | scaleBackdropColor : "rgba(255,255,255,0.75)", | |
2753 | ||
2754 | // Boolean - Whether the scale should begin at zero | |
2755 | scaleBeginAtZero : true, | |
2756 | ||
2757 | //Number - The backdrop padding above & below the label in pixels | |
2758 | scaleBackdropPaddingY : 2, | |
2759 | ||
2760 | //Number - The backdrop padding to the side of the label in pixels | |
2761 | scaleBackdropPaddingX : 2, | |
2762 | ||
2763 | //Boolean - Show line for each value in the scale | |
2764 | scaleShowLine : true, | |
2765 | ||
2766 | //Boolean - Stroke a line around each segment in the chart | |
2767 | segmentShowStroke : true, | |
2768 | ||
2769 | //String - The colour of the stroke on each segement. | |
2770 | segmentStrokeColor : "#fff", | |
2771 | ||
2772 | //Number - The width of the stroke value in pixels | |
2773 | segmentStrokeWidth : 2, | |
2774 | ||
2775 | //Number - Amount of animation steps | |
2776 | animationSteps : 100, | |
2777 | ||
2778 | //String - Animation easing effect. | |
2779 | animationEasing : "easeOutBounce", | |
2780 | ||
2781 | //Boolean - Whether to animate the rotation of the chart | |
2782 | animateRotate : true, | |
2783 | ||
2784 | //Boolean - Whether to animate scaling the chart from the centre | |
2785 | animateScale : false, | |
2786 | ||
2787 | //String - A legend template | |
2788 | legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>" | |
2789 | }; | |
2790 | ||
2791 | ||
2792 | Chart.Type.extend({ | |
2793 | //Passing in a name registers this chart in the Chart namespace | |
2794 | name: "PolarArea", | |
2795 | //Providing a defaults will also register the deafults in the chart namespace | |
2796 | defaults : defaultConfig, | |
2797 | //Initialize is fired when the chart is initialized - Data is passed in as a parameter | |
2798 | //Config is automatically merged by the core of Chart.js, and is available at this.options | |
2799 | initialize: function(data){ | |
2800 | this.segments = []; | |
2801 | //Declare segment class as a chart instance specific class, so it can share props for this instance | |
2802 | this.SegmentArc = Chart.Arc.extend({ | |
2803 | showStroke : this.options.segmentShowStroke, | |
2804 | strokeWidth : this.options.segmentStrokeWidth, | |
2805 | strokeColor : this.options.segmentStrokeColor, | |
2806 | ctx : this.chart.ctx, | |
2807 | innerRadius : 0, | |
2808 | x : this.chart.width/2, | |
2809 | y : this.chart.height/2 | |
2810 | }); | |
2811 | this.scale = new Chart.RadialScale({ | |
2812 | display: this.options.showScale, | |
2813 | fontStyle: this.options.scaleFontStyle, | |
2814 | fontSize: this.options.scaleFontSize, | |
2815 | fontFamily: this.options.scaleFontFamily, | |
2816 | fontColor: this.options.scaleFontColor, | |
2817 | showLabels: this.options.scaleShowLabels, | |
2818 | showLabelBackdrop: this.options.scaleShowLabelBackdrop, | |
2819 | backdropColor: this.options.scaleBackdropColor, | |
2820 | backdropPaddingY : this.options.scaleBackdropPaddingY, | |
2821 | backdropPaddingX: this.options.scaleBackdropPaddingX, | |
2822 | lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, | |
2823 | lineColor: this.options.scaleLineColor, | |
2824 | lineArc: true, | |
2825 | width: this.chart.width, | |
2826 | height: this.chart.height, | |
2827 | xCenter: this.chart.width/2, | |
2828 | yCenter: this.chart.height/2, | |
2829 | ctx : this.chart.ctx, | |
2830 | templateString: this.options.scaleLabel, | |
2831 | valuesCount: data.length | |
2832 | }); | |
2833 | ||
2834 | this.updateScaleRange(data); | |
2835 | ||
2836 | this.scale.update(); | |
2837 | ||
2838 | helpers.each(data,function(segment,index){ | |
2839 | this.addData(segment,index,true); | |
2840 | },this); | |
2841 | ||
2842 | //Set up tooltip events on the chart | |
2843 | if (this.options.showTooltips){ | |
2844 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ | |
2845 | var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; | |
2846 | helpers.each(this.segments,function(segment){ | |
2847 | segment.restore(["fillColor"]); | |
2848 | }); | |
2849 | helpers.each(activeSegments,function(activeSegment){ | |
2850 | activeSegment.fillColor = activeSegment.highlightColor; | |
2851 | }); | |
2852 | this.showTooltip(activeSegments); | |
2853 | }); | |
2854 | } | |
2855 | ||
2856 | this.render(); | |
2857 | }, | |
2858 | getSegmentsAtEvent : function(e){ | |
2859 | var segmentsArray = []; | |
2860 | ||
2861 | var location = helpers.getRelativePosition(e); | |
2862 | ||
2863 | helpers.each(this.segments,function(segment){ | |
2864 | if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); | |
2865 | },this); | |
2866 | return segmentsArray; | |
2867 | }, | |
2868 | addData : function(segment, atIndex, silent){ | |
2869 | var index = atIndex || this.segments.length; | |
2870 | ||
2871 | this.segments.splice(index, 0, new this.SegmentArc({ | |
2872 | fillColor: segment.color, | |
2873 | highlightColor: segment.highlight || segment.color, | |
2874 | label: segment.label, | |
2875 | value: segment.value, | |
2876 | outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value), | |
2877 | circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(), | |
2878 | startAngle: Math.PI * 1.5 | |
2879 | })); | |
2880 | if (!silent){ | |
2881 | this.reflow(); | |
2882 | this.update(); | |
2883 | } | |
2884 | }, | |
2885 | removeData: function(atIndex){ | |
2886 | var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; | |
2887 | this.segments.splice(indexToDelete, 1); | |
2888 | this.reflow(); | |
2889 | this.update(); | |
2890 | }, | |
2891 | calculateTotal: function(data){ | |
2892 | this.total = 0; | |
2893 | helpers.each(data,function(segment){ | |
2894 | this.total += segment.value; | |
2895 | },this); | |
2896 | this.scale.valuesCount = this.segments.length; | |
2897 | }, | |
2898 | updateScaleRange: function(datapoints){ | |
2899 | var valuesArray = []; | |
2900 | helpers.each(datapoints,function(segment){ | |
2901 | valuesArray.push(segment.value); | |
2902 | }); | |
2903 | ||
2904 | var scaleSizes = (this.options.scaleOverride) ? | |
2905 | { | |
2906 | steps: this.options.scaleSteps, | |
2907 | stepValue: this.options.scaleStepWidth, | |
2908 | min: this.options.scaleStartValue, | |
2909 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) | |
2910 | } : | |
2911 | helpers.calculateScaleRange( | |
2912 | valuesArray, | |
2913 | helpers.min([this.chart.width, this.chart.height])/2, | |
2914 | this.options.scaleFontSize, | |
2915 | this.options.scaleBeginAtZero, | |
2916 | this.options.scaleIntegersOnly | |
2917 | ); | |
2918 | ||
2919 | helpers.extend( | |
2920 | this.scale, | |
2921 | scaleSizes, | |
2922 | { | |
2923 | size: helpers.min([this.chart.width, this.chart.height]), | |
2924 | xCenter: this.chart.width/2, | |
2925 | yCenter: this.chart.height/2 | |
2926 | } | |
2927 | ); | |
2928 | ||
2929 | }, | |
2930 | update : function(){ | |
2931 | this.calculateTotal(this.segments); | |
2932 | ||
2933 | helpers.each(this.segments,function(segment){ | |
2934 | segment.save(); | |
2935 | }); | |
2936 | this.render(); | |
2937 | }, | |
2938 | reflow : function(){ | |
2939 | helpers.extend(this.SegmentArc.prototype,{ | |
2940 | x : this.chart.width/2, | |
2941 | y : this.chart.height/2 | |
2942 | }); | |
2943 | this.updateScaleRange(this.segments); | |
2944 | this.scale.update(); | |
2945 | ||
2946 | helpers.extend(this.scale,{ | |
2947 | xCenter: this.chart.width/2, | |
2948 | yCenter: this.chart.height/2 | |
2949 | }); | |
2950 | ||
2951 | helpers.each(this.segments, function(segment){ | |
2952 | segment.update({ | |
2953 | outerRadius : this.scale.calculateCenterOffset(segment.value) | |
2954 | }); | |
2955 | }, this); | |
2956 | ||
2957 | }, | |
2958 | draw : function(ease){ | |
2959 | var easingDecimal = ease || 1; | |
2960 | //Clear & draw the canvas | |
2961 | this.clear(); | |
2962 | helpers.each(this.segments,function(segment, index){ | |
2963 | segment.transition({ | |
2964 | circumference : this.scale.getCircumference(), | |
2965 | outerRadius : this.scale.calculateCenterOffset(segment.value) | |
2966 | },easingDecimal); | |
2967 | ||
2968 | segment.endAngle = segment.startAngle + segment.circumference; | |
2969 | ||
2970 | // If we've removed the first segment we need to set the first one to | |
2971 | // start at the top. | |
2972 | if (index === 0){ | |
2973 | segment.startAngle = Math.PI * 1.5; | |
2974 | } | |
2975 | ||
2976 | //Check to see if it's the last segment, if not get the next and update the start angle | |
2977 | if (index < this.segments.length - 1){ | |
2978 | this.segments[index+1].startAngle = segment.endAngle; | |
2979 | } | |
2980 | segment.draw(); | |
2981 | }, this); | |
2982 | this.scale.draw(); | |
2983 | } | |
2984 | }); | |
2985 | ||
2986 | }).call(this); | |
2987 | (function(){ | |
2988 | "use strict"; | |
2989 | ||
2990 | var root = this, | |
2991 | Chart = root.Chart, | |
2992 | helpers = Chart.helpers; | |
2993 | ||
2994 | ||
2995 | ||
2996 | Chart.Type.extend({ | |
2997 | name: "Radar", | |
2998 | defaults:{ | |
2999 | //Boolean - Whether to show lines for each scale point | |
3000 | scaleShowLine : true, | |
3001 | ||
3002 | //Boolean - Whether we show the angle lines out of the radar | |
3003 | angleShowLineOut : true, | |
3004 | ||
3005 | //Boolean - Whether to show labels on the scale | |
3006 | scaleShowLabels : false, | |
3007 | ||
3008 | // Boolean - Whether the scale should begin at zero | |
3009 | scaleBeginAtZero : true, | |
3010 | ||
3011 | //String - Colour of the angle line | |
3012 | angleLineColor : "rgba(0,0,0,.1)", | |
3013 | ||
3014 | //Number - Pixel width of the angle line | |
3015 | angleLineWidth : 1, | |
3016 | ||
3017 | //String - Point label font declaration | |
3018 | pointLabelFontFamily : "'Arial'", | |
3019 | ||
3020 | //String - Point label font weight | |
3021 | pointLabelFontStyle : "normal", | |
3022 | ||
3023 | //Number - Point label font size in pixels | |
3024 | pointLabelFontSize : 10, | |
3025 | ||
3026 | //String - Point label font colour | |
3027 | pointLabelFontColor : "#666", | |
3028 | ||
3029 | //Boolean - Whether to show a dot for each point | |
3030 | pointDot : true, | |
3031 | ||
3032 | //Number - Radius of each point dot in pixels | |
3033 | pointDotRadius : 3, | |
3034 | ||
3035 | //Number - Pixel width of point dot stroke | |
3036 | pointDotStrokeWidth : 1, | |
3037 | ||
3038 | //Number - amount extra to add to the radius to cater for hit detection outside the drawn point | |
3039 | pointHitDetectionRadius : 20, | |
3040 | ||
3041 | //Boolean - Whether to show a stroke for datasets | |
3042 | datasetStroke : true, | |
3043 | ||
3044 | //Number - Pixel width of dataset stroke | |
3045 | datasetStrokeWidth : 2, | |
3046 | ||
3047 | //Boolean - Whether to fill the dataset with a colour | |
3048 | datasetFill : true, | |
3049 | ||
3050 | //String - A legend template | |
3051 | legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>" | |
3052 | ||
3053 | }, | |
3054 | ||
3055 | initialize: function(data){ | |
3056 | this.PointClass = Chart.Point.extend({ | |
3057 | strokeWidth : this.options.pointDotStrokeWidth, | |
3058 | radius : this.options.pointDotRadius, | |
3059 | display: this.options.pointDot, | |
3060 | hitDetectionRadius : this.options.pointHitDetectionRadius, | |
3061 | ctx : this.chart.ctx | |
3062 | }); | |
3063 | ||
3064 | this.datasets = []; | |
3065 | ||
3066 | this.buildScale(data); | |
3067 | ||
3068 | //Set up tooltip events on the chart | |
3069 | if (this.options.showTooltips){ | |
3070 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ | |
3071 | var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; | |
3072 | ||
3073 | this.eachPoints(function(point){ | |
3074 | point.restore(['fillColor', 'strokeColor']); | |
3075 | }); | |
3076 | helpers.each(activePointsCollection, function(activePoint){ | |
3077 | activePoint.fillColor = activePoint.highlightFill; | |
3078 | activePoint.strokeColor = activePoint.highlightStroke; | |
3079 | }); | |
3080 | ||
3081 | this.showTooltip(activePointsCollection); | |
3082 | }); | |
3083 | } | |
3084 | ||
3085 | //Iterate through each of the datasets, and build this into a property of the chart | |
3086 | helpers.each(data.datasets,function(dataset){ | |
3087 | ||
3088 | var datasetObject = { | |
3089 | label: dataset.label || null, | |
3090 | fillColor : dataset.fillColor, | |
3091 | strokeColor : dataset.strokeColor, | |
3092 | pointColor : dataset.pointColor, | |
3093 | pointStrokeColor : dataset.pointStrokeColor, | |
3094 | points : [] | |
3095 | }; | |
3096 | ||
3097 | this.datasets.push(datasetObject); | |
3098 | ||
3099 | helpers.each(dataset.data,function(dataPoint,index){ | |
3100 | //Best way to do this? or in draw sequence...? | |
3101 | if (helpers.isNumber(dataPoint)){ | |
3102 | //Add a new point for each piece of data, passing any required data to draw. | |
3103 | var pointPosition; | |
3104 | if (!this.scale.animation){ | |
3105 | pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint)); | |
3106 | } | |
3107 | datasetObject.points.push(new this.PointClass({ | |
3108 | value : dataPoint, | |
3109 | label : data.labels[index], | |
3110 | datasetLabel: dataset.label, | |
3111 | x: (this.options.animation) ? this.scale.xCenter : pointPosition.x, | |
3112 | y: (this.options.animation) ? this.scale.yCenter : pointPosition.y, | |
3113 | strokeColor : dataset.pointStrokeColor, | |
3114 | fillColor : dataset.pointColor, | |
3115 | highlightFill : dataset.pointHighlightFill || dataset.pointColor, | |
3116 | highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor | |
3117 | })); | |
3118 | } | |
3119 | },this); | |
3120 | ||
3121 | },this); | |
3122 | ||
3123 | this.render(); | |
3124 | }, | |
3125 | eachPoints : function(callback){ | |
3126 | helpers.each(this.datasets,function(dataset){ | |
3127 | helpers.each(dataset.points,callback,this); | |
3128 | },this); | |
3129 | }, | |
3130 | ||
3131 | getPointsAtEvent : function(evt){ | |
3132 | var mousePosition = helpers.getRelativePosition(evt), | |
3133 | fromCenter = helpers.getAngleFromPoint({ | |
3134 | x: this.scale.xCenter, | |
3135 | y: this.scale.yCenter | |
3136 | }, mousePosition); | |
3137 | ||
3138 | var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount, | |
3139 | pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex), | |
3140 | activePointsCollection = []; | |
3141 | ||
3142 | // If we're at the top, make the pointIndex 0 to get the first of the array. | |
3143 | if (pointIndex >= this.scale.valuesCount || pointIndex < 0){ | |
3144 | pointIndex = 0; | |
3145 | } | |
3146 | ||
3147 | if (fromCenter.distance <= this.scale.drawingArea){ | |
3148 | helpers.each(this.datasets, function(dataset){ | |
3149 | activePointsCollection.push(dataset.points[pointIndex]); | |
3150 | }); | |
3151 | } | |
3152 | ||
3153 | return activePointsCollection; | |
3154 | }, | |
3155 | ||
3156 | buildScale : function(data){ | |
3157 | this.scale = new Chart.RadialScale({ | |
3158 | display: this.options.showScale, | |
3159 | fontStyle: this.options.scaleFontStyle, | |
3160 | fontSize: this.options.scaleFontSize, | |
3161 | fontFamily: this.options.scaleFontFamily, | |
3162 | fontColor: this.options.scaleFontColor, | |
3163 | showLabels: this.options.scaleShowLabels, | |
3164 | showLabelBackdrop: this.options.scaleShowLabelBackdrop, | |
3165 | backdropColor: this.options.scaleBackdropColor, | |
3166 | backdropPaddingY : this.options.scaleBackdropPaddingY, | |
3167 | backdropPaddingX: this.options.scaleBackdropPaddingX, | |
3168 | lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, | |
3169 | lineColor: this.options.scaleLineColor, | |
3170 | angleLineColor : this.options.angleLineColor, | |
3171 | angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0, | |
3172 | // Point labels at the edge of each line | |
3173 | pointLabelFontColor : this.options.pointLabelFontColor, | |
3174 | pointLabelFontSize : this.options.pointLabelFontSize, | |
3175 | pointLabelFontFamily : this.options.pointLabelFontFamily, | |
3176 | pointLabelFontStyle : this.options.pointLabelFontStyle, | |
3177 | height : this.chart.height, | |
3178 | width: this.chart.width, | |
3179 | xCenter: this.chart.width/2, | |
3180 | yCenter: this.chart.height/2, | |
3181 | ctx : this.chart.ctx, | |
3182 | templateString: this.options.scaleLabel, | |
3183 | labels: data.labels, | |
3184 | valuesCount: data.datasets[0].data.length | |
3185 | }); | |
3186 | ||
3187 | this.scale.setScaleSize(); | |
3188 | this.updateScaleRange(data.datasets); | |
3189 | this.scale.buildYLabels(); | |
3190 | }, | |
3191 | updateScaleRange: function(datasets){ | |
3192 | var valuesArray = (function(){ | |
3193 | var totalDataArray = []; | |
3194 | helpers.each(datasets,function(dataset){ | |
3195 | if (dataset.data){ | |
3196 | totalDataArray = totalDataArray.concat(dataset.data); | |
3197 | } | |
3198 | else { | |
3199 | helpers.each(dataset.points, function(point){ | |
3200 | totalDataArray.push(point.value); | |
3201 | }); | |
3202 | } | |
3203 | }); | |
3204 | return totalDataArray; | |
3205 | })(); | |
3206 | ||
3207 | ||
3208 | var scaleSizes = (this.options.scaleOverride) ? | |
3209 | { | |
3210 | steps: this.options.scaleSteps, | |
3211 | stepValue: this.options.scaleStepWidth, | |
3212 | min: this.options.scaleStartValue, | |
3213 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) | |
3214 | } : | |
3215 | helpers.calculateScaleRange( | |
3216 | valuesArray, | |
3217 | helpers.min([this.chart.width, this.chart.height])/2, | |
3218 | this.options.scaleFontSize, | |
3219 | this.options.scaleBeginAtZero, | |
3220 | this.options.scaleIntegersOnly | |
3221 | ); | |
3222 | ||
3223 | helpers.extend( | |
3224 | this.scale, | |
3225 | scaleSizes | |
3226 | ); | |
3227 | ||
3228 | }, | |
3229 | addData : function(valuesArray,label){ | |
3230 | //Map the values array for each of the datasets | |
3231 | this.scale.valuesCount++; | |
3232 | helpers.each(valuesArray,function(value,datasetIndex){ | |
3233 | if (helpers.isNumber(value)){ | |
3234 | var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value)); | |
3235 | this.datasets[datasetIndex].points.push(new this.PointClass({ | |
3236 | value : value, | |
3237 | label : label, | |
3238 | x: pointPosition.x, | |
3239 | y: pointPosition.y, | |
3240 | strokeColor : this.datasets[datasetIndex].pointStrokeColor, | |
3241 | fillColor : this.datasets[datasetIndex].pointColor | |
3242 | })); | |
3243 | } | |
3244 | },this); | |
3245 | ||
3246 | this.scale.labels.push(label); | |
3247 | ||
3248 | this.reflow(); | |
3249 | ||
3250 | this.update(); | |
3251 | }, | |
3252 | removeData : function(){ | |
3253 | this.scale.valuesCount--; | |
3254 | this.scale.labels.shift(); | |
3255 | helpers.each(this.datasets,function(dataset){ | |
3256 | dataset.points.shift(); | |
3257 | },this); | |
3258 | this.reflow(); | |
3259 | this.update(); | |
3260 | }, | |
3261 | update : function(){ | |
3262 | this.eachPoints(function(point){ | |
3263 | point.save(); | |
3264 | }); | |
3265 | this.reflow(); | |
3266 | this.render(); | |
3267 | }, | |
3268 | reflow: function(){ | |
3269 | helpers.extend(this.scale, { | |
3270 | width : this.chart.width, | |
3271 | height: this.chart.height, | |
3272 | size : helpers.min([this.chart.width, this.chart.height]), | |
3273 | xCenter: this.chart.width/2, | |
3274 | yCenter: this.chart.height/2 | |
3275 | }); | |
3276 | this.updateScaleRange(this.datasets); | |
3277 | this.scale.setScaleSize(); | |
3278 | this.scale.buildYLabels(); | |
3279 | }, | |
3280 | draw : function(ease){ | |
3281 | var easeDecimal = ease || 1, | |
3282 | ctx = this.chart.ctx; | |
3283 | this.clear(); | |
3284 | this.scale.draw(); | |
3285 | ||
3286 | helpers.each(this.datasets,function(dataset){ | |
3287 | ||
3288 | //Transition each point first so that the line and point drawing isn't out of sync | |
3289 | helpers.each(dataset.points,function(point,index){ | |
3290 | point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal); | |
3291 | },this); | |
3292 | ||
3293 | ||
3294 | ||
3295 | //Draw the line between all the points | |
3296 | ctx.lineWidth = this.options.datasetStrokeWidth; | |
3297 | ctx.strokeStyle = dataset.strokeColor; | |
3298 | ctx.beginPath(); | |
3299 | helpers.each(dataset.points,function(point,index){ | |
3300 | if (index === 0){ | |
3301 | ctx.moveTo(point.x,point.y); | |
3302 | } | |
3303 | else{ | |
3304 | ctx.lineTo(point.x,point.y); | |
3305 | } | |
3306 | },this); | |
3307 | ctx.closePath(); | |
3308 | ctx.stroke(); | |
3309 | ||
3310 | ctx.fillStyle = dataset.fillColor; | |
3311 | ctx.fill(); | |
3312 | ||
3313 | //Now draw the points over the line | |
3314 | //A little inefficient double looping, but better than the line | |
3315 | //lagging behind the point positions | |
3316 | helpers.each(dataset.points,function(point){ | |
3317 | point.draw(); | |
3318 | }); | |
3319 | ||
3320 | },this); | |
3321 | ||
3322 | } | |
3323 | ||
3324 | }); | |
3325 | ||
3326 | ||
3327 | ||
3328 | ||
3329 | ||
3330 | }).call(this);⏎ |
0 | .chart-legend,.bar-legend,.line-legend,.pie-legend,.radar-legend,.polararea-legend,.doughnut-legend{list-style-type:none;margin-top:5px;text-align:center;-webkit-padding-start:0;-moz-padding-start:0;padding-left:0}.chart-legend li,.bar-legend li,.line-legend li,.pie-legend li,.radar-legend li,.polararea-legend li,.doughnut-legend li{display:inline-block;white-space:nowrap;position:relative;margin-bottom:4px;border-radius:5px;padding:2px 8px 2px 28px;font-size:smaller;cursor:default}.chart-legend li span,.bar-legend li span,.line-legend li span,.pie-legend li span,.radar-legend li span,.polararea-legend li span,.doughnut-legend li span{display:block;position:absolute;left:0;top:0;width:20px;height:20px;border-radius:5px} | |
1 | /*# sourceMappingURL=angular-chart.css.map */⏎ |
0 | {"version":3,"sources":["angular-chart.less"],"names":[],"mappings":"AAAA;AAAe;AAAa;AAAc;AAAa;AAAe;AAAmB;EACvF,qBAAA;EACA,eAAA;EACA,kBAAA;;EAEA,wBAAA;;EACA,qBAAA;;EACA,eAAA;;;AAPF,aASE;AATa,WASb;AAT0B,YAS1B;AATwC,WASxC;AATqD,aASrD;AAToE,iBASpE;AATuF,gBASvF;EACE,qBAAA;EACA,mBAAA;EACA,kBAAA;EACA,kBAAA;EACA,kBAAA;EACA,yBAAA;EACA,kBAAA;EACA,eAAA;;AAjBJ,aASE,GAUE;AAnBW,WASb,GAUE;AAnBwB,YAS1B,GAUE;AAnBsC,WASxC,GAUE;AAnBmD,aASrD,GAUE;AAnBkE,iBASpE,GAUE;AAnBqF,gBASvF,GAUE;EACE,cAAA;EACA,kBAAA;EACA,OAAA;EACA,MAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA","file":"angular-chart.css","sourcesContent":[".chart-legend, .bar-legend, .line-legend, .pie-legend, .radar-legend, .polararea-legend, .doughnut-legend {\n list-style-type: none;\n margin-top: 5px;\n text-align: center;\n /* NOTE: Browsers automatically add 40px of padding-left to all lists, so we should offset that, otherwise the legend is off-center */\n -webkit-padding-start:0; /* Webkit */\n -moz-padding-start:0; /* Mozilla */\n padding-left:0; /* IE (handles all cases, really, but we should also include the vendor-specific properties just to be safe) */\n\n li {\n display: inline-block;\n white-space: nowrap;\n position: relative;\n margin-bottom: 4px;\n border-radius: 5px;\n padding: 2px 8px 2px 28px;\n font-size: smaller;\n cursor: default;\n\n span {\n display: block;\n position: absolute;\n left: 0;\n top: 0;\n width: 20px;\n height: 20px;\n border-radius: 5px;\n }\n }\n}\n"],"sourceRoot":"/source/"}⏎ |
0 | (function (factory) { | |
1 | 'use strict'; | |
2 | if (typeof define === 'function' && define.amd) { | |
3 | // AMD. Register as an anonymous module. | |
4 | define(['angular', 'chart'], factory); | |
5 | } else if (typeof exports === 'object') { | |
6 | // Node/CommonJS | |
7 | module.exports = factory(require('angular'), require('chart.js')); | |
8 | } else { | |
9 | // Browser globals | |
10 | factory(angular, Chart); | |
11 | } | |
12 | }(function (angular, Chart) { | |
13 | 'use strict'; | |
14 | ||
15 | Chart.defaults.global.responsive = true; | |
16 | Chart.defaults.global.multiTooltipTemplate = '<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value %>'; | |
17 | ||
18 | Chart.defaults.global.colours = [ | |
19 | '#97BBCD', // blue | |
20 | '#DCDCDC', // light grey | |
21 | '#F7464A', // red | |
22 | '#46BFBD', // green | |
23 | '#FDB45C', // yellow | |
24 | '#949FB1', // grey | |
25 | '#4D5360' // dark grey | |
26 | ]; | |
27 | ||
28 | var usingExcanvas = typeof window.G_vmlCanvasManager === 'object' && | |
29 | window.G_vmlCanvasManager !== null && | |
30 | typeof window.G_vmlCanvasManager.initElement === 'function'; | |
31 | ||
32 | if (usingExcanvas) Chart.defaults.global.animation = false; | |
33 | ||
34 | return angular.module('chart.js', []) | |
35 | .provider('ChartJs', ChartJsProvider) | |
36 | .factory('ChartJsFactory', ['ChartJs', '$timeout', ChartJsFactory]) | |
37 | .directive('chartBase', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory(); }]) | |
38 | .directive('chartLine', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('Line'); }]) | |
39 | .directive('chartBar', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('Bar'); }]) | |
40 | .directive('chartRadar', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('Radar'); }]) | |
41 | .directive('chartDoughnut', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('Doughnut'); }]) | |
42 | .directive('chartPie', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('Pie'); }]) | |
43 | .directive('chartPolarArea', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('PolarArea'); }]); | |
44 | ||
45 | /** | |
46 | * Wrapper for chart.js | |
47 | * Allows configuring chart js using the provider | |
48 | * | |
49 | * angular.module('myModule', ['chart.js']).config(function(ChartJsProvider) { | |
50 | * ChartJsProvider.setOptions({ responsive: true }); | |
51 | * ChartJsProvider.setOptions('Line', { responsive: false }); | |
52 | * }))) | |
53 | */ | |
54 | function ChartJsProvider () { | |
55 | var options = {}; | |
56 | var ChartJs = { | |
57 | Chart: Chart, | |
58 | getOptions: function (type) { | |
59 | var typeOptions = type && options[type] || {}; | |
60 | return angular.extend({}, options, typeOptions); | |
61 | } | |
62 | }; | |
63 | ||
64 | /** | |
65 | * Allow to set global options during configuration | |
66 | */ | |
67 | this.setOptions = function (type, customOptions) { | |
68 | // If no type was specified set option for the global object | |
69 | if (! customOptions) { | |
70 | customOptions = type; | |
71 | options = angular.extend(options, customOptions); | |
72 | return; | |
73 | } | |
74 | // Set options for the specific chart | |
75 | options[type] = angular.extend(options[type] || {}, customOptions); | |
76 | }; | |
77 | ||
78 | this.$get = function () { | |
79 | return ChartJs; | |
80 | }; | |
81 | } | |
82 | ||
83 | function ChartJsFactory (ChartJs, $timeout) { | |
84 | return function chart (type) { | |
85 | return { | |
86 | restrict: 'CA', | |
87 | scope: { | |
88 | data: '=?', | |
89 | labels: '=?', | |
90 | options: '=?', | |
91 | series: '=?', | |
92 | colours: '=?', | |
93 | getColour: '=?', | |
94 | chartType: '=', | |
95 | legend: '@', | |
96 | click: '=?', | |
97 | hover: '=?', | |
98 | ||
99 | chartData: '=?', | |
100 | chartLabels: '=?', | |
101 | chartOptions: '=?', | |
102 | chartSeries: '=?', | |
103 | chartColours: '=?', | |
104 | chartLegend: '@', | |
105 | chartClick: '=?', | |
106 | chartHover: '=?' | |
107 | }, | |
108 | link: function (scope, elem/*, attrs */) { | |
109 | var chart, container = document.createElement('div'); | |
110 | container.className = 'chart-container'; | |
111 | elem.replaceWith(container); | |
112 | container.appendChild(elem[0]); | |
113 | ||
114 | if (usingExcanvas) window.G_vmlCanvasManager.initElement(elem[0]); | |
115 | ||
116 | ['data', 'labels', 'options', 'series', 'colours', 'legend', 'click', 'hover'].forEach(deprecated); | |
117 | function aliasVar (fromName, toName) { | |
118 | scope.$watch(fromName, function (newVal) { | |
119 | if (typeof newVal === 'undefined') return; | |
120 | scope[toName] = newVal; | |
121 | }); | |
122 | } | |
123 | /* provide backward compatibility to "old" directive names, by | |
124 | * having an alias point from the new names to the old names. */ | |
125 | aliasVar('chartData', 'data'); | |
126 | aliasVar('chartLabels', 'labels'); | |
127 | aliasVar('chartOptions', 'options'); | |
128 | aliasVar('chartSeries', 'series'); | |
129 | aliasVar('chartColours', 'colours'); | |
130 | aliasVar('chartLegend', 'legend'); | |
131 | aliasVar('chartClick', 'click'); | |
132 | aliasVar('chartHover', 'hover'); | |
133 | ||
134 | // Order of setting "watch" matter | |
135 | ||
136 | scope.$watch('data', function (newVal, oldVal) { | |
137 | if (! newVal || ! newVal.length || (Array.isArray(newVal[0]) && ! newVal[0].length)) return; | |
138 | var chartType = type || scope.chartType; | |
139 | if (! chartType) return; | |
140 | ||
141 | if (chart) { | |
142 | if (canUpdateChart(newVal, oldVal)) return updateChart(chart, newVal, scope, elem); | |
143 | chart.destroy(); | |
144 | } | |
145 | ||
146 | createChart(chartType); | |
147 | }, true); | |
148 | ||
149 | scope.$watch('series', resetChart, true); | |
150 | scope.$watch('labels', resetChart, true); | |
151 | scope.$watch('options', resetChart, true); | |
152 | scope.$watch('colours', resetChart, true); | |
153 | ||
154 | scope.$watch('chartType', function (newVal, oldVal) { | |
155 | if (isEmpty(newVal)) return; | |
156 | if (angular.equals(newVal, oldVal)) return; | |
157 | if (chart) chart.destroy(); | |
158 | createChart(newVal); | |
159 | }); | |
160 | ||
161 | scope.$on('$destroy', function () { | |
162 | if (chart) chart.destroy(); | |
163 | }); | |
164 | ||
165 | function resetChart (newVal, oldVal) { | |
166 | if (isEmpty(newVal)) return; | |
167 | if (angular.equals(newVal, oldVal)) return; | |
168 | var chartType = type || scope.chartType; | |
169 | if (! chartType) return; | |
170 | ||
171 | // chart.update() doesn't work for series and labels | |
172 | // so we have to re-create the chart entirely | |
173 | if (chart) chart.destroy(); | |
174 | ||
175 | createChart(chartType); | |
176 | } | |
177 | ||
178 | function createChart (type) { | |
179 | if (isResponsive(type, scope) && elem[0].clientHeight === 0 && container.clientHeight === 0) { | |
180 | return $timeout(function () { | |
181 | createChart(type); | |
182 | }, 50); | |
183 | } | |
184 | if (! scope.data || ! scope.data.length) return; | |
185 | scope.getColour = typeof scope.getColour === 'function' ? scope.getColour : getRandomColour; | |
186 | scope.colours = getColours(type, scope); | |
187 | var cvs = elem[0], ctx = cvs.getContext('2d'); | |
188 | var data = Array.isArray(scope.data[0]) ? | |
189 | getDataSets(scope.labels, scope.data, scope.series || [], scope.colours) : | |
190 | getData(scope.labels, scope.data, scope.colours); | |
191 | var options = angular.extend({}, ChartJs.getOptions(type), scope.options); | |
192 | chart = new ChartJs.Chart(ctx)[type](data, options); | |
193 | scope.$emit('create', chart); | |
194 | ||
195 | ['hover', 'click'].forEach(function (action) { | |
196 | if (scope[action]) | |
197 | cvs[action === 'click' ? 'onclick' : 'onmousemove'] = getEventHandler(scope, chart, action); | |
198 | }); | |
199 | if (scope.legend && scope.legend !== 'false') setLegend(elem, chart); | |
200 | } | |
201 | ||
202 | function deprecated (attr) { | |
203 | if (typeof console !== 'undefined' && ChartJs.getOptions().env !== 'test') { | |
204 | var warn = typeof console.warn === 'function' ? console.warn : console.log; | |
205 | if (!! scope[attr]) { | |
206 | warn.call(console, '"%s" is deprecated and will be removed in a future version. ' + | |
207 | 'Please use "chart-%s" instead.', attr, attr); | |
208 | } | |
209 | } | |
210 | } | |
211 | } | |
212 | }; | |
213 | }; | |
214 | ||
215 | function canUpdateChart (newVal, oldVal) { | |
216 | if (newVal && oldVal && newVal.length && oldVal.length) { | |
217 | return Array.isArray(newVal[0]) ? | |
218 | newVal.length === oldVal.length && newVal.every(function (element, index) { | |
219 | return element.length === oldVal[index].length; }) : | |
220 | oldVal.reduce(sum, 0) > 0 ? newVal.length === oldVal.length : false; | |
221 | } | |
222 | return false; | |
223 | } | |
224 | ||
225 | function sum (carry, val) { | |
226 | return carry + val; | |
227 | } | |
228 | ||
229 | function getEventHandler (scope, chart, action) { | |
230 | return function (evt) { | |
231 | var atEvent = chart.getPointsAtEvent || chart.getBarsAtEvent || chart.getSegmentsAtEvent; | |
232 | if (atEvent) { | |
233 | var activePoints = atEvent.call(chart, evt); | |
234 | scope[action](activePoints, evt); | |
235 | scope.$apply(); | |
236 | } | |
237 | }; | |
238 | } | |
239 | ||
240 | function getColours (type, scope) { | |
241 | var colours = angular.copy(scope.colours || | |
242 | ChartJs.getOptions(type).colours || | |
243 | Chart.defaults.global.colours | |
244 | ); | |
245 | while (colours.length < scope.data.length) { | |
246 | colours.push(scope.getColour()); | |
247 | } | |
248 | return colours.map(convertColour); | |
249 | } | |
250 | ||
251 | function convertColour (colour) { | |
252 | if (typeof colour === 'object' && colour !== null) return colour; | |
253 | if (typeof colour === 'string' && colour[0] === '#') return getColour(hexToRgb(colour.substr(1))); | |
254 | return getRandomColour(); | |
255 | } | |
256 | ||
257 | function getRandomColour () { | |
258 | var colour = [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)]; | |
259 | return getColour(colour); | |
260 | } | |
261 | ||
262 | function getColour (colour) { | |
263 | return { | |
264 | fillColor: rgba(colour, 0.2), | |
265 | strokeColor: rgba(colour, 1), | |
266 | pointColor: rgba(colour, 1), | |
267 | pointStrokeColor: '#fff', | |
268 | pointHighlightFill: '#fff', | |
269 | pointHighlightStroke: rgba(colour, 0.8) | |
270 | }; | |
271 | } | |
272 | ||
273 | function getRandomInt (min, max) { | |
274 | return Math.floor(Math.random() * (max - min + 1)) + min; | |
275 | } | |
276 | ||
277 | function rgba (colour, alpha) { | |
278 | if (usingExcanvas) { | |
279 | // rgba not supported by IE8 | |
280 | return 'rgb(' + colour.join(',') + ')'; | |
281 | } else { | |
282 | return 'rgba(' + colour.concat(alpha).join(',') + ')'; | |
283 | } | |
284 | } | |
285 | ||
286 | // Credit: http://stackoverflow.com/a/11508164/1190235 | |
287 | function hexToRgb (hex) { | |
288 | var bigint = parseInt(hex, 16), | |
289 | r = (bigint >> 16) & 255, | |
290 | g = (bigint >> 8) & 255, | |
291 | b = bigint & 255; | |
292 | ||
293 | return [r, g, b]; | |
294 | } | |
295 | ||
296 | function getDataSets (labels, data, series, colours) { | |
297 | return { | |
298 | labels: labels, | |
299 | datasets: data.map(function (item, i) { | |
300 | return angular.extend({}, colours[i], { | |
301 | label: series[i], | |
302 | data: item | |
303 | }); | |
304 | }) | |
305 | }; | |
306 | } | |
307 | ||
308 | function getData (labels, data, colours) { | |
309 | return labels.map(function (label, i) { | |
310 | return angular.extend({}, colours[i], { | |
311 | label: label, | |
312 | value: data[i], | |
313 | color: colours[i].strokeColor, | |
314 | highlight: colours[i].pointHighlightStroke | |
315 | }); | |
316 | }); | |
317 | } | |
318 | ||
319 | function setLegend (elem, chart) { | |
320 | var $parent = elem.parent(), | |
321 | $oldLegend = $parent.find('chart-legend'), | |
322 | legend = '<chart-legend>' + chart.generateLegend() + '</chart-legend>'; | |
323 | if ($oldLegend.length) $oldLegend.replaceWith(legend); | |
324 | else $parent.append(legend); | |
325 | } | |
326 | ||
327 | function updateChart (chart, values, scope, elem) { | |
328 | if (Array.isArray(scope.data[0])) { | |
329 | chart.datasets.forEach(function (dataset, i) { | |
330 | (dataset.points || dataset.bars).forEach(function (dataItem, j) { | |
331 | dataItem.value = values[i][j]; | |
332 | }); | |
333 | }); | |
334 | } else { | |
335 | chart.segments.forEach(function (segment, i) { | |
336 | segment.value = values[i]; | |
337 | }); | |
338 | } | |
339 | chart.update(); | |
340 | scope.$emit('update', chart); | |
341 | if (scope.legend && scope.legend !== 'false') setLegend(elem, chart); | |
342 | } | |
343 | ||
344 | function isEmpty (value) { | |
345 | return ! value || | |
346 | (Array.isArray(value) && ! value.length) || | |
347 | (typeof value === 'object' && ! Object.keys(value).length); | |
348 | } | |
349 | ||
350 | function isResponsive (type, scope) { | |
351 | var options = angular.extend({}, Chart.defaults.global, ChartJs.getOptions(type), scope.options); | |
352 | return options.responsive; | |
353 | } | |
354 | } | |
355 | }));⏎ |
7 | 7 | async: false |
8 | 8 | }); |
9 | 9 | |
10 | var faradayApp = angular.module('faradayApp', ['ngRoute', 'selectionModel', 'ui.bootstrap', 'angularFileUpload', 'filter', 'ngClipboard', 'ngCookies', 'cfp.hotkeys']) | |
10 | var faradayApp = angular.module('faradayApp', ['ngRoute', 'selectionModel', 'ui.bootstrap', 'angularFileUpload', 'filter', 'ngClipboard', 'ngCookies', 'cfp.hotkeys', 'chart.js']) | |
11 | 11 | .constant("BASEURL", (function() { |
12 | 12 | var url = window.location.origin + "/"; |
13 | 13 | return url; |
52 | 52 | controller: 'workspacesCtrl', |
53 | 53 | title: 'Dashboard | ' |
54 | 54 | }). |
55 | when('/hosts/ws/:wsId/search/:search', { | |
56 | templateUrl: 'scripts/hosts/partials/list.html', | |
57 | controller: 'hostsCtrl', | |
58 | title: 'Hosts | ' | |
59 | }). | |
60 | when('/hosts/ws/:wsId/search', { | |
61 | templateUrl: 'scripts/hosts/partials/list.html', | |
62 | controller: 'hostsCtrl', | |
63 | title: 'Hosts | ' | |
64 | }). | |
55 | 65 | when('/hosts/ws/:wsId', { |
56 | 66 | templateUrl: 'scripts/hosts/partials/list.html', |
57 | 67 | controller: 'hostsCtrl', |
61 | 71 | templateUrl: 'scripts/commons/partials/workspaces.html', |
62 | 72 | controller: 'workspacesCtrl', |
63 | 73 | title: 'Hosts | ' |
74 | }). | |
75 | when('/host/ws/:wsId/hid/:hidId/search/:search', { | |
76 | templateUrl: 'scripts/services/partials/list.html', | |
77 | controller: 'hostCtrl', | |
78 | title: 'Services | ' | |
79 | }). | |
80 | when('/host/ws/:wsId/hid/:hidId/search', { | |
81 | templateUrl: 'scripts/services/partials/list.html', | |
82 | controller: 'hostCtrl', | |
83 | title: 'Services | ' | |
64 | 84 | }). |
65 | 85 | when('/hosts', { |
66 | 86 | templateUrl: 'scripts/commons/partials/workspaces.html', |
102 | 122 | controller: 'workspacesCtrl', |
103 | 123 | title: 'Workspaces | ' |
104 | 124 | }). |
125 | when('/communication', { | |
126 | templateUrl: 'scripts/commons/partials/commercial.html', | |
127 | controller: 'commercialCtrl', | |
128 | title: 'Communication | ' | |
129 | }). | |
130 | when('/comparison', { | |
131 | templateUrl: 'scripts/commons/partials/commercial.html', | |
132 | controller: 'commercialCtrl' | |
133 | }). | |
134 | when('/webshell', { | |
135 | templateUrl: 'scripts/commons/partials/commercial.html', | |
136 | controller: 'commercialCtrl' | |
137 | }). | |
138 | when('/executive', { | |
139 | templateUrl: 'scripts/commons/partials/commercial.html', | |
140 | controller: 'commercialCtrl', | |
141 | title: 'Executive Report | ' | |
142 | }). | |
143 | when('/users', { | |
144 | templateUrl: 'scripts/commons/partials/commercial.html', | |
145 | controller: 'commercialCtrl', | |
146 | title: 'Users | ' | |
147 | }). | |
105 | 148 | otherwise({ |
106 | 149 | templateUrl: 'scripts/commons/partials/home.html', |
107 | 150 | controller: 'statusReportCtrl' |
0 | // Faraday Penetration Test IDE | |
1 | // Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | // See the file 'doc/LICENSE' for the license information | |
3 | ||
4 | angular.module('faradayApp') | |
5 | .controller('commercialCtrl', | |
6 | ['$scope', '$location', | |
7 | function($scope, $location) { | |
8 | if ($location.path().split("/")[1] === "executive") { | |
9 | $scope.header = "executive report"; | |
10 | } else if ($location.path().split("/")[1] === "comparison") { | |
11 | $scope.header = "workspace comparison"; | |
12 | } else if ($location.path().split("/")[1] === "communication") { | |
13 | $scope.header = "chat"; | |
14 | } else { | |
15 | $scope.header = $location.path().split("/")[1]; | |
16 | } | |
17 | }]);⏎ |
0 | <!-- Faraday Penetration Test IDE --> | |
1 | <!-- Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) --> | |
2 | <!-- See the file 'doc/LICENSE' for the license information --> | |
3 | ||
4 | <section id="main" class="seccion clearfix"> | |
5 | <div class="right-main"><div id="reports-main" class="fila clearfix"> | |
6 | <div class="jumbotron" ng-class="{ 'jumbotron-font':header === 'workspace comparison' }"> | |
7 | <h1><b>Welcome to the <span style="text-transform: capitalize;">{{header}}</span> panel!</b></h1> | |
8 | <p>This feature belongs to our commercial versions</p> | |
9 | <p>For more information, please contact us at <span class="bold">[email protected]</span> or visit <a href="http://www.faradaysec.com">www.faradaysec.com</a> !</p> | |
10 | </div><!-- .jumbotron --> | |
11 | </div><!-- #reports-main --></div><!-- .right-main --> | |
12 | </section><!-- #main --> |
9 | 9 | </h2><!-- .ws-label --> |
10 | 10 | <div class="reports"> |
11 | 11 | <div class="reports"> |
12 | <div class="ws-list home-list community clearfix"> | |
12 | <div class="ws-list home-list corporate clearfix"> | |
13 | 13 | <a href="#/dashboard" class="ws-link item animated flipInX"> |
14 | 14 | <img src="images/ico-dashboard.svg" /> |
15 | 15 | <span class="ws-name">Dashboard</span> |
42 | 42 | <strong>Manage your hosts</strong> |
43 | 43 | </small> |
44 | 44 | </a> |
45 | <a href="#/users" class="ws-link item animated flipInX"> | |
46 | <img src="images/ico-users.svg" /> | |
47 | <span class="ws-name">Users</span> | |
48 | <small> | |
49 | Create and edit members.<br/> | |
50 | <strong>Manage your Team</strong> | |
51 | </small> | |
52 | </a> | |
53 | <a href="#/executive" class="ws-link item animated flipInX"> | |
54 | <img src="images/ico-executive.svg" /> | |
55 | <span class="ws-name">Executive Report</span> | |
56 | <small> | |
57 | Export project to a word file.<br/> | |
58 | <strong>Manage reports</strong> | |
59 | </small> | |
60 | </a> | |
61 | <a href="#/communication" class="ws-link item animated flipInX"> | |
62 | <img src="images/ico-communication.svg" /> | |
63 | <span class="ws-name">Chat</span> | |
64 | <small> | |
65 | Share information with other users.<br/> | |
66 | <strong>Join the conversation</strong> | |
67 | </small> | |
68 | </a> | |
69 | <a href="#/comparison" class="ws-link item animated flipInX"> | |
70 | <img src="images/ico-workspace-comparison.svg" /> | |
71 | <span class="ws-name">Workspace Comparison</span> | |
72 | <small> | |
73 | Compare two workspaces.<br/> | |
74 | <strong>Differences between projects</strong> | |
75 | </small> | |
76 | </a> | |
77 | <a href="#/webshell" class="ws-link item animated flipInX"> | |
78 | <img src="images/ico-web-shell.svg" /> | |
79 | <span class="ws-name">Web Shell</span> | |
80 | <small> | |
81 | Run commands directly from your.<br/> | |
82 | <strong>UI Web</strong> | |
83 | </small> | |
84 | </a> | |
45 | 85 | </div><!-- .ws-list --> |
46 | 86 | </div><!-- .reports --> |
47 | 87 | </div><!-- #reports-main --></div><!-- .right-main --> |
0 | # Faraday Penetration Test IDE | |
1 | # Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | # See the file 'doc/LICENSE' for the license information | |
3 | ||
4 | # Ignore config file | |
5 | config.json |
0 | // Faraday Penetration Test IDE | |
1 | // Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | // See the file 'doc/LICENSE' for the license information | |
3 | ||
4 | angular.module('faradayApp') | |
5 | .factory('configSrv', ['BASEURL', '$http', function(BASEURL, $http) { | |
6 | ||
7 | var p = $http.get('/reports/_design/reports/scripts/config/config.json').then(function(conf) { | |
8 | configSrv.faraday_version = conf.data.ver; | |
9 | }); | |
10 | ||
11 | configSrv = { | |
12 | faraday_version: null, | |
13 | promise: p | |
14 | } | |
15 | ||
16 | return configSrv; | |
17 | }]); |
156 | 156 | |
157 | 157 | // used to create pie chart for vulns |
158 | 158 | $scope.vulnsCountClass = {"children": angular.copy(tmp)}; |
159 | $scope.doughnut = {key: [], value: [], colors: [], options: {maintainAspectRatio: false}}; | |
159 | 160 | for(var i = 0; i < $scope.vulnsCountClass.children.length; i++) { |
160 | 161 | if($scope.vulnsCountClass.children[i].key == "unclassified") { |
161 | 162 | $scope.vulnsCountClass.children.splice(i, 1); |
162 | 163 | break; |
163 | 164 | } |
165 | $scope.doughnut.key.push($scope.vulnsCountClass.children[i].key); | |
166 | $scope.doughnut.value.push($scope.vulnsCountClass.children[i].value); | |
167 | $scope.doughnut.colors.push($scope.vulnsCountClass.children[i].color); | |
164 | 168 | }; |
165 | 169 | |
166 | 170 | $scope.$watch('vulnPrices', function(ps) { |
216 | 220 | return b.value-a.value; |
217 | 221 | }); |
218 | 222 | var colors = ["rgb(57, 59, 121)","rgb(82, 84, 163)","rgb(107, 110, 207)"]; |
219 | var tmp = []; | |
223 | var tmp = {key:[], colors:[], value:[]}; | |
224 | tmp.options = { | |
225 | showScale : false, | |
226 | maintainAspectRatio: false | |
227 | }; | |
220 | 228 | servicesCount.slice(0, 3).forEach(function(srv) { |
221 | srv.color = colors.shift(); | |
222 | tmp.push(srv); | |
229 | tmp.colors.push(colors.shift()); | |
230 | tmp.value.push(srv.value); | |
231 | tmp.key.push(host.name); | |
223 | 232 | }); |
224 | 233 | $scope.topHosts = tmp; |
225 | 234 | } |
0 | <canvas id="bar" class="chart chart-bar" chart-data="[topHosts.value]" chart-labels="topHosts.key" chart-series="topHosts.key" chart-legend="false" chart-options="topHosts.options"></canvas>⏎ |
0 | <canvas id="doughnut" class="chart chart-doughnut" chart-data="doughnut.value" chart-labels="doughnut.key" chart-colours="doughnut.colors" chart-options="doughnut.options"></canvas>⏎ |
35 | 35 | </button> |
36 | 36 | <p>At least 3 hosts needed to show this visualization</p> |
37 | 37 | </div> |
38 | <d3-bars data="topHosts"></d3-bars> | |
38 | <div id="bar" ng-include="'scripts/dashboard/partials/barChart.html'" ng-if="topHosts != undefined || topHosts.length > 3"></div> | |
39 | 39 | </article> |
40 | 40 | </div> |
41 | 41 | <div class='col-lg-2'> |
53 | 53 | </button> |
54 | 54 | <p>No vulnerabilities found yet</p> |
55 | 55 | </div> |
56 | <d3-cake data="vulnsCountClass"></d3-cake> | |
56 | <div id="doughnut" ng-include="'scripts/dashboard/partials/doughnut.html'" ng-if="vulnsCountClass != undefined || vulnsCountClass.length != 0"></div> | |
57 | 57 | </article> |
58 | 58 | </div> |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('hostCtrl', |
6 | ['$scope', '$filter', '$route', '$routeParams', '$modal', 'hostsManager', 'workspacesFact', 'dashboardSrv', 'servicesManager', | |
7 | function($scope, $filter, $route, $routeParams, $modal, hostsManager, workspacesFact, dashboardSrv, servicesManager) { | |
6 | ['$scope', '$cookies', '$filter', '$location', '$route', '$routeParams', '$modal', 'hostsManager', 'workspacesFact', 'dashboardSrv', 'servicesManager', | |
7 | function($scope, $cookies, $filter, $location, $route, $routeParams, $modal, hostsManager, workspacesFact, dashboardSrv, servicesManager) { | |
8 | 8 | |
9 | 9 | init = function() { |
10 | 10 | $scope.selectall = false; |
12 | 12 | $scope.workspace = $routeParams.wsId; |
13 | 13 | //ID of current host |
14 | 14 | var hostId = $routeParams.hidId; |
15 | ||
16 | $scope.sortField = "name"; | |
17 | ||
15 | 18 | // load all workspaces |
16 | 19 | workspacesFact.list().then(function(wss) { |
17 | 20 | $scope.workspaces = wss; |
42 | 45 | .catch(function(e) { |
43 | 46 | console.log(e); |
44 | 47 | }); |
48 | ||
49 | $scope.pageSize = 10; | |
50 | $scope.currentPage = 0; | |
51 | $scope.newCurrentPage = 0; | |
52 | ||
53 | if(!isNaN(parseInt($cookies.pageSize))) $scope.pageSize = parseInt($cookies.pageSize); | |
54 | $scope.newPageSize = $scope.pageSize; | |
55 | ||
56 | // current search | |
57 | $scope.search = $routeParams.search; | |
58 | $scope.searchParams = ""; | |
59 | $scope.expression = {}; | |
60 | if($scope.search != "" && $scope.search != undefined && $scope.search.indexOf("=") > -1) { | |
61 | // search expression for filter | |
62 | $scope.expression = $scope.decodeSearch($scope.search); | |
63 | // search params for search field, which shouldn't be used for filtering | |
64 | $scope.searchParams = $scope.stringSearch($scope.expression); | |
65 | } | |
45 | 66 | }; |
67 | ||
68 | $scope.selectedServices = function() { | |
69 | selected = []; | |
70 | ||
71 | tmp_services = filter($scope.services); | |
72 | tmp_services.forEach(function(service) { | |
73 | if(service.selected === true) { | |
74 | selected.push(service); | |
75 | } | |
76 | }); | |
77 | return selected; | |
78 | }; | |
79 | ||
80 | // changes the URL according to search params | |
81 | $scope.searchFor = function(search, params) { | |
82 | var url = "/host/ws/" + $routeParams.wsId + "/hid/" + $routeParams.hidId; | |
83 | ||
84 | if(search && params != "" && params != undefined) { | |
85 | url += "/search/" + $scope.encodeSearch(params); | |
86 | } | |
87 | ||
88 | $location.path(url); | |
89 | }; | |
90 | ||
91 | $scope.go = function() { | |
92 | $scope.pageSize = $scope.newPageSize; | |
93 | $cookies.pageSize = $scope.pageSize; | |
94 | $scope.currentPage = 0; | |
95 | if($scope.newCurrentPage <= parseInt($scope.services.length/$scope.pageSize) | |
96 | && $scope.newCurrentPage > -1 && !isNaN(parseInt($scope.newCurrentPage))) { | |
97 | $scope.currentPage = $scope.newCurrentPage; | |
98 | } | |
99 | }; | |
100 | ||
101 | // encodes search string in order to send it through URL | |
102 | $scope.encodeSearch = function(search) { | |
103 | var i = -1, | |
104 | encode = "", | |
105 | params = search.split(" "), | |
106 | chunks = {}; | |
107 | ||
108 | params.forEach(function(chunk) { | |
109 | i = chunk.indexOf(":"); | |
110 | if(i > 0) { | |
111 | chunks[chunk.slice(0, i)] = chunk.slice(i+1); | |
112 | } else { | |
113 | if(!chunks.hasOwnProperty("free")) { | |
114 | chunks.free = ""; | |
115 | } | |
116 | chunks.free += " ".concat(chunk); | |
117 | } | |
118 | }); | |
119 | ||
120 | if(chunks.hasOwnProperty("free")) { | |
121 | chunks.free = chunks.free.slice(1); | |
122 | } | |
123 | ||
124 | for(var prop in chunks) { | |
125 | if(chunks.hasOwnProperty(prop)) { | |
126 | if(chunks.prop != "") { | |
127 | encode += "&" + encodeURIComponent(prop) + "=" + encodeURIComponent(chunks[prop]); | |
128 | } | |
129 | } | |
130 | } | |
131 | return encode.slice(1); | |
132 | }; | |
133 | ||
134 | // decodes search parameters to object in order to use in filter | |
135 | $scope.decodeSearch = function(search) { | |
136 | var i = -1, | |
137 | decode = {}, | |
138 | params = search.split("&"); | |
139 | ||
140 | params.forEach(function(param) { | |
141 | i = param.indexOf("="); | |
142 | decode[decodeURIComponent(param.slice(0,i))] = decodeURIComponent(param.slice(i+1)); | |
143 | }); | |
144 | ||
145 | if(decode.hasOwnProperty("free")) { | |
146 | decode['$'] = decode.free; | |
147 | delete decode.free; | |
148 | } | |
149 | ||
150 | return decode; | |
151 | }; | |
152 | ||
153 | // converts current search object to string to be displayed in search field | |
154 | $scope.stringSearch = function(obj) { | |
155 | var search = ""; | |
156 | ||
157 | for(var prop in obj) { | |
158 | if(obj.hasOwnProperty(prop)) { | |
159 | if(search != "") { | |
160 | search += " "; | |
161 | } | |
162 | if(prop == "$") { | |
163 | search += obj[prop]; | |
164 | } else { | |
165 | search += prop + ":" + obj[prop]; | |
166 | } | |
167 | } | |
168 | } | |
169 | ||
170 | return search; | |
171 | }; | |
46 | 172 | |
47 | 173 | $scope.new = function() { |
48 | 174 | var modal = $modal.open({ |
89 | 215 | }; |
90 | 216 | |
91 | 217 | $scope.edit = function() { |
92 | var selected_service = []; | |
93 | ||
94 | $scope.services.forEach(function(service) { | |
95 | if(service.selected) { | |
96 | // if more than one service was selected, | |
97 | // we only use the last one, for now | |
98 | selected_service.push(service); | |
99 | } | |
100 | }); | |
101 | ||
102 | if(selected_service.length > 0) { | |
218 | if($scope.selectedServices().length > 0) { | |
103 | 219 | var modal = $modal.open({ |
104 | 220 | templateUrl: 'scripts/services/partials/modalEdit.html', |
105 | 221 | controller: 'serviceModalEdit', |
106 | 222 | size: 'lg', |
107 | 223 | resolve: { |
108 | 224 | service: function() { |
109 | return selected_service; | |
225 | return $scope.selectedServices(); | |
110 | 226 | }, |
111 | 227 | services: function() { |
112 | 228 | return $scope.services; |
115 | 231 | }); |
116 | 232 | |
117 | 233 | modal.result.then(function(data) { |
118 | $scope.update(selected_service, data); | |
234 | $scope.update($scope.selectedServices(), data); | |
119 | 235 | }); |
120 | 236 | } else { |
121 | 237 | $modal.open(config = { |
133 | 249 | |
134 | 250 | $scope.delete = function() { |
135 | 251 | var selected = []; |
136 | $scope.services.forEach(function(service){ | |
252 | $scope.selectedServices().forEach(function(service){ | |
137 | 253 | if(service.selected){ |
138 | 254 | selected.push(service._id); |
139 | 255 | } |
190 | 306 | }); |
191 | 307 | }; |
192 | 308 | |
193 | ||
194 | $scope.checkAll = function() { | |
195 | $scope.selectall = !$scope.selectall; | |
196 | ||
197 | angular.forEach($filter('filter')($scope.hosts, $scope.query), function(host) { | |
198 | host.selected = $scope.selectall; | |
199 | }); | |
200 | }; | |
201 | ||
202 | 309 | $scope.checkAllServices = function() { |
203 | 310 | $scope.selectall = !$scope.selectall; |
204 | 311 | |
205 | angular.forEach($filter('filter')($scope.services, $scope.query), function(service) { | |
312 | tmp_services = filter($scope.services); | |
313 | tmp_services.forEach(function(service) { | |
206 | 314 | service.selected = $scope.selectall; |
207 | 315 | }); |
208 | 316 | }; |
223 | 331 | $scope.reverse = !$scope.reverse; |
224 | 332 | } |
225 | 333 | |
334 | filter = function(data) { | |
335 | var tmp_data = $filter('orderObjectBy')(data, $scope.sortField, $scope.reverse); | |
336 | tmp_data = $filter('filter')(tmp_data, $scope.expression); | |
337 | tmp_data = tmp_data.splice($scope.pageSize * $scope.currentPage, $scope.pageSize); | |
338 | ||
339 | return tmp_data; | |
340 | }; | |
341 | ||
226 | 342 | init(); |
227 | 343 | }]); |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('hostsCtrl', |
6 | ['$scope', '$filter', '$route', '$routeParams', '$modal', 'hostsManager', 'workspacesFact', | |
7 | function($scope, $filter, $route, $routeParams, $modal, hostsManager, workspacesFact) { | |
6 | ['$scope', '$cookies', '$filter', '$location', '$route', '$routeParams', '$modal', 'hostsManager', 'workspacesFact', | |
7 | function($scope, $cookies, $filter, $location, $route, $routeParams, $modal, hostsManager, workspacesFact) { | |
8 | 8 | |
9 | 9 | init = function() { |
10 | 10 | $scope.selectall = false; |
12 | 12 | $scope.hosts = []; |
13 | 13 | // current workspace |
14 | 14 | $scope.workspace = $routeParams.wsId; |
15 | ||
16 | $scope.sortField = "name"; | |
17 | ||
15 | 18 | // load all workspaces |
16 | 19 | workspacesFact.list().then(function(wss) { |
17 | 20 | $scope.workspaces = wss; |
38 | 41 | .catch(function(e) { |
39 | 42 | console.log(e); |
40 | 43 | }); |
44 | ||
45 | $scope.pageSize = 10; | |
46 | $scope.currentPage = 0; | |
47 | $scope.newCurrentPage = 0; | |
48 | ||
49 | if(!isNaN(parseInt($cookies.pageSize))) $scope.pageSize = parseInt($cookies.pageSize); | |
50 | $scope.newPageSize = $scope.pageSize; | |
51 | ||
52 | // current search | |
53 | $scope.search = $routeParams.search; | |
54 | $scope.searchParams = ""; | |
55 | $scope.expression = {}; | |
56 | if($scope.search != "" && $scope.search != undefined && $scope.search.indexOf("=") > -1) { | |
57 | // search expression for filter | |
58 | $scope.expression = $scope.decodeSearch($scope.search); | |
59 | // search params for search field, which shouldn't be used for filtering | |
60 | $scope.searchParams = $scope.stringSearch($scope.expression); | |
61 | } | |
41 | 62 | }; |
42 | 63 | |
43 | 64 | $scope.loadIcons = function() { |
57 | 78 | } |
58 | 79 | }); |
59 | 80 | }); |
81 | }; | |
82 | ||
83 | // changes the URL according to search params | |
84 | $scope.searchFor = function(search, params) { | |
85 | var url = "/hosts/ws/" + $routeParams.wsId; | |
86 | ||
87 | if(search && params != "" && params != undefined) { | |
88 | url += "/search/" + $scope.encodeSearch(params); | |
89 | } | |
90 | ||
91 | $location.path(url); | |
92 | }; | |
93 | ||
94 | $scope.go = function() { | |
95 | $scope.pageSize = $scope.newPageSize; | |
96 | $cookies.pageSize = $scope.pageSize; | |
97 | $scope.currentPage = 0; | |
98 | if($scope.newCurrentPage <= parseInt($scope.hosts.length/$scope.pageSize) | |
99 | && $scope.newCurrentPage > -1 && !isNaN(parseInt($scope.newCurrentPage))) { | |
100 | $scope.currentPage = $scope.newCurrentPage; | |
101 | } | |
102 | }; | |
103 | ||
104 | // encodes search string in order to send it through URL | |
105 | $scope.encodeSearch = function(search) { | |
106 | var i = -1, | |
107 | encode = "", | |
108 | params = search.split(" "), | |
109 | chunks = {}; | |
110 | ||
111 | params.forEach(function(chunk) { | |
112 | i = chunk.indexOf(":"); | |
113 | if(i > 0) { | |
114 | chunks[chunk.slice(0, i)] = chunk.slice(i+1); | |
115 | } else { | |
116 | if(!chunks.hasOwnProperty("free")) { | |
117 | chunks.free = ""; | |
118 | } | |
119 | chunks.free += " ".concat(chunk); | |
120 | } | |
121 | }); | |
122 | ||
123 | if(chunks.hasOwnProperty("free")) { | |
124 | chunks.free = chunks.free.slice(1); | |
125 | } | |
126 | ||
127 | for(var prop in chunks) { | |
128 | if(chunks.hasOwnProperty(prop)) { | |
129 | if(chunks.prop != "") { | |
130 | encode += "&" + encodeURIComponent(prop) + "=" + encodeURIComponent(chunks[prop]); | |
131 | } | |
132 | } | |
133 | } | |
134 | return encode.slice(1); | |
135 | }; | |
136 | ||
137 | // decodes search parameters to object in order to use in filter | |
138 | $scope.decodeSearch = function(search) { | |
139 | var i = -1, | |
140 | decode = {}, | |
141 | params = search.split("&"); | |
142 | ||
143 | params.forEach(function(param) { | |
144 | i = param.indexOf("="); | |
145 | decode[decodeURIComponent(param.slice(0,i))] = decodeURIComponent(param.slice(i+1)); | |
146 | }); | |
147 | ||
148 | if(decode.hasOwnProperty("free")) { | |
149 | decode['$'] = decode.free; | |
150 | delete decode.free; | |
151 | } | |
152 | ||
153 | return decode; | |
154 | }; | |
155 | ||
156 | // converts current search object to string to be displayed in search field | |
157 | $scope.stringSearch = function(obj) { | |
158 | var search = ""; | |
159 | ||
160 | for(var prop in obj) { | |
161 | if(obj.hasOwnProperty(prop)) { | |
162 | if(search != "") { | |
163 | search += " "; | |
164 | } | |
165 | if(prop == "$") { | |
166 | search += obj[prop]; | |
167 | } else { | |
168 | search += prop + ":" + obj[prop]; | |
169 | } | |
170 | } | |
171 | } | |
172 | ||
173 | return search; | |
60 | 174 | }; |
61 | 175 | |
62 | 176 | $scope.remove = function(ids) { |
158 | 272 | }, function(message){ |
159 | 273 | console.log(message); |
160 | 274 | }); |
161 | } | |
275 | }; | |
162 | 276 | |
163 | 277 | $scope.edit = function() { |
164 | ||
165 | 278 | if($scope.selectedHosts().length == 1) { |
166 | 279 | var modal = $modal.open({ |
167 | 280 | templateUrl: 'scripts/hosts/partials/modalEdit.html', |
240 | 353 | |
241 | 354 | $scope.selectedHosts = function() { |
242 | 355 | selected = []; |
243 | $scope.hosts.forEach(function(host) { | |
356 | ||
357 | tmp_hosts = filter($scope.hosts); | |
358 | tmp_hosts.forEach(function(host) { | |
244 | 359 | if(host.selected === true) { |
245 | 360 | selected.push(host); |
246 | 361 | } |
251 | 366 | $scope.checkAll = function() { |
252 | 367 | $scope.selectall = !$scope.selectall; |
253 | 368 | |
254 | angular.forEach($filter('filter')($scope.hosts, $scope.query), function(host) { | |
369 | tmp_hosts = filter($scope.hosts); | |
370 | tmp_hosts.forEach(function(host) { | |
255 | 371 | host.selected = $scope.selectall; |
256 | 372 | }); |
257 | 373 | }; |
271 | 387 | $scope.toggleReverse = function() { |
272 | 388 | $scope.reverse = !$scope.reverse; |
273 | 389 | } |
390 | ||
391 | filter = function(data) { | |
392 | var tmp_data = $filter('orderObjectBy')(data, $scope.sortField, $scope.reverse); | |
393 | tmp_data = $filter('filter')(tmp_data, $scope.expression); | |
394 | tmp_data = tmp_data.splice($scope.pageSize * $scope.currentPage, $scope.pageSize); | |
395 | ||
396 | return tmp_data; | |
397 | }; | |
274 | 398 | |
275 | 399 | init(); |
276 | 400 | }]); |
31 | 31 | </button> |
32 | 32 | </h2><!-- .ws-label --> |
33 | 33 | <div class="reports col-md-9 col-sm-9 col-xs-12"> |
34 | <div class="col-md-6 col-sm-3 col-xs-11"> | |
35 | <form role="form" ng-submit="searchFor(true, searchParams)"> | |
36 | <div class="form-group"> | |
37 | <div class="input-group input-group-sm"> | |
38 | <span class="input-group-addon glyphicon-btn glyphicon glyphicon-remove" ng-click="searchFor(false, '')" ng-if="search"></span> | |
39 | <input type="text" class="form-control" id="filter-by" | |
40 | placeholder="enter keywords" ng-change="currentPage = 0" ng-model="searchParams" /> | |
41 | <span class="input-group-addon glyphicon-btn" ng-click="searchFor(true, searchParams)"> | |
42 | <i class="fa fa-search" ng-if="hosts"></i> | |
43 | <i class="fa fa-refresh fa-spin" ng-if="hosts.length == 0"></i> | |
44 | </span> | |
45 | </div> | |
46 | </div> | |
47 | </form> | |
48 | </div> | |
34 | 49 | <table class="status-report hosts-list table table-responsive"> |
35 | 50 | <thead> |
36 | 51 | <tr> |
56 | 71 | </tr> |
57 | 72 | </thead> |
58 | 73 | <tbody> |
59 | <tr ng-repeat="host in hosts | filter:query | orderBy:sortField:reverse" | |
74 | <tr ng-repeat="host in filtered = (hosts | filter:expression) | orderBy:sortField:reverse | startFrom:currentPage*pageSize | limitTo:pageSize" | |
60 | 75 | selection-model selection-model-type="checkbox" |
61 | 76 | selection-model-mode="multiple-additive" |
62 | 77 | selection-model-selected-class="multi-selected" |
79 | 94 | </tr> |
80 | 95 | </tbody> |
81 | 96 | </table><!-- #hosts --> |
97 | <div class="showPagination"> | |
98 | <div class="form-group"> | |
99 | <ul class="pagination"> | |
100 | <li><a ng-hide="currentPage <= 0" ng-click="currentPage = currentPage - 1"><span aria-hidden="true">«</span><span class="sr-only">Previous</span></a></li> | |
101 | <li><a>{{currentPage}}/{{ ((filtered.length / pageSize) | integer)}}</a></li> | |
102 | <li><a ng-hide="currentPage >= ((filtered.length / pageSize) | integer)" ng-click="currentPage = currentPage + 1"><span aria-hidden="true">»</span><span class="sr-only">Next</span></a></li> | |
103 | </ul> | |
104 | <form name="goToPage" id="goToPageStatus"> | |
105 | <div class="col-md-2"> | |
106 | <input type="number" min="0" max="{{ (filtered.length / pageSize) | integer }}" class="form-control" ng-model="newCurrentPage" placeholder="Go to page"/> | |
107 | </div> | |
108 | <button class="btn btn-default" ng-click="go()">GO</button> | |
109 | <input type="number" min="0" class="form-control vuln_per_page" ng-model=newPageSize placeholder="Number page" /> | |
110 | </form> | |
111 | </div> | |
112 | </div><!-- .showPagination --> | |
82 | 113 | </div><!-- .reports --> |
83 | 114 | </div><!-- #reports-main --></div><!-- .right-main --> |
84 | 115 | </section><!-- #main --> |
0 | // Faraday Penetration Test IDE | |
1 | // Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | // See the file 'doc/LICENSE' for the license information | |
3 | ||
4 | angular.module('faradayApp') | |
5 | .controller('indexCtrl', | |
6 | ['$scope', 'indexFact', | |
7 | function($scope, indexFact) { | |
8 | indexFact.getConf().then(function(conf) { | |
9 | $scope.version = conf.data.ver; | |
10 | }); | |
11 | ||
12 | }]); |
0 | // Faraday Penetration Test IDE | |
1 | // Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | // See the file 'doc/LICENSE' for the license information | |
3 | ||
4 | angular.module('faradayApp') | |
5 | .factory('indexFact', ['$http', function($http) { | |
6 | var indexFact = {}; | |
7 | ||
8 | indexFact.getConf = function() { | |
9 | return $http.get('/reports/_design/reports/scripts/config/config.json'); | |
10 | }; | |
11 | ||
12 | return indexFact; | |
13 | }]);⏎ |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .controller('navigationCtrl', ['$scope', '$http','$route', '$routeParams', '$cookies', '$location', '$interval', | |
6 | function($scope, $http, $route, $routeParams, $cookies, $location, $interval) { | |
5 | .controller('navigationCtrl', ['$scope', '$http', '$route', '$routeParams', '$cookies', '$location', '$interval', 'configSrv', | |
6 | function($scope, $http, $route, $routeParams, $cookies, $location, $interval, configSrv) { | |
7 | 7 | |
8 | 8 | $scope.workspace = ""; |
9 | 9 | $scope.component = ""; |
10 | 10 | |
11 | 11 | $scope.checkCwe = function() { |
12 | $http.get("https://www.faradaysec.com/scripts/updatedb.php").then(function() { | |
12 | $http.get("https://www.faradaysec.com/scripts/updatedb.php?version=" + configSrv.faraday_version).then(function() { | |
13 | 13 | }, function() { |
14 | 14 | console.log("CWE database couldn't be updated"); |
15 | 15 | }); |
16 | 16 | }; |
17 | 17 | |
18 | var timer = $interval($scope.checkCwe, 43200000); | |
19 | $scope.checkCwe(); | |
18 | configSrv.promise.then(function() { | |
19 | var timer = $interval($scope.checkCwe, 43200000); | |
20 | $scope.checkCwe(); | |
21 | }); | |
20 | 22 | |
21 | 23 | $scope.$on('$destroy', function() { |
22 | 24 | $interval.cancel(timer); |
23 | 23 | <i class="fa fa-sitemap host"></i> |
24 | 24 | </a> |
25 | 25 | </li> |
26 | <li> | |
27 | <a href="#/users" class="users" style="color: #ffffff !important" tooltip="Users" tooltip-placement="right"> | |
28 | <img src="images/ico-users-menu.svg" alt="Users"/> | |
29 | </a> | |
30 | </li> | |
31 | <li> | |
32 | <a href="#/executive" class="executive-report" style="color: #ffffff !important" tooltip="Executive Report" tooltip-placement="right"> | |
33 | <img src="images/ico-executive-menu.svg" alt="Executive Report"/> | |
34 | </a> | |
35 | </li> | |
36 | <li> | |
37 | <a href="#/communication" class="executive-report" style="color: #ffffff !important" tooltip="Chat" tooltip-placement="right"> | |
38 | <img src="images/ico-communication-menu.svg" alt="Communication"/> | |
39 | </a> | |
40 | </li> | |
41 | <li> | |
42 | <a href="#/comparison" class="executive-report" style="color: #ffffff !important" tooltip="Workspaces Comparison" tooltip-placement="right"> | |
43 | <img src="images/ico-workspace-comparison-menu.svg" alt="Workspaces Comparison"/> | |
44 | </a> | |
45 | </li> | |
46 | <li> | |
47 | <a href="#/webshell" class="executive-report" style="color: #ffffff !important" tooltip="Web Shell" tooltip-placement="right"> | |
48 | <img src="images/ico-web-shell-menu.svg" alt="Webshell"/> | |
49 | </a> | |
50 | </li> | |
26 | 51 | </ul> |
27 | 52 | </nav> |
28 | 53 | <div ng-show="isIceweasel" class="alert alert-danger alert-dismissible"> |
54 | 54 | </button> |
55 | 55 | </h2><!-- .ws-label --> |
56 | 56 | <div class="reports col-md-9 col-sm-9 col-xs-12"> |
57 | <div class="col-md-6 col-sm-3 col-xs-11"> | |
58 | <form role="form" ng-submit="searchFor(true, searchParams)"> | |
59 | <div class="form-group"> | |
60 | <div class="input-group input-group-sm"> | |
61 | <span class="input-group-addon glyphicon-btn glyphicon glyphicon-remove" ng-click="searchFor(false, '')" ng-if="search"></span> | |
62 | <input type="text" class="form-control" id="filter-by" | |
63 | placeholder="enter keywords" ng-change="currentPage = 0" ng-model="searchParams" /> | |
64 | <span class="input-group-addon glyphicon-btn" ng-click="searchFor(true, searchParams)"> | |
65 | <i class="fa fa-search" ng-if="services.length > 0"></i> | |
66 | <i class="fa fa-refresh fa-spin" ng-if="services.length == 0"></i> | |
67 | </span> | |
68 | </div> | |
69 | </div> | |
70 | </form> | |
71 | </div> | |
57 | 72 | <table class="status-report hosts-list table table-responsive"> |
58 | 73 | <thead> |
59 | 74 | <tr> |
79 | 94 | </tr> |
80 | 95 | </thead> |
81 | 96 | <tbody> |
82 | <tr ng-repeat="service in services | filter:query | orderBy:sortField:reverse" | |
97 | <tr ng-repeat="service in filtered = (services | filter:expression) | orderBy:sortField:reverse | startFrom:currentPage*pageSize | limitTo:pageSize" | |
83 | 98 | selection-model selection-model-type="checkbox" |
84 | 99 | selection-model-mode="multiple-additive" |
85 | 100 | selection-model-selected-class="multi-selected"> |
105 | 120 | </tr> |
106 | 121 | </tbody> |
107 | 122 | </table><!-- #hosts --> |
123 | <div class="showPagination"> | |
124 | <div class="form-group"> | |
125 | <ul class="pagination"> | |
126 | <li><a ng-hide="currentPage <= 0" ng-click="currentPage = currentPage - 1"><span aria-hidden="true">«</span><span class="sr-only">Previous</span></a></li> | |
127 | <li><a>{{currentPage}}/{{ ((filtered.length / pageSize) | integer)}}</a></li> | |
128 | <li><a ng-hide="currentPage >= ((filtered.length / pageSize) | integer)" ng-click="currentPage = currentPage + 1"><span aria-hidden="true">»</span><span class="sr-only">Next</span></a></li> | |
129 | </ul> | |
130 | <form name="goToPage" id="goToPageStatus"> | |
131 | <div class="col-md-2"> | |
132 | <input type="number" min="0" max="{{ (filtered.length / pageSize) | integer }}" class="form-control" ng-model="newCurrentPage" placeholder="Go to page"/> | |
133 | </div> | |
134 | <button class="btn btn-default" ng-click="go()">GO</button> | |
135 | <input type="number" min="0" class="form-control vuln_per_page" ng-model=newPageSize placeholder="Number page" /> | |
136 | </form> | |
137 | </div> | |
138 | </div><!-- .showPagination --> | |
108 | 139 | </div><!-- .reports --> |
109 | 140 | </div><!-- #reports-main --></div><!-- .right-main --> |
110 | 141 | </section><!-- #main --> |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('statusReportCtrl', |
6 | 6 | ['$scope', '$filter', '$routeParams', |
7 | '$location', '$modal', '$cookies', '$q', 'BASEURL', | |
7 | '$location', '$modal', '$cookies', '$q', '$window', 'BASEURL', | |
8 | 8 | 'SEVERITIES', 'EASEOFRESOLUTION', 'hostsManager', |
9 | 9 | 'vulnsManager', 'workspacesFact', 'csvService', |
10 | 10 | function($scope, $filter, $routeParams, |
11 | $location, $modal, $cookies, $q, BASEURL, | |
11 | $location, $modal, $cookies, $q, $window, BASEURL, | |
12 | 12 | SEVERITIES, EASEOFRESOLUTION, hostsManager, |
13 | 13 | vulnsManager, workspacesFact, csvService) { |
14 | 14 | $scope.baseurl; |
71 | 71 | }); |
72 | 72 | |
73 | 73 | // created object for columns cookie columns |
74 | if(typeof($cookies.SRcolumns) != 'undefined'){ | |
74 | if(typeof($cookies.SRcolumns) != 'undefined') { | |
75 | 75 | var objectoSRColumns = {}; |
76 | 76 | var arrayOfColumns = $cookies.SRcolumns.replace(/[{}"']/g, "").split(','); |
77 | 77 | arrayOfColumns.forEach(function(column){ |
83 | 83 | $scope.columns = objectoSRColumns || { |
84 | 84 | "date": true, |
85 | 85 | "severity": true, |
86 | "service": true, | |
86 | 87 | "target": true, |
87 | 88 | "name": true, |
88 | 89 | "desc": true, |
108 | 109 | $scope.vulnWebSelected = false; |
109 | 110 | }; |
110 | 111 | |
112 | $scope.processReference = function(text) { | |
113 | var url = 'http://google.com/', | |
114 | url_pattern = new RegExp('^(http|https):\\/\\/?'); | |
115 | ||
116 | var cve_pattern = new RegExp(/^CVE-\d{4}-\d{4,7}$/), | |
117 | cwe_pattern = new RegExp(/^CWE(-|:)\d{1,7}$/), | |
118 | edb_pattern = new RegExp(/^EDB-ID:\s?\d{1,}$/), | |
119 | osvdb_pattern = new RegExp(/^OSVDB:\s?\d{1,}$/); | |
120 | ||
121 | var cve = text.search(cve_pattern), | |
122 | cwe = text.search(cwe_pattern), | |
123 | edb = text.search(edb_pattern), | |
124 | osvdb = text.search(osvdb_pattern); | |
125 | ||
126 | if(url_pattern.test(text)) { | |
127 | url = text; | |
128 | } else if(cve > -1) { | |
129 | url = "https://cve.mitre.org/cgi-bin/cvename.cgi?name=" + text.substring(cve + 4); | |
130 | } else if(cwe > -1) { | |
131 | url = "https://cwe.mitre.org/data/definitions/" + text.substring(cwe + 4) + ".html"; | |
132 | } else if(osvdb > -1) { | |
133 | url = "http://osvdb.org/show/osvdb/" + text.substring(osvdb + 6); | |
134 | } else if(edb > -1) { | |
135 | url = "https://www.exploit-db.com/exploits/" + text.substring(edb + 7); | |
136 | } else { | |
137 | url += 'search?q=' + text; | |
138 | } | |
139 | ||
140 | $window.open(url, '_blank'); | |
141 | }; | |
142 | ||
111 | 143 | $scope.selectedVulns = function() { |
112 | 144 | selected = []; |
113 | 145 | var tmp_vulns = $filter('orderObjectBy')($scope.vulns, $scope.sortField, $scope.reverse); |
267 | 299 | 'Enter the new severity:', |
268 | 300 | 'severity', |
269 | 301 | {options: SEVERITIES}); |
270 | } | |
302 | }; | |
271 | 303 | |
272 | 304 | $scope.editEaseofresolution = function() { |
273 | 305 | editProperty( |
276 | 308 | 'Enter the new easeofresolution:', |
277 | 309 | 'easeofresolution', |
278 | 310 | {options: EASEOFRESOLUTION}); |
279 | } | |
311 | }; | |
280 | 312 | |
281 | 313 | $scope.editReferences = function() { |
282 | 314 | editProperty( |
295 | 327 | return {'refs': references}; |
296 | 328 | }} |
297 | 329 | ); |
298 | } | |
330 | }; | |
299 | 331 | |
300 | 332 | $scope.editImpact = function() { |
301 | 333 | editProperty( |
324 | 356 | } |
325 | 357 | } |
326 | 358 | ); |
327 | } | |
359 | }; | |
328 | 360 | |
329 | 361 | $scope.editString = function(property, message_word) { |
330 | 362 | var message; |
338 | 370 | 'commonsModalEditString', |
339 | 371 | message, |
340 | 372 | property); |
341 | } | |
373 | }; | |
342 | 374 | |
343 | 375 | $scope.editText = function(property, message_word) { |
344 | 376 | var message; |
352 | 384 | 'commonsModalEditString', |
353 | 385 | message, |
354 | 386 | property); |
355 | } | |
387 | }; | |
356 | 388 | |
357 | 389 | $scope.editCWE = function() { |
358 | 390 | var modal = $modal.open({ |
382 | 414 | }); |
383 | 415 | }); |
384 | 416 | }); |
385 | } | |
417 | }; | |
386 | 418 | |
387 | 419 | $scope.insert = function(vuln) { |
388 | 420 | vulnsManager.createVuln($scope.workspace, vuln).then(function() { |
13 | 13 | <div class="form-horizontal"> |
14 | 14 | <div class="form-group"> |
15 | 15 | <div class="col-md-12"> |
16 | <input type="text" ng-model="modal.target_filter" class="form-control input-sm" placeholder="Search" ng-change="modal.currentPage = 0"> | |
17 | <accordion close-others="true"> | |
18 | <accordion-group is-open="isopen" ng-repeat="host in modal.targets_filtered = (modal.targets | filter:modal.target_filter) | startFrom:modal.currentPage*modal.pageSize | limitTo:modal.pageSize"> | |
16 | <div class="form-group input-accordion"> | |
17 | <input type="text" ng-model="modal.target_filter" class="form-control input-sm" placeholder="Search" ng-change="modal.currentPage = 0"> | |
18 | </div> | |
19 | <accordion close-others="true"> | |
20 | <accordion-group is-open="isopen" ng-repeat="host in modal.targets_filtered = (modal.targets | filter:modal.target_filter) | startFrom:modal.currentPage*modal.pageSize | limitTo:modal.pageSize"> | |
19 | 21 | <accordion-heading> |
20 | 22 | <a ng-click="modal.setTarget(host)" ng-class="{'multi-selected': host.selected_modalNewCtrl == true}">{{host.name}} ({{host.hostnames[0]}})</a> |
21 | <i class="pull-right glyphicon" | |
22 | ng-class="{'glyphicon glyphicon-minus-sign': isopen, 'glyphicon glyphicon-plus-sign': !isopen}"></i> | |
23 | <i class="pull-right glyphicon" | |
24 | ng-class="{'glyphicon glyphicon-minus-sign': isopen, 'glyphicon glyphicon-plus-sign': !isopen}"></i> | |
23 | 25 | </accordion-heading> |
24 | 26 | <div class="panel-body" ng-repeat="service in host.services"> |
25 | 27 | <a ng-model="service" ng-click="modal.setTarget(service)" ng-class="{'multi-selected': service.selected_modalNewCtrl == true}">{{service.name}}</a> |
26 | </div> | |
27 | </accordion-group> | |
28 | </accordion> | |
29 | <div class="showPagination" ng-show="modal.targets_filtered.length > modal.pageSize"> | |
28 | </div> | |
29 | </accordion-group> | |
30 | </accordion> | |
31 | <div class="showPagination" ng-show="modal.targets_filtered.length > modal.pageSize"> | |
30 | 32 | <div class="form-group"> |
31 | 33 | <ul class="pagination"> |
32 | 34 | <li><a ng-hide="modal.currentPage <= 0" ng-click="modal.currentPage = modal.currentPage - 1"><span aria-hidden="true">«</span><span class="sr-only">Previous</span></a></li> |
105 | 105 | <a href="" ng-click="toggleSort('target')">Target</a> |
106 | 106 | <a href="" ng-click="toggleShow('target', true)"><span class="glyphicon glyphicon-remove"></span></a> |
107 | 107 | </th> |
108 | <th ng-if="columns.service"> | |
109 | <a href="" ng-click="toggleSort('service')">Service</a> | |
110 | <a href="" ng-click="toggleShow('service', true)"><span class="glyphicon glyphicon-remove"></span></a> | |
111 | </th> | |
108 | 112 | <th ng-if="columns.status"> |
109 | 113 | <a href="" ng-click="toggleSort('status')">Status</a> |
110 | 114 | <a href="" ng-click="toggleShow('status', true)"><span class="glyphicon glyphicon-remove"></span></a> |
199 | 203 | <td class="text-center"><span ng-click="deleteVuln(v)" class="glyphicon glyphicon-trash cursor" tooltip="Delete"></span></td> |
200 | 204 | <td ng-if="columns.date">{{v.metadata.create_time * 1000 | date:'MM/dd/yyyy'}}</td> |
201 | 205 | <td ng-if="columns.target"> |
202 | <a href="#/status/ws/{{workspace}}/search/target={{v.target}}">{{v.target}}</a> | |
203 | <a href="//www.shodan.io/search?query={{v.target}}" tooltip="Search in Shodan" target="_blank"> | |
206 | <a ng-href="#/status/ws/{{workspace}}/search/target={{v.target}}">{{v.target}}</a> | |
207 | <a ng-href="//www.shodan.io/search?query={{v.target}}" tooltip="Search in Shodan" target="_blank"> | |
204 | 208 | <img ng-src="../././reports/images/shodan.png" height="15px" width="15px" /> |
205 | 209 | </a> |
210 | </td> | |
211 | <td ng-if="columns.service"> | |
212 | <a ng-href="#/status/ws/{{workspace}}/search/service={{v.service | encodeURIComponent | encodeURIComponent}}">{{v.service}}</a> | |
206 | 213 | </td> |
207 | 214 | <td ng-if="columns.status">Vulnerable</td> |
208 | 215 | <td ng-if="columns.severity"><a href="#/status/ws/{{workspace}}/search/severity={{v.severity}}"><span class="label vuln fondo-{{v.severity}}">{{v.severity}}</span></a></td> |
222 | 229 | <span class="glyphicon glyphicon-remove" ng-show="v.type !== 'VulnerabilityWeb'"></span> |
223 | 230 | </td> |
224 | 231 | <td ng-if="columns.website"><a href="#/status/ws/{{workspace}}/search/website={{v.website}}">{{v.website}}</a></td> |
225 | <td ng-if="columns.refs"><p ng-repeat="refs in v.refs track by $index">{{refs}}</p></td> | |
232 | <td ng-if="columns.refs"> | |
233 | <p ng-repeat="refs in v.refs track by $index"><a ng-click="processReference(refs)">{{refs}}</a></p> | |
234 | </td> | |
226 | 235 | <td ng-if="columns.evidence"> |
227 | 236 | <div ng-repeat="(name, file) in v._attachments track by $index"> |
228 | 237 | <a ng-href="{{baseurl + workspace}}/{{v._id}}/{{name | encodeURIComponent}}" target="_blank">{{name | decodeURIComponent}}</a> |
11 | 11 | this._id = ""; |
12 | 12 | this._rev = ""; |
13 | 13 | this._attachments = {}; |
14 | this.confirmed = true; | |
14 | 15 | this.data = ""; |
15 | 16 | this.desc = ""; |
16 | 17 | this.easeofresolution = ""; |
37 | 38 | this.parent = ""; |
38 | 39 | this.refs = ""; |
39 | 40 | this.resolution = ""; |
41 | this.service = ""; | |
40 | 42 | this.severity = ""; |
41 | 43 | this.target = ""; |
42 | 44 | this.type = "Vulnerability"; |
52 | 54 | |
53 | 55 | Vuln.prototype = { |
54 | 56 | public_properties: [ |
55 | '_attachments', 'data', 'desc', 'easeofresolution', | |
57 | '_attachments', 'confirmed', 'data', 'desc', 'easeofresolution', | |
56 | 58 | 'impact', 'name', 'owned', 'refs', 'resolution', 'severity' |
57 | 59 | ], |
58 | 60 | set: function(ws, data) { |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .factory('vulnsManager', |
6 | ['Vuln', 'WebVuln', 'BASEURL', '$filter', '$http', '$q', 'attachmentsFact', 'hostsManager', | |
7 | function(Vuln, WebVuln, BASEURL, $filter, $http, $q, attachmentsFact, hostsManager) { | |
6 | ['Vuln', 'WebVuln', 'BASEURL', '$filter', '$http', '$q', 'attachmentsFact', 'hostsManager', 'servicesManager', | |
7 | function(Vuln, WebVuln, BASEURL, $filter, $http, $q, attachmentsFact, hostsManager, servicesManager) { | |
8 | 8 | var vulnsManager = {}; |
9 | 9 | |
10 | 10 | vulnsManager.vulns = []; |
28 | 28 | return res; |
29 | 29 | }; |
30 | 30 | |
31 | vulnsManager._loadServices = function(services) { | |
32 | var res = {}; | |
33 | ||
34 | services.forEach(function(service) { | |
35 | res[service._id] = "(" + service['ports'].join(",") + "/" + service['protocol'] + ") " + service['name']; | |
36 | }); | |
37 | ||
38 | return res; | |
39 | }; | |
40 | ||
31 | 41 | vulnsManager.createVuln = function(ws, data) { |
32 | 42 | var deferred = $q.defer(), |
33 | 43 | self = this; |
43 | 53 | .then(function(resp) { |
44 | 54 | self.vulns_indexes[vuln._id] = self.vulns.length; |
45 | 55 | self.vulns.push(vuln); |
46 | var parents = [hostsManager.getHosts(ws), hostsManager.getAllInterfaces(ws)]; | |
56 | var parents = [hostsManager.getHosts(ws), hostsManager.getAllInterfaces(ws), servicesManager.getServices(ws)]; | |
47 | 57 | |
48 | 58 | $q.all(parents) |
49 | 59 | .then(function(ps) { |
50 | 60 | var hosts = self._loadHosts(ps[0], ps[1]); |
61 | var services = self._loadServices(ps[2]); | |
51 | 62 | |
52 | 63 | self.vulns.forEach(function(vuln) { |
53 | 64 | var pid = vuln.parent.split(".")[0]; |
54 | 65 | if (hosts.hasOwnProperty(pid)) { |
55 | 66 | vuln.target = hosts[pid]["target"]; |
56 | 67 | vuln.hostnames = hosts[pid]["hostnames"]; |
57 | }; | |
68 | } | |
69 | if(services.hasOwnProperty(vuln.parent)) vuln.service = services[vuln.parent]; | |
58 | 70 | }); |
59 | 71 | }); |
60 | 72 | |
112 | 124 | } |
113 | 125 | } |
114 | 126 | |
115 | var parents = [hostsManager.getHosts(ws), hostsManager.getAllInterfaces(ws)]; | |
127 | var parents = [hostsManager.getHosts(ws), hostsManager.getAllInterfaces(ws), servicesManager.getServices(ws)]; | |
116 | 128 | |
117 | 129 | $q.all(parents) |
118 | 130 | .then(function(ps) { |
119 | 131 | var hosts = self._loadHosts(ps[0], ps[1]); |
132 | var services = self._loadServices(ps[2]); | |
120 | 133 | |
121 | 134 | self.vulns.forEach(function(vuln) { |
122 | 135 | var pid = vuln.parent.split(".")[0]; |
123 | if (hosts.hasOwnProperty(pid)) { | |
136 | ||
137 | if(hosts.hasOwnProperty(pid)) { | |
124 | 138 | vuln.target = hosts[pid]["target"]; |
125 | 139 | vuln.hostnames = hosts[pid]["hostnames"]; |
126 | 140 | } |
141 | if(services.hasOwnProperty(vuln.parent)) vuln.service = services[vuln.parent]; | |
127 | 142 | }); |
128 | 143 | }); |
129 | 144 |
1 | 1 | <!-- Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) --> |
2 | 2 | <!-- See the file 'doc/LICENSE' for the license information --> |
3 | 3 | |
4 | <form> | |
4 | <form novalidate> | |
5 | 5 | <div class="modal-header"> |
6 | 6 | <div class="modal-button"> |
7 | 7 | <button class="btn btn-success" ng-click="okEdit()">OK</button> |
20 | 20 | </div> |
21 | 21 | </div><!-- .form-group --> |
22 | 22 | <div class="form-group"> |
23 | <div class="col-md-6"> | |
23 | <div class="col-md-6 datepicker"> | |
24 | 24 | <h5>Start Date</h5> |
25 | 25 | <label class="sr-only" for="work-start">Start Date</label> |
26 | 26 | <p class="input-group"> |
30 | 30 | </span> |
31 | 31 | </p> |
32 | 32 | </div> |
33 | <div class="col-md-6"> | |
33 | <div class="col-md-6 datepicker"> | |
34 | 34 | <h5>End Date</h5> |
35 | 35 | <label class="sr-only" for="work-end">End Date</label> |
36 | 36 | <p class="input-group"> |
1 | 1 | <!-- Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) --> |
2 | 2 | <!-- See the file 'doc/LICENSE' for the license information --> |
3 | 3 | |
4 | <form name="form"> | |
4 | <form name="form" novalidate> | |
5 | 5 | <div class="modal-header"> |
6 | 6 | <div class="modal-button"> |
7 | 7 | <button class="btn btn-success" ng-disabled="form.$invalid || date.$invalid" ng-click="okNew()">Save</button> |
40 | 40 | </div><!-- .form-group --> |
41 | 41 | <div class="form-group"> |
42 | 42 | <ng-form name="date" novalidate> |
43 | <div class="col-md-6"> | |
43 | <div class="col-md-6 datepicker"> | |
44 | 44 | <label class="sr-only" for="work-start">Start Date</label> |
45 | 45 | <p class="input-group"> |
46 | 46 | <input type="text" class="form-control" datepicker-popup="MM/dd/yyyy" ng-model="workspace.start" is-open="openedStart" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" close-text="Close" placeholder="Start Date" /> |
49 | 49 | </span> |
50 | 50 | </p> |
51 | 51 | </div> |
52 | <div class="col-md-6"> | |
52 | <div class="col-md-6 datepicker"> | |
53 | 53 | <label class="sr-only" for="work-end">End Date</label> |
54 | 54 | <p class="input-group"> |
55 | 55 | <input type="text" class="form-control" datepicker-popup="MM/dd/yyyy" ng-model="workspace.end" is-open="openedEnd" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" close-text="Close" placeholder="End Date" /> |