├── app ├── model │ ├── Readme.md │ ├── Base.js │ └── Issues.js ├── store │ ├── Readme.md │ ├── Servers.js │ └── Issues.js ├── view │ ├── main │ │ ├── MainModel.js │ │ ├── Main.js │ │ └── MainController.js │ ├── issuesGrid │ │ ├── IssuesGridController.js │ │ └── IssuesGrid.js │ └── serversGrid │ │ ├── ServersGridController.js │ │ └── ServersGrid.js ├── singleton │ ├── Functions.js │ ├── Cache.js │ └── Socket.js ├── Application.js └── Readme.md ├── sass ├── config.rb ├── example │ ├── example.css │ ├── custom.js │ └── theme.html ├── Readme.md ├── src │ └── view │ │ ├── issuesGrid │ │ └── IssuesGrid.scss │ │ └── main │ │ └── Main.scss └── etc │ └── all.scss ├── resources ├── images │ ├── demo.png │ └── quadrata_logo.png ├── icons │ └── favicon.ico ├── fonts │ └── font-awesome-4.4.0 │ │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── Readme.md └── js │ ├── highcharts-4.1.7-no-data-to-display.min.js │ ├── highcharts-4.1.7-heatmap.min.js │ ├── highcharts-4.1.7-treemap.min.js │ └── underscore-1.8.3.min.js ├── config.js ├── .sencha ├── app │ ├── native.properties │ ├── testing.properties │ ├── development.properties │ ├── production.properties │ ├── package.properties │ ├── build.properties │ ├── testing.defaults.properties │ ├── resources-impl.xml │ ├── development.defaults.properties │ ├── native.defaults.properties │ ├── package.defaults.properties │ ├── production.defaults.properties │ ├── sencha.cfg │ ├── plugin.xml │ ├── ext.properties │ ├── find-cmd-impl.xml │ ├── watch-impl.xml │ ├── refresh-impl.xml │ ├── js-impl.xml │ ├── resolve-impl.xml │ ├── packager-impl.xml │ ├── microloader │ │ ├── development.js │ │ └── testing.js │ ├── init-impl.xml │ ├── sass-impl.xml │ ├── page-impl.xml │ ├── slice-impl.xml │ ├── bootstrap-impl.xml │ └── Microloader.js └── workspace │ ├── plugin.xml │ └── sencha.cfg ├── index.html ├── app.js ├── backend ├── package.json └── server.js ├── Application.md ├── Readme.md └── app.json /app/model/Readme.md: -------------------------------------------------------------------------------- 1 | This folder contains the Models for this application. 2 | -------------------------------------------------------------------------------- /sass/config.rb: -------------------------------------------------------------------------------- 1 | cur_dir = File.dirname(__FILE__) 2 | output_style = :nested 3 | -------------------------------------------------------------------------------- /resources/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/images/demo.png -------------------------------------------------------------------------------- /resources/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/icons/favicon.ico -------------------------------------------------------------------------------- /resources/images/quadrata_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/images/quadrata_logo.png -------------------------------------------------------------------------------- /app/store/Readme.md: -------------------------------------------------------------------------------- 1 | This folder contains store instances (identified by storeId) and store types 2 | (with "store.foo" aliases). 3 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OctoZab configuration file 3 | */ 4 | var config = { 5 | backendUrl: "localhost", 6 | backendPort: "8080" 7 | }; -------------------------------------------------------------------------------- /resources/fonts/font-awesome-4.4.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/fonts/font-awesome-4.4.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /resources/Readme.md: -------------------------------------------------------------------------------- 1 | # OctoZab/resources 2 | 3 | This folder contains resources (such as images) needed by the application. This file can 4 | be removed if not needed. 5 | -------------------------------------------------------------------------------- /resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SKUndef/octo-zab/HEAD/resources/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/store/Servers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ZABBIX SERVERS store class. 3 | */ 4 | 5 | Ext.define('OctoZab.store.Servers', { 6 | extend: 'Ext.data.Store', 7 | 8 | fields: [ 9 | 'server', 10 | 'user', 11 | 'psw' 12 | ], 13 | 14 | batchUpdateMode: 'complete' 15 | }); -------------------------------------------------------------------------------- /app/model/Base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * BASE MODEL on which other models extend their functionality. 3 | */ 4 | 5 | Ext.define('OctoZab.model.Base', { 6 | extend: 'Ext.data.Model', 7 | 8 | fields: [{ 9 | name: 'id', 10 | type: 'int' 11 | }], 12 | 13 | schema: { 14 | namespace: 'OctoZab.model' 15 | } 16 | }); -------------------------------------------------------------------------------- /app/view/main/MainModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the view model for the Main view of the application. 3 | */ 4 | Ext.define('OctoZab.view.main.MainModel', { 5 | extend: 'Ext.app.ViewModel', 6 | 7 | alias: 'viewmodel.main', 8 | 9 | data: { 10 | appName: 'OctoZab', 11 | issuesMapView: 'ALL' 12 | } 13 | }); -------------------------------------------------------------------------------- /sass/example/example.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | * This file is generated by Sencha Cmd and should NOT be edited. It redirects 5 | * to the most recently built CSS file for the application to allow theme.html 6 | * to load properly for image slicing (required to support non-CSS3 browsers 7 | * such as IE9 and below). 8 | */ 9 | @import '../../build/temp/production/OctoZab/slicer-temp/OctoZab-example.css'; 10 | 11 | -------------------------------------------------------------------------------- /sass/example/custom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated as a starting point by Sencha Cmd - it will not be replaced or 3 | * updated by "sencha package upgrade". 4 | * 5 | * This file can be removed and the script tag in theme.html removed if this theme does 6 | * not need custom additional manifest or shortcut entries. These are documented in 7 | * ./packages/ext-theme-base/sass/example/render.js. 8 | */ 9 | 10 | //Ext.theme.addManifest(); 11 | 12 | //Ext.theme.addShortcuts(); 13 | -------------------------------------------------------------------------------- /.sencha/app/native.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file provides an override point for default variables defined in 3 | # native.defaults.properties. These properties are only imported when building 4 | # for the "native" environment. 5 | # 6 | # Properties defined in this file take priority over build.properties but are 7 | # only loaded for "native" builds. 8 | # ============================================================================= 9 | -------------------------------------------------------------------------------- /.sencha/app/testing.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file provides an override point for default variables defined in 3 | # testing.defaults.properties. These properties are only imported when building 4 | # for the "testing" environment. 5 | # 6 | # Properties defined in this file take priority over build.properties but are 7 | # only loaded for "testing" builds. 8 | # ============================================================================= 9 | -------------------------------------------------------------------------------- /.sencha/app/development.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file provides an override point for default variables defined in 3 | # testing.defaults.properties. These properties are only imported when building 4 | # for the "development" environment. 5 | # 6 | # Properties defined in this file take priority over build.properties but are 7 | # only loaded for "development" builds. 8 | # ============================================================================= 9 | -------------------------------------------------------------------------------- /.sencha/app/production.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file provides an override point for default variables defined in 3 | # production.defaults.properties. These properties are only imported when building 4 | # for the "production" environment. 5 | # 6 | # Properties defined in this file take priority over build.properties but are 7 | # only loaded for "production" builds. 8 | # ============================================================================= 9 | -------------------------------------------------------------------------------- /.sencha/workspace/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OctoZab 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/store/Issues.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Last ISSUES GRID store class. 3 | */ 4 | 5 | Ext.define('OctoZab.store.Issues', { 6 | extend: 'Ext.data.Store', 7 | 8 | model: 'OctoZab.model.Issues', 9 | 10 | // autoLoad: true, 11 | // autoSync: true, 12 | batchUpdateMode: 'complete', 13 | 14 | groupField: 'server', 15 | 16 | sorters: [{ 17 | property: 'acknowledged' 18 | },{ 19 | property: 'priority', 20 | direction: 'DESC' 21 | },{ 22 | property: 'lastchange', 23 | direction: 'DESC' 24 | }], 25 | 26 | filters: [ 27 | Cache.getIssuesServerFilter(), 28 | Cache.getIssuesPriorityFilter() 29 | ] 30 | }); -------------------------------------------------------------------------------- /.sencha/app/package.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file provides an override point for default variables defined in 3 | # package.defaults.properties. These properties are only imported when building 4 | # for the "package" environment. 5 | # 6 | # Properties defined in this file take priority over build.properties but are 7 | # only loaded for "package" builds. 8 | # 9 | # NOTE: This use of "package" applies to native packaged application, not a 10 | # Package in the general since of code libraries. 11 | # ============================================================================= 12 | -------------------------------------------------------------------------------- /app/singleton/Functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Singleton class with useful functions. 3 | */ 4 | 5 | Ext.define('OctoZab.singleton.Functions', { 6 | singleton: true, 7 | alternateClassName: ['Functions'], 8 | 9 | requires: [ 10 | 'Ext.window.Toast' 11 | ], 12 | 13 | constructor: function(config) { 14 | this.initConfig(config); 15 | }, 16 | 17 | toastShow: function(text, bgColor, textColor) { 18 | Ext.toast({ 19 | html: text, 20 | closable: false, 21 | stickOnClick: false, 22 | align: 't', 23 | slideInDuration: 100, 24 | autoCloseDelay: 2500, 25 | cls: 'x-toast', 26 | bodyStyle: { background: bgColor, color: textColor } 27 | }); 28 | } 29 | }); -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated and updated by Sencha Cmd. You can edit this file as 3 | * needed for your application, but these edits will have to be merged by 4 | * Sencha Cmd when upgrading. 5 | */ 6 | Ext.application({ 7 | name: 'OctoZab', 8 | 9 | extend: 'OctoZab.Application', 10 | 11 | // autoCreateViewport: 'OctoZab.view.main.Main' 12 | 13 | //------------------------------------------------------------------------- 14 | // Most customizations should be made to OctoZab.Application. If you need to 15 | // customize this file, doing so below this section reduces the likelihood 16 | // of merge conflicts when upgrading to new versions of Sencha Cmd. 17 | //------------------------------------------------------------------------- 18 | }); 19 | -------------------------------------------------------------------------------- /.sencha/app/build.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file provides an override point for default variables defined in these 3 | # lower priority files: 4 | # 5 | # - ext.properties 6 | # - *.defaults.properties 7 | # - defaults.properties 8 | # 9 | # To override a property based on build.environment instead add properties to 10 | # one of these higher priority files: 11 | # 12 | # - production.properties 13 | # - testing.properties 14 | # - native.properties 15 | # - package.properties 16 | # 17 | # IMPORTANT - Sencha Cmd will merge your changes with its own during upgrades. 18 | # To avoid potential merge conflicts avoid making large, sweeping changes to 19 | # this file. 20 | # ============================================================================= 21 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octo-zab", 3 | "version": "0.2.0", 4 | "description": "Backend application to remotely control multiple Zabbix servers", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com/Quadrata-it/octo-zab.git#master" 13 | }, 14 | "keywords": [ 15 | "zabbix" 16 | ], 17 | "author": "Pietro Antonacci (SKUndef)", 18 | "license": "GPL-2.0", 19 | "bugs": { 20 | "url": "https://github.com/Quadrata-it/octo-zab/issues" 21 | }, 22 | "homepage": "https://github.com/Quadrata-it/octo-zab/tree/master#readme", 23 | "dependencies": { 24 | "ioredis": "^1.7.4", 25 | "request": "^2.60.0", 26 | "socket.io": "^1.3.6", 27 | "underscore": "^1.8.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Application.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The main application class. An instance of this class is created by app.js when it calls 3 | * Ext.application(). This is the ideal place to handle application launch and initialization 4 | * details. 5 | */ 6 | Ext.define('OctoZab.Application', { 7 | 8 | extend: 'Ext.app.Application', 9 | 10 | name: 'OctoZab', 11 | 12 | requires: [ 13 | 'OctoZab.singleton.Socket', 14 | 'OctoZab.singleton.Cache', 15 | 'OctoZab.singleton.Functions' 16 | ], 17 | 18 | views: [ 19 | 'main.Main', 20 | 'issuesGrid.IssuesGrid', 21 | 'serversGrid.ServersGrid' 22 | ], 23 | 24 | stores: [ 25 | 'Issues', 26 | 'Servers' 27 | ], 28 | 29 | launch: function () { 30 | Highcharts.setOptions({ 31 | global : { useUTC: false }, 32 | chart : { style: { fontFamily: "helvetica,arial,verdana,sans-serif" } } 33 | }); 34 | 35 | Cache.connectServer(Ext.createByAlias('widget.app-main')); 36 | } 37 | }); -------------------------------------------------------------------------------- /.sencha/app/testing.defaults.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file defines default property values that apply to the "testing" build 3 | # environment. 4 | # 5 | # Please use testing.properties to customize these properties unless you want 6 | # your customizations to be for all environments. In that case, you can instead 7 | # override these properties in build.properties. 8 | # 9 | # The properties defined in this file take priority over defaults.properties 10 | # but are lower priority than build.properties which in turn is lower priority 11 | # than testing.properties. 12 | # 13 | # IMPORTANT - This file should not be modified by an app as it is overwritten 14 | # during each app upgrade. 15 | # ============================================================================= 16 | 17 | build.options.logger=yes 18 | 19 | build.options.debug=true 20 | 21 | build.css.compress=false 22 | -------------------------------------------------------------------------------- /.sencha/app/resources-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/view/issuesGrid/IssuesGridController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Last ISSUES GRID viewcontroller class 3 | */ 4 | Ext.define('OctoZab.view.main.IssuesGridController', { 5 | extend: 'Ext.app.ViewController', 6 | 7 | alias: 'controller.issuesGrid', 8 | 9 | control: { 10 | '#': { 11 | cellclick: 'onIssueClick' 12 | } 13 | }, 14 | 15 | onIssueClick: function(table, td, index, rec) { 16 | var server = rec.data.server, 17 | hostid = rec.data.hostid, 18 | triggerid = rec.data.triggerid, 19 | eventid = rec.data.eventid; 20 | 21 | switch (index) { 22 | case 0: 23 | case 4: 24 | window.open('//' + server + '/zabbix/tr_events.php?triggerid=' + triggerid + '&eventid=' + eventid, '_blank'); break; 25 | case 2: 26 | window.open('//' + server + '/zabbix/dashboard.php', '_blank'); break; 27 | case 3: 28 | window.open('//' + server + '/zabbix/latest.php?hostids%5B%5D=' + hostid + '&show_without_data=1&filter_set=Filter', '_blank'); break; 29 | } 30 | } 31 | }); -------------------------------------------------------------------------------- /.sencha/app/development.defaults.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file defines default property values that apply to the "development" build 3 | # environment. 4 | # 5 | # Please use testing.properties to customize these properties unless you want 6 | # your customizations to be for all environments. In that case, you can instead 7 | # override these properties in build.properties. 8 | # 9 | # The properties defined in this file take priority over defaults.properties 10 | # but are lower priority than build.properties which in turn is lower priority 11 | # than development.properties. 12 | # 13 | # IMPORTANT - This file should not be modified by an app as it is overwritten 14 | # during each app upgrade. 15 | # ============================================================================= 16 | 17 | build.options.logger=yes 18 | 19 | build.options.debug=true 20 | 21 | build.css.compress=false 22 | 23 | build.include.all.css=true 24 | -------------------------------------------------------------------------------- /sass/example/theme.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Ext JS Theme Harness 8 | 9 | 10 | 13 | 14 | 15 | 16 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.sencha/app/native.defaults.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file defines default property values that apply to the "native" build 3 | # environment. 4 | # 5 | # Please use native.properties to customize these properties unless you want 6 | # your customizations to be for all environments. In that case, you can instead 7 | # override these properties in build.properties. 8 | # 9 | # The properties defined in this file take priority over defaults.properties 10 | # but are lower priority than build.properties which in turn is lower priority 11 | # than native.properties. 12 | # 13 | # IMPORTANT - This file should not be modified by an app as it is overwritten 14 | # during each app upgrade. 15 | # ============================================================================= 16 | 17 | build.options.logger=no 18 | 19 | build.options.debug=false 20 | 21 | # enable yui compression 22 | build.compression.yui=1 23 | 24 | enable.standalone.manifest=true 25 | 26 | app.microloader.name=testing.js 27 | 28 | skip.native-package=false 29 | -------------------------------------------------------------------------------- /.sencha/app/package.defaults.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file defines default property values that apply to the "package" build 3 | # environment. 4 | # 5 | # Please use package.properties to customize these properties unless you want 6 | # your customizations to be for all environments. In that case, you can instead 7 | # override these properties in build.properties. 8 | # 9 | # The properties defined in this file take priority over defaults.properties 10 | # but are lower priority than build.properties which in turn is lower priority 11 | # than package.properties. 12 | # 13 | # IMPORTANT - This file should not be modified by an app as it is overwritten 14 | # during each app upgrade. 15 | # 16 | # NOTE: This use of "package" applies to native packaged application, not a 17 | # Package in the general since of code libraries. 18 | # ============================================================================= 19 | 20 | build.options.logger=no 21 | 22 | build.options.debug=false 23 | 24 | # enable yui compression 25 | build.compression.yui=1 26 | 27 | app.microloader.name=testing.js 28 | -------------------------------------------------------------------------------- /app/Readme.md: -------------------------------------------------------------------------------- 1 | # ./controller 2 | 3 | This folder contains the application's global controllers. ViewControllers are located 4 | alongside their respective view class in `"./view"`. These controllers are used for routing 5 | and other activities that span all views. 6 | 7 | # ./model 8 | 9 | This folder contains the application's (data) Model classes. 10 | 11 | # ./view 12 | 13 | This folder contains the views as well as ViewModels and ViewControllers depending on the 14 | application's architecture. Pure MVC applications may not have ViewModels, for example. For 15 | MVCVM applications or MVC applications that use ViewControllers, the following directory 16 | structure is recommended: 17 | 18 | ./view/ 19 | foo/ # Some meaningful grouping of one or more views 20 | Foo.js # The view class 21 | FooController.js # The controller for Foo (a ViewController) 22 | FooModel.js # The ViewModel for Foo 23 | 24 | This structure helps keep these closely related classes together and easily identifiable in 25 | most tabbed IDE's or text editors. 26 | 27 | # ./store 28 | 29 | This folder contains any number of store instances or types that can then be reused in the 30 | application. 31 | -------------------------------------------------------------------------------- /.sencha/app/production.defaults.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file defines default property values that apply to the "production" build 3 | # environment. 4 | # 5 | # Please use production.properties to customize these properties unless you want 6 | # your customizations to be for all environments. In that case, you can instead 7 | # override these properties in build.properties. 8 | # 9 | # The properties defined in this file take priority over defaults.properties 10 | # but are lower priority than build.properties which in turn is lower priority 11 | # than production.properties. 12 | # 13 | # IMPORTANT - This file should not be modified by an app as it is overwritten 14 | # during each app upgrade. 15 | # ============================================================================= 16 | 17 | build.options.logger=no 18 | 19 | build.options.debug=false 20 | 21 | # enable yui compression 22 | build.compression.yui=1 23 | 24 | # enable the full class system optimizer 25 | build.optimize=${build.optimize.disable} 26 | 27 | enable.cache.manifest=true 28 | 29 | enable.resource.compression=true 30 | 31 | build.enable.embedded.manifest=false 32 | 33 | build.embedded.microloader.compressor=-closure 34 | -------------------------------------------------------------------------------- /app/singleton/Cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Singleton class to retain values retrieved from backend. 3 | */ 4 | 5 | Ext.define('OctoZab.singleton.Cache', { 6 | singleton: true, 7 | alternateClassName: ['Cache'], 8 | 9 | constructor: function(config) { 10 | this.initConfig(config); 11 | }, 12 | 13 | config: { 14 | backendUrl: config.backendUrl + ':' + config.backendPort, 15 | nodeTask: null, 16 | servers: null, 17 | issues: {}, 18 | issuesServerFilter: new Ext.util.Filter({ 19 | id: 'server-filter', 20 | property: 'server', 21 | value: "" 22 | }), 23 | issuesPriorityFilter: new Ext.util.Filter({ 24 | id: 'priority-filter', 25 | property: 'priority', 26 | operator: 'in', 27 | value: ['0','1','2','3','4','5'] 28 | }), 29 | issuesMapData: {}, 30 | issuesMap: null 31 | }, 32 | 33 | connectServer: function(view) { 34 | this.setNodeTask(Ext.TaskManager.start({ 35 | interval: 10*1000, 36 | 37 | run: function() { 38 | if (Socket.getSocket() === null) { 39 | $.getScript('//' + Cache.getBackendUrl() + '/socket.io/socket.io.js') 40 | .done(function() { 41 | Socket.connect(view); 42 | Ext.TaskManager.stop(Cache.getNodeTask()); 43 | }); 44 | } 45 | } 46 | })); 47 | } 48 | }); -------------------------------------------------------------------------------- /resources/js/highcharts-4.1.7-no-data-to-display.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highcharts JS v4.1.7 (2015-06-26) 3 | Plugin for displaying a message when there is no data visible in chart. 4 | 5 | (c) 2010-2014 Highsoft AS 6 | Author: Oystein Moseng 7 | 8 | License: www.highcharts.com/license 9 | */ 10 | (function(c){function i(){return!!this.points.length}function e(){this.hasData()?this.hideNoData():this.showNoData()}var f=c.seriesTypes,d=c.Chart.prototype,g=c.getOptions(),h=c.extend,j=c.each;h(g.lang,{noData:"No data to display"});g.noData={position:{x:0,y:0,align:"center",verticalAlign:"middle"},attr:{},style:{fontWeight:"bold",fontSize:"12px",color:"#60606a"}};j(["pie","gauge","waterfall","bubble"],function(a){if(f[a])f[a].prototype.hasData=i});c.Series.prototype.hasData=function(){return this.visible&& 11 | this.dataMax!==void 0&&this.dataMin!==void 0};d.showNoData=function(a){var b=this.options,a=a||b.lang.noData,b=b.noData;if(!this.noDataLabel)this.noDataLabel=this.renderer.label(a,0,0,null,null,null,b.useHTML,null,"no-data").attr(b.attr).css(b.style).add(),this.noDataLabel.align(h(this.noDataLabel.getBBox(),b.position),!1,"plotBox")};d.hideNoData=function(){if(this.noDataLabel)this.noDataLabel=this.noDataLabel.destroy()};d.hasData=function(){for(var a=this.series,b=a.length;b--;)if(a[b].hasData()&& 12 | !a[b].options.isInternal)return!0;return!1};d.callbacks.push(function(a){c.addEvent(a,"load",e);c.addEvent(a,"redraw",e)})})(Highcharts); 13 | -------------------------------------------------------------------------------- /.sencha/app/sencha.cfg: -------------------------------------------------------------------------------- 1 | # This value has been replaced by "build.dir" set in defaults.properties but is 2 | # preserved for compatibility. 3 | app.build.dir=${workspace.build.dir}/${app.name} 4 | 5 | # Path to sass rule definition files corresponding to JavaScript classes. 6 | app.sass.srcpath=${app.dir}/sass/src 7 | 8 | # Path to sass variable definition files corresponding to JavaScript classes. 9 | app.sass.varpath=${app.dir}/sass/var 10 | 11 | # Path to sass function and mixin files. 12 | app.sass.etcpath=${app.dir}/sass/etc/all.scss 13 | 14 | # Path to extra ruby files to include into the generated sass config.rb, 15 | # /sass/config.rb will be included automatically if present and does 16 | # not need to be specified. 17 | # app.sass.rubypath= 18 | 19 | # This property can be modified to change the input and output page file 20 | # used in the compile command. (eg: index.aspx, index.jsp ... ) 21 | app.page.name=index.html 22 | 23 | # the input page file 24 | app.page.file=${app.dir}/${app.page.name} 25 | 26 | # this property specifies a comma separated list of paths containing 27 | # resources to copy to the build directory 28 | app.resource.paths=${app.dir}/resources 29 | 30 | #============================================================================== 31 | # Custom Properties - Place customizations below this line to avoid merge 32 | # conflicts with newer versions 33 | 34 | app.sass.namespace=${app.name} 35 | app.theme=ext-theme-classic 36 | 37 | app.framework.version=5.0.0.970 38 | app.cmd.version=5.0.0.160 39 | -------------------------------------------------------------------------------- /app/model/Issues.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Last ISSUES GRID elements MODEL class. 3 | * It displays TRIGGER.GET api output, filtered to display issues only. 4 | */ 5 | 6 | Ext.define('OctoZab.model.Issues', { 7 | extend: 'OctoZab.model.Base', 8 | 9 | fields: [{ 10 | name: 'lastchange', 11 | type: 'date', 12 | convert: function(value) { 13 | if (value instanceof Date === false) { 14 | return new Date(parseInt(value, 10)*1000); 15 | } else { 16 | return value; 17 | } 18 | } 19 | },{ 20 | name: 'age', 21 | calculate: function(data) { 22 | var lastChange = new Date(data.lastchange), 23 | now = new Date(), 24 | days, hours, mins; 25 | 26 | days = Ext.Date.diff( 27 | lastChange, 28 | now, 29 | Ext.Date.DAY 30 | ); 31 | hours = Ext.Date.diff( 32 | lastChange, 33 | Ext.Date.subtract(now, Ext.Date.DAY, days), 34 | Ext.Date.HOUR 35 | ); 36 | mins = Ext.Date.diff( 37 | lastChange, 38 | Ext.Date.subtract( 39 | Ext.Date.subtract(now, Ext.Date.DAY, days), 40 | Ext.Date.HOUR, 41 | hours 42 | ), 43 | Ext.Date.MINUTE 44 | ); 45 | 46 | return days + 'd ' + hours + 'h ' + mins + 'm'; 47 | } 48 | }, 49 | 'server', 50 | 'hostid', 51 | 'hostname', 52 | 'description', 53 | 'comments', 54 | 'priority', 55 | 'triggerid', 56 | { 57 | name: 'eventid', 58 | mapping: 'lastEvent.eventid' 59 | },{ 60 | name: 'acknowledged', 61 | mapping: 'lastEvent.acknowledged', 62 | type: 'boolean' 63 | }], 64 | 65 | proxy: { type: 'memory' } 66 | }); -------------------------------------------------------------------------------- /.sencha/app/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 32 | 33 | -------------------------------------------------------------------------------- /sass/Readme.md: -------------------------------------------------------------------------------- 1 | # ./sass 2 | 3 | This folder contains the styling for the application's views. The primary pi 4 | 5 | ## Styling 6 | 7 | Sencha Cmd supports styling using Sass and integrates the styling from the theme 8 | and required packages (specified in `"app.json"`) with application-defined views. 9 | 10 | ### ./sass/etc 11 | 12 | This folder contains misc. support code for Sass builds (global functions, 13 | mixins, etc.). 14 | 15 | ### ./sass/src 16 | 17 | This folder contains Sass files defining CSS rules corresponding to classes 18 | included in the application's JavaScript code build. By default, files in this 19 | folder are mapped to the application's root namespace, 'OctoZab'. This is set in 20 | `"app.json"`: 21 | 22 | "sass": { 23 | "namespace": "OctoZab" 24 | } 25 | 26 | ### ./sass/var 27 | 28 | This folder contains Sass files defining Sass variables corresponding to classes 29 | included in the application's JavaScript code build. By default, files in this 30 | folder are mapped to the application's root namespace, 'OctoZab' in the same way 31 | as `"OctoZab/sass/src"`. 32 | 33 | ## Slicing 34 | 35 | Internet Explorer 8 and 9 do not support linear gradients and IE8 does not support 36 | border-radius. To compensate for this, Sencha Cmd provides "image slicing" using an 37 | internal WebKit based renderer. To enable this, there is a special web page that 38 | renders all components and states so they can be captured and turned into image 39 | sprites. 40 | 41 | ### ./sass/example 42 | 43 | This folder contains the web page used to present all components and states so they 44 | can be captured as an image and used to produce images for IE8 and 9. 45 | -------------------------------------------------------------------------------- /.sencha/workspace/sencha.cfg: -------------------------------------------------------------------------------- 1 | #Wed, 10 Jun 2015 14:40:31 +0200 2 | # ----------------------------------------------------------------------------- 3 | # This file contains configuration options that apply to all applications in 4 | # the workspace. By convention, these options start with "workspace." but any 5 | # option can be set here. Options specified in an application's sencha.cfg will 6 | # take priority over those values contained in this file. These options will 7 | # take priority over configuration values in Sencha Cmd or a framework plugin. 8 | 9 | # ----------------------------------------------------------------------------- 10 | # This configuration property (if set) is included by default in all compile 11 | # commands executed according to this formulation: 12 | # 13 | # sencha compile -classpath=...,${framework.classpath},${workspace.classpath},${app.classpath} 14 | 15 | #workspace.classpath= 16 | 17 | #------------------------------------------------------------------------------ 18 | # This is the folder for build outputs in the workspace 19 | 20 | workspace.build.dir=${workspace.dir}/build 21 | 22 | #------------------------------------------------------------------------------ 23 | # This folder contains all generated and extracted packages. 24 | 25 | workspace.packages.dir=${workspace.dir}/packages 26 | 27 | workspace.theme.dir=${workspace.packages.dir}/${args.themeName} 28 | 29 | # ============================================================================= 30 | # Customizations go below this divider to avoid merge conflicts on upgrade 31 | # ============================================================================= 32 | 33 | workspace.cmd.version=5.0.0.160 34 | 35 | ext.dir=${workspace.dir}/ext 36 | -------------------------------------------------------------------------------- /.sencha/app/ext.properties: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # This file defines default property values that apply to all builds based on 3 | # Ext JS 5.x framework. 4 | # 5 | # Please use build.properties to customize these properties. 6 | # 7 | # To override a property based on build.environment instead add properties to 8 | # one of these higher priority files: 9 | # 10 | # - production.properties 11 | # - testing.properties 12 | # - native.properties 13 | # - package.properties 14 | # 15 | # The properties defined in this file take priority over defaults.properties 16 | # and *.defaults.properties. 17 | # 18 | # IMPORTANT - This file should not be modified by an app as it is overwritten 19 | # during each app upgrade. 20 | # ============================================================================= 21 | 22 | enable.ext42.themes=true 23 | 24 | build.output.markuponly=false 25 | 26 | enable.sencha-core.filter=true 27 | 28 | build.options.product=ext 29 | 30 | build.options.minVersion=5 31 | 32 | bootstrap.include.boot=true 33 | bootstrap.override.tpl=Ext.Loader.loadScriptsSync 34 | bootstrap.override.tpltype=jsonp 35 | 36 | app.microloader.name=Microloader.js 37 | app.microloader.dir=${app.config.dir} 38 | app.microloader.bootstrap=${app.microloader.dir}/${app.microloader.name} 39 | app.microloader.path=${app.microloader.dir}/${app.microloader.name} 40 | 41 | build.microloader.json.tpl.embedded=var Ext = Ext || '{' '}'; Ext.manifest = {0}; 42 | build.microloader.manifest.name=app 43 | build.microloader.json.tpl.external=var Ext = Ext || '{' '}'; Ext.manifest = "${build.microloader.manifest.name}"; 44 | 45 | build.skip.versions.file=true 46 | build.enable.appmanifest=true 47 | build.optimize.defines=false 48 | build.optimize.callparent=false 49 | build.optimize.string.references=false 50 | compass.compile.force=false 51 | -------------------------------------------------------------------------------- /app/view/serversGrid/ServersGridController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Last ISSUES GRID viewcontroller class 3 | */ 4 | Ext.define('OctoZab.view.main.ServersGridController', { 5 | extend: 'Ext.app.ViewController', 6 | 7 | alias: 'controller.serversGrid', 8 | 9 | onAddServer: function() { 10 | var grid = this.getView(), 11 | store = grid.getStore(); 12 | 13 | grid.getPlugin('rowEdit').startEdit(store.add({ 14 | server: '', 15 | user: '', 16 | psw: '' 17 | })[0]); 18 | }, 19 | 20 | onServerBeforeEdit: function() { 21 | this.lookupReference('addBtn').disable(); 22 | }, 23 | 24 | onServerEdit: function() { 25 | this.lookupReference('addBtn').enable(); 26 | }, 27 | 28 | onServerEditCanc: function(editor, context) { 29 | if (!context.value) { 30 | this.getView().getStore().remove(context.record); 31 | } 32 | 33 | this.onServerEdit(); 34 | }, 35 | 36 | onDeleteServer: function(btn) { 37 | this.getView().getStore().remove(btn.getWidgetRecord()); 38 | }, 39 | 40 | onResetServers: function() { 41 | var store = this.getView().getStore(); 42 | 43 | store.rejectChanges(); 44 | store.reload(); 45 | }, 46 | 47 | onSaveServers: function() { 48 | var store = this.getView().getStore(), 49 | recs = store.getRange(), 50 | modRecs = store.getModifiedRecords(), 51 | delRecs = store.getRemovedRecords(); 52 | 53 | if (modRecs.length || delRecs.length) { 54 | Ext.Msg.show({ 55 | title: 'SAVE', 56 | message: 'Do you want to update servers configuration?', 57 | closable: false, 58 | buttons: Ext.Msg.OKCANCEL, 59 | fn: function(btn) { 60 | if (btn == 'ok') { 61 | Socket.updateServers(recs); 62 | } 63 | } 64 | }); 65 | } else { 66 | Ext.Msg.show({ 67 | title: 'ERROR!', 68 | message: 'No modification to the actual servers configuration', 69 | closable: false, 70 | buttons: Ext.Msg.OK 71 | }); 72 | } 73 | } 74 | }); -------------------------------------------------------------------------------- /app/view/main/Main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the main view for the application. It is specified in app.js as the 3 | * "autoCreateViewport" property. That setting automatically applies the "viewport" 4 | * plugin to promote that instance of this class to the body element. 5 | */ 6 | Ext.define('OctoZab.view.main.Main', { 7 | extend: 'Ext.tab.Panel', 8 | 9 | requires: [ 10 | 'Ext.grid.filters.Filters', 11 | 'Ext.grid.column.Date' 12 | ], 13 | 14 | xtype: 'app-main', 15 | controller: 'main', 16 | viewModel: { type: 'main' }, 17 | 18 | plugins: 'viewport', 19 | 20 | header: { cls: 'main-bar' }, 21 | tabBar: { cls: 'main-bar' }, 22 | 23 | headerPosition: 'left', 24 | tabBarHeaderPosition: 0, 25 | tabPosition: 'left', 26 | tabRotation: 0, 27 | 28 | tools: [{ 29 | xtype: 'tbtext', 30 | itemId: 'logo-text', 31 | text: 'OCTOZAB', 32 | cls: 'logo-text' 33 | }], 34 | 35 | defaults: { 36 | bodyPadding: '20 50 20 50', 37 | tabConfig: { tooltipType: 'title' } 38 | }, 39 | items: [{ 40 | glyph: 'xf0e4@FontAwesome', 41 | tooltip: 'Dashboard', 42 | 43 | layout: { type: 'vbox', align: 'stretch' }, 44 | 45 | items: [{ 46 | xtype: 'tbtext', 47 | text: "DASHBOARD", 48 | height: 26, 49 | margin: '0 0 20 0', 50 | cls: 'tab-panel-title' 51 | },{ 52 | xtype: 'panel', 53 | itemId: 'panel-issues-map', 54 | margin: '0 0 10 0', 55 | flex: 3, 56 | bind: { 57 | title: 'ISSUES MAP / server: {issuesMapView}' 58 | } 59 | },{ 60 | xtype: 'grid-issues', 61 | margin: '10 0 0 0', 62 | flex: 4 63 | }] 64 | },{ 65 | glyph: 'xf013@FontAwesome', 66 | tooltip: 'Settings', 67 | 68 | layout: { type: 'vbox', align: 'stretch' }, 69 | 70 | items: [{ 71 | xtype: 'tbtext', 72 | text: "SETTINGS", 73 | height: 26, 74 | margin: '0 0 20 0', 75 | cls: 'tab-panel-title' 76 | },{ 77 | xtype: 'grid-servers', 78 | margin: '0 0 10 0', 79 | height: 300 80 | }] 81 | }] 82 | }); -------------------------------------------------------------------------------- /Application.md: -------------------------------------------------------------------------------- 1 | # OctoZab 2 | 3 | This folder is primarily a container for the top-level pieces of the application. 4 | While you can remove some files and folders that this application does not use, 5 | be sure to read below before deciding what can be deleted and what needs to be 6 | kept in source control. 7 | 8 | The following files are all needed to build and load the application. 9 | 10 | - `"app.json"` - The application descriptor which controls how the application is 11 | built and loaded. 12 | - `"app.js"` - The file that launches the application. This is primarily used to 13 | launch an instance of the `MyApp.Application` class. 14 | - `"index.html"` - The default web page for this application. This can be customized 15 | in `"app.json"`. 16 | - `"build.xml"` - The entry point for Sencha Cmd to access the generated build 17 | script. This file is a place where you can hook into these processes and tune 18 | them. See the comments in that file for more information. 19 | - `".sencha"` - This (typically hidden) folder contains the generated build scripts 20 | and configuration files for the application. This folder is required in order to 21 | build the application but its content should not need to be edited in most cases. 22 | The content of this folder is updated by "sencha app upgrade". 23 | 24 | These files can be ignored from source control as they are regenerated by the build 25 | process. 26 | 27 | - `"build"` - This folder contain the output of the build. The generated CSS file, 28 | consolidated resources and concatenated JavaScript file are all stored in this 29 | folder. 30 | - `"bootstrap.*"` - These files are generated by the build and watch commands to 31 | enable the application to load in "development mode". 32 | 33 | # Other Folders 34 | 35 | ## OctoZab/app 36 | 37 | This folder contains the JavaScript files for the application. 38 | 39 | ## OctoZab/resources 40 | 41 | This folder contains static resources (typically an `"images"` folder as well). 42 | 43 | ## OctoZab/overrides 44 | 45 | This folder contains override classes. All overrides in this folder will be 46 | automatically included in application builds if the target class of the override 47 | is loaded. 48 | 49 | ## OctoZab/sass 50 | 51 | This folder contains the styling for the application's views. See OctoZab/sass/Readme.md 52 | for details. 53 | -------------------------------------------------------------------------------- /sass/src/view/issuesGrid/IssuesGrid.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * ISSUES GRID CSS rules 3 | */ 4 | 5 | /** EVENT SEVERITY CELL styling */ 6 | .disaster-cell { background-color: #FF1E1E !important; } 7 | .high-cell { background-color: #FF7373 !important; } 8 | .average-cell { background-color: #FFA866 !important; } 9 | .warning-cell { background-color: #FFFF80 !important; } 10 | .info-cell { background-color: #BFFFFF !important; } 11 | .not-classified-cell { background-color: #D9D9D9 !important; } 12 | .ok-cell { background-color: #8CFF8C !important; } 13 | .no-ack-cell { background-color: #FFF380 !important; } 14 | 15 | /** EVENT SEVERITY ROWS styling */ 16 | .x-grid-item .disaster-row .x-grid-cell { background-color: #FF1E1E; } 17 | .x-grid-item-selected .disaster-row .x-grid-cell { background-color: #D91919; } 18 | .x-grid-item-over .disaster-row .x-grid-cell { background-color: #D91919; } 19 | 20 | .x-grid-item .high-row .x-grid-cell { background-color: #FF7373; } 21 | .x-grid-item-selected .high-row .x-grid-cell { background-color: #D96262; } 22 | .x-grid-item-over .high-row .x-grid-cell { background-color: #D96262; } 23 | 24 | .x-grid-item .average-row .x-grid-cell { background-color: #FFA866; } 25 | .x-grid-item-selected .average-row .x-grid-cell { background-color: #D98F57; } 26 | .x-grid-item-over .average-row .x-grid-cell { background-color: #D98F57; } 27 | 28 | .x-grid-item .warning-row .x-grid-cell { background-color: #FFFF80; } 29 | .x-grid-item-selected .warning-row .x-grid-cell { background-color: #D9D96D; } 30 | .x-grid-item-over .warning-row .x-grid-cell { background-color: #D9D96D; } 31 | 32 | .x-grid-item .info-row .x-grid-cell { background-color: #BFFFFF; } 33 | .x-grid-item-selected .info-row .x-grid-cell { background-color: #A2D9D9; } 34 | .x-grid-item-over .info-row .x-grid-cell { background-color: #A2D9D9; } 35 | 36 | .x-grid-item .not-classified-row .x-grid-cell { background-color: #D9D9D9; } 37 | .x-grid-item-selected .not-classified-row .x-grid-cell { background-color: #B3B3B3; } 38 | .x-grid-item-over .not-classified-row .x-grid-cell { background-color: #B3B3B3; } 39 | 40 | .x-grid-item .acknowledged-row .x-grid-cell { background-color: #8CFF8C; } 41 | .x-grid-item-selected .acknowledged-row .x-grid-cell { background-color: #77D977; } 42 | .x-grid-item-over .acknowledged-row .x-grid-cell { background-color: #77D977; } -------------------------------------------------------------------------------- /sass/etc/all.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * GLOBAL SASS VARIABLES 3 | */ 4 | 5 | /** FONT */ 6 | $font-size: 12px; 7 | $font-family: helvetica,arial,verdana,sans-serif; 8 | 9 | /** BODY */ 10 | $body-background-color: #E6E6E6; 11 | 12 | /** BUTTON */ 13 | $button-small-font-size: 12px; 14 | $button-default-background-color: #E6E6E6; 15 | $button-default-border-color: #E6E6E6; 16 | $button-default-color: #000; 17 | $button-default-glyph-opacity: 0.6; 18 | $button-default-background-color-over: #D9D9D9; 19 | $button-default-background-color-pressed: #D4D4D4; 20 | $button-default-background-color-focus: #D4D4D4; 21 | $button-default-background-color-disabled: #CCCCCC; 22 | $button-small-border-radius: 2px; 23 | 24 | /** TOOLBAR */ 25 | $toolbar-footer-background-color: #505359; 26 | 27 | /** PANEL */ 28 | $panel-header-color: #FFF; 29 | $panel-header-background-color: #505359; 30 | $panel-header-font-size: 14px; 31 | $panel-header-font-weight: bold; 32 | $panel-header-text-margin: 0 0 0 6px; 33 | $panel-body-background-color: transparent; 34 | 35 | /** GRID */ 36 | $grid-body-border-width: 0; 37 | $grid-header-background-color: #505359; 38 | $grid-header-border-width: 0; 39 | $grid-header-over-background-color: transparent; 40 | $grid-column-header-color: #FFF; 41 | $grid-column-header-font-size: 11px; 42 | $grid-column-header-font-weight: bold; 43 | $grid-column-header-border-width: 0; 44 | $grid-grouped-header-background-color: #505359; 45 | $grid-grouped-header-border-width: 0; 46 | $grid-grouped-title-color: #FFF; 47 | $grid-grouped-title-font-weight: bold; 48 | $grid-grouped-title-line-height: 11px; 49 | $grid-row-cell-line-height: 16px; 50 | $grid-row-cell-over-background-color: #E6E6E6; 51 | $grid-row-cell-selected-background-color: #E6E6E6; 52 | $grid-row-editor-background-color: #E6E6E6; 53 | $grid-row-editor-border-color: #E6E6E6; 54 | 55 | /** TAB */ 56 | $tab-glyph-opacity: 1; 57 | 58 | /** WINDOW */ 59 | $window-header-background-color: #505359; 60 | $window-border-width: 0; 61 | $window-body-color: #FFF; 62 | 63 | /** MESSAGEBOX */ 64 | $messagebox-body-background-color: #505359; 65 | 66 | /** LOADMASK */ 67 | $loadmask-opacity: 0.5; 68 | $loadmask-background-color: #E6E6E6; 69 | $loadmask-msg-background-color: #505359; 70 | $loadmask-msg-border-radius: 0; 71 | $loadmask-msg-text-padding: 5px; 72 | $loadmask-msg-inner-color: #FFF; 73 | $loadmask-msg-inner-font-weight: bold; 74 | $loadmask-msg-inner-icon: none; -------------------------------------------------------------------------------- /.sencha/app/find-cmd-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 50 | 51 | 52 | source ~/.bash_profile; sencha which -p cmd.dir -o '$cmddir$' 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /.sencha/app/watch-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/view/serversGrid/ServersGrid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ZABBIX SERVERS GRID class 3 | */ 4 | 5 | Ext.define('OctoZab.view.serversGrid.ServersGrid', { 6 | extend: 'Ext.grid.Panel', 7 | 8 | xtype: 'grid-servers', 9 | controller: 'serversGrid', 10 | 11 | store: 'Servers', 12 | 13 | title: 'SERVERS', 14 | columnLines: false, 15 | rowLines: false, 16 | reserveScrollbar: true, 17 | sortableColumns: false, 18 | bufferedRenderer: false, 19 | 20 | bodyStyle: { backgroundColor: "#676B73" }, 21 | 22 | tools: [{ 23 | xtype: 'button', 24 | reference: 'addBtn', 25 | glyph: 'xf067@FontAwesome', 26 | tooltip: 'Add server', 27 | tooltipType: 'title', 28 | cls: 'dark-tool-button', 29 | handler: 'onAddServer' 30 | }], 31 | 32 | plugins: [{ 33 | ptype: 'rowediting', 34 | pluginId: 'rowEdit', 35 | autoCancel: false, 36 | errorSummary: false, 37 | listeners: { 38 | beforeedit: 'onServerBeforeEdit', 39 | edit: 'onServerEdit', 40 | canceledit: 'onServerEditCanc', 41 | scope: 'controller' 42 | } 43 | }], 44 | 45 | viewConfig: { 46 | getRowClass: function(record) { return 'dark-big-row'; } 47 | }, 48 | 49 | columns: { 50 | defaults: { 51 | align: 'center' 52 | }, 53 | items: [{ 54 | text: 'HOST', 55 | dataIndex: 'server', 56 | editor: { xtype: 'textfield', allowBlank: false }, 57 | renderer: function(value, metaData) { 58 | metaData.style = "font-weight: bold"; 59 | return value; 60 | }, 61 | flex: 1 62 | },{ 63 | text: 'USER', 64 | dataIndex: 'user', 65 | sortable: false, 66 | editor: { xtype: 'textfield', allowBlank: false }, 67 | flex: 1 68 | },{ 69 | text: 'PASSWORD', 70 | dataIndex: 'psw', 71 | sortable: false, 72 | editor: { xtype: 'textfield', allowBlank: false }, 73 | renderer: function(value, metaData) { 74 | return Array(value.length+1).join("\u2022"); 75 | }, 76 | flex: 1 77 | },{ 78 | xtype: 'widgetcolumn', 79 | sortable: false, 80 | menuDisabled: true, 81 | width: 52, 82 | widget: { 83 | xtype: 'button', 84 | glyph: 'xf014@FontAwesome', 85 | tooltip: 'Delete server', 86 | tooltipType: 'title', 87 | cls: 'dark-grid-icon', 88 | handler: 'onDeleteServer' 89 | } 90 | }] 91 | }, 92 | 93 | buttons: [{ 94 | text: 'Reset', 95 | tooltip: 'Reset configuration', 96 | tooltipType: 'title', 97 | handler: 'onResetServers' 98 | },{ 99 | text: 'Save', 100 | tooltip: 'Save configuration', 101 | tooltipType: 'title', 102 | handler: 'onSaveServers' 103 | }] 104 | }); -------------------------------------------------------------------------------- /sass/src/view/main/Main.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * MAIN APP CSS rules. 3 | */ 4 | 5 | /** PANEL DARK TOOL */ 6 | .dark-tool-button.x-btn-default-small { background-color: #505359; border-color: #505359; border-radius: 0; } 7 | .dark-tool-button.x-btn-over.x-btn-default-small { background-color: #676B73; border-color: #676B73; } 8 | .dark-tool-button .x-btn-icon-el-default-small.x-btn-glyph { color: #FFF; opacity: 1; } 9 | 10 | /** GRID */ 11 | .x-grid-filters-filtered-column { color: #66B4FF; } 12 | .wrap-text-cell div { white-space: normal; } 13 | 14 | /** GRID ROWEDIT PLUGIN */ 15 | .x-grid-row-editor .x-panel-body { margin-top: 5px; } 16 | .x-grid-row-editor-buttons-default-top { margin-bottom: -5px; } 17 | .x-grid-row-editor-buttons-default-bottom { padding: 10px 5px 5px 5px; } 18 | 19 | /** GRID DARK BIG ROWS */ 20 | .x-grid-item .dark-big-row .x-grid-cell { 21 | background-color: #676B73; 22 | color: #FFF; 23 | font-family: verdana,helvetica,arial,sans-serif; 24 | line-height: 28px; 25 | vertical-align: middle; 26 | } 27 | .x-grid-item-over .dark-big-row .x-grid-cell { 28 | background-color: #727780; 29 | } 30 | .x-grid-item-selected .dark-big-row .x-grid-cell { 31 | background-color: #727780; 32 | } 33 | 34 | /** GRID DARK ICON */ 35 | .dark-grid-icon.x-btn-default-small { 36 | background-color: transparent; 37 | border-color: transparent; 38 | width: 34px !important; 39 | vertical-align: middle; 40 | } 41 | .dark-grid-icon .x-btn-icon-el-default-small.x-btn-glyph { 42 | color: #FFF; 43 | font-size: 18px; 44 | opacity: 1; 45 | } 46 | 47 | /** WINDOW */ 48 | .x-window-default { border-radius: 0; } 49 | .x-window-header-default { border-radius: 0; } 50 | 51 | /** MESSAGEBOX */ 52 | .x-message-box .x-form-display-field { color: #FFF; } 53 | 54 | /** TOAST */ 55 | .x-toast { height: 38px !important; } 56 | .x-toast .x-window-body-default { font-weight: bold; } 57 | 58 | /** MAIN TAB BAR */ 59 | .main-bar.x-panel-header-default-vertical { padding: 10px 0; } 60 | .main-bar .x-tab-bar-default { background-color: #505359; } 61 | .main-bar .x-tab-bar-default-left > .x-tab-bar-body-default { padding: 0; } 62 | .main-bar .x-tab-default { border-color: transparent; } 63 | .main-bar .x-tab-default-left { padding: 9px 22px; margin: 7px 0; background-color: transparent; } 64 | .main-bar .x-tab-active.x-tab-default { background-color: #E6E6E6; border-color: #E6E6E6; } 65 | .main-bar .x-tab-active.x-tab-default .x-tab-glyph { color: #505359; } 66 | .main-bar .x-tab-icon-el-default.x-tab-glyph { font-size: 26px; line-height: 24px; width: 26px; height: 26px; color: #E6E6E6; } 67 | 68 | /** LOGO */ 69 | .logo-text { 70 | -webkit-transform: rotate(-90deg); 71 | -moz-transform: rotate(-90deg); 72 | -ms-transform: rotate(-90deg); 73 | -o-transform: rotate(-90deg); 74 | font-weight: bold; 75 | font-size: 24px; 76 | font-family: verdana,sans-serif; 77 | } 78 | 79 | /** TAB PANEL TITLE */ 80 | .tab-panel-title { 81 | color: #7A818C; 82 | font-size: 26px; 83 | font-weight: bold; 84 | line-height: 100%; 85 | font-variant: small-caps; 86 | } -------------------------------------------------------------------------------- /app/singleton/Socket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Singleton class to manage Socket.IO connection 3 | */ 4 | 5 | Ext.define('OctoZab.singleton.Socket', { 6 | singleton: true, 7 | alternateClassName: ['Socket'], 8 | 9 | constructor: function(config) { 10 | this.initConfig(config); 11 | }, 12 | 13 | config: { 14 | socket: null 15 | }, 16 | 17 | connect: function(view) { 18 | this.setSocket(io.connect(Cache.getBackendUrl())); 19 | 20 | this.getSocket().on('connect', function() { 21 | view.unmask(); 22 | }); 23 | 24 | this.getSocket().on('reconnect', function() { 25 | view.unmask(); 26 | }); 27 | 28 | this.getSocket().on('disconnect', function() { 29 | view.mask('UNREACHABLE BACKEND'); 30 | }); 31 | 32 | this.getSocket().on('servers.update', function(servers) { 33 | var store = Ext.StoreMgr.lookup('Servers'), 34 | serversData = [], 35 | serversToRemove = [], 36 | issuesStore = Ext.StoreMgr.lookup('Issues'), 37 | issuesMapSeries = Cache.getIssuesMap().get('issues'); 38 | 39 | if (store.isLoaded()) { 40 | serversToRemove = _.difference(_.keys(Cache.getServers()), _.keys(servers)); 41 | 42 | _.each(serversToRemove, function(server) { 43 | delete Cache.config.issues[server]; 44 | delete Cache.config.issuesMapData[server]; 45 | }); 46 | 47 | Functions.toastShow('SERVERS CONFIGURATION UPDATED!', '#D94141', '#FFF'); 48 | } 49 | 50 | Cache.setServers(servers); 51 | 52 | _.each(servers, function(v,k) { 53 | serversData.push({ 54 | server : k, 55 | user : v.user, 56 | psw : v.psw 57 | }); 58 | }); 59 | 60 | store.getProxy().setData(serversData); 61 | store.reload(); 62 | 63 | if (_.isEmpty(servers)) { 64 | issuesStore.getProxy().setData([]); 65 | issuesStore.reload(); 66 | 67 | issuesMapSeries.setData([]); 68 | } 69 | }); 70 | 71 | this.getSocket().on('trigger.get.issues', function(data) { 72 | var store = Ext.StoreMgr.lookup('Issues'), 73 | issues = []; 74 | 75 | // console.log('[UPDATE] ' + data.server + ': issues'); 76 | Cache.config.issues[data.server] = JSON.parse(data.val); 77 | 78 | _.each(Cache.getIssues(), function(v,k) { 79 | issues = issues.concat(v); 80 | }); 81 | 82 | store.getProxy().setData(issues); 83 | store.reload(); 84 | }); 85 | 86 | this.getSocket().on('trigger.get.issues.map', function(data) { 87 | var issuesMapData = []; 88 | 89 | // console.log('[UPDATE] ' + data.server + ': issues.map'); 90 | Cache.config.issuesMapData[data.server] = JSON.parse(data.val); 91 | 92 | _.each(Cache.getIssuesMapData(), function(v,k) { 93 | issuesMapData = issuesMapData.concat(v); 94 | }); 95 | 96 | Cache.getIssuesMap().get('issues').setData(issuesMapData); 97 | }); 98 | }, 99 | 100 | updateServers: function(recs) { 101 | var servers = {}; 102 | 103 | _.each(recs, function(rec) { 104 | servers[rec.data.server] = { 105 | server : rec.data.server, 106 | user : rec.data.user, 107 | psw : rec.data.psw 108 | }; 109 | }); 110 | 111 | this.getSocket().emit('servers.set', servers); 112 | } 113 | }); -------------------------------------------------------------------------------- /.sencha/app/refresh-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | /** 59 | * This file is generated by Sencha Cmd and should NOT be edited. It is a 60 | * combination of content from app.json, and all required package's package.json 61 | * files. Customizations should be placed in app.json. 62 | */ 63 | 64 | 65 | 73 | 74 | 75 | 76 | 80 | 84 | 85 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /.sencha/app/js-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 56 | 57 | 58 | 59 | 60 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/view/issuesGrid/IssuesGrid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Last ISSUES GRID class 3 | */ 4 | 5 | Ext.define('OctoZab.view.issuesGrid.IssuesGrid', { 6 | extend: 'Ext.grid.Panel', 7 | 8 | xtype: 'grid-issues', 9 | controller: 'issuesGrid', 10 | 11 | store: 'Issues', 12 | 13 | title: 'ISSUES DETAILS', 14 | columnLines: false, 15 | rowLines: false, 16 | reserveScrollbar: true, 17 | sortableColumns: false, 18 | 19 | plugins: 'gridfilters', 20 | 21 | features: [{ 22 | ftype: 'grouping', 23 | disabled: true, 24 | groupHeaderTpl: '{name} ({children.length} issues)' 25 | }], 26 | 27 | viewConfig: { 28 | getRowClass: function(record) { 29 | if (record.data.acknowledged === true) { 30 | return 'acknowledged-row'; 31 | } 32 | 33 | switch (record.data.priority) { 34 | case "0": return 'not-classified-row'; 35 | case "1": return 'info-row'; 36 | case "2": return 'warning-row'; 37 | case "3": return 'average-row'; 38 | case "4": return 'high-row'; 39 | case "5": return 'disaster-row'; 40 | default : return 'not-classified-row'; 41 | } 42 | } 43 | }, 44 | 45 | columns: { 46 | defaults: { 47 | sortable: false 48 | }, 49 | items: [{ 50 | text: 'LAST CHANGE', 51 | dataIndex: 'lastchange', 52 | align: 'center', 53 | width: 145, 54 | xtype: 'datecolumn', 55 | // format: 'd/m/y H:i:s', 56 | filter: true, 57 | renderer: function(value, metaData) { 58 | metaData.style = 'cursor: pointer;'; 59 | metaData.tdAttr = 'title="Click for issue details"'; 60 | 61 | return Ext.Date.format(value, "d/m/y H:i:s"); 62 | } 63 | },{ 64 | text: 'AGE', 65 | dataIndex: 'age', 66 | width: 100 67 | },{ 68 | text: 'ZABBIX SERVER', 69 | dataIndex: 'server', 70 | align: 'center', 71 | flex: 1, 72 | filter: 'string', 73 | renderer: function(value, metaData) { 74 | metaData.style = 'cursor: pointer;'; 75 | metaData.tdAttr = 'title="Click for Zabbix server overview"'; 76 | 77 | return value; 78 | } 79 | },{ 80 | text: 'HOSTNAME', 81 | dataIndex: 'hostname', 82 | flex: 2, 83 | filter: 'string', 84 | renderer: function(value, metaData) { 85 | metaData.style = 'cursor: pointer;'; 86 | metaData.tdAttr = 'title="Click for latest host data"'; 87 | 88 | return value; 89 | } 90 | },{ 91 | text: 'TRIGGER', 92 | dataIndex: 'description', 93 | flex: 3, 94 | renderer: function(value, metaData) { 95 | metaData.style = 'cursor: pointer;'; 96 | metaData.tdAttr = 'title="Click for issue details"'; 97 | 98 | return value; 99 | } 100 | },{ 101 | text: 'ACK', 102 | dataIndex: 'acknowledged', 103 | align: 'center', 104 | width: 65, 105 | xtype: 'booleancolumn', 106 | renderer: function(value, metaData) { 107 | return (value) ? 'Yes' : 'No'; 108 | } 109 | },{ 110 | text: 'SEVERITY', 111 | dataIndex: 'priority', 112 | align: 'center', 113 | width: 100, 114 | renderer: function(value, metaData, record) { 115 | switch (value) { 116 | case "0": 117 | metaData.tdCls = 'not-classified-cell'; return "Not classified"; 118 | case "1": 119 | metaData.tdCls = 'info-cell'; return "Info"; 120 | case "2": 121 | metaData.tdCls = 'warning-cell'; return "Warning"; 122 | case "3": 123 | metaData.tdCls = 'average-cell'; return "Average"; 124 | case "4": 125 | metaData.tdCls = 'high-cell'; return "High"; 126 | case "5": 127 | metaData.tdCls = 'disaster-cell'; return "Disaster"; 128 | default: 129 | return value; 130 | } 131 | }, 132 | filter: { 133 | type: 'list', 134 | options: [ 135 | [ '0', "Not classified" ], 136 | [ '1', "Info" ], 137 | [ '2', "Warning" ], 138 | [ '3', "Average" ], 139 | [ '4', "High" ], 140 | [ '5', "Disaster" ] 141 | ] 142 | } 143 | }] 144 | } 145 | }); -------------------------------------------------------------------------------- /.sencha/app/resolve-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 60 | 61 | 62 | 70 | 71 | 72 | 73 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /app/view/main/MainController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the main view for the application. It is specified in app.js as the 3 | * "autoCreateViewport" property. That setting automatically applies the "viewport" 4 | * plugin to promote that instance of this class to the body element. 5 | * 6 | * TODO - Replace this content of this view to suite the needs of your application. 7 | */ 8 | Ext.define('OctoZab.view.main.MainController', { 9 | extend: 'Ext.app.ViewController', 10 | 11 | alias: 'controller.main', 12 | 13 | control: { 14 | '#': { 15 | afterrender: 'onAfterRender' 16 | }, 17 | "#logo-text": { 18 | move: 'onLogoMove' 19 | }, 20 | "#panel-issues-map": { 21 | boxready: 'onIssuesMapRender', 22 | resize: 'onIssuesMapResize' 23 | } 24 | }, 25 | 26 | onAfterRender: function(view) { 27 | setTimeout(function() { 28 | if (Socket.getSocket() === null) { 29 | view.mask('UNREACHABLE BACKEND'); 30 | } 31 | }, 1000); 32 | }, 33 | 34 | onLogoMove: function(view) { 35 | view.alignTo(this.getView().getEl(), 'bl', [19,-155]); 36 | }, 37 | 38 | onIssuesMapRender: function(panel) { 39 | Cache.setIssuesMap(new Highcharts.Chart({ 40 | chart: { 41 | renderTo: panel.body.dom, backgroundColor: '#505359', spacing: [18, 60, 18, 60] 42 | }, 43 | title: { text: null }, 44 | credits: { enabled: false }, 45 | noData: { 46 | style: { fontSize: '12px', color: '#FFF' } 47 | }, 48 | // navigation: { buttonOptions: { symbolStroke: '#F3F3F3', // symbolY: 45, theme: { fill: "#7E878C", states: { hover: { stroke: '#40484C', fill: '#40484C' }, select: { stroke: '#40484C', fill: '#40484C' } } } } }, 49 | legend: { 50 | layout: 'vertical', align: 'right', verticalAlign: 'bottom', 51 | itemMarginTop: 1, itemMarginBottom: 1, itemHoverStyle: null, // itemHiddenStyle: null, 52 | itemStyle: { color: "#FFF", fontWeight: 'bold', fontSize: '11px' } 53 | }, 54 | colorAxis: { 55 | dataClasses: [ 56 | { color: '#DDD', name: 'NOT CLASSIFIED' , from: 0, to: 0}, 57 | { color: '#BFF', name: 'INFORMATION' , from: 1, to: 1}, 58 | { color: '#FF8', name: 'WARNING' , from: 2, to: 2}, 59 | { color: '#FA6', name: 'AVERAGE' , from: 3, to: 3}, 60 | { color: '#F77', name: 'HIGH' , from: 4, to: 4}, 61 | { color: '#F11', name: 'DISASTER' , from: 5, to: 5} 62 | ] 63 | }, 64 | xAxis: { 65 | events: { 66 | afterSetExtremes: function(e) { 67 | if ((e.min === 0) && (e.max === 100)) { 68 | var mainPanel = Ext.ComponentQuery.query('app-main')[0]; 69 | 70 | Cache.getIssuesServerFilter().setValue(""); 71 | Cache.getIssuesPriorityFilter().setValue(['0','1','2','3','4','5']); 72 | 73 | mainPanel.getViewModel().set('issuesMapView', 'ALL'); 74 | mainPanel.down('grid-issues').filters.clearFilters(); 75 | 76 | Ext.StoreMgr.lookup('Issues').reload(); 77 | } 78 | } 79 | } 80 | }, 81 | tooltip: { 82 | pointFormat: "{point.node.val} issues", 83 | backgroundColor: "#505459", 84 | borderColor: "#505459", 85 | borderRadius: 0, 86 | style: {color: "#FFF" } 87 | }, 88 | series: [{ 89 | id: 'issues', type: "treemap", layoutAlgorithm: 'sliceAndDice', 90 | allowDrillToNode: true, levelIsConstant: false, 91 | borderColor: '#505459', cursor: 'pointer', 92 | dataLabels: { enabled: false }, 93 | drillUpButton: { 94 | position: { align: 'right', x: 65, y: 5 }, 95 | theme: { 96 | fill: "#E6E6E6", stroke: "#E6E6E6", r: 1, 97 | states: { 98 | hover: { fill: "#CCC", stroke: "#CCC" } 99 | } 100 | } 101 | }, 102 | levels: [{ 103 | level: 1, borderWidth: 10, 104 | dataLabels: { enabled: true, style: { fontSize: '16px' } } 105 | },{ 106 | level: 2, borderColor: 'transparent' 107 | }], 108 | events: { 109 | click: function(e) { 110 | var mainPanel = Ext.ComponentQuery.query('app-main')[0]; 111 | 112 | if (!this.rootNode) { 113 | Cache.getIssuesServerFilter().setValue(e.point.id); 114 | Cache.getIssuesPriorityFilter().setValue(['0','1','2','3','4','5']); 115 | 116 | mainPanel.getViewModel().set('issuesMapView', e.point.name); 117 | mainPanel.down('grid-issues').filters.clearFilters(); 118 | 119 | Ext.StoreMgr.lookup('Issues').reload(); 120 | } else { 121 | Cache.getIssuesPriorityFilter().setValue([e.point.colorValue.toString()]); 122 | 123 | mainPanel.getViewModel().set('issuesMapView', e.point.parent.toUpperCase() + ' / severity: ' + e.point.name.toUpperCase()); 124 | mainPanel.down('grid-issues').filters.clearFilters(); 125 | 126 | Ext.StoreMgr.lookup('Issues').reload(); 127 | } 128 | } 129 | } 130 | }] 131 | })); 132 | }, 133 | 134 | onIssuesMapResize: function(panel, width, height) { 135 | Cache.getIssuesMap().setSize(width, height-36); 136 | } 137 | }); 138 | -------------------------------------------------------------------------------- /.sencha/app/packager-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Running mobile packager action @{action} on file @{configFile} 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 59 | 61 | 63 | 65 | 66 | 67 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | Running default mobile packager config. 107 | 109 | 110 | 111 | Building all specified mobile packager configs. 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # OctoZab 2 | 3 | ![Alt text](resources/images/demo.png "Demo Example") 4 | 5 | This application is a backend/frontend system to remotely control multiple Zabbix servers (http://www.zabbix.com/). 6 | 7 | Demo: [here](http://demo.quadrata.it/octozab/) 8 | 9 | 10 | ## Features 11 | 12 | - Issues list sorted by Severity -> Last Change 13 | - Issues treemap overview, with drilldown per server 14 | - Dynamic filtering on issues list through interaction with treemap objects 15 | - Redirect to issue details from issues list 16 | - NEW! Zabbix servers configuration management, ability to add, remove and choose credentials! 17 | - NEW! Notification to all connected users on configuration changes (like the servers one) 18 | 19 | 20 | ## Dependencies 21 | 22 | - [NodeJS](http://nodejs.org/) tested >= 0.12.6 23 | - [Redis](http://redis.io/) tested >= 3.0.2 24 | 25 | 26 | ## How does it work 27 | 28 | Backend is made by a NodeJS server, that operates in order to authenticate Zabbix servers and communicate with APIs they expose. Collected data is then stored on Redis DB, that caches it. Redis keys are monitored by the server in order to send changes back to client frontends whenever they occur. Clients are connected to server through WebSockets. 29 | 30 | ## Docker Container 31 | 32 | An easy and fast way to test the application is using our Docker container. Install [docker](https://docs.docker.com/) on your machine, pull the image and run it: 33 | 34 | ```shell 35 | docker pull quadrata/octozab 36 | docker run -it -d -p $frontendHostPort:80 -p $backendHostPort:8080 --name="octoZab" quadrata/octozab 37 | ``` 38 | 39 | where $frontendHostPort and $backendHostPort are ports on your host where you desire that frontend and backend respectively should run. Now you have to configure the container. Attach to it and start necessary services: 40 | 41 | ```shell 42 | docker attach octoZab 43 | ``` 44 | 45 | ```shell 46 | service httpd start 47 | service octozab-redis start 48 | service octozab-node start 49 | ``` 50 | 51 | Modify frontend `"/var/www/html/octozab/config.js"` with your host url and host backend port you defined above at container run. 52 | 53 | IMPORTANT! When we speak about host, it is meant host, not the container. So above with $frontendHostPort and $backendHostPort we were referring to host ports on which are mapped container ports. And with host url in "config.js" we mean the url on which you access your host, not the container. 54 | 55 | Then detach from container with Ctrl+P-Ctrl+Q. 56 | 57 | 58 | That's it! Now you should be able to connect to application browsing at `http://yourHostUrl:yourHostFrontendPort/octozab` (if you mapped container port 80, webserver one, with port 80 on your host, you can omit the `:yourHostFrontendPort` url part). 59 | 60 | 61 | ## Installation 62 | 63 | In order to test the application, first step is to install dependencies listed above. Once `node`, `npm`, `redis-server` and `redis-cli` executables are installed on server, you can start deploying both backend and frontend by cloning repository on webserver root. 64 | 65 | If you prefer to deploy backend and frontend on different machines, copy only `"backend"` folder on backend machine, and the rest of the repository on frontend machine. Then modify in the file `"config.js"` the `backendUrl` parameter with backend domain name. Be sure port `8080` is exposed to frontend from backend. 66 | 67 | Install NodeJS module dependencies. Move from command-line to backend folder and run following command: 68 | 69 | ```shell 70 | npm install 71 | ``` 72 | 73 | Frontend is based on ExtJS framework. In order to make the source code work, you have first to install SenchaCmd tool version [5.0.0.160](http://cdn.sencha.com/cmd/5.0.0.160/SenchaCmd-5.0.0.160-linux-x64.run.zip) (link is for Linux 64bit version). Check SenchaCmd dependencies [here](http://docs.sencha.com/cmd/5.x/intro_to_cmd.html#System_Setup) (Java >=1.7, Ruby >=2.0.0, Compass). 74 | Then from command-line move to frontend folder, and give the following commands: 75 | 76 | ```shell 77 | sencha app upgrade 78 | sencha app build 79 | ``` 80 | 81 | Now run backend services. The simplest way is to open separate shells for Redis DB: 82 | 83 | ```shell 84 | redis-server $backend/redis.conf 85 | ``` 86 | 87 | and NodeJS server: 88 | 89 | ```shell 90 | node $backend/server.js 91 | ``` 92 | 93 | where `$backend` is the path to `"backend"` folder deployed before. 94 | 95 | 96 | ## Usage 97 | 98 | To see OctoZab in action, it's enough to connect to frontend, based on where you deployed frontend source code. To edit a Zabbix server details (url, user, psw, ...) from related settings, __double click on corresponding row__ and you'll have a row editor right on the line. 99 | 100 | If you're not using Docker container and you want to have a production release of it, use the SenchaCmd tool. Move from command-line to frontend folder, and run: 101 | 102 | ```shell 103 | sencha app build 104 | ``` 105 | 106 | You will find the production release under `"$frontend/build/production/OctoZab"` (`$frontend` is the path where frontend is deployed). 107 | 108 | 109 | ## Roadmap 110 | 111 | - ~~Allow to specify credentials for each Zabbix server~~ 112 | - ~~Add, remove, modify Zabbix servers configuration from frontend~~ 113 | - Replace treemap overview with some other way/chart that gives a better understanding of each Zabbix server healthy (give us your feedback on this) 114 | - Many more Zabbix features based on audience feedback 115 | 116 | 117 | ## License 118 | 119 | GPL v2 -------------------------------------------------------------------------------- /.sencha/app/microloader/development.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sencha Blink - Development 3 | * @author Jacky Nguyen 4 | */ 5 | (function() { 6 | var head = document.head || document.getElementsByTagName('head')[0]; 7 | 8 | function write(content) { 9 | document.write(content); 10 | } 11 | 12 | function addMeta(name, content) { 13 | var meta = document.createElement('meta'); 14 | 15 | meta.setAttribute('name', name); 16 | meta.setAttribute('content', content); 17 | head.appendChild(meta); 18 | } 19 | 20 | var xhr = new XMLHttpRequest(); 21 | xhr.open('GET', 'bootstrap.json', false); 22 | xhr.send(null); 23 | 24 | var options = eval("(" + xhr.responseText + ")"), 25 | scripts = options.js || [], 26 | styleSheets = options.css || [], 27 | i, ln, path, platform, theme, exclude; 28 | 29 | if(options.platform && options.platforms && options.platforms[options.platform] && options.platforms[options.platform].js) { 30 | scripts = options.platforms[options.platform].js.concat(scripts); 31 | } 32 | 33 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 34 | var msViewportStyle = document.createElement("style"); 35 | msViewportStyle.appendChild( 36 | document.createTextNode( 37 | "@media screen and (orientation: portrait) {" + 38 | "@-ms-viewport {width: 320px !important;}" + 39 | "}" + 40 | "@media screen and (orientation: landscape) {" + 41 | "@-ms-viewport {width: 560px !important;}" + 42 | "}" 43 | ) 44 | ); 45 | document.getElementsByTagName("head")[0].appendChild(msViewportStyle); 46 | } 47 | 48 | addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no'); 49 | addMeta('apple-mobile-web-app-capable', 'yes'); 50 | addMeta('apple-touch-fullscreen', 'yes'); 51 | 52 | if (!window.Ext) { 53 | window.Ext = {}; 54 | } 55 | Ext.microloaded = true; 56 | 57 | var filterPlatform = window.Ext.filterPlatform = function(platform) { 58 | var profileMatch = false, 59 | ua = navigator.userAgent, 60 | j, jln; 61 | 62 | platform = [].concat(platform); 63 | 64 | function isPhone(ua) { 65 | var isMobile = /Mobile(\/|\s)/.test(ua); 66 | 67 | // Either: 68 | // - iOS but not iPad 69 | // - Android 2 70 | // - Android with "Mobile" in the UA 71 | 72 | return /(iPhone|iPod)/.test(ua) || 73 | (!/(Silk)/.test(ua) && (/(Android)/.test(ua) && (/(Android 2)/.test(ua) || isMobile))) || 74 | (/(BlackBerry|BB)/.test(ua) && isMobile) || 75 | /(Windows Phone)/.test(ua); 76 | } 77 | 78 | function isTablet(ua) { 79 | return !isPhone(ua) && (/iPad/.test(ua) || /Android|Silk/.test(ua) || /(RIM Tablet OS)/.test(ua) || 80 | (/MSIE 10/.test(ua) && /; Touch/.test(ua))); 81 | } 82 | 83 | // Check if the ?platform parameter is set in the URL 84 | var paramsString = window.location.search.substr(1), 85 | paramsArray = paramsString.split("&"), 86 | params = {}, 87 | testPlatform, i; 88 | 89 | for (i = 0; i < paramsArray.length; i++) { 90 | var tmpArray = paramsArray[i].split("="); 91 | params[tmpArray[0]] = tmpArray[1]; 92 | } 93 | 94 | testPlatform = params.platform; 95 | if (testPlatform) { 96 | return platform.indexOf(testPlatform) != -1; 97 | } 98 | 99 | for (j = 0, jln = platform.length; j < jln; j++) { 100 | switch (platform[j]) { 101 | case 'phone': 102 | profileMatch = isPhone(ua); 103 | break; 104 | case 'tablet': 105 | profileMatch = isTablet(ua); 106 | break; 107 | case 'desktop': 108 | profileMatch = !isPhone(ua) && !isTablet(ua); 109 | break; 110 | case 'ios': 111 | profileMatch = /(iPad|iPhone|iPod)/.test(ua); 112 | break; 113 | case 'android': 114 | profileMatch = /(Android|Silk)/.test(ua); 115 | break; 116 | case 'blackberry': 117 | profileMatch = /(BlackBerry|BB)/.test(ua); 118 | break; 119 | case 'safari': 120 | profileMatch = /Safari/.test(ua) && !(/(BlackBerry|BB)/.test(ua)); 121 | break; 122 | case 'chrome': 123 | profileMatch = /Chrome/.test(ua); 124 | break; 125 | case 'ie10': 126 | profileMatch = /MSIE 10/.test(ua); 127 | break; 128 | case 'windows': 129 | profileMatch = /MSIE 10/.test(ua) || /Trident/.test(ua); 130 | break; 131 | case 'tizen': 132 | profileMatch = /Tizen/.test(ua); 133 | break; 134 | case 'firefox': 135 | profileMatch = /Firefox/.test(ua); 136 | } 137 | if (profileMatch) { 138 | return true; 139 | } 140 | } 141 | return false; 142 | }; 143 | 144 | 145 | for (i = 0,ln = styleSheets.length; i < ln; i++) { 146 | path = styleSheets[i]; 147 | 148 | if (typeof path != 'string') { 149 | platform = path.platform; 150 | exclude = path.exclude; 151 | theme = path.theme; 152 | path = path.path; 153 | } 154 | 155 | if (platform) { 156 | if (!filterPlatform(platform) || filterPlatform(exclude)) { 157 | continue; 158 | } 159 | 160 | if(!Ext.theme) { 161 | Ext.theme = {}; 162 | } 163 | if(!Ext.theme.name) { 164 | Ext.theme.name = theme || 'Default'; 165 | } 166 | } 167 | 168 | write(''); 169 | } 170 | 171 | for (i = 0,ln = scripts.length; i < ln; i++) { 172 | path = scripts[i]; 173 | 174 | if (typeof path != 'string') { 175 | platform = path.platform; 176 | exclude = path.exclude; 177 | path = path.path; 178 | } 179 | 180 | if (platform) { 181 | if (!filterPlatform(platform) || filterPlatform(exclude)) { 182 | continue; 183 | } 184 | } 185 | 186 | write(' 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 101 | 102 | 103 | 110 | 111 | 112 | 116 | 119 | 120 | 121 | 122 | 123 | 124 | 134 | 135 | 136 | 150 | 151 | 152 | 161 | 162 | 163 | 164 | 165 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 208 | 236 | 237 | 238 | 239 | 240 | 241 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /.sencha/app/sass-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | include 22 | -all 23 | 24 | 25 | 26 | 27 | restore 28 | page 29 | 30 | 31 | 32 | 33 | 34 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 86 | 87 | 88 | 89 | 97 | 98 | 99 | 100 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 127 | 128 | 129 | 130 | Preprocessing @{cssfile} to ${css.output.name} 131 | 135 | 136 | 137 | 138 | 139 | 140 | Compressing @{cssfile} to ${css.output.name} 141 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 169 | 170 | 174 | 175 | 176 | 177 | 178 | 179 | 184 | 185 | 186 | 187 | 188 | 189 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 202 | 203 | 204 | 205 | 206 | Compiling sass directory : @{theme}/sass 207 | 213 | 214 | 216 | 217 | 218 | 219 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 240 | 241 | 242 | 248 | 249 | 250 | 251 | 255 | 259 | 260 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /.sencha/app/page-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 65 | 66 | 67 | 68 | 69 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 120 | 121 | 122 | 123 | 124 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 176 | 177 | 178 | 179 | 180 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 242 | 243 | 246 | 247 | 248 | 249 | 250 | 251 | 256 | 257 | -------------------------------------------------------------------------------- /resources/js/highcharts-4.1.7-treemap.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highcharts JS v4.1.7 (2015-06-26) 3 | 4 | (c) 2014 Highsoft AS 5 | Authors: Jon Arild Nygard / Oystein Moseng 6 | 7 | License: www.highcharts.com/license 8 | */ 9 | (function(g){var i=g.seriesTypes,m=g.merge,s=g.extend,t=g.extendClass,u=g.getOptions().plotOptions,v=function(){},j=g.each,x=HighchartsAdapter.grep,n=g.pick,q=g.Series,r=g.Color;u.treemap=m(u.scatter,{showInLegend:!1,marker:!1,borderColor:"#E0E0E0",borderWidth:1,dataLabels:{enabled:!0,defer:!1,verticalAlign:"middle",formatter:function(){return this.point.name||this.point.id},inside:!0},tooltip:{headerFormat:"",pointFormat:"{point.name}: {point.node.val}
"},layoutAlgorithm:"sliceAndDice", 10 | layoutStartingDirection:"vertical",alternateStartingDirection:!1,levelIsConstant:!0,states:{hover:{borderColor:"#A0A0A0",brightness:i.heatmap?0:0.1,shadow:!1}},drillUpButton:{position:{align:"left",x:10,y:-50}}});i.treemap=t(i.scatter,m({pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color",dashstyle:"borderDashStyle"},pointArrayMap:["value"],axisTypes:i.heatmap?["xAxis","yAxis","colorAxis"]:["xAxis","yAxis"],optionalAxis:"colorAxis",getSymbol:v,parallelArrays:["x","y", 11 | "value","colorValue"],colorKey:"colorValue",translateColors:i.heatmap&&i.heatmap.prototype.translateColors},{type:"treemap",trackerGroups:["group","dataLabelsGroup"],pointClass:t(g.Point,{setState:function(a,b){g.Point.prototype.setState.call(this,a,b);a==="hover"?this.dataLabel&&this.dataLabel.attr({zIndex:1002}):this.dataLabel&&this.dataLabel.attr({zIndex:this.pointAttr[""].zIndex+1})},setVisible:i.pie.prototype.pointClass.prototype.setVisible}),handleLayout:function(){var a=this.tree,b;if(this.points.length)this.rootNode= 12 | n(this.rootNode,""),a=this.tree=this.getTree(),this.levelMap=this.getLevels(),b=this.getSeriesArea(a.val),this.calculateChildrenAreas(a,b),this.setPointValues()},getTree:function(){var a,b=[],c=[],d=function(a){j(b[a],function(a){b[""].push(a)})};this.nodeMap=[];j(this.points,function(a,d){var h="";c.push(a.id);if(a.parent!==void 0)h=a.parent;b[h]===void 0&&(b[h]=[]);b[h].push(d)});for(a in b)b.hasOwnProperty(a)&&a!==""&&HighchartsAdapter.inArray(a,c)===-1&&(d(a),delete b[a]);a=this.buildNode("", 13 | -1,0,b,null);this.eachParents(this.nodeMap[this.rootNode],function(a){a.visible=!0});this.eachChildren(this.nodeMap[this.rootNode],function(a){a.visible=!0});this.setTreeValues(a);return a},buildNode:function(a,b,c,d,e){var f=this,h=[],w=f.points[b],g;j(d[a]||[],function(b){g=f.buildNode(f.points[b].id,b,c+1,d,a);h.push(g)});b={id:a,i:b,children:h,level:c,parent:e,visible:!1};f.nodeMap[b.id]=b;if(w)w.node=b;return b},setTreeValues:function(a){var b=this,c=0,d=[],e,f=b.points[a.i];j(a.children,function(a){a= 14 | b.setTreeValues(a);b.insertElementSorted(d,a,function(a,b){return a.val>b.val});a.ignore?b.eachChildren(a,function(a){s(a,{ignore:!0,isLeaf:!1,visible:!1})}):c+=a.val});e=n(f&&f.value,c);s(a,{children:d,childrenTotal:c,ignore:!(n(f&&f.visible,!0)&&e>0),isLeaf:a.visible&&!c,name:n(f&&f.name,""),val:e});return a},eachChildren:function(a,b){var c=this,d=a.children;b(a);d.length&&j(d,function(a){c.eachChildren(a,b)})},eachParents:function(a,b){var c=this.nodeMap[a.parent];b(a);c&&this.eachParents(c,b)}, 15 | calculateChildrenAreas:function(a,b){var c=this,d=c.options,e=d.levelIsConstant?a.level:a.level-this.nodeMap[this.rootNode].level,f=this.levelMap[e+1],h=n(c[f&&f.layoutAlgorithm]&&f.layoutAlgorithm,d.layoutAlgorithm),g=d.alternateStartingDirection,p=[],k,d=x(a.children,function(a){return!a.ignore});if(f&&f.layoutStartingDirection)b.direction=f.layoutStartingDirection==="vertical"?0:1;p=c[h](b,d);j(d,function(a,d){k=c.points[a.i];k.level=e+1;a.values=m(p[d],{val:a.childrenTotal,direction:g?1-b.direction: 16 | b.direction});a.children.length&&c.calculateChildrenAreas(a,a.values)})},setPointValues:function(){var a=this,b=a.xAxis,c=a.yAxis;a.nodeMap[""].values={x:0,y:0,width:100,height:100};j(a.points,function(d){var e=d.node.values,f,h,g;e?(e.x/=a.axisRatio,e.width/=a.axisRatio,f=Math.round(b.translate(e.x,0,0,0,1)),h=Math.round(b.translate(e.x+e.width,0,0,0,1)),g=Math.round(c.translate(e.y,0,0,0,1)),e=Math.round(c.translate(e.y+e.height,0,0,0,1)),d.shapeType="rect",d.shapeArgs={x:Math.min(f,h),y:Math.min(g, 17 | e),width:Math.abs(h-f),height:Math.abs(e-g)},d.plotX=d.shapeArgs.x+d.shapeArgs.width/2,d.plotY=d.shapeArgs.y+d.shapeArgs.height/2):(delete d.plotX,delete d.plotY)})},getSeriesArea:function(a){var b=this.options.layoutStartingDirection==="vertical"?0:1,a={x:0,y:0,width:100*(this.axisRatio=this.xAxis.len/this.yAxis.len),height:100,direction:b,val:a};return this.nodeMap[""].values=a},getLevels:function(){var a=[],b=this.options.levels;b&&j(b,function(b){b.level!==void 0&&(a[b.level]=b)});return a},setColorRecursive:function(a, 18 | b){var c=this,d,e;if(a){d=c.points[a.i];e=c.levelMap[a.level];b=n(d&&d.options.color,e&&e.color,b);if(d)d.color=b;a.children.length&&j(a.children,function(a){c.setColorRecursive(a,b)})}},alg_func_group:function(a,b,c,d){this.height=a;this.width=b;this.plot=d;this.startDirection=this.direction=c;this.lH=this.nH=this.lW=this.nW=this.total=0;this.elArr=[];this.lP={total:0,lH:0,nH:0,lW:0,nW:0,nR:0,lR:0,aspectRatio:function(a,b){return Math.max(a/b,b/a)}};this.addElement=function(a){this.lP.total=this.elArr[this.elArr.length- 19 | 1];this.total+=a;this.direction===0?(this.lW=this.nW,this.lP.lH=this.lP.total/this.lW,this.lP.lR=this.lP.aspectRatio(this.lW,this.lP.lH),this.nW=this.total/this.height,this.lP.nH=this.lP.total/this.nW,this.lP.nR=this.lP.aspectRatio(this.nW,this.lP.nH)):(this.lH=this.nH,this.lP.lW=this.lP.total/this.lH,this.lP.lR=this.lP.aspectRatio(this.lP.lW,this.lH),this.nH=this.total/this.width,this.lP.nW=this.lP.total/this.nH,this.lP.nR=this.lP.aspectRatio(this.lP.nW,this.nH));this.elArr.push(a)};this.reset=function(){this.lW= 20 | this.nW=0;this.elArr=[];this.total=0}},alg_func_calcPoints:function(a,b,c,d){var e,f,h,g,p=c.lW,k=c.lH,l=c.plot,i,o=0,n=c.elArr.length-1;b?(p=c.nW,k=c.nH):i=c.elArr[c.elArr.length-1];j(c.elArr,function(a){if(b||ok.lP.lR&&e.alg_func_calcPoints(a,!1,k,d,h);g===i&&e.alg_func_calcPoints(a,!0,k,d,h);g+=1});return d},alg_func_fill:function(a,b,c){var d=[],e,f=b.direction,h=b.x,g=b.y,i=b.width,k=b.height,l,n,o,m;j(c,function(c){e=b.width*b.height*(c.val/ 22 | b.val);l=h;n=g;f===0?(m=k,o=e/m,i-=o,h+=o):(o=i,m=e/o,k-=m,g+=m);d.push({x:l,y:n,width:o,height:m});a&&(f=1-f)});return d},strip:function(a,b){return this.alg_func_lowAspectRatio(!1,a,b)},squarified:function(a,b){return this.alg_func_lowAspectRatio(!0,a,b)},sliceAndDice:function(a,b){return this.alg_func_fill(!0,a,b)},stripes:function(a,b){return this.alg_func_fill(!1,a,b)},translate:function(){q.prototype.translate.call(this);this.handleLayout();this.colorAxis?this.translateColors():this.options.colorByPoint|| 23 | this.setColorRecursive(this.tree,void 0)},drawDataLabels:function(){var a=this,b=a.dataLabelsGroup,c,d;j(a.points,function(b){d=a.levelMap[b.level];c={style:{}};if(!b.node.isLeaf)c.enabled=!1;if(d&&d.dataLabels)c=m(c,d.dataLabels),a._hasPointLabels=!0;if(b.shapeArgs)c.style.width=b.shapeArgs.width;b.dlOptions=m(c,b.options.dataLabels)});this.dataLabelsGroup=this.group;q.prototype.drawDataLabels.call(this);this.dataLabelsGroup=b},alignDataLabel:i.column.prototype.alignDataLabel,drawPoints:function(){var a= 24 | this,b=a.points,c=a.options,d,e,f;j(b,function(b){f=a.levelMap[b.level];d={stroke:c.borderColor,"stroke-width":c.borderWidth,dashstyle:c.borderDashStyle,r:0,fill:n(b.color,a.color)};if(f)d.stroke=f.borderColor||d.stroke,d["stroke-width"]=f.borderWidth||d["stroke-width"],d.dashstyle=f.borderDashStyle||d.dashstyle;d.stroke=b.borderColor||d.stroke;d["stroke-width"]=b.borderWidth||d["stroke-width"];d.dashstyle=b.borderDashStyle||d.dashstyle;d.zIndex=1E3-b.level*2;b.pointAttr=m(b.pointAttr);e=b.pointAttr.hover; 25 | e.zIndex=1001;e.fill=r(d.fill).brighten(c.states.hover.brightness).get();if(!b.node.isLeaf)n(c.interactByLeaf,!c.allowDrillToNode)?(d.fill="none",delete e.fill):(d.fill=r(d.fill).setOpacity(0.15).get(),e.fill=r(e.fill).setOpacity(0.75).get());if(b.node.level<=a.nodeMap[a.rootNode].level)d.fill="none",d.zIndex=0,delete e.fill;b.pointAttr[""]=g.extend(b.pointAttr[""],d);b.dataLabel&&b.dataLabel.attr({zIndex:b.pointAttr[""].zIndex+1})});i.column.prototype.drawPoints.call(this);j(b,function(a){a.graphic&& 26 | a.graphic.attr(a.pointAttr[""])});c.allowDrillToNode&&a.drillTo()},insertElementSorted:function(a,b,c){var d=0,e=!1;a.length!==0&&j(a,function(f){c(b,f)&&!e&&(a.splice(d,0,b),e=!0);d+=1});e||a.push(b)},drillTo:function(){var a=this;j(a.points,function(b){var c,d;g.removeEvent(b,"click.drillTo");b.graphic&&b.graphic.css({cursor:"default"});if(c=a.options.interactByLeaf?a.drillToByLeaf(b):a.drillToByGroup(b))d=a.nodeMap[a.rootNode].name||a.rootNode,b.graphic&&b.graphic.css({cursor:"pointer"}),g.addEvent(b, 27 | "click.drillTo",function(){b.setState("");a.drillToNode(c);a.showDrillUpButton(d)})})},drillToByGroup:function(a){var b=!1;if(a.node.level-this.nodeMap[this.rootNode].level===1&&!a.node.isLeaf)b=a.id;return b},drillToByLeaf:function(a){var b=!1;if(a.node.parent!==this.rootNode&&a.node.isLeaf)for(a=a.node;!b;)if(a=this.nodeMap[a.parent],a.parent===this.rootNode)b=a.id;return b},drillUp:function(){var a=null;this.rootNode&&(a=this.nodeMap[this.rootNode],a=a.parent!==null?this.nodeMap[a.parent]:this.nodeMap[""]); 28 | if(a!==null)this.drillToNode(a.id),a.id===""?this.drillUpButton=this.drillUpButton.destroy():(a=this.nodeMap[a.parent],this.showDrillUpButton(a.name||a.id))},drillToNode:function(a){var b=this.nodeMap[a].values;this.rootNode=a;this.xAxis.setExtremes(b.x,b.x+b.width,!1);this.yAxis.setExtremes(b.y,b.y+b.height,!1);this.isDirty=!0;this.chart.redraw()},showDrillUpButton:function(a){var b=this,a=a||"< Back",c=b.options.drillUpButton,d,e;if(c.text)a=c.text;this.drillUpButton?this.drillUpButton.attr({text:a}).align(): 29 | (e=(d=c.theme)&&d.states,this.drillUpButton=this.chart.renderer.button(a,null,null,function(){b.drillUp()},d,e&&e.hover,e&&e.select).attr({align:c.position.align,zIndex:9}).add().align(c.position,!1,c.relativeTo||"plotBox"))},buildKDTree:v,drawLegendSymbol:g.LegendSymbolMixin.drawRectangle,getExtremes:function(){q.prototype.getExtremes.call(this,this.colorValueData);this.valueMin=this.dataMin;this.valueMax=this.dataMax;q.prototype.getExtremes.call(this)},getExtremesFromAll:!0,bindAxes:function(){var a= 30 | {endOnTick:!1,gridLineWidth:0,lineWidth:0,min:0,dataMin:0,minPadding:0,max:100,dataMax:100,maxPadding:0,startOnTick:!1,title:null,tickPositions:[]};q.prototype.bindAxes.call(this);g.extend(this.yAxis.options,a);g.extend(this.xAxis.options,a)}}))})(Highcharts); 31 | -------------------------------------------------------------------------------- /.sencha/app/slice-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 64 | 65 | 66 | 67 | 70 | 71 | 79 | 80 | 81 | 85 | 86 | 87 | 88 | 89 | 92 | 93 | 100 | 108 | 109 | 110 | Capture theme image to ${build.capture.png} 111 | 112 | 119 | 120 | 121 | Slicing theme images to ${build.resources.dir} 122 | 123 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | Copying base framework images from ${framework.theme.dir} to ${tmp.img.dir} 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Building sass for theme ${theme.name} 171 | 172 | 173 | 174 | 175 | 176 | Slicing images for theme ${theme.name} to ${tmp.img.dir} 177 | 178 | 186 | 187 | 188 | 189 | 190 | 191 | Copying user defined images from @{theme}/images to ${tmp.img.dir} 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 205 | 206 | 207 | 208 | 210 | 211 | 212 | 215 | 216 | 217 | 219 | 220 | 221 | 222 | 223 | 227 | 228 | Processing theme directories from ${app.theme.dir} 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /.sencha/app/bootstrap-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | = 0) { 79 | project.setProperty('sencha.5.filters', 'true'); 80 | } else { 81 | project.setProperty('sencha.5.filters', 'false'); 82 | } 83 | } catch (err) { 84 | project.setProperty('sencha.5.filters', 'false'); 85 | } 86 | ]]> 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 100 | 101 | 102 | 103 | 104 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | include 117 | -namespace=Ext.ux 118 | and 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 261 | 262 | 267 | 268 | 272 | 273 | 277 | 278 | 282 | 283 | 286 | 287 | ${bootstrap.file.content} 288 | ${bootstrap.files.content} 289 | ${bootstrap.data.content} 290 | ${bootstrap.overrides.content} 291 | ${bootstrap.launch.content} 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | -------------------------------------------------------------------------------- /.sencha/app/Microloader.js: -------------------------------------------------------------------------------- 1 | // here, the extra check for window['Ext'] is needed for use with cmd-test 2 | // code injection. we need to make that this file will sync up with page global 3 | // scope to avoid duplicate Ext.Boot state. That check is after the initial Ext check 4 | // to allow the sandboxing template to inject an appropriate Ext var and prevent the 5 | // global detection. 6 | var Ext = Ext || window['Ext'] || {}; 7 | 8 | 9 | // 10 | /** 11 | * @Class Ext.Microloader 12 | * @singleton 13 | */ 14 | Ext.Microloader = Ext.Microloader || (function () { 15 | var apply = function (dest, src, defaults) { 16 | if (defaults) { 17 | apply(dest, defaults); 18 | } 19 | 20 | if (dest && src && typeof src == 'object') { 21 | for (var key in src) { 22 | dest[key] = src[key]; 23 | } 24 | } 25 | return dest; 26 | }, 27 | Boot = Ext.Boot, 28 | _listeners = [], 29 | _loaded = false, 30 | _tags = {}, 31 | Microloader = { 32 | 33 | /** 34 | * the global map of tags used 35 | */ 36 | platformTags: _tags, 37 | 38 | /** 39 | * The defult function that detects various platforms and sets tags 40 | * in the platform map accrodingly. Examples are iOS, android, tablet, etc. 41 | * @param tags the set of tags to populate 42 | */ 43 | detectPlatformTags: function () { 44 | var ua = navigator.userAgent, 45 | isMobile = _tags.isMobile = /Mobile(\/|\s)/.test(ua), 46 | isPhone, isDesktop, isTablet, touchSupported, isIE10, isBlackberry, 47 | element = document.createElement('div'), 48 | uaTagChecks = [ 49 | 'iPhone', 50 | 'iPod', 51 | 'Android', 52 | 'Silk', 53 | 'Android 2', 54 | 'BlackBerry', 55 | 'BB', 56 | 'iPad', 57 | 'RIM Tablet OS', 58 | 'MSIE 10', 59 | 'Trident', 60 | 'Chrome', 61 | 'Tizen', 62 | 'Firefox', 63 | 'Safari', 64 | 'Windows Phone' 65 | ], 66 | isEventSupported = function(name, tag) { 67 | if (tag === undefined) { 68 | tag = window; 69 | } 70 | 71 | var eventName = 'on' + name.toLowerCase(), 72 | isSupported = (eventName in element); 73 | 74 | if (!isSupported) { 75 | if (element.setAttribute && element.removeAttribute) { 76 | element.setAttribute(eventName, ''); 77 | isSupported = typeof element[eventName] === 'function'; 78 | 79 | if (typeof element[eventName] !== 'undefined') { 80 | element[eventName] = undefined; 81 | } 82 | 83 | element.removeAttribute(eventName); 84 | } 85 | } 86 | 87 | return isSupported; 88 | }, 89 | uaTags = {}, 90 | len = uaTagChecks.length, check, c; 91 | 92 | for (c = 0; c < len; c++) { 93 | check = uaTagChecks[c]; 94 | uaTags[check] = new RegExp(check).test(ua); 95 | } 96 | 97 | isPhone = 98 | (uaTags.iPhone || uaTags.iPod) || 99 | (!uaTags.Silk && (uaTags.Android && (uaTags['Android 2'] || isMobile))) || 100 | ((uaTags.BlackBerry || uaTags.BB) && uaTags.isMobile) || 101 | (uaTags['Windows Phone']); 102 | 103 | isTablet = 104 | (!_tags.isPhone) && ( 105 | uaTags.iPad || 106 | uaTags.Android || 107 | uaTags.Silk || 108 | uaTags['RIM Tablet OS'] || 109 | (uaTags['MSIE 10'] && /; Touch/.test(ua)) 110 | ); 111 | 112 | touchSupported = 113 | // if the browser has touch events we can be reasonably sure the device has 114 | // a touch screen 115 | isEventSupported('touchend') || 116 | // browsers that use pointer event have maxTouchPoints > 0 if the 117 | // device supports touch input 118 | // http://www.w3.org/TR/pointerevents/#widl-Navigator-maxTouchPoints 119 | navigator.maxTouchPoints || 120 | // IE10 uses a vendor-prefixed maxTouchPoints property 121 | navigator.msMaxTouchPoints; 122 | 123 | isDesktop = !isPhone && !isTablet; 124 | isIE10 = uaTags['MSIE 10']; 125 | isBlackberry = uaTags.Blackberry || uaTags.BB; 126 | 127 | apply(_tags, Microloader.loadPlatformsParam(), { 128 | phone: isPhone, 129 | tablet: isTablet, 130 | desktop: isDesktop, 131 | touch: touchSupported, 132 | ios: (uaTags.iPad || uaTags.iPhone || uaTags.iPod), 133 | android: uaTags.Android || uaTags.Silk, 134 | blackberry: isBlackberry, 135 | safari: uaTags.Safari && isBlackberry, 136 | chrome: uaTags.Chrome, 137 | ie10: isIE10, 138 | windows: isIE10 || uaTags.Trident, 139 | tizen: uaTags.Tizen, 140 | firefox: uaTags.Firefox 141 | }); 142 | 143 | if (Ext.beforeLoad) { 144 | Ext.beforeLoad(_tags); 145 | } 146 | }, 147 | 148 | /** 149 | * Extracts user supplied platform tags from the "platformTags" query parameter 150 | * of the form: 151 | * 152 | * ?platformTags=name:state,name:state,... 153 | * 154 | * (each tag defaults to true when state is unspecified) 155 | * 156 | * Example: 157 | * ?platformTags=isTablet,isPhone:false,isDesktop:0,iOS:1,Safari:true, ... 158 | * 159 | * @returns {Object} the platform tags supplied by the query string 160 | */ 161 | loadPlatformsParam: function () { 162 | // Check if the ?platform parameter is set in the URL 163 | var paramsString = window.location.search.substr(1), 164 | paramsArray = paramsString.split("&"), 165 | params = {}, i, 166 | platforms = {}, 167 | tmpArray, tmplen, platform, name, enabled; 168 | 169 | for (i = 0; i < paramsArray.length; i++) { 170 | tmpArray = paramsArray[i].split("="); 171 | params[tmpArray[0]] = tmpArray[1]; 172 | } 173 | 174 | if (params.platformTags) { 175 | tmpArray = params.platform.split(/\W/); 176 | for (tmplen = tmpArray.length, i = 0; i < tmplen; i++) { 177 | platform = tmpArray[i].split(":"); 178 | name = platform[0]; 179 | if (platform.length > 1) { 180 | enabled = platform[1]; 181 | if (enabled === 'false' || enabled === '0') { 182 | enabled = false; 183 | } else { 184 | enabled = true; 185 | } 186 | } 187 | platforms[name] = enabled; 188 | } 189 | } 190 | return platform; 191 | }, 192 | 193 | initPlatformTags: function () { 194 | Microloader.detectPlatformTags(); 195 | }, 196 | 197 | getPlatformTags: function () { 198 | return Microloader.platformTags; 199 | }, 200 | 201 | filterPlatform: function (platform) { 202 | platform = [].concat(platform); 203 | var tags = Microloader.getPlatformTags(), 204 | len, p, tag; 205 | 206 | for (len = platform.length, p = 0; p < len; p++) { 207 | tag = platform[p]; 208 | if (tags.hasOwnProperty(tag)) { 209 | return !!tags[tag]; 210 | } 211 | } 212 | return false; 213 | }, 214 | 215 | init: function () { 216 | Microloader.initPlatformTags(); 217 | Ext.filterPlatform = Microloader.filterPlatform; 218 | }, 219 | 220 | initManifest: function (manifest) { 221 | Microloader.init(); 222 | var tmpManifest = manifest || Ext.manifest; 223 | 224 | if (typeof tmpManifest === "string") { 225 | var url = Boot.baseUrl + tmpManifest + ".json", 226 | content = Boot.fetchSync(url); 227 | tmpManifest = JSON.parse(content.content); 228 | } 229 | 230 | Ext.manifest = tmpManifest; 231 | return tmpManifest; 232 | }, 233 | 234 | /** 235 | * 236 | * @param manifestDef 237 | */ 238 | load: function (manifestDef) { 239 | var manifest = Microloader.initManifest(manifestDef), 240 | loadOrder = manifest.loadOrder, 241 | loadOrderMap = (loadOrder) ? Boot.createLoadOrderMap(loadOrder) : null, 242 | urls = [], 243 | js = manifest.js || [], 244 | css = manifest.css || [], 245 | resources = js.concat(css), 246 | resource, i, len, include, 247 | loadedFn = function () { 248 | _loaded = true; 249 | Microloader.notify(); 250 | }; 251 | 252 | for (len = resources.length, i = 0; i < len; i++) { 253 | resource = resources[i]; 254 | include = true; 255 | if (resource.platform && !Microloader.filterPlatform(resource.platform)) { 256 | include = false; 257 | } 258 | if (include) { 259 | urls.push(resource.path); 260 | } 261 | } 262 | 263 | 264 | if (loadOrder) { 265 | manifest.loadOrderMap = loadOrderMap; 266 | } 267 | 268 | Boot.load({ 269 | url: urls, 270 | loadOrder: loadOrder, 271 | loadOrderMap: loadOrderMap, 272 | sequential: true, 273 | success: loadedFn, 274 | failure: loadedFn 275 | }); 276 | }, 277 | 278 | onMicroloaderReady: function (listener) { 279 | if (_loaded) { 280 | listener(); 281 | } else { 282 | _listeners.push(listener); 283 | } 284 | }, 285 | 286 | /** 287 | * @private 288 | */ 289 | notify: function () { 290 | var listener; 291 | while((listener = _listeners.shift())) { 292 | listener(); 293 | } 294 | } 295 | }; 296 | 297 | return Microloader; 298 | }()); 299 | 300 | // 301 | 302 | /** 303 | * the current application manifest 304 | * 305 | * 306 | * { 307 | * name: 'name', 308 | * version: , 309 | * debug: { 310 | * hooks: { 311 | * "*": true 312 | * } 313 | * }, 314 | * localStorage: false, 315 | * mode: production, 316 | * js: [ 317 | * ... 318 | * { 319 | * path: '../boo/baz.js', 320 | * version: , 321 | * update: full | delta | , 322 | * platform: ['phone', 'ios', 'android'] 323 | * }, 324 | * { 325 | * path: 'http://some.domain.com/api.js', 326 | * remote: true 327 | * }, 328 | * ... 329 | * ], 330 | * css: [ 331 | * ... 332 | * { 333 | * path: '../boo/baz.css', 334 | * version: , 335 | * update: full | delta | , 336 | * platform: ['phone', 'ios', 'android'] 337 | * }, 338 | * ... 339 | * ], 340 | * localStorage: false, 341 | * paths: {...}, 342 | * loadOrder: [ 343 | * ... 344 | * { 345 | * path: '../foo/bar.js", 346 | * idx: 158, 347 | * requires; [1,2,3,...,145,157], 348 | * uses: [182, 193] 349 | * }, 350 | * ... 351 | * ], 352 | * classes: { 353 | * ... 354 | * 'Ext.panel.Panel': { 355 | * requires: [...], 356 | * uses: [...], 357 | * aliases: [...], 358 | * alternates: [...], 359 | * mixins: [...] 360 | * }, 361 | * 'Ext.rtl.util.Renderable': { 362 | * requires: [...], 363 | * uses: [...], 364 | * aliases: [...], 365 | * alternates: [...], 366 | * mixins: [...] 367 | * override: 'Ext.util.Renderable' 368 | * }, 369 | * ... 370 | * }, 371 | * packages: { 372 | * ... 373 | * "sencha-core": { 374 | * version: '1.2.3.4', 375 | * requires: [] 376 | * }, 377 | * "ext": { 378 | * version: '5.0.0.0', 379 | * requires: ["sencha-core"] 380 | * }. 381 | * ... 382 | * } 383 | * } 384 | * 385 | * 386 | * @type {String/Object} 387 | */ 388 | Ext.manifest = Ext.manifest || "bootstrap"; 389 | 390 | Ext.Microloader.load(); -------------------------------------------------------------------------------- /resources/js/underscore-1.8.3.min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.8.3 2 | // http://underscorejs.org 3 | // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); 6 | //# sourceMappingURL=underscore-min.map --------------------------------------------------------------------------------