diff --git a/.editorconfig b/.editorconfig deleted file mode 100755 index 4a7ea303..00000000 --- a/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 59042574..eb79bfdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -vendor node_modules *.log +*/linuxDash.min.js.map +temp diff --git a/README.md b/README.md index 237e2f0a..df11e3ab 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,110 @@ -

- linux-dash + + +

- A simple, low-overhead web dashboard for Linux + A simple & low-overhead web dashboard for linux systems

- - Demo  |  - Features  |  - - Installation  |  - Support - + + Demo  |  + + Docs + +

-

- - Linux Dash Gitter chat - -

-

- - Linux Dash screenshot + + linux-dash Gitter chat


## Features -* A beautiful, simple web-based dashboard for monitoring a linux server -* Only ~1MB on disk! *(.git removed)* -* Live graphs, refresh-able widgets, and a growing # of supported modules -* Drop-in installation for PHP, Node.js, Python, and Go +* **Small** ----- Under 400KB on disk _(with .git removed)!_ +* **Simple** ---- A minimalist, beautiful dashboard +* **Easy** ------ Drop-in installation +* **Versatile** -- Choose your stack from Node.js, Go, Python, PHP ## Installation -#### Step 1: Download Linux Dash - -Clone the git repo +### Step 1 ```sh -git clone https://github.com/afaqurk/linux-dash.git +## 1. clone the repo +git clone --depth 1 https://github.com/afaqurk/linux-dash.git + +## 2. go to the cloned directory +cd linux-dash/app/server + ``` +OR, if you prefer to download manually: -Or download it **[here](https://github.com/afaqurk/linux-dash/archive/master.zip)**. +```sh +## 1. Download the .zip +curl -LOk https://github.com/afaqurk/linux-dash/archive/master.zip && unzip master.zip -#### Step 2: Secure Linux Dash +## 2. navigate to downloaded & unzipped dir +cd linux-dash-master/app/server -Linux Dash does not provide any security or authentication features. +``` -**It is strongly recommended** that all Linux Dash installations be protected via a security measure of your choice. +### Step 2 -#### Step 3: Start Linux Dash -
-Linux Dash can be run in: Node.js, PHP, Go, or Python. -
- -* Node.js is the recommended platform since it has native support for websockets and fast I/O. - -
+See instructions for preferred server linux-dash server _(all included)_: -First, navigate to the `linux-dash` folder you downloaded or cloned. +* [Node.js](#if-using-nodejs) _(recommended)_ +* [Go](#if-using-go) +* [Python](#if-using-python) +* [PHP](#if-using-php) -Then, refer to the section for your preferred platform: +#### If Using Node.js +```sh +## install dependencies +npm install --production -##### Node.js +## start linux-dash (on port 80 by default; may require sudo) +## You may change this with the `LINUX_DASH_SERVER_PORT` environment variable (eg. `LINUX_DASH_SERVER_PORT=8080 node server`) +## or provide a --port flag to the command below +node index.js -Install NPM dependencies -``` -npm install ``` -Start Linux Dash +#### If Using Go +```sh +## start the server (on port 80 by default; may require sudo) +go run index.go ``` -node server/ + +To build a binary, run `go build && ./server -h`. See [@tehbilly](https://github.com/sergeifilippov)'s notes [here](https://github.com/afaqurk/linux-dash/pull/281) for binary usage options + +#### If Using Python +```sh +# Start the server (on port 80 by default; may require sudo). +python index.py ``` -Default port for Linux Dash is 80. You may change this with the `LINUX_DASH_SERVER_PORT` environment variable (eg. `LINUX_DASH_SERVER_PORT=8080 node server`) or editing the [server/index.js on line 9](https://github.com/afaqurk/linux-dash/blob/master/server/index.js#L9) +#### If Using PHP -##### PHP 1. Make sure you have the `exec`, `shell_exec`, and `escapeshellarg` functions enabled -2. Restart your web server (Apache, nginx, etc.) +2. Point your web server to `app/` directory under `linux-dash` +2. Restart your web server (Apache, nginx, etc.) - For PHP + Apache setup follow the [Digital Ocean tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-linux-dash-on-ubuntu-14-04). - For help with nginx setup, see [this gist](https://gist.github.com/sergeifilippov/8909839) by [@sergeifilippov](https://github.com/sergeifilippov). -##### Go -Go to the `linux-dash/server` folder and run -``` -go run index.go -``` - -To build a binary, run `go build && ./server -h`. See [@tehbilly](https://github.com/sergeifilippov)'s notes [here](https://github.com/afaqurk/linux-dash/pull/281) for binary usage options +## Support -##### Python 2 -Run `./python-server.py` will run a server on port 80 which is the default. You can provide a specific port via the `--port` flag. +For general help, please use the [Gitter chat room](https://gitter.im/afaqurk/linux-dash). -## Support +## Security -For help with general setup and configuration issues please use the [Linux Dash Gitter chat room](https://gitter.im/afaqurk/linux-dash). +**It is strongly recommended** that all linux-dash installations be protected via a security measure of your choice. -The following distributions are supported: -* Arch -* Debian 6,7 -* Ubuntu 11.04+ -* Linux Mint 16+ -* CentOS 5, 6 -* openSUSE +linux-dash does not provide any security or authentication features. diff --git a/app/index.html b/app/index.html new file mode 100644 index 00000000..e186d792 --- /dev/null +++ b/app/index.html @@ -0,0 +1,27 @@ + + + + + Linux Dash : Simple, beautiful server monitoring web dashboard + + + + + + + + + + + + + + +
+ + + + + diff --git a/app/linuxDash.min.css b/app/linuxDash.min.css new file mode 100644 index 00000000..874f91a4 --- /dev/null +++ b/app/linuxDash.min.css @@ -0,0 +1,10 @@ +button{outline:0} +html{margin-top:0;padding-top:0;-webkit-transition:all 1s ease;-moz-transition:all 1s ease;-ms-transition:all 1s ease;-o-transition:all 1s ease;transition:all 1s ease}body{letter-spacing:.1rem;font-family:Merriweather,Arial,sans-serif;padding:0;margin:0}.centered{margin:0 auto} +#plugins{text-align:center;padding:0 20px 20px;margin-top:0;border:1px}@media (min-width:1080px){#plugins{float:none;margin:0 auto;clear:both}}@media (max-width:1079px){#plugins{float:none;margin:0 auto;clear:both}} +table td,table th{border-bottom:1px solid #f1f1f1}table,table td{font-size:10px}table{width:100%;margin:0;border-collapse:collapse;text-align:left;table-layout:fixed}table td,table th{padding:2px;max-width:250px;word-wrap:break-word}table th{font-weight:600;text-transform:uppercase}table td{font-family:Arial,sans-serif;color:rgba(0,0,0,.65)}table tbody tr:hover td{background-color:#fafafa}table.metrics-table{text-align:center}table.metrics-table tr:last-child td{border:none} +.spinner{margin:100px auto;width:50px;height:30px;text-align:center;font-size:10px}.spinner>div{background-color:#009587;height:100%;width:6px;display:inline-block;-webkit-animation:stretchdelay 1.2s infinite ease-in-out;animation:stretchdelay 1.2s infinite ease-in-out}.spinner .rect2{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.spinner .rect3{-webkit-animation-delay:-1s;animation-delay:-1s}.spinner .rect4{-webkit-animation-delay:-.9s;animation-delay:-.9s}.spinner .rect5{-webkit-animation-delay:-.8s;animation-delay:-.8s}@-webkit-keyframes stretchdelay{0%,100%,40%{-webkit-transform:scaleY(.4)}20%{-webkit-transform:scaleY(1)}}@keyframes stretchdelay{0%,100%,40%{transform:scaleY(.4);-webkit-transform:scaleY(.4)}20%{transform:scaleY(1);-webkit-transform:scaleY(1)}} +nav-bar{display:block;background-color:#f6f8f8;height:40px;padding-left:25px;padding-top:3px}nav-bar ul{margin-left:10px;padding:0;list-style-type:none;display:inline}nav-bar ul li a{font-size:11px;text-transform:uppercase;text-decoration:none;line-height:2.3rem;color:grey}nav-bar ul li a:hover{color:#000}nav-bar ul li.active{border-bottom:2px solid #009587}nav-bar ul li.active a{color:#009587}nav-bar ul li{margin-left:20px;display:inline;padding-left:5px;padding-bottom:10px}nav-bar .title{color:#009587;font-weight:300;font-size:20px;letter-spacing:.1rem;float:left;padding-top:5px}nav-bar .right-content{float:right}nav-bar .right-content,nav-bar .right-content a{font-size:11px;color:grey;padding-top:10px}nav-bar .right-content a:hover{color:#000} +.plugin{transition:all .1s linear;vertical-align:text-top;width:400px;display:inline-block;padding:0 0 15px;color:#000;text-align:center;border-radius:2px;box-shadow:0 1px 10px rgba(0,0,0,.13),0 5px 10px rgba(0,0,0,.16);margin:20px auto 20px 15px;max-height:400px;overflow:hidden;resize:both;background-color:rgba(0,0,0,.015)}.plugin-hidden{width:250px}.plugin-hidden .top-bar .heading{color:grey;font-style:italic}.plugin-enlarged{width:50%}@media (max-width:768px){.plugin{max-width:80%;float:none;margin:0 auto 10px}.plugin-body{max-height:323px}.plugin-enlarged{width:80%}}@media (max-width:950px){.plugin-enlarged{width:90%}}.plugin.chart-plugin{resize:none}.plugin-body{background-color:#fff;height:323px;font-size:12px;padding:10px;line-height:30px;overflow:auto;border-top:1px solid #ececec}.plugin.chart-plugin,.plugin.chart-plugin .plugin-body{overflow:hidden;padding:0}.plugin-body-short{height:263px}.plugin last-update{font-size:11px;float:left}.plugin ::-webkit-scrollbar{width:8px;height:8px}.plugin ::-webkit-scrollbar-track{background:#eee;border:thin solid #d3d3d3;box-shadow:0 0 3px #dfdfdf inset}.plugin ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border:thin solid rgba(0,0,0,.1);border-radius:0}.plugin ::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.2)} +.progress-bar{background-color:#eec;border-radius:10px;padding:0;clear:both;display:inline-block;overflow:hidden;white-space:nowrap}.progress-bar>div{background-color:#1EAEDB;width:0;height:5px;border-radius:5px} +.table-data-plugin .filter-container{padding-bottom:0;margin:0}.table-data-plugin .filter,.table-data-plugin .filter:active,.table-data-plugin .filter:focus{height:20px;padding:5px;border:none;outline-color:transparent;background:0 0;width:100%;margin:0;text-align:center;font-size:15px}.table-data-plugin .filter:focus{border-bottom:1px solid #ff5722}.table-data-plugin thead tr th a,.table-data-plugin thead tr th a:visited{color:#000;text-decoration:none}.table-data-plugin .column-sort-caret{font-size:10px;color:#1EAEDB} +.top-bar{height:15px;padding:15px;font-size:13px;text-transform:capitalize;color:#009587;background-color:#f6f8f8}.top-bar .heading{float:left;cursor:grab;cursor:-moz-grab;cursor:-webkit-grab}.ld-top-bar-btn{float:right;font-size:17px;color:#009587;background:#fff;border:1px solid #eee;border-radius:50%;width:30px;height:30px;margin-top:-5px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-ms-transition:all .5s ease;-o-transition:all .5s ease;cursor:pointer;transition:all .5s ease}.minimize-btn{font-size:19px}.ld-refresh-btn:hover{background-color:#ffeb3b;color:#000}.ld-refresh-btn:active{background-color:#0f9d58}.plugin-hidden .width-toggle-btn{display:none}.plugin-enlarged .width-toggle-btn,.plugin-hidden .minimize-btn{background:#eee;color:#000} \ No newline at end of file diff --git a/js/angular.min.js b/app/linuxDash.min.js similarity index 57% rename from js/angular.min.js rename to app/linuxDash.min.js index 836aa1e3..78ff5d9a 100644 --- a/js/angular.min.js +++ b/app/linuxDash.min.js @@ -248,3 +248,2082 @@ l);e.$watchCollection(function(){var a=M(e),c;if(a&&D(a)){c=Array(a.length);for( E);t&&(k.$isEmpty=function(a){return!a||0===a.length});q?p(e,g,k):t?m(e,g,k):l(e,g,k,s)}}}}],Vd=["$interpolate",function(a){var c={addOption:x,removeOption:x};return{restrict:"E",priority:100,compile:function(d,e){if(G(e.value)){var f=a(d.text(),!0);f||e.$set("value",d.text())}return function(a,d,e){var l=d.parent(),m=l.data("$selectController")||l.parent().data("$selectController");m&&m.databound||(m=c);f?a.$watch(f,function(a,c){e.$set("value",a);c!==a&&m.removeOption(c);m.addOption(a,d)}):m.addOption(e.value, d);d.on("$destroy",function(){m.removeOption(e.value)})}}}}],Ud=ca({restrict:"E",terminal:!1});U.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(Kd(),Md(ha),A(V).ready(function(){Gd(V,rc)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document).find("head").prepend(''); //# sourceMappingURL=angular.min.js.map + +/* + AngularJS v1.3.4 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(p,d,C){'use strict';function v(r,h,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,y){function z(){k&&(g.cancel(k),k=null);l&&(l.$destroy(),l=null);m&&(k=g.leave(m),k.then(function(){k=null}),m=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),f=r.current;m=y(b,function(b){g.enter(b,null,m||c).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||h()});z()});l=f.scope=b;l.$emit("$viewContentLoaded"); +l.$eval(w)}else z()}var l,m,k,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,h,g){return{restrict:"ECA",priority:-400,link:function(a,c){var b=g.current,f=b.locals;c.html(f.$template);var y=d(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));y(a)}}}p=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,c){return d.extend(Object.create(a), +c)}function h(a,d){var b=d.caseInsensitiveMatch,f={originalPath:a,regexp:a},g=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;g.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=new RegExp("^"+a+"$",b?"i":"");return f}var g={};this.when=function(a,c){var b=d.copy(c);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); +d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);g[a]=d.extend(b,a&&h(a,b));if(a){var f="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[f]=d.extend({redirectTo:a},h(f,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,c,b,f,h,p,x){function l(b){var e=s.current; +(v=(n=k())&&e&&n.$$route===e.$$route&&d.equals(n.pathParams,e.pathParams)&&!n.reloadOnSearch&&!w)||!e&&!n||a.$broadcast("$routeChangeStart",n,e).defaultPrevented&&b&&b.preventDefault()}function m(){var u=s.current,e=n;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?c.path(t(e.redirectTo,e.params)).search(e.params).replace():c.url(e.redirectTo(e.pathParams,c.path(),c.search())).replace()),f.when(e).then(function(){if(e){var a= +d.extend({},e.resolve),b,c;d.forEach(a,function(b,e){a[e]=d.isString(b)?h.get(b):h.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(c=e.templateUrl)&&(d.isFunction(c)&&(c=c(e.params)),c=x.getTrustedResourceUrl(c),d.isDefined(c)&&(e.loadedTemplateUrl=c,b=p(c)));d.isDefined(b)&&(a.$template=b);return f.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", +e,u,b)})}function k(){var a,b;d.forEach(g,function(f,g){var q;if(q=!b){var h=c.path();q=f.keys;var l={};if(f.regexp)if(h=f.regexp.exec(h)){for(var k=1,m=h.length;kTimeSeries with optional data options. + * + * Options are of the form (defaults shown): + * + *
+   * {
+   *   resetBounds: true,        // enables/disables automatic scaling of the y-axis
+   *   resetBoundsInterval: 3000 // the period between scaling calculations, in millis
+   * }
+   * 
+ * + * Presentation options for TimeSeries are specified as an argument to SmoothieChart.addTimeSeries. + * + * @constructor + */ + function TimeSeries(options) { + this.options = Util.extend({}, TimeSeries.defaultOptions, options); + this.clear(); + } + + TimeSeries.defaultOptions = { + resetBoundsInterval: 3000, + resetBounds: true + }; + + /** + * Clears all data and state from this TimeSeries object. + */ + TimeSeries.prototype.clear = function() { + this.data = []; + this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries. + this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries. + }; + + /** + * Recalculate the min/max values for this TimeSeries object. + * + * This causes the graph to scale itself in the y-axis. + */ + TimeSeries.prototype.resetBounds = function() { + if (this.data.length) { + // Walk through all data points, finding the min/max value + this.maxValue = this.data[0][1]; + this.minValue = this.data[0][1]; + for (var i = 1; i < this.data.length; i++) { + var value = this.data[i][1]; + if (value > this.maxValue) { + this.maxValue = value; + } + if (value < this.minValue) { + this.minValue = value; + } + } + } else { + // No data exists, so set min/max to NaN + this.maxValue = Number.NaN; + this.minValue = Number.NaN; + } + }; + + /** + * Adds a new data point to the TimeSeries, preserving chronological order. + * + * @param timestamp the position, in time, of this data point + * @param value the value of this data point + * @param sumRepeatedTimeStampValues if timestamp has an exact match in the series, this flag controls + * whether it is replaced, or the values summed (defaults to false.) + */ + TimeSeries.prototype.append = function(timestamp, value, sumRepeatedTimeStampValues) { + // Rewind until we hit an older timestamp + var i = this.data.length - 1; + while (i >= 0 && this.data[i][0] > timestamp) { + i--; + } + + if (i === -1) { + // This new item is the oldest data + this.data.splice(0, 0, [timestamp, value]); + } else if (this.data.length > 0 && this.data[i][0] === timestamp) { + // Update existing values in the array + if (sumRepeatedTimeStampValues) { + // Sum this value into the existing 'bucket' + this.data[i][1] += value; + value = this.data[i][1]; + } else { + // Replace the previous value + this.data[i][1] = value; + } + } else if (i < this.data.length - 1) { + // Splice into the correct position to keep timestamps in order + this.data.splice(i + 1, 0, [timestamp, value]); + } else { + // Add to the end of the array + this.data.push([timestamp, value]); + } + + this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value); + this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value); + }; + + TimeSeries.prototype.dropOldData = function(oldestValidTime, maxDataSetLength) { + // We must always keep one expired data point as we need this to draw the + // line that comes into the chart from the left, but any points prior to that can be removed. + var removeCount = 0; + while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) { + removeCount++; + } + if (removeCount !== 0) { + this.data.splice(0, removeCount); + } + }; + + /** + * Initialises a new SmoothieChart. + * + * Options are optional, and should be of the form below. Just specify the values you + * need and the rest will be given sensible defaults as shown: + * + *
+   * {
+   *   minValue: undefined,                      // specify to clamp the lower y-axis to a given value
+   *   maxValue: undefined,                      // specify to clamp the upper y-axis to a given value
+   *   maxValueScale: 1,                         // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
+   *   yRangeFunction: undefined,                // function({min: , max: }) { return {min: , max: }; }
+   *   scaleSmoothing: 0.125,                    // controls the rate at which y-value zoom animation occurs
+   *   millisPerPixel: 20,                       // sets the speed at which the chart pans by
+   *   enableDpiScaling: true,                   // support rendering at different DPI depending on the device
+   *   yMinFormatter: function(min, precision) { // callback function that formats the min y value label
+   *     return parseFloat(min).toFixed(precision);
+   *   },
+   *   yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
+   *     return parseFloat(max).toFixed(precision);
+   *   },
+   *   maxDataSetLength: 2,
+   *   interpolation: 'bezier'                   // one of 'bezier', 'linear', or 'step'
+   *   timestampFormatter: null,                 // optional function to format time stamps for bottom of chart
+   *                                             // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
+   *   horizontalLines: [],                      // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
+   *   grid:
+   *   {
+   *     fillStyle: '#000000',                   // the background colour of the chart
+   *     lineWidth: 1,                           // the pixel width of grid lines
+   *     strokeStyle: '#777777',                 // colour of grid lines
+   *     millisPerLine: 1000,                    // distance between vertical grid lines
+   *     sharpLines: false,                      // controls whether grid lines are 1px sharp, or softened
+   *     verticalSections: 2,                    // number of vertical sections marked out by horizontal grid lines
+   *     borderVisible: true                     // whether the grid lines trace the border of the chart or not
+   *   },
+   *   labels
+   *   {
+   *     disabled: false,                        // enables/disables labels showing the min/max values
+   *     fillStyle: '#ffffff',                   // colour for text of labels,
+   *     fontSize: 15,
+   *     fontFamily: 'sans-serif',
+   *     precision: 2
+   *   }
+   * }
+   * 
+ * + * @constructor + */ + function SmoothieChart(options) { + this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options); + this.seriesSet = []; + this.currentValueRange = 1; + this.currentVisMinValue = 0; + this.lastRenderTimeMillis = 0; + } + + SmoothieChart.defaultChartOptions = { + millisPerPixel: 20, + enableDpiScaling: true, + yMinFormatter: function(min, precision) { + return parseFloat(min).toFixed(precision); + }, + yMaxFormatter: function(max, precision) { + return parseFloat(max).toFixed(precision); + }, + maxValueScale: 1, + interpolation: 'bezier', + scaleSmoothing: 0.125, + maxDataSetLength: 2, + grid: { + fillStyle: '#000000', + strokeStyle: '#777777', + lineWidth: 1, + sharpLines: false, + millisPerLine: 1000, + verticalSections: 2, + borderVisible: true + }, + labels: { + fillStyle: '#ffffff', + disabled: false, + fontSize: 10, + fontFamily: 'monospace', + precision: 2 + }, + horizontalLines: [] + }; + + // Based on http://inspirit.github.com/jsfeat/js/compatibility.js + SmoothieChart.AnimateCompatibility = (function() { + var requestAnimationFrame = function(callback, element) { + var requestAnimationFrame = + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(function() { + callback(new Date().getTime()); + }, 16); + }; + return requestAnimationFrame.call(window, callback, element); + }, + cancelAnimationFrame = function(id) { + var cancelAnimationFrame = + window.cancelAnimationFrame || + function(id) { + clearTimeout(id); + }; + return cancelAnimationFrame.call(window, id); + }; + + return { + requestAnimationFrame: requestAnimationFrame, + cancelAnimationFrame: cancelAnimationFrame + }; + })(); + + SmoothieChart.defaultSeriesPresentationOptions = { + lineWidth: 1, + strokeStyle: '#ffffff' + }; + + /** + * Adds a TimeSeries to this chart, with optional presentation options. + * + * Presentation options should be of the form (defaults shown): + * + *
+   * {
+   *   lineWidth: 1,
+   *   strokeStyle: '#ffffff',
+   *   fillStyle: undefined
+   * }
+   * 
+ */ + SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) { + this.seriesSet.push({timeSeries: timeSeries, options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options)}); + if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) { + timeSeries.resetBoundsTimerId = setInterval( + function() { + timeSeries.resetBounds(); + }, + timeSeries.options.resetBoundsInterval + ); + } + }; + + /** + * Removes the specified TimeSeries from the chart. + */ + SmoothieChart.prototype.removeTimeSeries = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + this.seriesSet.splice(i, 1); + break; + } + } + // If a timer was operating for that timeseries, remove it + if (timeSeries.resetBoundsTimerId) { + // Stop resetting the bounds, if we were + clearInterval(timeSeries.resetBoundsTimerId); + } + }; + + /** + * Gets render options for the specified TimeSeries. + * + * As you may use a single TimeSeries in multiple charts with different formatting in each usage, + * these settings are stored in the chart. + */ + SmoothieChart.prototype.getTimeSeriesOptions = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + return this.seriesSet[i].options; + } + } + }; + + /** + * Brings the specified TimeSeries to the top of the chart. It will be rendered last. + */ + SmoothieChart.prototype.bringToFront = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + var set = this.seriesSet.splice(i, 1); + this.seriesSet.push(set[0]); + break; + } + } + }; + + /** + * Instructs the SmoothieChart to start rendering to the provided canvas, with specified delay. + * + * @param canvas the target canvas element + * @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series + * from appearing on screen, with new values flashing into view, at the expense of some latency. + */ + SmoothieChart.prototype.streamTo = function(canvas, delayMillis) { + this.canvas = canvas; + this.delay = delayMillis; + this.start(); + }; + + /** + * Make sure the canvas has the optimal resolution for the device's pixel ratio. + */ + SmoothieChart.prototype.resize = function() { + // TODO this function doesn't handle the value of enableDpiScaling changing during execution + if (!this.options.enableDpiScaling || !window || window.devicePixelRatio === 1) + return; + + var dpr = window.devicePixelRatio; + var width = parseInt(this.canvas.getAttribute('width')); + var height = parseInt(this.canvas.getAttribute('height')); + + if (!this.originalWidth || (Math.floor(this.originalWidth * dpr) !== width)) { + this.originalWidth = width; + this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); + this.canvas.style.width = width + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + + if (!this.originalHeight || (Math.floor(this.originalHeight * dpr) !== height)) { + this.originalHeight = height; + this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); + this.canvas.style.height = height + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + }; + + /** + * Starts the animation of this chart. + */ + SmoothieChart.prototype.start = function() { + if (this.frame) { + // We're already running, so just return + return; + } + + // Renders a frame, and queues the next frame for later rendering + var animate = function() { + this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() { + this.render(); + animate(); + }.bind(this)); + }.bind(this); + + animate(); + }; + + /** + * Stops the animation of this chart. + */ + SmoothieChart.prototype.stop = function() { + if (this.frame) { + SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame); + delete this.frame; + } + }; + + SmoothieChart.prototype.updateValueRange = function() { + // Calculate the current scale of the chart, from all time series. + var chartOptions = this.options, + chartMaxValue = Number.NaN, + chartMinValue = Number.NaN; + + for (var d = 0; d < this.seriesSet.length; d++) { + // TODO(ndunn): We could calculate / track these values as they stream in. + var timeSeries = this.seriesSet[d].timeSeries; + if (!isNaN(timeSeries.maxValue)) { + chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue; + } + + if (!isNaN(timeSeries.minValue)) { + chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue; + } + } + + // Scale the chartMaxValue to add padding at the top if required + if (chartOptions.maxValue != null) { + chartMaxValue = chartOptions.maxValue; + } else { + chartMaxValue *= chartOptions.maxValueScale; + } + + // Set the minimum if we've specified one + if (chartOptions.minValue != null) { + chartMinValue = chartOptions.minValue; + } + + // If a custom range function is set, call it + if (this.options.yRangeFunction) { + var range = this.options.yRangeFunction({min: chartMinValue, max: chartMaxValue}); + chartMinValue = range.min; + chartMaxValue = range.max; + } + + if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) { + var targetValueRange = chartMaxValue - chartMinValue; + var valueRangeDiff = (targetValueRange - this.currentValueRange); + var minValueDiff = (chartMinValue - this.currentVisMinValue); + this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1; + this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff; + this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff; + } + + this.valueRange = { min: chartMinValue, max: chartMaxValue }; + }; + + SmoothieChart.prototype.render = function(canvas, time) { + var nowMillis = new Date().getTime(); + + if (!this.isAnimatingScale) { + // We're not animating. We can use the last render time and the scroll speed to work out whether + // we actually need to paint anything yet. If not, we can return immediately. + + // Render at least every 1/6th of a second. The canvas may be resized, which there is + // no reliable way to detect. + var maxIdleMillis = Math.min(1000/6, this.options.millisPerPixel); + + if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) { + return; + } + } + + this.resize(); + + this.lastRenderTimeMillis = nowMillis; + + canvas = canvas || this.canvas; + time = time || nowMillis - (this.delay || 0); + + // Round time down to pixel granularity, so motion appears smoother. + time -= time % this.options.millisPerPixel; + + var context = canvas.getContext('2d'), + chartOptions = this.options, + dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight }, + // Calculate the threshold time for the oldest data points. + oldestValidTime = time - (dimensions.width * chartOptions.millisPerPixel), + valueToYPixel = function(value) { + var offset = value - this.currentVisMinValue; + return this.currentValueRange === 0 + ? dimensions.height + : dimensions.height - (Math.round((offset / this.currentValueRange) * dimensions.height)); + }.bind(this), + timeToXPixel = function(t) { + return Math.round(dimensions.width - ((time - t) / chartOptions.millisPerPixel)); + }; + + this.updateValueRange(); + + context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily; + + // Save the state of the canvas context, any transformations applied in this method + // will get removed from the stack at the end of this method when .restore() is called. + context.save(); + + // Move the origin. + context.translate(dimensions.left, dimensions.top); + + // Create a clipped rectangle - anything we draw will be constrained to this rectangle. + // This prevents the occasional pixels from curves near the edges overrunning and creating + // screen cheese (that phrase should need no explanation). + context.beginPath(); + context.rect(0, 0, dimensions.width, dimensions.height); + context.clip(); + + // Clear the working area. + context.save(); + context.fillStyle = chartOptions.grid.fillStyle; + context.clearRect(0, 0, dimensions.width, dimensions.height); + context.fillRect(0, 0, dimensions.width, dimensions.height); + context.restore(); + + // Grid lines... + context.save(); + context.lineWidth = chartOptions.grid.lineWidth; + context.strokeStyle = chartOptions.grid.strokeStyle; + // Vertical (time) dividers. + if (chartOptions.grid.millisPerLine > 0) { + context.beginPath(); + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + if (chartOptions.grid.sharpLines) { + gx -= 0.5; + } + context.moveTo(gx, 0); + context.lineTo(gx, dimensions.height); + } + context.stroke(); + context.closePath(); + } + + // Horizontal (value) dividers. + for (var v = 1; v < chartOptions.grid.verticalSections; v++) { + var gy = Math.round(v * dimensions.height / chartOptions.grid.verticalSections); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; + } + context.beginPath(); + context.moveTo(0, gy); + context.lineTo(dimensions.width, gy); + context.stroke(); + context.closePath(); + } + // Bounding rectangle. + if (chartOptions.grid.borderVisible) { + context.beginPath(); + context.strokeRect(0, 0, dimensions.width, dimensions.height); + context.closePath(); + } + context.restore(); + + // Draw any horizontal lines... + if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) { + for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) { + var line = chartOptions.horizontalLines[hl], + hly = Math.round(valueToYPixel(line.value)) - 0.5; + context.strokeStyle = line.color || '#ffffff'; + context.lineWidth = line.lineWidth || 1; + context.beginPath(); + context.moveTo(0, hly); + context.lineTo(dimensions.width, hly); + context.stroke(); + context.closePath(); + } + } + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + context.save(); + var timeSeries = this.seriesSet[d].timeSeries, + dataSet = timeSeries.data, + seriesOptions = this.seriesSet[d].options; + + // Delete old data that's moved off the left of the chart. + timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength); + + // Set style for this dataSet. + context.lineWidth = seriesOptions.lineWidth; + context.strokeStyle = seriesOptions.strokeStyle; + // Draw the line... + context.beginPath(); + // Retain lastX, lastY for calculating the control points of bezier curves. + var firstX = 0, lastX = 0, lastY = 0; + for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) { + var x = timeToXPixel(dataSet[i][0]), + y = valueToYPixel(dataSet[i][1]); + + if (i === 0) { + firstX = x; + context.moveTo(x, y); + } else { + switch (chartOptions.interpolation) { + case "linear": + case "line": { + context.lineTo(x,y); + break; + } + case "bezier": + default: { + // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves + // + // Assuming A was the last point in the line plotted and B is the new point, + // we draw a curve with control points P and Q as below. + // + // A---P + // | + // | + // | + // Q---B + // + // Importantly, A and P are at the same y coordinate, as are B and Q. This is + // so adjacent curves appear to flow as one. + // + context.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop + Math.round((lastX + x) / 2), lastY, // controlPoint1 (P) + Math.round((lastX + x)) / 2, y, // controlPoint2 (Q) + x, y); // endPoint (B) + break; + } + case "step": { + context.lineTo(x,lastY); + context.lineTo(x,y); + break; + } + } + } + + lastX = x; lastY = y; + } + + if (dataSet.length > 1) { + if (seriesOptions.fillStyle) { + // Close up the fill region. + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY); + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1); + context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); + context.fillStyle = seriesOptions.fillStyle; + context.fill(); + } + + if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') { + context.stroke(); + } + context.closePath(); + } + context.restore(); + } + + // Draw the axis values on the chart. + if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) { + var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision), + minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision); + context.fillStyle = chartOptions.labels.fillStyle; + context.fillText(maxValueString, dimensions.width - context.measureText(maxValueString).width - 2, chartOptions.labels.fontSize); + context.fillText(minValueString, dimensions.width - context.measureText(minValueString).width - 2, dimensions.height - 2); + } + + // Display timestamps along x-axis at the bottom of the chart. + if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) { + var textUntilX = dimensions.width - context.measureText(minValueString).width + 4; + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + // Only draw the timestamp if it won't overlap with the previously drawn one. + if (gx < textUntilX) { + // Formats the timestamp based on user specified formatting function + // SmoothieChart.timeFormatter function above is one such formatting option + var tx = new Date(t), + ts = chartOptions.timestampFormatter(tx), + tsWidth = context.measureText(ts).width; + textUntilX = gx - tsWidth - 2; + context.fillStyle = chartOptions.labels.fillStyle; + context.fillText(ts, gx - tsWidth, dimensions.height - 2); + } + } + } + + context.restore(); // See .save() above. + }; + + // Sample timestamp formatting function + SmoothieChart.timeFormatter = function(date) { + function pad2(number) { return (number < 10 ? '0' : '') + number } + return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds()); + }; + + exports.TimeSeries = TimeSeries; + exports.SmoothieChart = SmoothieChart; + +})(typeof exports === 'undefined' ? this : exports); + + +/*! Sortable 1.5.1 - MIT | git://github.com/rubaxa/Sortable.git */ +!function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():window.Sortable=a()}(function(){"use strict";function a(a,b){if(!a||!a.nodeType||1!==a.nodeType)throw"Sortable: `el` must be HTMLElement, and not "+{}.toString.call(a);this.el=a,this.options=b=t({},b),a[T]=this;var c={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(a.nodeName)?"li":">*",ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0}};for(var d in c)!(d in b)&&(b[d]=c[d]);ga(b);for(var e in this)"_"===e.charAt(0)&&"function"==typeof this[e]&&(this[e]=this[e].bind(this));this.nativeDraggable=!b.forceFallback&&$,f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),f(a,"pointerdown",this._onTapStart),this.nativeDraggable&&(f(a,"dragover",this),f(a,"dragenter",this)),ea.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a,b){"clone"!==a.lastPullMode&&(b=!0),z&&z.state!==b&&(i(z,"display",b?"none":""),b||z.state&&(a.options.group.revertClone?(A.insertBefore(z,B),a._animate(w,z)):A.insertBefore(z,w)),z.state=b)}function c(a,b,c){if(a){c=c||V;do if(">*"===b&&a.parentNode===c||r(a,b))return a;while(a=d(a))}return null}function d(a){var b=a.host;return b&&b.nodeType?b:a.parentNode}function e(a){a.dataTransfer&&(a.dataTransfer.dropEffect="move"),a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,Z)}function g(a,b,c){a.removeEventListener(b,c,Z)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(R," ").replace(" "+b+" "," ");a.className=(d+(c?" "+b:"")).replace(R," ")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return V.defaultView&&V.defaultView.getComputedStyle?c=V.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;e5||b.clientX-(d.right+d.width)>5)&&c}function p(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function q(a,b){var c=0;if(!a||!a.parentNode)return-1;for(;a&&(a=a.previousElementSibling);)"TEMPLATE"===a.nodeName.toUpperCase()||">*"!==b&&!r(a,b)||c++;return c}function r(a,b){if(a){b=b.split(".");var c=b.shift().toUpperCase(),d=new RegExp("\\s("+b.join("|")+")(?=\\s)","g");return!(""!==c&&a.nodeName.toUpperCase()!=c||b.length&&((" "+a.className+" ").match(d)||[]).length!=b.length)}return!1}function s(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}function t(a,b){if(a&&b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function u(a){return X?X(a).clone(!0)[0]:Y&&Y.dom?Y.dom(a).cloneNode(!0):a.cloneNode(!0)}function v(a){for(var b=a.getElementsByTagName("input"),c=b.length;c--;){var d=b[c];d.checked&&da.push(d)}}if("undefined"==typeof window||!window.document)return function(){throw new Error("Sortable.js requires a window with a document")};var w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q={},R=/\s+/g,S=/left|right|inline/,T="Sortable"+(new Date).getTime(),U=window,V=U.document,W=U.parseInt,X=U.jQuery||U.Zepto,Y=U.Polymer,Z=!1,$=!!("draggable"in V.createElement("div")),_=function(a){return!navigator.userAgent.match(/Trident.*rv[ :]?11\./)&&(a=V.createElement("x"),a.style.cssText="pointer-events:auto","auto"===a.style.pointerEvents)}(),aa=!1,ba=Math.abs,ca=Math.min,da=[],ea=[],fa=s(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h,i,j=c[T],k=b.scrollSensitivity,l=b.scrollSpeed,m=a.clientX,n=a.clientY,o=window.innerWidth,p=window.innerHeight;if(E!==c&&(D=b.scroll,E=c,F=b.scrollFn,D===!0)){D=c;do if(D.offsetWidth-1:e==a)}}var c={},d=a.group;d&&"object"==typeof d||(d={name:d}),c.name=d.name,c.checkPull=b(d.pull,!0),c.checkPut=b(d.put),c.revertClone=d.revertClone,a.group=c};a.prototype={constructor:a,_onTapStart:function(a){var b,d=this,e=this.el,f=this.options,g=f.preventOnFilter,h=a.type,i=a.touches&&a.touches[0],j=(i||a).target,l=a.target.shadowRoot&&a.path[0]||j,m=f.filter;if(v(e),!w&&!("mousedown"===h&&0!==a.button||f.disabled)&&(j=c(j,f.draggable,e),j&&C!==j)){if(b=q(j,f.draggable),"function"==typeof m){if(m.call(this,a,j,this))return k(d,l,"filter",j,e,b),void(g&&a.preventDefault())}else if(m&&(m=m.split(",").some(function(a){if(a=c(l,a.trim(),e))return k(d,a,"filter",j,e,b),!0})))return void(g&&a.preventDefault());f.handle&&!c(l,f.handle,e)||this._prepareDragStart(a,i,j,b)}},_prepareDragStart:function(a,b,c,d){var e,g=this,i=g.el,l=g.options,n=i.ownerDocument;c&&!w&&c.parentNode===i&&(N=a,A=i,w=c,x=w.parentNode,B=w.nextSibling,C=c,L=l.group,J=d,this._lastX=(b||a).clientX,this._lastY=(b||a).clientY,w.style["will-change"]="transform",e=function(){g._disableDelayedDrag(),w.draggable=g.nativeDraggable,h(w,l.chosenClass,!0),g._triggerDragStart(a,b),k(g,A,"choose",w,A,J)},l.ignore.split(",").forEach(function(a){j(w,a.trim(),m)}),f(n,"mouseup",g._onDrop),f(n,"touchend",g._onDrop),f(n,"touchcancel",g._onDrop),f(n,"pointercancel",g._onDrop),f(n,"selectstart",g),l.delay?(f(n,"mouseup",g._disableDelayedDrag),f(n,"touchend",g._disableDelayedDrag),f(n,"touchcancel",g._disableDelayedDrag),f(n,"mousemove",g._disableDelayedDrag),f(n,"touchmove",g._disableDelayedDrag),f(n,"pointermove",g._disableDelayedDrag),g._dragStartTimer=setTimeout(e,l.delay)):e())},_disableDelayedDrag:function(){var a=this.el.ownerDocument;clearTimeout(this._dragStartTimer),g(a,"mouseup",this._disableDelayedDrag),g(a,"touchend",this._disableDelayedDrag),g(a,"touchcancel",this._disableDelayedDrag),g(a,"mousemove",this._disableDelayedDrag),g(a,"touchmove",this._disableDelayedDrag),g(a,"pointermove",this._disableDelayedDrag)},_triggerDragStart:function(a,b){b=b||("touch"==a.pointerType?a:null),b?(N={target:w,clientX:b.clientX,clientY:b.clientY},this._onDragStart(N,"touch")):this.nativeDraggable?(f(w,"dragend",this),f(A,"dragstart",this._onDragStart)):this._onDragStart(N,!0);try{V.selection?setTimeout(function(){V.selection.empty()}):window.getSelection().removeAllRanges()}catch(a){}},_dragStarted:function(){if(A&&w){var b=this.options;h(w,b.ghostClass,!0),h(w,b.dragClass,!1),a.active=this,k(this,A,"start",w,A,J)}else this._nulling()},_emulateDragOver:function(){if(O){if(this._lastX===O.clientX&&this._lastY===O.clientY)return;this._lastX=O.clientX,this._lastY=O.clientY,_||i(y,"display","none");var a=V.elementFromPoint(O.clientX,O.clientY),b=a,c=ea.length;if(b)do{if(b[T]){for(;c--;)ea[c]({clientX:O.clientX,clientY:O.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);_||i(y,"display","")}},_onTouchMove:function(b){if(N){var c=this.options,d=c.fallbackTolerance,e=c.fallbackOffset,f=b.touches?b.touches[0]:b,g=f.clientX-N.clientX+e.x,h=f.clientY-N.clientY+e.y,j=b.touches?"translate3d("+g+"px,"+h+"px,0)":"translate("+g+"px,"+h+"px)";if(!a.active){if(d&&ca(ba(f.clientX-this._lastX),ba(f.clientY-this._lastY))w.offsetWidth,D=e.offsetHeight>w.offsetHeight,E=(v?(d.clientX-g.left)/t:(d.clientY-g.top)/u)>.5,F=e.nextElementSibling,J=l(A,j,w,f,e,g,d),K=!1;if(J!==!1){if(aa=!0,setTimeout(n,30),b(p,q),1===J||J===-1)K=1===J;else if(v){var N=w.offsetTop,O=e.offsetTop;K=N===O?e.previousElementSibling===w&&!C||E&&C:e.previousElementSibling===w||w.previousElementSibling===e?(d.clientY-g.top)/u>.5:O>N}else r||(K=F!==w&&!D||E&&D);w.contains(j)||(K&&!F?j.appendChild(w):e.parentNode.insertBefore(w,K?F:e)),x=w.parentNode,this._animate(f,w),this._animate(g,e)}}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();1===a.nodeType&&(a=a.getBoundingClientRect()),i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){i(b,"transition",""),i(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){var a=this.el.ownerDocument;g(V,"touchmove",this._onTouchMove),g(V,"pointermove",this._onTouchMove),g(a,"mouseup",this._onDrop),g(a,"touchend",this._onDrop),g(a,"pointerup",this._onDrop),g(a,"touchcancel",this._onDrop),g(a,"selectstart",this)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(Q.pid),clearTimeout(this._dragStartTimer),g(V,"mousemove",this._onTouchMove),this.nativeDraggable&&(g(V,"drop",this),g(c,"dragstart",this._onDragStart)),this._offUpEvents(),b&&(P&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation()),y&&y.parentNode.removeChild(y),A!==x&&"clone"===a.active.lastPullMode||z&&z.parentNode.removeChild(z),w&&(this.nativeDraggable&&g(w,"dragend",this),m(w),w.style["will-change"]="",h(w,this.options.ghostClass,!1),h(w,this.options.chosenClass,!1),A!==x?(K=q(w,d.draggable),K>=0&&(k(null,x,"add",w,A,J,K),k(this,A,"remove",w,A,J,K),k(null,x,"sort",w,A,J,K),k(this,A,"sort",w,A,J,K))):w.nextSibling!==B&&(K=q(w,d.draggable),K>=0&&(k(this,A,"update",w,A,J,K),k(this,A,"sort",w,A,J,K))),a.active&&(null!=K&&K!==-1||(K=J),k(this,A,"end",w,A,J,K),this.save()))),this._nulling()},_nulling:function(){A=w=x=y=B=z=C=D=E=N=O=P=K=G=H=M=L=a.active=null,da.forEach(function(a){a.checked=!0}),da.length=0},handleEvent:function(a){switch(a.type){case"drop":case"dragend":this._onDrop(a);break;case"dragover":case"dragenter":w&&(this._onDragOver(a),e(a));break;case"selectstart":a.preventDefault()}},toArray:function(){for(var a,b=[],d=this.el.children,e=0,f=d.length,g=this.options;e', + '', + 'Loading...', + '', + ].join(''), + controller: ['$scope', '$location', '$rootScope', appLoadController], + }) + + .when('/system-status', { + template: [ + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ].join(''), + }) + + .when('/basic-info', { + template: [ + '', + '', + '', + '', + '', + '', + ].join(''), + }) + + .when('/network', { + template: [ + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ].join(''), + }) + + .when('/accounts', { + template: [ + ' ', + ' ', + ' ', + ].join(''), + }) + + .when('/apps', { + template: [ + '', + '', + '', + '', + ].join(''), + }) + .otherwise({ + redirectTo: '/loading' + }) +} + +angular.module('linuxDash').config(['$routeProvider', routesFn]) + +angular + .module('linuxDash') + .service('server', [ + '$http', '$rootScope', '$location', + function($http, $rootScope, $location) { + + var websocket = { + connection: null, + onMessageEventHandlers: {} + }; + + /** + * @description: + * Establish a websocket connection with server + * + * @return Null + */ + var establishWebsocketConnection = function() { + + var websocketUrl = (location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.hostname + ':' + window.location.port; + + if (websocket.connection === null) { + + websocket.connection = new WebSocket(websocketUrl); + + websocket.connection.onopen = function() { + $rootScope.$broadcast("start-linux-dash", {}); + $rootScope.$apply(); + console.info('Websocket connection is open'); + }; + + websocket.connection.onmessage = function(event) { + + var response = JSON.parse(event.data); + var moduleName = response.moduleName; + var moduleData = JSON.parse(response.output); + + if (!!websocket.onMessageEventHandlers[moduleName]) { + websocket.onMessageEventHandlers[moduleName](moduleData); + } else { + console.info("Websocket could not find module", moduleName, "in:", websocket.onMessageEventHandlers); + } + + }; + + websocket.connection.onclose = function() { + websocket.connection = null; + } + } + + }; + + /** + * @description: + * Check if websockets are supported + * If so, call establishWebsocketConnection() + * + * @return Null + */ + this.checkIfWebsocketsAreSupported = function() { + + var websocketSupport = { + browser: null, + server: null, + }; + + // does browser support websockets? + if (window.WebSocket) { + + websocketSupport.browser = true; + + // does backend support websockets? + $http.get("/websocket").then(function(response) { + + // if websocket_support property exists and is trurthy + // websocketSupport.server will equal true. + websocketSupport.server = !!response.data["websocket_support"]; + + }).catch(function websocketNotSupportedByServer() { + + websocketSupport.server = false; + $rootScope.$broadcast("start-linux-dash", {}); + + }).then(function finalDecisionOnWebsocket() { + + if (websocketSupport.browser && websocketSupport.server) { + + establishWebsocketConnection(); + + } else { + $rootScope.$broadcast("start-linux-dash", {}); + } + + }); + + } + + }; + + /** + * Handles requests from modules for data from server + * + * @param {String} moduleName + * @param {Function} callback + * @return {[ Null || callback(server response) ]} + */ + this.get = function(moduleName, callback) { + + // if we have a websocket connection + if (websocket.connection) { + + // and the connection is ready + if (websocket.connection.readyState === 1) { + + // set the callback as the event handler + // for server response. + // + // Callback instance needs to be overwritten + // each time for this to work. Not sure why. + websocket.onMessageEventHandlers[moduleName] = callback; + + // + websocket.connection.send(moduleName); + + } else { + console.log("Websocket not ready yet.", moduleName); + } + + } + // otherwise + else { + + var moduleAddress = 'server/?module=' + moduleName; + + return $http.get(moduleAddress).then(function(response) { + return callback(response.data); + }); + + } + + }; + + } +]) + +angular.module('linuxDash').directive('cpuAvgLoadChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: '\ + \ + \ + ', + link: function(scope) { + scope.units = '%' + } + } +}]) + +angular.module('linuxDash').directive('cpuTemp', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.min = 0 + scope.max = 100 + + scope.displayValue = function (serverResponseData) { + return serverResponseData + } + + scope.utilMetrics = [{ + name: 'Temprature', + generate: function (serverResponseData) { + return serverResponseData + ' °C' + } + }] + + } + } +}]) + +angular.module('linuxDash').directive('cpuUtilizationChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.min = 0 + scope.max = 100 + + scope.displayValue = function(serverResponseData) { + return serverResponseData + } + + scope.utilMetrics = [{ + name: 'Usage', + generate: function(serverResponseData) { + return serverResponseData + ' %' + } + }] + + } + } +}]) + +angular.module('linuxDash').directive('downloadTransferRateChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.delay = 2000 + scope.units = 'KB/s' + } + } +}]) + +angular.module('linuxDash').directive('ramChart', ['server', function (server) { + return { + restrict: 'E', + scope: {}, + template: '\ + \ + \ + ', + link: function(scope) { + + // get max ram available on machine before we + // can start charting + server.get('current_ram', function(resp) { + scope.maxRam = resp.total + scope.minRam = 0 + }) + + scope.ramToDisplay = function(serverResponseData) { + return serverResponseData.used + } + + var humanizeRam = function (ramInMB) { + var ram = { + value: parseInt(ramInMB, 10), + unit: 'MB', + } + + // if ram > 1,000 MB, use GB + if (ram.value > 1000) { + ram = { + value: (ramInMB/1024).toFixed(2), + unit: 'GB', + } + } + + return ram.value + ' ' + ram.unit + } + + scope.ramMetrics = [{ + name: 'Used', + generate: function(serverResponseData) { + var ratio = serverResponseData.used / serverResponseData.total + var percentage = parseInt(ratio * 100) + + var usedRam = humanizeRam(serverResponseData.used) + return usedRam + ' (' + percentage.toString() + '%)' + } + }, + { + name: 'Available', + generate: function(serverResponseData) { + + var availableRam = humanizeRam(serverResponseData.available) + var totalRam = humanizeRam(serverResponseData.total) + return availableRam + ' of ' + totalRam + } + }] + } + } +}]) + +var simpleTableModules = [ + { + name: 'machineInfo', + template: '' + }, + { + name: 'ipAddresses', + template: '' + }, + { + name: 'ramIntensiveProcesses', + template: '' + }, + { + name: 'cpuIntensiveProcesses', + template: '' + }, + { + name: 'dockerProcesses', + template: '' + }, + { + name: 'networkConnections', + template: '' + }, + { + name: 'serverAccounts', + template: '' + }, + { + name: 'loggedInAccounts', + template: '' + }, + { + name: 'recentLogins', + template: '' + }, + { + name: 'arpCacheTable', + template: '' + }, + { + name: 'commonApplications', + template: '' + }, + { + name: 'pingSpeeds', + template: '' + }, + { + name: 'bandwidth', + template: '' + }, + { + name: 'swapUsage', + template: '' + }, + { + name: 'internetSpeed', + template: '' + }, + { + name: 'memcached', + template: '' + }, + { + name: 'redis', + template: '' + }, + { + name: 'pm2', + template: '' + }, + { + name: 'memoryInfo', + template: '' + }, + { + name: 'cpuInfo', + template: '' + }, + { + name: 'ioStats', + template: '' + }, + { + name: 'scheduledCrons', + template: '' + }, + { + name: 'cronHistory', + template: '' + } +] + +simpleTableModules.forEach(function(module, key) { + + angular.module('linuxDash').directive(module.name, ['server', function(server) { + + var moduleDirective = { + restrict: 'E', + scope: {} + } + + moduleDirective['template'] = module.template + + return moduleDirective + }]) + +}) + +angular.module('linuxDash').directive('uploadTransferRateChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.delay = 2000 + scope.units = 'KB/s' + } + } +}]) + +angular + .module('linuxDash') + .run(['$rootScope', '$location', function ($rootScope, $location) { + + var key = 'hiddenPlugins' + + var getHiddenPlugins = function () { + var hiddenPluginsCSV = localStorage.getItem(key) || '' + return hiddenPluginsCSV.split(',') + } + + var updateHiddenPlugins = function (hiddenPlugins) { + localStorage.setItem(key, hiddenPlugins.join(',')) + } + + $rootScope.$on('hide-plugin', function (e, m) { + var hiddenPlugins = getHiddenPlugins() + + if(hiddenPlugins.indexOf(m) < 0) + hiddenPlugins.push(m) + + updateHiddenPlugins(hiddenPlugins) + }) + + $rootScope.$on('show-plugin', function (e, m) { + var hiddenPlugins = getHiddenPlugins() + var indexOfPlugin = hiddenPlugins.indexOf(m) + + if(indexOfPlugin > -1) + hiddenPlugins.splice(indexOfPlugin, 1) + + updateHiddenPlugins(hiddenPlugins) + }) + + $rootScope.hiddenPlugins = getHiddenPlugins() + + }]) + +angular + .module('linuxDash') + .run(['$rootScope', '$location', function ($rootScope, $location) { + + $rootScope.$on('$routeChangeSuccess', function () { + + var intervalId = setInterval(function () { + + var el = document.getElementById('plugins') + + if (el) { + + var sortable = Sortable.create(el, { + group: 'plugin-order-' + $location.path().replace('/', ''), + handle: '.heading', + ghostClass: 'ld-ghost', + chosenClass: 'ld-chosen', + dataIdAttr: 'sortablejs-id', + animation: 1050, + store: { + get: function (sortable) { + var order = localStorage.getItem(sortable.options.group.name); + return order ? order.split('|') : []; + }, + set: function (sortable) { + var order = sortable.toArray(); + localStorage.setItem(sortable.options.group.name, order.join('|')); + } + } + }) + + clearInterval(intervalId) + } + }) + }) + + }]) + +angular.module('linuxDash').directive('diskSpace', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + templateUrl: 'src/js/plugins/disk-space/disk-space.html', + link: function(scope) { + + var getKBMultiplierFn = function (size, power) { + return function () { + return size * Math.pow(1024, power) + } + } + + var kbDictionary = { + 'M': function () { return getKBMultiplierFn(size, 1) }, + 'G': function () { return getKBMultiplierFn(size, 2) }, + 'T': function () { return getKBMultiplierFn(size, 3) }, + 'P': function () { return getKBMultiplierFn(size, 4) }, + 'E': function () { return getKBMultiplierFn(size, 5) }, + 'Z': function () { return getKBMultiplierFn(size, 6) }, + 'Y': function () { return getKBMultiplierFn(size, 7) }, + } + + scope.heading = "Disk Partitions" + scope.moduleName = 'disk_partitions' + + scope.getData = function() { + server.get(scope.moduleName, function(serverResponseData) { + scope.diskSpaceData = serverResponseData + }) + + scope.lastGet = new Date().getTime() + } + + scope.getData() + + scope.getKB = function(stringSize) { + + var lastChar = stringSize.slice(-1) + var size = parseFloat(stringSize.replace(",", ".")) + + try { + return kbDictionary[lastChar](size) + } catch (err) { + return size + } + } + + } + } +}]) + +angular.module('linuxDash').directive('keyValueList', ['server', '$rootScope', function (server, $rootScope) { + return { + scope: { + heading: '@', + info: '@', + moduleName: '@', + }, + templateUrl: 'src/js/core/features/key-value-list/key-value-list.html', + link: function(scope, element) { + + scope.getData = function() { + delete scope.tableRows + + server.get(scope.moduleName, function(serverResponseData) { + scope.tableRows = serverResponseData + scope.lastGet = new Date().getTime() + + if (Object.keys(serverResponseData).length === 0) { + scope.emptyResult = true + } + + if (!scope.$$phase && !$rootScope.$$phase) scope.$digest() + }) + } + + scope.getData() + } + } +}]) + +angular.module('linuxDash').directive('lineChartPlugin', [ + '$interval', '$compile', 'server', '$window', + function ($interval, $compile, server, $window) { + return { + scope: { + heading: '@', + moduleName: '@', + refreshRate: '=', + maxValue: '=', + minValue: '=', + getDisplayValue: '=', + metrics: '=', + color: '@' + }, + templateUrl: 'src/js/core/features/line-chart/line-chart-plugin.html', + link: function(scope, element) { + + scope.initializing = true + + // wrap the entire plugin into an initializing function + var start_rendering_line_chart = function () { + + if (!scope.color) + scope.color = '0, 255, 0' + + var series, w, h, canvas + + angular.element($window).bind('resize', function() { + canvas.width = w + canvas.height = h + }) + + // smoothieJS - Create new chart + var chart = new SmoothieChart({ + borderVisible: false, + sharpLines: true, + grid: { + fillStyle: '#ffffff', + strokeStyle: 'rgba(232,230,230,0.93)', + sharpLines: true, + millisPerLine: 3000, + borderVisible: false + }, + labels: { + fontSize: 11, + precision: 0, + fillStyle: '#0f0e0e' + }, + maxValue: parseInt(scope.maxValue), + minValue: parseInt(scope.minValue), + horizontalLines: [{ + value: 5, + color: '#eff', + lineWidth: 1 + }] + }) + + var initializeChart = function () { + // smoothieJS - set up canvas element for chart + var checkForCanvasReadyState = $interval(function () { + if (element.find('canvas')[0]) { + canvas = element.find('canvas')[0] + series = series || new TimeSeries() + w = canvas.width + h = canvas.height + + if (chart.seriesSet.length > 0) + chart.removeTimeSeries(chart.seriesSet[0].timeSeries) + + chart.addTimeSeries(series, { + strokeStyle: 'rgba(' + scope.color + ', 1)', + fillStyle: 'rgba(' + scope.color + ', 0.2)', + lineWidth: 2 + }) + + chart.streamTo(canvas, 1000) + $interval.cancel(checkForCanvasReadyState) + } + }, 100) + } + + scope.reInitializeChart = function () { + initializeChart() + } + + if (!scope.isHidden) + initializeChart() + + var dataCallInProgress = false + + // update data on chart + scope.getData = function() { + + if(scope.initializing) + scope.initializing = false + + if (dataCallInProgress || !element.find('canvas')[0]) return + + dataCallInProgress = true + + server.get(scope.moduleName, function(serverResponseData) { + + if (serverResponseData.length < 1) { + scope.emptyResult = true + return + } + + dataCallInProgress = false + scope.lastGet = new Date().getTime() + + // change graph colour depending on usage + if (scope.maxValue / 4 * 3 < scope.getDisplayValue(serverResponseData)) { + chart.seriesSet[0].options.strokeStyle = 'rgba(255, 89, 0, 1)' + chart.seriesSet[0].options.fillStyle = 'rgba(255, 89, 0, 0.2)' + } else if (scope.maxValue / 3 < scope.getDisplayValue(serverResponseData)) { + chart.seriesSet[0].options.strokeStyle = 'rgba(255, 238, 0, 1)' + chart.seriesSet[0].options.fillStyle = 'rgba(255, 238, 0, 0.2)' + } else { + chart.seriesSet[0].options.strokeStyle = 'rgba(' + scope.color + ', 1)' + chart.seriesSet[0].options.fillStyle = 'rgba(' + scope.color + ', 0.2)' + } + + scope.newData = scope.getDisplayValue(serverResponseData) + + // update chart with this response + series.append(scope.lastGet, scope.newData) + + // update the metrics for this chart + scope.metrics.forEach(function(metricObj) { + metricObj.data = metricObj.generate(serverResponseData) + }) + + }) + } + + // set the directive-provided interval + // at which to run the chart update + var intervalRef = $interval(scope.getData, scope.refreshRate) + var removeInterval = function() { + $interval.cancel(intervalRef) + } + + element.on("$destroy", removeInterval) + } + + // only start rendering plugin when we know the scale of max/min for the canvas chart (smoothie) + var stopWatching = scope.$watch('maxValue', function (n, o) { + if (n) { + start_rendering_line_chart() + stopWatching() + } + }) + + + } + } + } +]) + +angular.module('linuxDash').directive('loader', function() { + return { + scope: { + width: '@' + }, + template: '\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + ' + } +}) + +angular.module('linuxDash').directive('multiLineChartPlugin', [ + '$interval', '$compile', 'server', '$window', + function ($interval, $compile, server, $window) { + return { + scope: { + heading: '@', + moduleName: '@', + refreshRate: '=', + getDisplayValue: '=', + units: '=', + delay: '=' + }, + templateUrl: 'src/js/core/features/multi-line-chart/multi-line-chart-plugin.html', + link: function(scope, element) { + + var w, h, canvas + + angular.element($window).bind('resize', function() { + canvas.width = w + canvas.height = h + }) + + // smoothieJS - Create new chart + var chart = new SmoothieChart({ + borderVisible: false, + sharpLines: true, + grid: { + fillStyle: '#ffffff', + strokeStyle: 'rgba(232,230,230,0.93)', + sharpLines: true, + borderVisible: false + }, + labels: { + fontSize: 12, + precision: 0, + fillStyle: '#0f0e0e' + }, + maxValue: 100, + minValue: 0, + horizontalLines: [{ + value: 1, + color: '#ecc', + lineWidth: 1 + }] + }) + + var seriesOptions = [ + { + strokeStyle: 'rgba(255, 0, 0, 1)', + lineWidth: 2 + }, + { + strokeStyle: 'rgba(0, 255, 0, 1)', + lineWidth: 2 + }, + { + strokeStyle: 'rgba(0, 0, 255, 1)', + lineWidth: 2 + }, + { + strokeStyle: 'rgba(255, 255, 0, 1)', + lineWidth: 1 + } + ] + + // smoothieJS - set up canvas element for chart + scope.seriesArray = [] + scope.metricsArray = [] + + var delay = 1000 + + if (angular.isDefined(scope.delay)) + delay = scope.delay + + var initializeChart = function () { + // smoothieJS - set up canvas element for chart + var checkForCanvasReadyState = $interval(function () { + if (element.find('canvas')[0]) { + canvas = element.find('canvas')[0] + w = canvas.width + h = canvas.height + + // get the data once to set up # of lines on chart + server.get(scope.moduleName, function(serverResponseData) { + + var numberOfLines = Object.keys(serverResponseData).length + + for (var x = 0; x < numberOfLines; x++) { + + var keyForThisLine = Object.keys(serverResponseData)[x]; + + scope.seriesArray[x] = new TimeSeries(); + chart.addTimeSeries(scope.seriesArray[x], seriesOptions[x]); + scope.metricsArray[x] = { + name: keyForThisLine, + color: seriesOptions[x].strokeStyle, + } + } + + }) + + chart.streamTo(canvas, delay) + $interval.cancel(checkForCanvasReadyState) + } + }, 100) + } + + scope.reInitializeChart = function () { + chart.seriesSet.forEach(function (ts) { + chart.removeTimeSeries(ts.timeSeries) + }) + + initializeChart() + } + + if (!scope.isHidden) + initializeChart() + + var dataCallInProgress = false + + // update data on chart + scope.getData = function() { + + if (dataCallInProgress) return + + if (!scope.seriesArray.length) return + + dataCallInProgress = true + + server.get(scope.moduleName, function(serverResponseData) { + + dataCallInProgress = false + scope.lastGet = new Date().getTime() + var keyCount = 0 + var maxAvg = 100 + + // update chart with current response + for (var key in serverResponseData) { + scope.seriesArray[keyCount].append(scope.lastGet, serverResponseData[key]) + keyCount++ + maxAvg = Math.max(maxAvg, serverResponseData[key]) + } + + // update the metrics for this chart + scope.metricsArray.forEach(function(metricObj) { + metricObj.data = serverResponseData[metricObj.name].toString() + ' ' + scope.units + }) + + // round up the average and set the maximum scale + var len = parseInt(Math.log(maxAvg) / Math.log(10)) + var div = Math.pow(10, len) + chart.options.maxValue = Math.ceil(maxAvg / div) * div + + }) + + } + + var refreshRate = (angular.isDefined(scope.refreshRate)) ? scope.refreshRate : 1000 + var intervalRef = $interval(scope.getData, refreshRate) + var removeInterval = function() { + $interval.cancel(intervalRef) + } + + element.on("$destroy", removeInterval) + } + } +}]) + +angular.module('linuxDash').directive('navBar', ['$location', function($location) { + return { + template: '\ + \ + Linux Dash (Demo)\ + \ +
    \ +
  • \ + \ +
  • \ +
\ + \ + Resources:\ + GitHub | \ + Gitter Chat Room | \ + Docs \ + \ + ', + link: function(scope) { + scope.items = [ + 'system-status', + 'basic-info', + 'network', + 'accounts', + 'apps' + ] + + scope.getNavItemName = function(url) { + return url.replace('-', ' ') + } + + scope.isActive = function(route) { + return '/' + route === $location.path() + } + } + } +}]) + +angular.module('linuxDash').directive('plugin', ['$rootScope', function($rootScope) { + return { + transclude: true, + templateUrl: 'src/js/core/features/plugin/plugin.html', + link: function (s, el, attr) { + + if (attr.hasOwnProperty('chartPlugin')) + s.isChartPlugin = true + + if ($rootScope.hiddenPlugins.indexOf(s.moduleName) > -1) + s.isHidden = true + + s.toggleWidth = function () { + el.find('div')[0].removeAttribute('style') + s.enlarged = !s.enlarged + } + + var setPluginVisibility = function (shouldShow) { + s.isHidden = !shouldShow + + if (shouldShow) { + $rootScope.$emit('show-plugin', s.moduleName) + if (s.isChartPlugin) s.reInitializeChart() + } else { + $rootScope.$emit('hide-plugin', s.moduleName) + } + } + + s.toggleVisibility = function () { + setPluginVisibility(s.isHidden) + } + + + s.$watch('emptyResult', function (n, o) { + if (n) { + setPluginVisibility(false) + } + }) + } + } +}]) + +angular.module('linuxDash').directive('progressBarPlugin', function() { + return { + scope: { + width: '@', + moduleName: '@', + name: '@', + value: '@', + max: '@' + }, + template: '\ +
\ +
\ +
\ +
\ +
\ + ' + } +}) + +angular.module('linuxDash').directive('tableData', ['server', '$rootScope', function (server, $rootScope) { + return { + scope: { + heading: '@', + info: '@', + moduleName: '@', + width: '@', + height: '@' + }, + templateUrl: 'src/js/core/features/table-data/table-data.html', + link: function(scope, element) { + + scope.sortByColumn = null + scope.sortReverse = null + + // set the column to sort by + scope.setSortColumn = function(column) { + + // if the column is already being sorted + // reverse the order + if (column === scope.sortByColumn) { + scope.sortReverse = !scope.sortReverse + } else { + scope.sortByColumn = column + } + + scope.sortTableRows() + } + + scope.sortTableRows = function() { + scope.tableRows.sort(function(currentRow, nextRow) { + + var sortResult = 0 + + if (currentRow[scope.sortByColumn] < nextRow[scope.sortByColumn]) { + sortResult = -1 + } else if (currentRow[scope.sortByColumn] === nextRow[scope.sortByColumn]) { + sortResult = 0 + } else { + sortResult = 1 + } + + if (scope.sortReverse) { + sortResult = -1 * sortResult + } + + return sortResult + }) + } + + scope.getData = function() { + delete scope.tableRows + + server.get(scope.moduleName, function(serverResponseData) { + + if (serverResponseData.length > 0) { + scope.tableHeaders = Object.keys(serverResponseData[0]) + } + + scope.tableRows = serverResponseData + + if (scope.sortByColumn) { + scope.sortTableRows() + } + + scope.lastGet = new Date().getTime() + + if (serverResponseData.length < 1) { + scope.emptyResult = true + } + + if (!scope.$$phase && !$rootScope.$$phase) scope.$digest() + }) + } + + scope.getData() + } + } +}]) + +angular.module('linuxDash').directive('topBar', ['$rootScope', function($rootScope) { + return { + scope: { + heading: '=', + refresh: '&', + lastUpdated: '=', + toggleVisibility: '&', + isHidden: '=', + toggleWidth: '&', + isChart: '=', + info: '=', // not being used; needs a good ui solution + }, + template: '\ +
\ + ☰ {{ heading }} \ + \ + \ + \ + \ + \ + \ +
\ + ', + } +}]) + +angular.module("linuxDash").run(["$templateCache", function($templateCache) {$templateCache.put("src/js/plugins/disk-space/disk-space.html","\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameStatsUsedMount
{{partition[\'file_system\']}}\n \n \n \n {{ partition[\'used\'] }} / {{ partition[\'size\'] }}\n \n {{ partition[\'used%\'] }}\n {{ partition[\'mounted\'] }}
\n\n\n"); +$templateCache.put("src/js/core/features/key-value-list/key-value-list.html","\n\n \n\n
\n \n \n \n \n \n \n \n
{{ name }}{{ value }}
\n\n
\n\n No data\n\n"); +$templateCache.put("src/js/core/features/line-chart/line-chart-plugin.html","\n\n \n\n \n \n\n \n \n \n \n \n \n \n
{{ metric.name }}{{ metric.data }}
\n\n No data\n\n
\n"); +$templateCache.put("src/js/core/features/multi-line-chart/multi-line-chart-plugin.html","\n\n \n\n \n \n \n \n \n \n \n \n
\n \n \n {{ metric.name }}{{ metric.data }}
\n\n
\n"); +$templateCache.put("src/js/core/features/plugin/plugin.html","\n\n \n \n\n \n \n\n\n"); +$templateCache.put("src/js/core/features/table-data/table-data.html","\n\n \n\n
\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n {{ header }}\n \n {{ (header === sortByColumn && !sortReverse) ? \'▲\': \'\'; }}\n {{ (header === sortByColumn && sortReverse) ? \'▼\': \'\'; }}\n \n
\n {{ row[header] }}\n
\n\n
\n\n No data\n\n");}]); \ No newline at end of file diff --git a/server/modules/config/ping_hosts b/app/server/config/ping_hosts similarity index 100% rename from server/modules/config/ping_hosts rename to app/server/config/ping_hosts diff --git a/server/index.go b/app/server/index.go similarity index 78% rename from server/index.go rename to app/server/index.go index 4ec8787e..3827425f 100644 --- a/server/index.go +++ b/app/server/index.go @@ -7,13 +7,11 @@ import ( "net/http" "os" "os/exec" - "path/filepath" ) var ( listenAddress = flag.String("listen", "0.0.0.0:80", "Where the server listens for connections. [interface]:port") staticPath = flag.String("static", "../", "Location of static files.") - scriptPath = flag.String("scripts", "./modules/shell_files", "Location of shell scripts used to gather stats.") ) func init() { @@ -24,19 +22,18 @@ func main() { http.Handle("/", http.FileServer(http.Dir(*staticPath))) http.HandleFunc("/server/", func(w http.ResponseWriter, r *http.Request) { module := r.URL.Query().Get("module") - script := filepath.Join(*scriptPath, module+".sh") if module == "" { http.Error(w, "No module specified, or requested module doesn't exist.", 406) return } // Execute the command - cmd := exec.Command(script) + cmd := exec.Command("./linux_json_api.sh", module) var output bytes.Buffer cmd.Stdout = &output err := cmd.Run() if err != nil { - fmt.Printf("Error executing '%s': %s\n\tScript output: %s\n", script, err.Error(), output.String()) + fmt.Printf("Error executing '%s': %s\n\tScript output: %s\n", module, err.Error(), output.String()) http.Error(w, "Unable to execute module.", http.StatusInternalServerError) return } diff --git a/app/server/index.js b/app/server/index.js new file mode 100644 index 00000000..476d149f --- /dev/null +++ b/app/server/index.js @@ -0,0 +1,76 @@ +var express = require('express') +var app = require('express')() +var server = require('http').Server(app) +var path = require('path') +var spawn = require('child_process').spawn +var fs = require('fs') +var ws = require('websocket').server +var args = require('yargs').argv +var port = args.port || process.env.LINUX_DASH_SERVER_PORT || 80 + +server.listen(port, function() { + console.log('Linux Dash Server Started on port ' + port + '!'); +}) + +app.use(express.static(path.resolve(__dirname + '/../'))) + +app.get('/', function (req, res) { + res.sendFile(path.resolve(__dirname + '/../index.html')) +}) + +app.get('/websocket', function (req, res) { + + res.send({ + websocket_support: true, + }) + +}) + +wsServer = new ws({ + httpServer: server +}) + +var nixJsonAPIScript = __dirname + '/linux_json_api.sh' + +function getPluginData(pluginName, callback) { + var command = spawn(nixJsonAPIScript, [ pluginName, '' ]) + var output = [] + + command.stdout.on('data', function(chunk) { + output.push(chunk.toString()) + }) + + command.on('close', function (code) { + callback(code, output) + }) +} + +wsServer.on('request', function(request) { + + var wsClient = request.accept('', request.origin) + + wsClient.on('message', function(wsReq) { + + var moduleName = wsReq.utf8Data + var sendDataToClient = function(code, output) { + if (code === 0) { + var wsResponse = '{ "moduleName": "' + moduleName + '", "output": "'+ output.join('') +'" }' + wsClient.sendUTF(wsResponse) + } + } + + getPluginData(moduleName, sendDataToClient) + + }) + +}) + +app.get('/server/', function (req, res) { + + var respondWithData = function(code, output) { + if (code === 0) res.send(output.toString()) + else res.sendStatus(500) + } + + getPluginData(req.query.module, respondWithData) +}) diff --git a/server/index.php b/app/server/index.php similarity index 56% rename from server/index.php rename to app/server/index.php index 296a5519..983875f2 100644 --- a/server/index.php +++ b/app/server/index.php @@ -2,7 +2,7 @@ header("Cache-Control: no-store, no-cache, must-revalidate"); header("Pragma: no-cache"); - $modules_dir = dirname(__FILE__) . '/modules/shell_files/'; + $shell_file = dirname(__FILE__) . '/linux_json_api.sh'; $module = escapeshellcmd($_GET['module']); - echo shell_exec( $modules_dir . $module . '.sh' ); + echo shell_exec( $shell_file . " " . $module ); diff --git a/python-server.py b/app/server/index.py old mode 100755 new mode 100644 similarity index 83% rename from python-server.py rename to app/server/index.py index c6440abb..f3f29d7a --- a/python-server.py +++ b/app/server/index.py @@ -13,8 +13,8 @@ parser.add_argument('--port', metavar='PORT', type=int, nargs='?', default=80, help='Port to run the server on.') -modulesSubPath = '/server/modules/shell_files/' -serverPath = os.path.dirname(os.path.realpath(__file__)) +modulesSubPath = '/server/linux_json_api.sh' +appRootPath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): pass @@ -27,16 +27,16 @@ def do_GET(self): if self.path.startswith("/server/"): module = self.path.split('=')[1] output = subprocess.Popen( - serverPath + modulesSubPath + module + '.sh', + appRootPath + modulesSubPath + " " + module, shell = True, stdout = subprocess.PIPE) data = output.communicate()[0] else: if self.path == '/': self.path = 'index.html' - f = open(os.path.dirname(os.path.realpath(__file__)) + os.sep + self.path) + f = open(appRootPath + os.sep + self.path) data = f.read() - if self.path.startswith('/css/'): + if self.path.startswith('/main.css'): contentType = 'text/css' f.close() self.send_response(200) diff --git a/app/server/linux_json_api.sh b/app/server/linux_json_api.sh new file mode 100755 index 00000000..6d5222c1 --- /dev/null +++ b/app/server/linux_json_api.sh @@ -0,0 +1,641 @@ +#!/bin/bash + +_parseAndPrint() { + while read data; do + echo -n "$data" | sed -r "s/\"/\\\\\"/g" | tr -d "\n"; + done; +} + +arp_cache() { + arpCommand=$(command -v arp) + + result=$($arpCommand | awk 'BEGIN {print "["} NR>1 \ + {print "{ \"addr\": \"" $1 "\", " \ + "\"hw_type\": \"" $2 "\", " \ + "\"hw_addr.\": \"" $3 "\", " \ + "\"mask\": \"" $5 "\" }, " \ + } \ + END {print "]"}' \ + | /bin/sed 'N;$s/},/}/;P;D') + + if [ -z "$result" ]; then echo {} + else echo $result | _parseAndPrint + fi +} + +bandwidth() { + + /bin/cat /proc/net/dev \ + | awk 'BEGIN {print "["} NR>2 {print "{ \"interface\": \"" $1 "\"," \ + " \"tx\": " $2 "," \ + " \"rx\": " $10 " }," } END {print "]"}' \ + | /bin/sed 'N;$s/,\n/\n/;P;D' \ + | _parseAndPrint +} + +common_applications() { + result=$(whereis php node mysql mongo vim python ruby java apache2 nginx openssl vsftpd make \ + | awk -F: '{if(length($2)==0) { installed="false"; } else { installed="true"; } \ + print \ + "{ \ + \"binary\": \""$1"\", \ + \"location\": \""$2"\", \ + \"installed\": "installed" \ + },"}') + + echo "[" ${result%?} "]" | _parseAndPrint +} + +cpu_info() { + + result=$(/usr/bin/lscpu \ + | /usr/bin/awk -F: '{print "\""$1"\": \""$2"\"," } '\ + ) + + echo "{" ${result%?} "}" | _parseAndPrint +} + +cpu_intensive_processes() { + + result=$(/bin/ps axo pid,user,pcpu,rss,vsz,comm --sort -pcpu,-rss,-vsz \ + | head -n 15 \ + | /usr/bin/awk 'BEGIN{OFS=":"} NR>1 {print "{ \"pid\": " $1 \ + ", \"user\": \"" $2 "\"" \ + ", \"cpu%\": " $3 \ + ", \"rss\": " $4 \ + ", \"vsz\": " $5 \ + ", \"cmd\": \"" $6 "\"" "},"\ + }') + + echo "[" ${result%?} "]" | _parseAndPrint +} + +cpu_temp() { + + if [ `which sensors` ]; then + returnString=`sensors` + #amd + if [[ "${returnString/"k10"}" != "${returnString}" ]] ; then + echo ${returnString##*k10} | cut -d ' ' -f 6 | cut -c 2- | cut -c 1-4 + #intel + elif [[ "${returnString/"core"}" != "${returnString}" ]] ; then + fromcore=${returnString##*"coretemp"} + echo ${fromcore##*Physical} | cut -d ' ' -f 3 | cut -c 2-5 | _parseAndPrint + fi + else + echo "[]" | _parseAndPrint + fi +} + +# by Paul Colby (http://colby.id.au), no rights reserved ;) +cpu_utilization() { + + PREV_TOTAL=0 + PREV_IDLE=0 + iteration=0 + + while [[ iteration -lt 2 ]]; do + # Get the total CPU statistics, discarding the 'cpu ' prefix. + CPU=(`sed -n 's/^cpu\s//p' /proc/stat`) + IDLE=${CPU[3]} # Just the idle CPU time. + + # Calculate the total CPU time. + TOTAL=0 + for VALUE in "${CPU[@]}"; do + let "TOTAL=$TOTAL+$VALUE" + done + + # Calculate the CPU usage since we last checked. + let "DIFF_IDLE=$IDLE-$PREV_IDLE" + let "DIFF_TOTAL=$TOTAL-$PREV_TOTAL" + let "DIFF_USAGE=(1000*($DIFF_TOTAL-$DIFF_IDLE)/$DIFF_TOTAL+5)/10" + #echo -en "\rCPU: $DIFF_USAGE% \b\b" + + # Remember the total and idle CPU times for the next check. + PREV_TOTAL="$TOTAL" + PREV_IDLE="$IDLE" + + # Wait before checking again. + sleep 1 + iteration="$iteration+1" + done + echo -en "$DIFF_USAGE" +} + +cron_history() { + + grepCmd=$(which grep) + cronLog='/var/log/syslog' + numberOfLines='50' + + # Month, Day, Time, Hostname, tag, user, + + result=$($grepCmd -m$numberOfLines CRON $cronLog \ + | awk '{ s = ""; for (i = 6; i <= NF; i++) s = s $i " "; \ + print "{\"time\" : \"" $1" "$2" "$3 "\"," \ + "\"user\" : \"" $6 "\"," \ + "\"message\" : \"" $5" "gensub("\"", "\\\\\"", "g", s) "\"" \ + "}," + }' + ) + + echo [${result%?}] | _parseAndPrint +} + +current_ram() { + + awkCmd=`which awk` + catCmd=`which cat` + grepCmd=`which grep` + memInfoFile="/proc/meminfo" + + # References: + # Calculations: http://zcentric.com/2012/05/29/mapping-procmeminfo-to-output-of-free-command/ + # Fields: https://www.kernel.org/doc/Documentation/filesystems/proc.txt + + memInfo=`$catCmd $memInfoFile | $grepCmd 'MemTotal\|MemFree\|Buffers\|Cached'` + + echo $memInfo | $awkCmd '{print "{ \"total\": " ($2/1024) ", \"used\": " ( ($2-($5+$8+$11))/1024 ) ", \"available\": " (($5+$8+$11)/1024) " }" }' | _parseAndPrint +} + +disk_partitions() { + + result=$(/bin/df -Ph | awk 'NR>1 {print "{\"file_system\": \"" $1 "\", \"size\": \"" $2 "\", \"used\": \"" $3 "\", \"avail\": \"" $4 "\", \"used%\": \"" $5 "\", \"mounted\": \"" $6 "\"},"}') + + echo [ ${result%?} ] | _parseAndPrint +} + +docker_processes() { + + result="" + containers="$(docker ps | awk '{if(NR>1) print $NF}')" + for i in $containers; do + result="$result $(/usr/bin/docker top $i axo pid,user,pcpu,pmem,comm --sort -pcpu,-pmem \ + | head -n 15 \ + | /usr/bin/awk -v cnt="$i" 'BEGIN{OFS=":"} NR>1 {print "{ \"cname\": \"" cnt \ + "\", \"pid\": " $1 \ + ", \"user\": \"" $2 "\"" \ + ", \"cpu%\": " $3 \ + ", \"mem%\": " $4 \ + ", \"cmd\": \"" $5 "\"" "},"\ + }')" + done + + echo "[" ${result%?} "]" | _parseAndPrint +} + +download_transfer_rate() { + + files=(/sys/class/net/*) + pos=$(( ${#files[*]} - 1 )) + last=${files[$pos]} + + json_output="{" + + for interface in "${files[@]}" + do + basename=$(basename "$interface") + + # find the number of bytes transfered for this interface + in1=$(cat /sys/class/net/"$basename"/statistics/rx_bytes) + + # wait a second + sleep 1 + + # check same interface again + in2=$(cat /sys/class/net/"$basename"/statistics/rx_bytes) + + # get the difference (transfer rate) + in_bytes=$((in2 - in1)) + + # convert transfer rate to KB + in_kbytes=$((in_bytes / 1024)) + + # convert transfer rate to KB + json_output="$json_output \"$basename\": $in_kbytes" + + # if it is not the last line + if [[ ! $interface == $last ]] + then + # add a comma to the line (JSON formatting) + json_output="$json_output," + fi + done + + # close the JSON object & print to screen + echo "$json_output}" | _parseAndPrint +} + +general_info() { + + function displaytime { + local T=$1 + local D=$((T/60/60/24)) + local H=$((T/60/60%24)) + local M=$((T/60%60)) + local S=$((T%60)) + [[ $D > 0 ]] && printf '%d days ' $D + [[ $H > 0 ]] && printf '%d hours ' $H + [[ $M > 0 ]] && printf '%d minutes ' $M + [[ $D > 0 || $H > 0 || $M > 0 ]] && printf 'and ' + printf '%d seconds\n' $S + } + + lsbRelease=$(/usr/bin/lsb_release -ds | sed -e 's/^"//' -e 's/"$//') + uname=$(/bin/uname -r | sed -e 's/^"//' -e 's/"$//') + os=`echo $lsbRelease $uname` + hostname=$(/bin/hostname) + uptime_seconds=$(/bin/cat /proc/uptime | awk '{print $1}') + server_time=$(date) + + echo "{ \"OS\": \"$os\", \"Hostname\": \"$hostname\", \"Uptime\": \" $(displaytime ${uptime_seconds%.*}) \", \"Server Time\": \"$server_time\" }" | _parseAndPrint +} + +io_stats() { + + result=$(/bin/cat /proc/diskstats | /usr/bin/awk \ + '{ if($4==0 && $8==0 && $12==0 && $13==0) next } \ + {print "{ \"device\": \"" $3 "\", \"reads\": \""$4"\", \"writes\": \"" $8 "\", \"in_prog.\": \"" $12 "\", \"time\": \"" $13 "\"},"}' + ) + + echo [ ${result%?} ] | _parseAndPrint +} + +ip_addresses() { + + awkCmd=`which awk` + grepCmd=`which grep` + sedCmd=`which sed` + ifconfigCmd=`which ifconfig` + trCmd=`which tr` + digCmd=`which dig` + + externalIp=`$digCmd +short myip.opendns.com @resolver1.opendns.com` + + echo -n "[" + + for item in $($ifconfigCmd | $grepCmd -oP "^[a-zA-Z0-9:]*(?=:)") + do + echo -n "{\"interface\" : \""$item"\", \"ip\" : \"$( $ifconfigCmd $item | $grepCmd "inet" | $awkCmd '{match($0,"inet (addr:)?([0-9.]*)",a)}END{ if (NR != 0){print a[2]; exit}{print "none"}}')\"}, " + done + + echo "{ \"interface\": \"external\", \"ip\": \"$externalIp\" } ]" | _parseAndPrint +} + +load_avg() { + + grepCmd=`which grep` + awkCmd=`which awk` + catCmd=`which cat` + + numberOfCores=$($grepCmd -c 'processor' /proc/cpuinfo) + + if [ $numberOfCores -eq 0 ]; then + numberOfCores=1 + fi + + result=$($catCmd /proc/loadavg | $awkCmd '{print "{ \"1_min_avg\": " ($1*100)/'$numberOfCores' ", \"5_min_avg\": " ($2*100)/'$numberOfCores' ", \"15_min_avg\": " ($3*100)/'$numberOfCores' "}," }') + + echo ${result%?} | _parseAndPrint +} + +logged_in_users() { + + result=$(COLUMNS=300 /usr/bin/w -h | /usr/bin/awk '{print "{\"user\": \"" $1 "\", \"from\": \"" $3 "\", \"when\": \"" $4 "\"},"}') + + echo [ ${result%?} ] | _parseAndPrint +} + +memcached() { + echo "stats" \ + | /bin/nc -w 1 127.0.0.1 11211 \ + | /bin/grep 'bytes' \ + | /usr/bin/awk 'BEGIN {print "{"} {print "\"" $2 "\": " $3 } END {print "}"}' \ + | /usr/bin/tr '\r' ',' \ + | /bin/sed 'N;$s/,\n/\n/;P;D' \ + | _parseAndPrint +} + +memory_info() { + + /bin/cat /proc/meminfo \ + | /usr/bin/awk -F: 'BEGIN {print "{"} {print "\"" $1 "\": \"" $2 "\"," } END {print "}"}' \ + | /bin/sed 'N;$s/,\n/\n/;P;D' \ + | _parseAndPrint +} + +network_connections() { + + netstatCmd=`which netstat` + awkCmd=`which awk` + sortCmd=`which sort` + uniqCmd=`which uniq` + sedCmd=`which sed` + + $netstatCmd -ntu \ + | $awkCmd 'NR>2 {print $5}' \ + | $sortCmd \ + | $uniqCmd -c \ + | $awkCmd 'BEGIN {print "["} {print "{ \"connections\": " $1 ", \"address\": \"" $2 "\" }," } END {print "]"}' \ + | $sedCmd 'N;$s/},/}/;P;D' \ + | _parseAndPrint +} + +number_of_cpu_cores() { + + numberOfCPUCores=$(/bin/grep -c 'model name' /proc/cpuinfo) + + if [ length $numberOfCPUCores ]; then + echo "cannnot be found"; + fi +} + +# http://askubuntu.com/questions/413367/ping-multiple-ips-using-bash +ping() { + + # get absolute path to config file + SCRIPTPATH=`dirname $(readlink -f $0)` + CONFIG_PATH=$SCRIPTPATH"/config/ping_hosts" + + catCmd=`which cat` + pingCmd=`which ping` + awkCmd=`which awk` + sedCmd=`which sed` + numOfLinesInConfig=`$sedCmd -n '$=' $CONFIG_PATH` + result='[' + + $catCmd $CONFIG_PATH \ + | while read output + do + singlePing=$($pingCmd -qc 2 $output \ + | $awkCmd -F/ 'BEGIN { endLine="}," } /^rtt/ { if ('$numOfLinesInConfig'==1){endLine="}"} print "{" "\"host\": \"'$output'\", \"ping\": " $5 " " endLine }' \ + ) + numOfLinesInConfig=$(($numOfLinesInConfig-1)) + result=$result$singlePing + if [ $numOfLinesInConfig -eq 0 ] + then + echo $result"]" + fi + done \ + | $sedCmd 's/\},]/}]/g' \ + | _parseAndPrint +} + +pm2_stats() { + + #get data + command="pm2 list" + data="$($command)" + + #only process data if variable has a length + #this should handle cases where pm2 is not installed + if [ -n "$data" ]; then + + #start processing data on line 4 + #don't process last 2 lines + json=$( echo "$data" | tail -n +4 | head -n +2 \ + | awk '{print "{"}\ + {print "\"appName\":\"" $2 "\","} \ + {print "\"id\":\"" $4 "\","} \ + {print "\"mode\":\"" $6 "\","} \ + {print "\"pid\":\"" $8 "\","}\ + {print "\"status\":\"" $10 "\","}\ + {print "\"restart\":\"" $12 "\","}\ + {print "\"uptime\":\"" $14 "\","}\ + {print "\"memory\":\"" $16 $17 "\","}\ + {print "\"watching\":\"" $19 "\""}\ + {print "},"}') + #make sure to remove last comma and print in array + echo "[" ${json%?} "]" | _parseAndPrint + else + #no data found + echo "[]" | _parseAndPrint + fi +} + +ram_intensive_processes() { + + result=$(/bin/ps axo pid,user,pmem,rss,vsz,comm --sort -pmem,-rss,-vsz \ + | head -n 15 \ + | /usr/bin/awk 'NR>1 {print "{ \"pid\": " $1 \ + ", \"user\": \"" $2 \ + "\", \"mem%\": " $3 \ + ", \"rss\": " $4 \ + ", \"vsz\": " $5 \ + ", \"cmd\": \"" $6 \ + "\"},"}') + + echo [ ${result%?} ] | _parseAndPrint +} + +recent_account_logins() { + + result=$(/usr/bin/lastlog -t 365 \ + | /usr/bin/awk 'NR>1 {\ + print "{ \ + \"user\": \"" $1 "\", \ + \"ip\": \"" $3 "\","" \ + \"date\": \"" $5" "$6" "$7" "$8" "$9 "\"}," + }' + ) + echo [ ${result%?} ] | _parseAndPrint +} + +redis() { + + ########### Enter Your Redis Password HERE ######### + redisPassword='' + ########### Enter Your Redis Password HERE ######### + + redisCommand=$(which redis-cli); + + if [ -n "$redisPassword" ]; then + redisCommand="$redisCommand -a $redisPassword" + fi + + result=$($redisCommand INFO \ + | grep 'redis_version\|connected_clients\|connected_slaves\|used_memory_human\|total_connections_received\|total_commands_processed' \ + | awk -F: '{print "\"" $1 "\":" "\"" $2 }' \ + | tr '\r' '"' | tr '\n' ',' + ) + echo { ${result%?} } | _parseAndPrint +} + +scheduled_crons() { + + ###### + # Credit: http://stackoverflow.com/questions/134906/how-do-i-list-all-cron-jobs-for-all-users#answer-137173 + ###### + + catCmd=`which cat` + awkCmd=`which awk` + sedCmd=`which sed` + egrepCmd=`which egrep` + echoCmd=`which echo` + crontabCmd=`which crontab` + trCmd=`which tr` + + # System-wide crontab file and cron job directory. Change these for your system. + CRONTAB='/etc/crontab' + CRONDIR='/etc/cron.d' + + # Single tab character. Annoyingly necessary. + tab=$(echo -en "\t") + + # Given a stream of crontab lines, exclude non-cron job lines, replace + # whitespace characters with a single space, and remove any spaces from the + # beginning of each line. + function clean_cron_lines() { + while read line ; do + $echoCmd "${line}" | + $egrepCmd --invert-match '^($|\s*#|\s*[[:alnum:]_]+=)' | + $sedCmd --regexp-extended "s/\s+/ /g" | + $sedCmd --regexp-extended "s/^ //" + done; + } + + # Given a stream of cleaned crontab lines, $echoCmd any that don't include the + # run-parts command, and for those that do, show each job file in the run-parts + # directory as if it were scheduled explicitly. + function lookup_run_parts() { + while read line ; do + match=$($echoCmd "${line}" | $egrepCmd -o 'run-parts (-{1,2}\S+ )*\S+') + + if [[ -z "${match}" ]] ; then + $echoCmd "${line}" + else + cron_fields=$($echoCmd "${line}" | cut -f1-6 -d' ') + cron_job_dir=$($echoCmd "${match}" | awk '{print $NF}') + + if [[ -d "${cron_job_dir}" ]] ; then + for cron_job_file in "${cron_job_dir}"/* ; do # */ + [[ -f "${cron_job_file}" ]] && $echoCmd "${cron_fields} ${cron_job_file}" + done + fi + fi + done; + } + + # Temporary file for crontab lines. + temp=$(mktemp) || exit 1 + + # Add all of the jobs from the system-wide crontab file. + $catCmd "${CRONTAB}" | clean_cron_lines | lookup_run_parts >"${temp}" + + # Add all of the jobs from the system-wide cron directory. + $catCmd "${CRONDIR}"/* | clean_cron_lines >>"${temp}" # */ + + # Add each user's crontab (if it exists). Insert the user's name between the + # five time fields and the command. + while read user ; do + $crontabCmd -l -u "${user}" 2>/dev/null | + clean_cron_lines | + $sedCmd --regexp-extended "s/^((\S+ +){5})(.+)$/\1${user} \3/" >>"${temp}" + done < <(cut --fields=1 --delimiter=: /etc/passwd) + + # Output the collected crontab lines. + + ## Changes: Parses output into JSON + + $catCmd "${temp}" \ + | awk 'BEGIN {print "["} \ + {print "{ \"min\": \"" $1 \ + "\", \"hrs\": \"" $2 "\", " \ + " \"day\": \"" $3 "\", " \ + " \"month\": \"" $4 "\", " \ + " \"wkday\": \"" $5 "\", " \ + " \"user\": \"" $6 "\", " \ + " \"CMD\": \""} \ + {for(i=7;i<=NF;++i) printf("%s ", gensub("\"", "\\\\\"", "g", $i) ) } \ + {print "\" " \ + "}," } \ + END {print "]"}' \ + | $sedCmd 'N;$s/,\n//;P;D' \ + | _parseAndPrint + + rm --force "${temp}" +} + +swap() { + + catCmd=`which cat`; + wcCmd=`which wc`; + awkCmd=`which awk` + + swapLineCount=$($catCmd /proc/swaps | $wcCmd -l) + + if [ "$swapLineCount" -gt 1 ]; then + + result=$($catCmd /proc/swaps \ + | $awkCmd 'NR>1 {print "{ \"filename\": \"" $1"\", \"type\": \""$2"\", \"size\": \""$3"\", \"used\": \""$4"\", \"priority\": \""$5"\"}," }' + ) + + echo [ ${result%?} ] | _parseAndPrint + + else + echo [] | _parseAndPrint + fi +} + +upload_transfer_rate() { + + files=(/sys/class/net/*) + pos=$(( ${#files[*]} - 1 )) + last=${files[$pos]} + + json_output="{" + + for interface in "${files[@]}" + do + basename=$(basename "$interface") + + # find the number of bytes transfered for this interface + out1=$(cat /sys/class/net/"$basename"/statistics/tx_bytes) + + # wait a second + sleep 1 + + # check same interface again + out2=$(cat /sys/class/net/"$basename"/statistics/tx_bytes) + + # get the difference (transfer rate) + out_bytes=$((out2 - out1)) + + # convert transfer rate to KB + out_kbytes=$((out_bytes / 1024)) + + # convert transfer rate to KB + json_output="$json_output \"$basename\": $out_kbytes" + + # if it is not the last line + if [[ ! $interface == $last ]] + then + # add a comma to the line (JSON formatting) + json_output="$json_output," + fi + done + + # close the JSON object & print to screen + echo "$json_output}" | _parseAndPrint +} + +user_accounts() { + + result=$(/usr/bin/awk -F: '{ \ + if ($3<=499){userType="system";} \ + else {userType="user";} \ + print "{ \"type\": \"" userType "\"" ", \"user\": \"" $1 "\", \"home\": \"" $6 "\" }," }' < /etc/passwd + ) + + length=$(echo ${#result}) + + if [ $length -eq 0 ]; then + result=$(getent passwd | /usr/bin/awk -F: '{ if ($3<=499){userType="system";} else {userType="user";} print "{ \"type\": \"" userType "\"" ", \"user\": \"" $1 "\", \"home\": \"" $6 "\" }," }') + fi + + echo [ ${result%?} ] | _parseAndPrint +} + +fnCalled="$1" + +${fnCalled} diff --git a/bin/linux-dash b/bin/linux-dash index 6093229e..591c4230 100755 --- a/bin/linux-dash +++ b/bin/linux-dash @@ -1,3 +1,2 @@ #!/usr/bin/env node - -require('../server/index.js') +require('../app/server/index.js') diff --git a/composer.json b/composer.json deleted file mode 100644 index 3e627604..00000000 --- a/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "afaqurk/linux-dash", - "description": "A drop-in, low-overhead monitoring web dashboard for a linux machine.", - "license": "MIT", - "version": "1.2.0", - "keywords": ["linux", "dashboard", "linux-dash", "server dashboard"], - "authors": [ - { - "name": "Afaq Tariq", - "email": "afaq05@gmail.com" - } - ], - "minimum-stability": "stable", - "require": { - "php": ">=5.4.0" - }, - "support": { - "issues": "https://github.com/afaqurk/linux-dash/issues?state=open", - "forum": "https://gitter.im/afaqurk/linux-dash", - "source": "https://github.com/afaqurk/linux-dash" - } -} diff --git a/css/animate.css b/css/animate.css deleted file mode 100644 index 14afba9c..00000000 --- a/css/animate.css +++ /dev/null @@ -1,6 +0,0 @@ -@charset "UTF-8";/*! -Animate.css - http://daneden.me/animate -Licensed under the MIT license - http://opensource.org/licenses/MIT - -Copyright (c) 2015 Daniel Eden -*/.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes pulse{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes rubberBand{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}@keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes tada{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg)}100%{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0) rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0) rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0) rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0) rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0) rotate3d(0,0,1,-1deg)}100%{-webkit-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}100%{opacity:1;-webkit-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-360deg);transform:perspective(400px) rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-360deg);transform:perspective(400px) rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);transform:perspective(400px) translate3d(0,0,150px) rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}100%{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}100%{-webkit-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}100%{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}100%{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{-webkit-transform-origin:center;transform-origin:center;opacity:1}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}@keyframes rotateOut{0%{-webkit-transform-origin:center;transform-origin:center;opacity:1}100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}@keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}100%{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}100%{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg)}100%{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0) rotate3d(0,0,1,-120deg)}100%{opacity:1;-webkit-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg)}}@keyframes rollOut{0%{opacity:1}100%{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0) rotate3d(0,0,1,120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}100%{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}100%{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}100%{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp} \ No newline at end of file diff --git a/css/main.css b/css/main.css deleted file mode 100644 index ac8b55c3..00000000 --- a/css/main.css +++ /dev/null @@ -1,485 +0,0 @@ -/***** Template ****/ -* { - font-family: 'Merriweather', Arial, sans-serif; - letter-spacing: .1rem; -} -html { - margin-top: 0px; - padding-top: 0; - border-top: 1px solid #4F8EF7; - - background: url(../img/themes/crossword.png); - - -webkit-transition: all 1s ease; - -moz-transition: all 1s ease; - -ms-transition: all 1s ease; - -o-transition: all 1s ease; - transition: all 1s ease; -} -body { - padding: 0; -} -.title { - color: rgb(34, 34, 34); - display: block; - font-size: 30px; - font-weight: 300; - height: 32px; - letter-spacing: .1rem; - line-height: 52.5px; - margin-bottom: 20px; - margin-top: 0px; - text-align: center; -} -.plugin ::-webkit-scrollbar { - width: 8px; - height: 8px; -} -.plugin ::-webkit-scrollbar-track { - background: #eee; - border: thin solid lightgray; - box-shadow: 0px 0px 3px #dfdfdf inset; -} -.plugin ::-webkit-scrollbar-thumb { - background: #999; - border: thin solid gray; - border-radius: 0px; -} -.plugin ::-webkit-scrollbar-thumb:hover { - background: #7d7d7d; -} -.centered { - margin: 0 auto; -} -.hero { - padding: 10px 0 20px 0; - text-align: center; -} -.hero nav-bar { - display: block; -} -.hero nav-bar ul { - margin: 0; - padding: 0; - list-style-type: none; - display: inline; -} -.hero nav-bar ul li a { - font-size: 11px; - text-transform: uppercase; - font-weight: 600; - letter-spacing: .2rem; - margin-right: 35px; - text-decoration: none; - line-height: 2.3rem; - color: #222; -} -.hero nav-bar ul li.active a, -.hero nav-bar ul li a:hover { - color: #1EAEDB; -} -.hero nav-bar ul li { - margin-left: 20px; - display: inline; -} -.hero nav-bar ul li a { - display: inline; -} -#theme-switcher { - position: fixed; - top: 25px; - right: -371px; - border-radius: 5px 0px 0px 5px; - padding: 4px; - z-index: 999999; - cursor: pointer; - color: rgb(255, 255, 255); - background-color: #517fa4; - - -webkit-transition: all 0.5s ease; - -moz-transition: all 0.5s ease; - -ms-transition: all 0.5s ease; - -o-transition: all 0.5s ease; - transition: all 0.5s ease; -} -#theme-switcher .settings-icon{ - font-size: 25px; - height: 100%; - margin-top: 5px; - float: left; - display: inline-block; -} -#theme-switcher .settings-icon:hover{ - color: #243949; -} -#theme-switcher.open { - right: 0; -} -#theme-switcher .option { - margin: 5px; - border: 1px solid transparent; - display: inline-block; - padding: 5px; - color: black; - background-color: #ececec; - border-radius: 2px; - -webkit-transition: all 0.5s ease; - -moz-transition: all 0.5s ease; - -ms-transition: all 0.5s ease; - -o-transition: all 0.5s ease; - transition: all 0.5s ease; -} -#theme-switcher .option:hover, -#theme-switcher .option.selected { - box-shadow: 0 5px 10px rgba(0,0,0,.53),0 3px 10px rgba(0,0,0,.16); - background-color: #FFF94C; -} -#theme-switcher .option.selected:hover { - background-color: lightgrey; -} -@media (min-width: 1080px) { - #plugins { - float: none; - margin: 0 auto; - clear: both; - } -} -@media (max-width: 1079px) { - #plugins { - float: none; - margin: 0 auto; - clear: both; - } -} - -/***** Plugins Container ****/ -#plugins { - text-align: center; - padding: 20px; - padding-top: 0; - margin-top: 0; - border: 1px; -} - -/***** Plugin ****/ -.plugin { - vertical-align: text-top; - width: 470px; - display: inline-block; - padding: 0; - background-color: #FFFFFF; - color: black; - text-align: center; - border-radius: 2px; - box-shadow: 0 5px 10px rgba(0,0,0,.53),0 3px 10px rgba(0,0,0,.16); - margin-top: 10px; - margin: 0 auto; - margin-top: 20px; - margin-left: 20px; - margin-bottom: 50px; -} -@media (max-width: 768px) { - .plugin { - max-width: 80%; - float: none; - margin: 0 auto; - margin-bottom: 10px; - } - .plugin-body { - max-height: 400px; - } -} -.plugin .top-bar { - height: 25px; - max-width: 100%; - padding: 20px; - letter-spacing: .1rem; - line-height: 1.3rem; - font-size: 11px; - text-transform: uppercase; - font-weight: 600; - color: #009587; - text-align: center; -} -.plugin .no-padding { - padding: 0px; -} -.plugin-body { - height: 400px; - font-size: 12px; - padding: 10px; - line-height: 30px; - overflow: auto; - border-top: 1px solid #ececec; -} -.plugin last-update{ - font-size: 11px; - float: left; -} - -no-data { - font-style: italic; -} -refresh-btn button { - background-color: #009587; - border: 0; - float: right; - font-size: 15px; - color: white; - border-radius: 50%; - width: 30px; - height: 30px; - padding: 5px; - - -webkit-transition: all 0.5s ease; - -moz-transition: all 0.5s ease; - -ms-transition: all 0.5s ease; - -o-transition: all 0.5s ease; - transition: all 0.5s ease; - - box-shadow: 0 1px 6px rgba(0,0,0,.12),0 1px 6px rgba(0,0,0,.5); -} -refresh-btn button:hover { - background-color: #ffeb3b; - color: black; -} -refresh-btn button:active { - background-color: #0f9d58; -} - -/**** Loader ****/ -.spinner { - margin: 100px auto; - width: 50px; - height: 30px; - text-align: center; - font-size: 10px; -} - -.spinner > div { - background-color: #009587; - height: 100%; - width: 6px; - display: inline-block; - - -webkit-animation: stretchdelay 1.2s infinite ease-in-out; - animation: stretchdelay 1.2s infinite ease-in-out; -} - -.spinner .rect2 { - -webkit-animation-delay: -1.1s; - animation-delay: -1.1s; -} - -.spinner .rect3 { - -webkit-animation-delay: -1.0s; - animation-delay: -1.0s; -} - -.spinner .rect4 { - -webkit-animation-delay: -0.9s; - animation-delay: -0.9s; -} - -.spinner .rect5 { - -webkit-animation-delay: -0.8s; - animation-delay: -0.8s; -} - -@-webkit-keyframes stretchdelay { - 0%, 40%, 100% { -webkit-transform: scaleY(0.4) } - 20% { -webkit-transform: scaleY(1.0) } -} - -@keyframes stretchdelay { - 0%, 40%, 100% { - transform: scaleY(0.4); - -webkit-transform: scaleY(0.4); - } 20% { - transform: scaleY(1.0); - -webkit-transform: scaleY(1.0); - } -} - -/**** General Elements ****/ -table -{ - width: 100%; - font-size: 10px; - margin: 0; - border-collapse: collapse; - text-align: left; - table-layout:fixed; -} -table th, -table td { - padding: 5px; - max-width: 250px; - word-wrap: break-word; -} -table th -{ - font-weight: 600; - text-transform: uppercase; - border-bottom: 1px solid #f1f1f1; -} -table td { - border-bottom: 1px solid #f1f1f1; - padding: 9px 8px; - font-family: Arial, sans-serif; - font-size: 11px; - letter-spacing: .1em; - color: rgba(0,0,0,.65); -} -table tbody tr:hover td -{ - background-color: #fafafa; -} -table.metrics-table { - text-align: center; -} -canvas { - float: none; - margin: 0 auto; - max-width: 100%; -} -/********************************************* - Widget Elements -*********************************************/ -.progress-bar { - background-color: #eec; - border-radius: 10px; /* (height of inner div) / 2 + padding */ - padding: 0px; - clear: both; - display: inline-block; - overflow: hidden; - white-space: nowrap; -} -.progress-bar > div { - background-color: #1EAEDB; - width: 0%; - height: 5px; - border-radius: 5px; -} -.table-data-plugin .filter-container { - padding-bottom: 0; - margin: 0; -} -.table-data-plugin .filter, -.table-data-plugin .filter:focus, -.table-data-plugin .filter:active { - height: 20px; - padding: 5px; - margin: 5px; - border: none; - outline-color: transparent; - background: transparent; - width: 100%; - margin: 0; - text-align: center; - font-size: 15px; -} -.table-data-plugin .filter:focus { - border-bottom: 1px solid #ff5722; -} -.table-data-plugin thead tr th a, -.table-data-plugin thead tr th a:visited { - color: black; - text-decoration: none; -} -.table-data-plugin .column-sort-caret { - font-size: 10px; - color: #1EAEDB; -} -/* - * Popover - * http://codepen.io/derekpcollins/pen/JCLhG/ - */ - -/* The element to hover over */ -.qs { - cursor: default; - display: inline-block; - position: relative; -} -.qs .popover { - text-transform: none; - background-color: rgba(0, 0, 0, 0.85); - border-radius: 5px; - bottom: 42px; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); - color: #fff; - display: none; - font-size: 12px; - font-family: 'Helvetica',sans-serif; - padding: 7px 10px; - position: absolute; - width: 200px; - z-index: 4; -} -.qs .popover:before { - border-top: 7px solid rgba(0, 0, 0, 0.85); - border-right: 7px solid transparent; - border-left: 7px solid transparent; - bottom: -7px; - content: ''; - display: block; - left: 50%; - margin-left: -7px; - position: absolute; -} -.qs:hover .popover { - display: block; - -webkit-animation: fade-in .3s linear 1, move-up .3s linear 1; - -moz-animation: fade-in .3s linear 1, move-up .3s linear 1; - -ms-animation: fade-in .3s linear 1, move-up .3s linear 1; -} - -@-webkit-keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@-moz-keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@-ms-keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@-webkit-keyframes move-up { - from { - bottom: 30px; - } - to { - bottom: 42px; - } -} -@-moz-keyframes move-up { - from { - bottom: 30px; - } - to { - bottom: 42px; - } -} -@-ms-keyframes move-up { - from { - bottom: 30px; - } - to { - bottom: 42px; - } -} diff --git a/css/theme-old.css b/css/theme-old.css deleted file mode 100644 index 1accf2ff..00000000 --- a/css/theme-old.css +++ /dev/null @@ -1,170 +0,0 @@ -/************************************** - Theme: linux-dash beta -**************************************/ -html.old { - background: #F9F6F1; -} -html.old body { - margin: 0; -} -html.old body * { - font-family: "Open Sans"; - letter-spacing: 0; -} -html.old body .hero { - background: #00BA8B; - color: #ffffff; - padding: 0; -} -html.old body .hero h4 { - color: #ffffff; - display: inline-block; - font-size: 20px; - font-weight: 600; - height: 40px; - line-height: 35px; - margin: 0; - vertical-align: middle; -} -html.old body .hero small { - letter-spacing: 0.1rem; - line-height: 40px; - margin-left: 20px; - opacity: 0.9; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -html.old body .hero #theme-switcher { - right: -315px; -} -html.old body .hero #theme-switcher.open { - right: 0; -} -html.old body .hero nav-bar { - background: #ffffff; - border-bottom: 1px solid #d6d6d6; - color: #333333; -} -html.old body .hero nav-bar br { - display: none; -} -html.old body .hero nav-bar ul { - display: inline-block; -} -html.old body .hero nav-bar ul li { - display: inline-block; - margin: 0; -} -html.old body .hero nav-bar ul li:not(:first-child) { - border-left: 1px solid #e6e6e6; -} -html.old body .hero nav-bar ul li a { - color: #B2AFAA; - display: block; - font-size: 12px; - font-weight: bold; - line-height: 30px; - margin: 0; - padding: 0 15px; - text-transform: capitalize; -} -html.old body .hero nav-bar ul li:hover a { - color: #888888; -} -html.old body #plugins { - display: flex; - flex-flow: row wrap; - justify-content: center; -} -html.old body .plugin { - border: 1px solid #d5d5d5; - border-radius: 0 0 5px 5px; - box-shadow: none; - margin: 10px; -} -html.old body .plugin .top-bar { - background: transparent linear-gradient(to bottom, #f9f6f1 0%, #f2efea 100%) repeat scroll 0px 0px; - border-bottom: 1px solid #d6d6d6; - color: #525252; - font-size: 14px; - font-weight: bold; - height: 40px; - line-height: 40px; - padding: 0 0 0 15px; - position: relative; - text-align: left; - text-transform: none; -} -html.old body .plugin .top-bar last-update { - float: right; - margin: 0 10px; - opacity: 0.8; -} -html.old body .plugin .top-bar refresh-btn { - float: right; -} -html.old body .plugin .top-bar refresh-btn button { - background: #ffffff; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1) inset, 0px 1px 2px rgba(0, 0, 0, 0.1); - color: #555555; - cursor: pointer; - box-shadow: none; - display: inline-block; - float: none; - font-size: 14px; - height: auto; - margin: 0 -5px 0 10px; - padding: 0 4px; - width: auto; -} -html.old body .plugin .top-bar refresh-btn button:hover { - background: #e6e6e6; -} -html.old body .plugin .plugin-body { - border: none; - height: auto; - line-height: normal; - max-height: 300px; - padding: 0; -} -html.old body .plugin .plugin-body table { - border-collapse: separate; - border-spacing: 0; - font-size: 12px; - min-width: 300px; -} -html.old body .plugin .plugin-body table tr:not(:first-child) th { - border-top: 1px solid #dddddd; -} -html.old body .plugin .plugin-body table td, -html.old body .plugin .plugin-body table th { - border-bottom: none; - padding: 4px 5px; -} -html.old body .plugin .plugin-body table td:not(:first-child), -html.old body .plugin .plugin-body table th:not(:first-child) { - border-left: 1px solid #dddddd; -} -html.old body .plugin .plugin-body table th:not(.filter-container) { - background: transparent -moz-linear-gradient(center top, #fafafa 0%, #e9e9e9 100%) repeat scroll 0% 0%; - text-transform: uppercase; -} -html.old body .plugin .plugin-body table th.filter-container .filter { - border-bottom: none; - font-size: 12px; - height: auto; - padding: 2px; -} -html.old body .plugin .plugin-body table th.filter-container .filter::before { - content: 'Search >'; - opacity: 0.5; - position: absolute; - left: 0; -} -html.old body .plugin .plugin-body table td { - border-top: 1px solid #dddddd; -} -html.old body .plugin canvas { - width: 100%; -} diff --git a/css/themes.css b/css/themes.css deleted file mode 100644 index e3a18aa0..00000000 --- a/css/themes.css +++ /dev/null @@ -1,88 +0,0 @@ -@import "theme-old.css"; - -/************************************** - Theme: Winter -**************************************/ - -html.winter { - background: url(../img/themes/contemporary_china_2.png) ; -} -html.winter .hero nav-bar ul li.active a, -html.winter .hero nav-bar ul li a:hover { - color: #23568f; -} -html.winter .plugin { - background-color: rgba(255, 255, 255, 0.60); -} -html.winter .plugin .top-bar { - color: #012e40; -} -html.winter .plugin refresh-btn button { - background-color: #4c6c73; -} -table th { - color: #012e40; -} - -/************************************** - Theme: Summer -**************************************/ - -html.summer { - clear: both; - background: url(../img/themes/congruent_pentagon.png); -} -html.summer .hero nav-bar ul li.active a, -html.summer .hero nav-bar ul li a:hover { - color: #D84315; -} -html.summer .plugin{ - background-color: rgba(255, 255, 255, 0.8); -} -html.summer .plugin .top-bar { - color: #BF360C; -} -html.summer .plugin refresh-btn button { - background-color: #BF360C; -} - -/************************************** - Theme: Spring -**************************************/ - -html.spring { - clear: both; - background: url(../img/themes/food.png); -} -html.spring .hero nav-bar ul li.active a, -html.spring .hero nav-bar ul li a:hover { - color: #E65100; -} -html.spring .plugin { - background-color: rgba(255,255,255,0.95); -} -html.spring .plugin .top-bar { - color: #FF6D00; -} -html.spring .plugin refresh-btn button { - background-color: #E65100; -} - -/************************************** - Theme: Fall -**************************************/ - -html.fall { - background: url(../img/themes/skulls.png); -} -html.fall .plugin { - background: url(../img/themes/crossword.png); -} -html.fall .hero nav-bar ul li.active a, -html.fall .hero nav-bar ul li a:hover, -html.fall .plugin .top-bar { - color: #F09819; -} -html.fall .plugin refresh-btn button { - background-color: #FF512F; -} diff --git a/demo.html b/demo.html deleted file mode 100644 index 4d999a1e..00000000 --- a/demo.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - linux-dash : Server Monitoring Web Dashboard - - - - - - - - - - - - - - - - - - - - Fork me on GitHub - - -
-

Linux Dash

- A simple linux dashboard - - -
- - -
-
- - - - - - - - - diff --git a/demo.js b/demo.js new file mode 100644 index 00000000..bd30caaf --- /dev/null +++ b/demo.js @@ -0,0 +1,133 @@ +angular + .module('linuxDashDemo', ['linuxDash', 'ngMockE2E']) + .run(function($httpBackend) { + +// signal to not use websockets for demo +$httpBackend.whenGET('/websocket').respond(false) + + /** System Status */ + $httpBackend.whenGET('server/?module=current_ram').respond(function () { + return [200, {total: 512, used: 200, available: 312 }] + }) + + $httpBackend.whenGET('server/?module=ram_intensive_processes').respond(function () { + return [200, [ { 'pid': 2782, 'user': 'user1', 'mem%': 2.2, 'rss': 232332, 'vsz': 1118240, 'cmd': 'nginx'}, { 'pid': 2623, 'user': 'user1', 'mem%': 2.0, 'rss': 209732, 'vsz': 1321800, 'cmd': 'nginx'}, { 'pid': 1558, 'user': 'user1', 'mem%': 1.3, 'rss': 140796, 'vsz': 1321400, 'cmd': 'gnome-software'}, { 'pid': 5026, 'user': 'user1', 'mem%': 1.2, 'rss': 126312, 'vsz': 875072, 'cmd': 'nginx'}, { 'pid': 5051, 'user': 'user1', 'mem%': 1.1, 'rss': 112776, 'vsz': 872512, 'cmd': 'nginx'}, { 'pid': 1477, 'user': 'user1', 'mem%': 1.0, 'rss': 108248, 'vsz': 1471548, 'cmd': 'compiz'}, { 'pid': 2997, 'user': 'user1', 'mem%': 1.0, 'rss': 107208, 'vsz': 869076, 'cmd': 'nginx'}, { 'pid': 2298, 'user': 'root', 'mem%': 1.0, 'rss': 101356, 'vsz': 286772, 'cmd': 'aptd'}, { 'pid': 2700, 'user': 'user1', 'mem%': 0.9, 'rss': 95784, 'vsz': 460272, 'cmd': 'nginx'}, { 'pid': 2799, 'user': 'user1', 'mem%': 0.8, 'rss': 83940, 'vsz': 778512, 'cmd': 'nginx'}, { 'pid': 4248, 'user': 'user1', 'mem%': 0.8, 'rss': 82276, 'vsz': 1059420, 'cmd': 'redis'}, { 'pid': 1095, 'user': 'root', 'mem%': 0.7, 'rss': 78424, 'vsz': 457156, 'cmd': 'Xorg'}, { 'pid': 2810, 'user': 'user1', 'mem%': 0.7, 'rss': 72884, 'vsz': 750856, 'cmd': 'nginx'}, { 'pid': 2809, 'user': 'user1', 'mem%': 0.7, 'rss': 72872, 'vsz': 752908, 'cmd': 'nginx'} ]] + }) + + $httpBackend.whenGET('server/?module=cpu_intensive_processes').respond(function () { + return [200, [ { 'pid': 5026, 'user': 'user1', 'cpu%': 25.5, 'rss': 137944, 'vsz': 907236, 'cmd': 'nginx'}, { 'pid': 2623, 'user': 'user1', 'cpu%': 9.5, 'rss': 229016, 'vsz': 1327948, 'cmd': 'nginx'}, { 'pid': 2700, 'user': 'user1', 'cpu%': 8.8, 'rss': 106324, 'vsz': 474796, 'cmd': 'nginx'}, { 'pid': 4248, 'user': 'user1', 'cpu%': 8.8, 'rss': 96848, 'vsz': 1151460, 'cmd': 'sublime_text'}, { 'pid': 1477, 'user': 'user1', 'cpu%': 8.6, 'rss': 111252, 'vsz': 1476524, 'cmd': 'compiz'}, { 'pid': 1571, 'user': 'user1', 'cpu%': 7.7, 'rss': 62984, 'vsz': 551884, 'cmd': 'orca'}, { 'pid': 1095, 'user': 'root', 'cpu%': 6.2, 'rss': 86048, 'vsz': 457112, 'cmd': 'Xorg'}, { 'pid': 1535, 'user': 'user1', 'cpu%': 3.9, 'rss': 17152, 'vsz': 574624, 'cmd': 'pulseaudio'}, { 'pid': 5051, 'user': 'user1', 'cpu%': 1.6, 'rss': 131828, 'vsz': 888712, 'cmd': 'nginx'}, { 'pid': 2890, 'user': 'user1', 'cpu%': 1.1, 'rss': 52968, 'vsz': 657408, 'cmd': 'unity-panel-ser'}, { 'pid': 2782, 'user': 'user1', 'cpu%': 1.0, 'rss': 226536, 'vsz': 1110556, 'cmd': 'nginx'}, { 'pid': 1438, 'user': 'user1', 'cpu%': 0.9, 'rss': 45840, 'vsz': 660352, 'cmd': 'hud-service'}, { 'pid': 1631, 'user': 'user1', 'cpu%': 0.9, 'rss': 26208, 'vsz': 514684, 'cmd': 'indicator-multi'}, { 'pid': 1804, 'user': 'user1', 'cpu%': 0.9, 'rss': 9752, 'vsz': 523936, 'cmd': 'sd_espeak'} ]] + }) + + $httpBackend.whenGET('server/?module=disk_partitions').respond(function () { + return [200, [ {'file_system': 'udev', 'size': '4.9G', 'used': '0', 'avail': '4.9G', 'used%': '0%', 'mounted': '/dev'}, {'file_system': '/dev/sda1', 'size': '449G', 'used': '224.5G', 'avail': '224.5G', 'used%': '50%', 'mounted': '/'}, {'file_system': 'tmpfs', 'size': '5.0M', 'used': '4.0K', 'avail': '5.0M', 'used%': '1%', 'mounted': '/run/lock'} ]] + }) + + $httpBackend.whenGET('server/?module=cpu_temp').respond(function () { + return [200, [ ]] + }) + + $httpBackend.whenGET('server/?module=cpu_temp').respond(function () { + return [200, [ ]] + }) + + $httpBackend.whenGET('server/?module=cpu_utilization').respond(function () { + return [200, 25] + }) + + $httpBackend.whenGET('server/?module=load_avg').respond(function () { + return [200, { '1_min_avg': 40, '5_min_avg': 22, '15_min_avg': 10}] + }) + + $httpBackend.whenGET('server/?module=docker_processes').respond(function () { + return [200, []] + }) + + $httpBackend.whenGET('server/?module=swap').respond(function () { + return [200, [ { 'filename': '/dev/sda5', 'type': 'partition', 'size': '10364924', 'used': '0', 'priority': '-1'} ]] + }) + + /** Basic Info **/ + $httpBackend.whenGET('server/?module=cron_history').respond(function () { + return [200, []] + }) + + $httpBackend.whenGET('server/?module=io_stats').respond(function () { + return [200, [ { 'device': 'sda', 'reads': '75406', 'writes': '119939', 'in_prog.': '0', 'time': '816604'}, { 'device': 'sda1', 'reads': '74587', 'writes': '81983', 'in_prog.': '0', 'time': '796964'}, { 'device': 'sda2', 'reads': '6', 'writes': '0', 'in_prog.': '0', 'time': '304'}, { 'device': 'sda5', 'reads': '73', 'writes': '0', 'in_prog.': '0', 'time': '3344'}, { 'device': 'sdb', 'reads': '276', 'writes': '0', 'in_prog.': '0', 'time': '72'}, { 'device': 'sdb1', 'reads': '234', 'writes': '0', 'in_prog.': '0', 'time': '64'} ]] + }) + + $httpBackend.whenGET('server/?module=cpu_info').respond(function () { + return [200, { "Architecture": " x86_64", "CPU op-mode(s)": " 32-bit, 64-bit", "Byte Order": " Little Endian", "CPU(s)": " 4", "On-line CPU(s) list": " 0-3", "Thread(s) per core": " 2", "Core(s) per socket": " 2", "Socket(s)": " 1", "NUMA node(s)": " 1", "Vendor ID": " GenuineIntel", "CPU family": " 6", "Model": " 69", "Model name": " Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz", "Stepping": " 1", "CPU MHz": " 1402.101", "CPU max MHz": " 2600.0000", "CPU min MHz": " 800.0000", "BogoMIPS": " 4589.14", "Virtualization": " VT-x", "L1d cache": " 32K", "L1i cache": " 32K", "L2 cache": " 256K", "L3 cache": " 3072K", "NUMA node0 CPU(s)": " 0-3", "Flags": " fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts" }] + }) + + $httpBackend.whenGET('server/?module=scheduled_crons').respond(function () { + return [200, [ ]] + }) + + $httpBackend.whenGET('server/?module=memory_info').respond(function () { + return [200, {'MemTotal': '10120412 kB','MemFree': '7257156 kB','MemAvailable': '8173888 kB','Buffers': ' 257824 kB','Cached': ' 1175940 kB','SwapCached': ' 0 kB','Active': ' 1815224 kB','Inactive': ' 783844 kB','Active(anon)': '1174832 kB','Inactive(anon)': ' 334816 kB','Active(file)': ' 640392 kB','Inactive(file)': ' 449028 kB','Unevictable': ' 384 kB','Mlocked': ' 384 kB','SwapTotal': ' 10364924 kB','SwapFree': '10364924 kB','Dirty': ' 156 kB','Writeback': ' 0 kB','AnonPages': '1165736 kB','Mapped': '420416 kB','Shmem': ' 344344 kB','Slab': ' 143556 kB','SReclaimable': ' 109592 kB','SUnreclaim': ' 33964 kB','KernelStack': ' 8560 kB','PageTables': ' 32700 kB','NFS_Unstable': ' 0 kB','Bounce': ' 0 kB','WritebackTmp': ' 0 kB','CommitLimit': '15425128 kB','Committed_AS': '5073516 kB','VmallocTotal': ' 34359738367 kB','VmallocUsed': '0 kB','VmallocChunk': ' 0 kB','HardwareCorrupted': ' 0 kB','AnonHugePages': '352256 kB','CmaTotal': '0 kB','CmaFree': ' 0 kB','HugePages_Total': '0','HugePages_Free': ' 0','HugePages_Rsvd': ' 0','HugePages_Surp': ' 0','Hugepagesize': '2048 kB','DirectMap4k': ' 130800 kB','DirectMap2M': ' 4993024 kB','DirectMap1G': ' 6291456 kB'}] + }) + + $httpBackend.whenGET('server/?module=general_info').respond(function () { + return [200, { 'OS': 'Ubuntu 16.04.2 LTS 4.4.0-77-generic', 'Hostname': 'webserver-prod-983742', 'Uptime': '5 hours and 32 minutes and 27 seconds ', 'Server Time': 'Sat May 13 00:11:39 EST 2017' }] + }) + + /** Network **/ + $httpBackend.whenGET('server/?module=download_transfer_rate').respond(function () { + return [200, { "lo": 2048, "wlp3s0": 1024 }] + }) + + $httpBackend.whenGET('server/?module=upload_transfer_rate').respond(function () { + return [200, { "lo": 1024, "wlp3s0": 512 }] + }) + + $httpBackend.whenGET('server/?module=bandwidth').respond(function () { + return [200, [{ 'interface': 'wlp3s0:', 'tx': 226076758, 'rx': 9037438 },{ 'interface': 'lo:', 'tx': 12016995, 'rx': 12016995 }]] + }) + + $httpBackend.whenGET('server/?module=ping').respond(function () { + return [200, [{'host': 'google.com', 'ping': 23.234 },{'host': 'yahoo.com', 'ping': 67.412 },{'host': 'twitter.com', 'ping': 34.560 }]] + }) + + $httpBackend.whenGET('server/?module=ip_addresses').respond(function () { + return [200, [{ 'interface': 'external', 'ip': '70.113.122.2' } ]] + }) + + $httpBackend.whenGET('server/?module=network_connections').respond(function () { + return [200, [{ 'connections': 1, 'address': '127.0.0.1:48562' } + ,{ 'connections': 1, 'address': '127.0.0.1:48564' } + ,{ 'connections': 1, 'address': '127.0.0.1:48708' } + ,{ 'connections': 3, 'address': '127.0.0.1:8080' } + ,{ 'connections': 1, 'address': '192.241.178.140:443' } + ,{ 'connections': 2, 'address': '2657:f9b0:9000:802::443' } + ,{ 'connections': 1, 'address': '2657:f9b0:0000:80c::443' } + ,{ 'connections': 1, 'address': '2657:f9b0:0000:80f:::80' } + ,{ 'connections': 2, 'address': '2657:f9b0:0000:816:::80' } + ,{ 'connections': 1, 'address': '2657:f9b0:0003:c09:5228' }]] + }) + + $httpBackend.whenGET('server/?module=arp_cache').respond(function () { + return [200, [ { 'addr': '192.168.0.1', 'hw_type': 'ether', 'hw_addr.': '15:db:45:eb:4d:6a', 'mask': 'wlp3s0' } ]] + }) + + $httpBackend.whenGET('server/?module=logged_in_users').respond(function () { + return [200, [ {'user': 'user1', 'from': ':0', 'when': 'Fri23'}, {'user': 'user1', 'from': 'webserver-prod-983742', 'when': 'Fri23'}, {'user': 'user1', 'from': 'webserver-prod-983742', 'when': 'Fri23'}, {'user': 'user1', 'from': 'webserver-prod-983742', 'when': '01:19'} ]] + }) + + $httpBackend.whenGET('server/?module=user_accounts').respond(function () { + return [200, [ {"type":"system", "user":"root", "home":"/root"}, {"type":"system", "user":"daemon", "home":"/usr/sbin"}, {"type":"system", "user":"bin", "home":"/bin"}, {"type":"system", "user":"sys", "home":"/dev"}, {"type":"system", "user":"sync", "home":"/bin"}, {"type":"system", "user":"games", "home":"/usr/games"}, {"type":"system", "user":"man", "home":"/var/cache/man"}, {"type":"system", "user":"lp", "home":"/var/spool/lpd"}, {"type":"system", "user":"mail", "home":"/var/mail"}, {"type":"system", "user":"news", "home":"/var/spool/news"}, {"type":"system", "user":"uucp", "home":"/var/spool/uucp"}, {"type":"system", "user":"proxy", "home":"/bin"}, {"type":"system", "user":"www-data", "home":"/var/www"}, {"type":"system", "user":"backup", "home":"/var/backups"}, {"type":"system", "user":"list", "home":"/var/list"}, {"type":"system", "user":"irc", "home":"/var/run/ircd"}, {"type":"system", "user":"gnats", "home":"/var/lib/gnats"}, {"type":"user", "user":"nobody", "home":"/nonexistent"}, {"type":"system", "user":"systemd-timesync", "home":"/run/systemd"} ]] + }) + + $httpBackend.whenGET('server/?module=recent_account_logins').respond(function () { + return [200, [ {"type":"system", "user":"root", "home":"/root"}, {"type":"system", "user":"daemon", "home":"/usr/sbin"}, {"type":"system", "user":"bin", "home":"/bin"}, {"type":"system", "user":"sys", "home":"/dev"}, {"type":"system", "user":"sync", "home":"/bin"}, {"type":"system", "user":"games", "home":"/usr/games"}, {"type":"system", "user":"man", "home":"/var/cache/man"}, {"type":"system", "user":"lp", "home":"/var/spool/lpd"}, {"type":"system", "user":"mail", "home":"/var/mail"}, {"type":"system", "user":"news", "home":"/var/spool/news"}, {"type":"system", "user":"uucp", "home":"/var/spool/uucp"}, {"type":"system", "user":"proxy", "home":"/bin"}, {"type":"system", "user":"www-data", "home":"/var/www"}, {"type":"system", "user":"backup", "home":"/var/backups"}, {"type":"system", "user":"list", "home":"/var/list"}, {"type":"system", "user":"irc", "home":"/var/run/ircd"}, {"type":"system", "user":"gnats", "home":"/var/lib/gnats"}, {"type":"user", "user":"nobody", "home":"/nonexistent"}, {"type":"system", "user":"systemd-timesync", "home":"/run/systemd"} ]] + }) + + /** Applications **/ + $httpBackend.whenGET('server/?module=common_applications').respond(function () { + return [200, [ { "binary": "php", "location": "", "installed": "installed" }, { "binary": "node", "location": " /usr/bin/node /usr/include/node /usr/share/man/man1/node.1.gz", "installed": "installed" }, { "binary": "mysql", "location": "", "installed": "installed" }, { "binary": "mongo", "location": "", "installed": "installed" }, { "binary": "vim", "location": " /usr/bin/vim.basic /usr/bin/vim /usr/bin/vim.tiny /etc/vim /usr/share/vim /usr/share/man/man1/vim.1.gz", "installed": "installed" }, { "binary": "python", "location": " /usr/bin/python3.5 /usr/bin/python /usr/bin/python2.7-config /usr/bin/python3.5m /usr/bin/python2.7 /usr/lib/python3.5 /usr/lib/python2.7 /etc/python3.5 /etc/python /etc/python2.7 /usr/local/lib/python3.5 /usr/local/lib/python2.7 /usr/include/python3.5m /usr/include/python2.7 /usr/share/python /usr/share/man/man1/python.1.gz", "installed": "installed" }, { "binary": "ruby", "location": "", "installed": "installed" }, { "binary": "java", "location": " /usr/share/java", "installed": "installed" }, { "binary": "apache2", "location": "", "installed": "installed" }, { "binary": "nginx", "location": "", "installed": "installed" }, { "binary": "openssl", "location": " /usr/bin/openssl /usr/share/man/man1/openssl.1ssl.gz", "installed": "installed" }, { "binary": "vsftpd", "location": "", "installed": "installed" }, { "binary": "make", "location": " /usr/bin/make /usr/share/man/man1/make.1.gz", "installed": "installed" } ]] + }) + + $httpBackend.whenGET('server/?module=memcached').respond(function () { return [200, {}] }) + $httpBackend.whenGET('server/?module=redis').respond(function () { return [200, {}] }) + $httpBackend.whenGET('server/?module=pm2_stats').respond(function () { return [200, []] }) + + }) diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index cf410099..00000000 Binary files a/favicon.ico and /dev/null differ diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..73b9a4ec --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,52 @@ +var g = require('gulp') +var concat = require('gulp-concat') +var uglify = require('gulp-uglify') +var cssmin = require('gulp-cssmin') +var gutil = require('gulp-util') +var ngAnnotate = require('gulp-ng-annotate') +var templateCache = require('gulp-angular-templatecache') + +g.task('template-cache', function () { + return g.src('src/**/*.html') + .pipe(templateCache('templates.js', { + module: 'linuxDash', + standAlone: false, + root: 'src/' + })) + .pipe(g.dest('temp/')) +}) + +g.task('generate-js-dist', ['template-cache'], function () { + return g.src([ + 'node_modules/angular/angular.min.js', + 'node_modules/angular-route/angular-route.min.js', + 'node_modules/smoothie/smoothie.js', + 'node_modules/sortablejs/Sortable.min.js', + 'src/js/**/*.js', + 'temp/templates.js' + ]) + .pipe(concat('linuxDash.min.js')) + .pipe(ngAnnotate()) + // .pipe(uglify()) + .on('error', gutil.log) + .pipe(g.dest('app/')) +}) + +g.task('generate-css-dist', function () { + return g.src([ 'src/**/*.css' ]) + .pipe(cssmin()) + .pipe(concat('linuxDash.min.css')) + .pipe(g.dest('app/')) +}) + +g.task('build', [ + 'generate-js-dist', + 'generate-css-dist' +]) + +g.task('watch', function () { + g.watch('src/**/*.css', ['generate-css-dist']) + g.watch(['src/**/*.js', 'src/**/*.html'], ['generate-js-dist']) +}) + +g.task('default', ['build', 'watch']) diff --git a/img/themes/congruent_pentagon.png b/img/themes/congruent_pentagon.png deleted file mode 100644 index c7126603..00000000 Binary files a/img/themes/congruent_pentagon.png and /dev/null differ diff --git a/img/themes/contemporary_china_2.png b/img/themes/contemporary_china_2.png deleted file mode 100644 index 4bd667df..00000000 Binary files a/img/themes/contemporary_china_2.png and /dev/null differ diff --git a/img/themes/crossword.png b/img/themes/crossword.png deleted file mode 100644 index 2f9f1ad0..00000000 Binary files a/img/themes/crossword.png and /dev/null differ diff --git a/img/themes/food.png b/img/themes/food.png deleted file mode 100644 index 14b6bbcb..00000000 Binary files a/img/themes/food.png and /dev/null differ diff --git a/img/themes/skulls.png b/img/themes/skulls.png deleted file mode 100644 index 93508ffa..00000000 Binary files a/img/themes/skulls.png and /dev/null differ diff --git a/index.html b/index.html index b5689d9c..08c4b465 100644 --- a/index.html +++ b/index.html @@ -1,46 +1,30 @@ - + - linux-dash : Server Monitoring Web Dashboard + Linux Dash : Simple, beautiful server monitoring web dashboard - - - - - + - - - + + - -
-

Linux Dash

- A simple linux dashboard - - -
- + + + -
-
+
- - - - - + + + diff --git a/js/angular-route.js b/js/angular-route.js deleted file mode 100644 index 68203187..00000000 --- a/js/angular-route.js +++ /dev/null @@ -1,996 +0,0 @@ -/** - * @license AngularJS v1.3.4 - * (c) 2010-2014 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -/** - * @ngdoc module - * @name ngRoute - * @description - * - * # ngRoute - * - * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * - *
- */ - /* global -ngRouteModule */ -var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider), - $routeMinErr = angular.$$minErr('ngRoute'); - -/** - * @ngdoc provider - * @name $routeProvider - * - * @description - * - * Used for configuring routes. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * ## Dependencies - * Requires the {@link ngRoute `ngRoute`} module to be installed. - */ -function $RouteProvider() { - function inherit(parent, extra) { - return angular.extend(Object.create(parent), extra); - } - - var routes = {}; - - /** - * @ngdoc method - * @name $routeProvider#when - * - * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redundant trailing slash or is missing one, the route will still match and the - * `$location.path` will be updated to add or drop the trailing slash to exactly match the - * route definition. - * - * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up - * to the next slash are matched and stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain named groups starting with a colon and ending with a star: - * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain optional named groups with a question mark: e.g.`:name?`. - * - * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match - * `/color/brown/largecode/code/with/slashes/edit` and extract: - * - * * `color: brown` - * * `largecode: code/with/slashes`. - * - * - * @param {Object} route Mapping information to be assigned to `$route.current` on route - * match. - * - * Object properties: - * - * - `controller` – `{(string|function()=}` – Controller fn that should be associated with - * newly created scope or the name of a {@link angular.Module#controller registered - * controller} if passed as a string. - * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be - * published to scope under the `controllerAs` name. - * - `template` – `{string=|function()=}` – html template as a string or a function that - * returns an html template as a string which should be used by {@link - * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. - * This property takes precedence over `templateUrl`. - * - * If `template` is a function, it will be called with the following parameters: - * - * - `{Array.}` - route parameters extracted from the current - * `$location.path()` by applying the current route - * - * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html - * template that should be used by {@link ngRoute.directive:ngView ngView}. - * - * If `templateUrl` is a function, it will be called with the following parameters: - * - * - `{Array.}` - route parameters extracted from the current - * `$location.path()` by applying the current route - * - * - `resolve` - `{Object.=}` - An optional map of dependencies which should - * be injected into the controller. If any of these dependencies are promises, the router - * will wait for them all to be resolved or one to be rejected before the controller is - * instantiated. - * If all the promises are resolved successfully, the values of the resolved promises are - * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is - * fired. If any of the promises are rejected the - * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object - * is: - * - * - `key` – `{string}`: a name of a dependency to be injected into the controller. - * - `factory` - `{string|function}`: If `string` then it is an alias for a service. - * Otherwise if function, then it is {@link auto.$injector#invoke injected} - * and the return value is treated as the dependency. If the result is a promise, it is - * resolved before its value is injected into the controller. Be aware that - * `ngRoute.$routeParams` will still refer to the previous route within these resolve - * functions. Use `$route.current.params` to access the new route parameters, instead. - * - * - `redirectTo` – {(string|function())=} – value to update - * {@link ng.$location $location} path with and trigger route redirection. - * - * If `redirectTo` is a function, it will be called with the following parameters: - * - * - `{Object.}` - route parameters extracted from the current - * `$location.path()` by applying the current route templateUrl. - * - `{string}` - current `$location.path()` - * - `{Object}` - current `$location.search()` - * - * The custom `redirectTo` function is expected to return a string which will be used - * to update `$location.path()` and `$location.search()`. - * - * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` - * or `$location.hash()` changes. - * - * If the option is set to `false` and url in the browser changes, then - * `$routeUpdate` event is broadcasted on the root scope. - * - * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive - * - * If the option is set to `true`, then the particular route can be matched without being - * case sensitive - * - * @returns {Object} self - * - * @description - * Adds a new route definition to the `$route` service. - */ - this.when = function(path, route) { - //copy original route object to preserve params inherited from proto chain - var routeCopy = angular.copy(route); - if (angular.isUndefined(routeCopy.reloadOnSearch)) { - routeCopy.reloadOnSearch = true; - } - if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { - routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; - } - routes[path] = angular.extend( - routeCopy, - path && pathRegExp(path, routeCopy) - ); - - // create redirection for trailing slashes - if (path) { - var redirectPath = (path[path.length - 1] == '/') - ? path.substr(0, path.length - 1) - : path + '/'; - - routes[redirectPath] = angular.extend( - {redirectTo: path}, - pathRegExp(redirectPath, routeCopy) - ); - } - - return this; - }; - - /** - * @ngdoc property - * @name $routeProvider#caseInsensitiveMatch - * @description - * - * A boolean property indicating if routes defined - * using this provider should be matched using a case sensitive - * algorithm. Defaults to `false`. - */ - this.caseInsensitiveMatch = false; - - /** - * @param path {string} path - * @param opts {Object} options - * @return {?Object} - * - * @description - * Normalizes the given path, returning a regular expression - * and the original path. - * - * Inspired by pathRexp in visionmedia/express/lib/utils.js. - */ - function pathRegExp(path, opts) { - var insensitive = opts.caseInsensitiveMatch, - ret = { - originalPath: path, - regexp: path - }, - keys = ret.keys = []; - - path = path - .replace(/([().])/g, '\\$1') - .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { - var optional = option === '?' ? option : null; - var star = option === '*' ? option : null; - keys.push({ name: key, optional: !!optional }); - slash = slash || ''; - return '' - + (optional ? '' : slash) - + '(?:' - + (optional ? slash : '') - + (star && '(.+?)' || '([^/]+)') - + (optional || '') - + ')' - + (optional || ''); - }) - .replace(/([\/$\*])/g, '\\$1'); - - ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); - return ret; - } - - /** - * @ngdoc method - * @name $routeProvider#otherwise - * - * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. - * - * @param {Object|string} params Mapping information to be assigned to `$route.current`. - * If called with a string, the value maps to `redirectTo`. - * @returns {Object} self - */ - this.otherwise = function(params) { - if (typeof params === 'string') { - params = {redirectTo: params}; - } - this.when(null, params); - return this; - }; - - - this.$get = ['$rootScope', - '$location', - '$routeParams', - '$q', - '$injector', - '$templateRequest', - '$sce', - function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { - - /** - * @ngdoc service - * @name $route - * @requires $location - * @requires $routeParams - * - * @property {Object} current Reference to the current route definition. - * The route definition contains: - * - * - `controller`: The controller constructor as define in route definition. - * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for - * controller instantiation. The `locals` contain - * the resolved values of the `resolve` map. Additionally the `locals` also contain: - * - * - `$scope` - The current route scope. - * - `$template` - The current route template HTML. - * - * @property {Object} routes Object with all route configuration Objects as its properties. - * - * @description - * `$route` is used for deep-linking URLs to controllers and views (HTML partials). - * It watches `$location.url()` and tries to map the path to an existing route definition. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. - * - * The `$route` service is typically used in conjunction with the - * {@link ngRoute.directive:ngView `ngView`} directive and the - * {@link ngRoute.$routeParams `$routeParams`} service. - * - * @example - * This example shows how changing the URL hash causes the `$route` to match a route against the - * URL, and the `ngView` pulls in the partial. - * - * - * - *
- * Choose: - * Moby | - * Moby: Ch1 | - * Gatsby | - * Gatsby: Ch4 | - * Scarlet Letter
- * - *
- * - *
- * - *
$location.path() = {{$location.path()}}
- *
$route.current.templateUrl = {{$route.current.templateUrl}}
- *
$route.current.params = {{$route.current.params}}
- *
$route.current.scope.name = {{$route.current.scope.name}}
- *
$routeParams = {{$routeParams}}
- *
- *
- * - * - * controller: {{name}}
- * Book Id: {{params.bookId}}
- *
- * - * - * controller: {{name}}
- * Book Id: {{params.bookId}}
- * Chapter Id: {{params.chapterId}} - *
- * - * - * angular.module('ngRouteExample', ['ngRoute']) - * - * .controller('MainController', function($scope, $route, $routeParams, $location) { - * $scope.$route = $route; - * $scope.$location = $location; - * $scope.$routeParams = $routeParams; - * }) - * - * .controller('BookController', function($scope, $routeParams) { - * $scope.name = "BookController"; - * $scope.params = $routeParams; - * }) - * - * .controller('ChapterController', function($scope, $routeParams) { - * $scope.name = "ChapterController"; - * $scope.params = $routeParams; - * }) - * - * .config(function($routeProvider, $locationProvider) { - * $routeProvider - * .when('/Book/:bookId', { - * templateUrl: 'book.html', - * controller: 'BookController', - * resolve: { - * // I will cause a 1 second delay - * delay: function($q, $timeout) { - * var delay = $q.defer(); - * $timeout(delay.resolve, 1000); - * return delay.promise; - * } - * } - * }) - * .when('/Book/:bookId/ch/:chapterId', { - * templateUrl: 'chapter.html', - * controller: 'ChapterController' - * }); - * - * // configure html5 to get links working on jsfiddle - * $locationProvider.html5Mode(true); - * }); - * - * - * - * - * it('should load and compile correct template', function() { - * element(by.linkText('Moby: Ch1')).click(); - * var content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: ChapterController/); - * expect(content).toMatch(/Book Id\: Moby/); - * expect(content).toMatch(/Chapter Id\: 1/); - * - * element(by.partialLinkText('Scarlet')).click(); - * - * content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: BookController/); - * expect(content).toMatch(/Book Id\: Scarlet/); - * }); - * - *
- */ - - /** - * @ngdoc event - * @name $route#$routeChangeStart - * @eventType broadcast on root scope - * @description - * Broadcasted before a route change. At this point the route services starts - * resolving all of the dependencies needed for the route change to occur. - * Typically this involves fetching the view template as well as any dependencies - * defined in `resolve` route property. Once all of the dependencies are resolved - * `$routeChangeSuccess` is fired. - * - * The route change (and the `$location` change that triggered it) can be prevented - * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} - * for more details about event object. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} next Future route information. - * @param {Route} current Current route information. - */ - - /** - * @ngdoc event - * @name $route#$routeChangeSuccess - * @eventType broadcast on root scope - * @description - * Broadcasted after a route dependencies are resolved. - * {@link ngRoute.directive:ngView ngView} listens for the directive - * to instantiate the controller and render the view. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} current Current route information. - * @param {Route|Undefined} previous Previous route information, or undefined if current is - * first route entered. - */ - - /** - * @ngdoc event - * @name $route#$routeChangeError - * @eventType broadcast on root scope - * @description - * Broadcasted if any of the resolve promises are rejected. - * - * @param {Object} angularEvent Synthetic event object - * @param {Route} current Current route information. - * @param {Route} previous Previous route information. - * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. - */ - - /** - * @ngdoc event - * @name $route#$routeUpdate - * @eventType broadcast on root scope - * @description - * - * The `reloadOnSearch` property has been set to false, and we are reusing the same - * instance of the Controller. - */ - - var forceReload = false, - preparedRoute, - preparedRouteIsUpdateOnly, - $route = { - routes: routes, - - /** - * @ngdoc method - * @name $route#reload - * - * @description - * Causes `$route` service to reload the current route even if - * {@link ng.$location $location} hasn't changed. - * - * As a result of that, {@link ngRoute.directive:ngView ngView} - * creates new scope and reinstantiates the controller. - */ - reload: function() { - forceReload = true; - $rootScope.$evalAsync(function() { - // Don't support cancellation of a reload for now... - prepareRoute(); - commitRoute(); - }); - }, - - /** - * @ngdoc method - * @name $route#updateParams - * - * @description - * Causes `$route` service to update the current URL, replacing - * current route parameters with those specified in `newParams`. - * Provided property names that match the route's path segment - * definitions will be interpolated into the location's path, while - * remaining properties will be treated as query params. - * - * @param {Object} newParams mapping of URL parameter names to values - */ - updateParams: function(newParams) { - if (this.current && this.current.$$route) { - var searchParams = {}, self=this; - - angular.forEach(Object.keys(newParams), function(key) { - if (!self.current.pathParams[key]) searchParams[key] = newParams[key]; - }); - - newParams = angular.extend({}, this.current.params, newParams); - $location.path(interpolate(this.current.$$route.originalPath, newParams)); - $location.search(angular.extend({}, $location.search(), searchParams)); - } - else { - throw $routeMinErr('norout', 'Tried updating route when with no current route'); - } - } - }; - - $rootScope.$on('$locationChangeStart', prepareRoute); - $rootScope.$on('$locationChangeSuccess', commitRoute); - - return $route; - - ///////////////////////////////////////////////////// - - /** - * @param on {string} current url - * @param route {Object} route regexp to match the url against - * @return {?Object} - * - * @description - * Check if the route matches the current url. - * - * Inspired by match in - * visionmedia/express/lib/router/router.js. - */ - function switchRouteMatcher(on, route) { - var keys = route.keys, - params = {}; - - if (!route.regexp) return null; - - var m = route.regexp.exec(on); - if (!m) return null; - - for (var i = 1, len = m.length; i < len; ++i) { - var key = keys[i - 1]; - - var val = m[i]; - - if (key && val) { - params[key.name] = val; - } - } - return params; - } - - function prepareRoute($locationEvent) { - var lastRoute = $route.current; - - preparedRoute = parseRoute(); - preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route - && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) - && !preparedRoute.reloadOnSearch && !forceReload; - - if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { - if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { - if ($locationEvent) { - $locationEvent.preventDefault(); - } - } - } - } - - function commitRoute() { - var lastRoute = $route.current; - var nextRoute = preparedRoute; - - if (preparedRouteIsUpdateOnly) { - lastRoute.params = nextRoute.params; - angular.copy(lastRoute.params, $routeParams); - $rootScope.$broadcast('$routeUpdate', lastRoute); - } else if (nextRoute || lastRoute) { - forceReload = false; - $route.current = nextRoute; - if (nextRoute) { - if (nextRoute.redirectTo) { - if (angular.isString(nextRoute.redirectTo)) { - $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) - .replace(); - } else { - $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) - .replace(); - } - } - } - - $q.when(nextRoute). - then(function() { - if (nextRoute) { - var locals = angular.extend({}, nextRoute.resolve), - template, templateUrl; - - angular.forEach(locals, function(value, key) { - locals[key] = angular.isString(value) ? - $injector.get(value) : $injector.invoke(value, null, null, key); - }); - - if (angular.isDefined(template = nextRoute.template)) { - if (angular.isFunction(template)) { - template = template(nextRoute.params); - } - } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { - if (angular.isFunction(templateUrl)) { - templateUrl = templateUrl(nextRoute.params); - } - templateUrl = $sce.getTrustedResourceUrl(templateUrl); - if (angular.isDefined(templateUrl)) { - nextRoute.loadedTemplateUrl = templateUrl; - template = $templateRequest(templateUrl); - } - } - if (angular.isDefined(template)) { - locals['$template'] = template; - } - return $q.all(locals); - } - }). - // after route change - then(function(locals) { - if (nextRoute == $route.current) { - if (nextRoute) { - nextRoute.locals = locals; - angular.copy(nextRoute.params, $routeParams); - } - $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); - } - }, function(error) { - if (nextRoute == $route.current) { - $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); - } - }); - } - } - - - /** - * @returns {Object} the current active route, by matching it against the URL - */ - function parseRoute() { - // Match a route - var params, match; - angular.forEach(routes, function(route, path) { - if (!match && (params = switchRouteMatcher($location.path(), route))) { - match = inherit(route, { - params: angular.extend({}, $location.search(), params), - pathParams: params}); - match.$$route = route; - } - }); - // No route matched; fallback to "otherwise" route - return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); - } - - /** - * @returns {string} interpolation of the redirect path with the parameters - */ - function interpolate(string, params) { - var result = []; - angular.forEach((string || '').split(':'), function(segment, i) { - if (i === 0) { - result.push(segment); - } else { - var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); - var key = segmentMatch[1]; - result.push(params[key]); - result.push(segmentMatch[2] || ''); - delete params[key]; - } - }); - return result.join(''); - } - }]; -} - -ngRouteModule.provider('$routeParams', $RouteParamsProvider); - - -/** - * @ngdoc service - * @name $routeParams - * @requires $route - * - * @description - * The `$routeParams` service allows you to retrieve the current set of route parameters. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * The route parameters are a combination of {@link ng.$location `$location`}'s - * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. - * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. - * - * In case of parameter name collision, `path` params take precedence over `search` params. - * - * The service guarantees that the identity of the `$routeParams` object will remain unchanged - * (but its properties will likely change) even when a route change occurs. - * - * Note that the `$routeParams` are only updated *after* a route change completes successfully. - * This means that you cannot rely on `$routeParams` being correct in route resolve functions. - * Instead you can use `$route.current.params` to access the new route's parameters. - * - * @example - * ```js - * // Given: - * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby - * // Route: /Chapter/:chapterId/Section/:sectionId - * // - * // Then - * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} - * ``` - */ -function $RouteParamsProvider() { - this.$get = function() { return {}; }; -} - -ngRouteModule.directive('ngView', ngViewFactory); -ngRouteModule.directive('ngView', ngViewFillContentFactory); - - -/** - * @ngdoc directive - * @name ngView - * @restrict ECA - * - * @description - * # Overview - * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * @param {string=} onload Expression to evaluate whenever the view updates. - * - * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the view is updated. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated - * as an expression yields a truthy value. - * @example - - -
- Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
- -
-
-
-
- -
$location.path() = {{main.$location.path()}}
-
$route.current.templateUrl = {{main.$route.current.templateUrl}}
-
$route.current.params = {{main.$route.current.params}}
-
$routeParams = {{main.$routeParams}}
-
-
- - -
- controller: {{book.name}}
- Book Id: {{book.params.bookId}}
-
-
- - -
- controller: {{chapter.name}}
- Book Id: {{chapter.params.bookId}}
- Chapter Id: {{chapter.params.chapterId}} -
-
- - - .view-animate-container { - position:relative; - height:100px!important; - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .view-animate { - padding:10px; - } - - .view-animate.ng-enter, .view-animate.ng-leave { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; - - display:block; - width:100%; - border-left:1px solid black; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - padding:10px; - } - - .view-animate.ng-enter { - left:100%; - } - .view-animate.ng-enter.ng-enter-active { - left:0; - } - .view-animate.ng-leave.ng-leave-active { - left:-100%; - } - - - - angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) - .config(['$routeProvider', '$locationProvider', - function($routeProvider, $locationProvider) { - $routeProvider - .when('/Book/:bookId', { - templateUrl: 'book.html', - controller: 'BookCtrl', - controllerAs: 'book' - }) - .when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: 'ChapterCtrl', - controllerAs: 'chapter' - }); - - $locationProvider.html5Mode(true); - }]) - .controller('MainCtrl', ['$route', '$routeParams', '$location', - function($route, $routeParams, $location) { - this.$route = $route; - this.$location = $location; - this.$routeParams = $routeParams; - }]) - .controller('BookCtrl', ['$routeParams', function($routeParams) { - this.name = "BookCtrl"; - this.params = $routeParams; - }]) - .controller('ChapterCtrl', ['$routeParams', function($routeParams) { - this.name = "ChapterCtrl"; - this.params = $routeParams; - }]); - - - - - it('should load and compile correct template', function() { - element(by.linkText('Moby: Ch1')).click(); - var content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: ChapterCtrl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element(by.partialLinkText('Scarlet')).click(); - - content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: BookCtrl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
- */ - - -/** - * @ngdoc event - * @name ngView#$viewContentLoaded - * @eventType emit on the current ngView scope - * @description - * Emitted every time the ngView content is reloaded. - */ -ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; -function ngViewFactory($route, $anchorScroll, $animate) { - return { - restrict: 'ECA', - terminal: true, - priority: 400, - transclude: 'element', - link: function(scope, $element, attr, ctrl, $transclude) { - var currentScope, - currentElement, - previousLeaveAnimation, - autoScrollExp = attr.autoscroll, - onloadExp = attr.onload || ''; - - scope.$on('$routeChangeSuccess', update); - update(); - - function cleanupLastView() { - if (previousLeaveAnimation) { - $animate.cancel(previousLeaveAnimation); - previousLeaveAnimation = null; - } - - if (currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if (currentElement) { - previousLeaveAnimation = $animate.leave(currentElement); - previousLeaveAnimation.then(function() { - previousLeaveAnimation = null; - }); - currentElement = null; - } - } - - function update() { - var locals = $route.current && $route.current.locals, - template = locals && locals.$template; - - if (angular.isDefined(template)) { - var newScope = scope.$new(); - var current = $route.current; - - // Note: This will also link all children of ng-view that were contained in the original - // html. If that content contains controllers, ... they could pollute/change the scope. - // However, using ng-view on an element with additional content does not make sense... - // Note: We can't remove them in the cloneAttchFn of $transclude as that - // function is called before linking the content, which would apply child - // directives to non existing elements. - var clone = $transclude(newScope, function(clone) { - $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { - if (angular.isDefined(autoScrollExp) - && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }); - cleanupLastView(); - }); - - currentElement = clone; - currentScope = current.scope = newScope; - currentScope.$emit('$viewContentLoaded'); - currentScope.$eval(onloadExp); - } else { - cleanupLastView(); - } - } - } - }; -} - -// This directive is called during the $transclude call of the first `ngView` directive. -// It will replace and compile the content of the element with the loaded template. -// We need this directive so that the element content is already filled when -// the link function of another directive on the same element as ngView -// is called. -ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; -function ngViewFillContentFactory($compile, $controller, $route) { - return { - restrict: 'ECA', - priority: -400, - link: function(scope, $element) { - var current = $route.current, - locals = current.locals; - - $element.html(locals.$template); - - var link = $compile($element.contents()); - - if (current.controller) { - locals.$scope = scope; - var controller = $controller(current.controller, locals); - if (current.controllerAs) { - scope[current.controllerAs] = controller; - } - $element.data('$ngControllerController', controller); - $element.children().data('$ngControllerController', controller); - } - - link(scope); - } - }; -} - - -})(window, window.angular); diff --git a/js/linuxDash.js b/js/linuxDash.js deleted file mode 100644 index aeabd6fb..00000000 --- a/js/linuxDash.js +++ /dev/null @@ -1,811 +0,0 @@ -(function() { - - angular.module('linuxDash', ['ngRoute']); - - /** - * Routes for different tabs on UI - */ - angular.module('linuxDash').config(['$routeProvider', - function($routeProvider) { - - $routeProvider. - when('/loading', { - templateUrl: 'templates/app/loading.html', - controller: function appLoadController ($scope, $location, $rootScope) { - - var loadUrl = localStorage.getItem('currentTab') || 'system-status'; - - var loadLinuxDash = function () { - $location.path(loadUrl); - }; - - $rootScope.$on('start-linux-dash', loadLinuxDash); - - }, - }). - when('/system-status', { - templateUrl: 'templates/sections/system-status.html', - }). - when('/basic-info', { - templateUrl: 'templates/sections/basic-info.html', - }). - when('/network', { - templateUrl: 'templates/sections/network.html', - }). - when('/accounts', { - templateUrl: 'templates/sections/accounts.html', - }). - when('/apps', { - templateUrl: 'templates/sections/applications.html', - }). - otherwise({ - redirectTo: '/loading' - }); - - } - ]); - - - /** - * Service which gets data from server - * via HTTP or Websocket (if supported) - */ - angular.module('linuxDash').service('server', ['$http', '$rootScope', '$location', function($http, $rootScope, $location) { - - var websocket = { - connection: null, - onMessageEventHandlers: {} - }; - - /** - * @description: - * Establish a websocket connection with server - * - * @return Null - */ - var establishWebsocketConnection = function() { - - var websocketUrl = (location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.hostname + ':' + window.location.port; - - if (websocket.connection === null) { - - websocket.connection = new WebSocket(websocketUrl, 'linux-dash'); - - websocket.connection.onopen = function() { - $rootScope.$broadcast("start-linux-dash", {}); - $rootScope.$apply(); - console.info('Websocket connection is open'); - }; - - websocket.connection.onmessage = function(event) { - - var response = JSON.parse(event.data); - var moduleName = response.moduleName; - var moduleData = JSON.parse(response.output); - - if (!!websocket.onMessageEventHandlers[moduleName]) { - websocket.onMessageEventHandlers[moduleName](moduleData); - } else { - console.info("Websocket could not find module", moduleName, "in:", websocket.onMessageEventHandlers); - } - - }; - - websocket.connection.onclose = function() { - websocket.connection = null; - } - } - - }; - - /** - * @description: - * Check if websockets are supported - * If so, call establishWebsocketConnection() - * - * @return Null - */ - this.checkIfWebsocketsAreSupported = function() { - - var websocketSupport = { - browser: null, - server: null, - }; - - // does browser support websockets? - if (window.WebSocket) { - - websocketSupport.browser = true; - - // does backend support websockets? - $http.get("/websocket").then(function(response) { - - // if websocket_support property exists and is trurthy - // websocketSupport.server will equal true. - websocketSupport.server = !!response.data["websocket_support"]; - - }).catch(function websocketNotSupportedByServer() { - - websocketSupport.server = false; - $rootScope.$broadcast("start-linux-dash", {}); - - }).then(function finalDecisionOnWebsocket() { - - if (websocketSupport.browser && websocketSupport.server) { - - establishWebsocketConnection(); - - } else { - // rootScope event not propogating from here. - // instead, we manually route to url - $location.path('/system-status'); - } - - }); - - } - - }; - - /** - * Handles requests from modules for data from server - * - * @param {String} moduleName - * @param {Function} callback - * @return {[ Null || callback(server response) ]} - */ - this.get = function(moduleName, callback) { - - // if we have a websocket connection - if (websocket.connection) { - - // and the connection is ready - if (websocket.connection.readyState === 1) { - - // set the callback as the event handler - // for server response. - // - // Callback instance needs to be overwritten - // each time for this to work. Not sure why. - websocket.onMessageEventHandlers[moduleName] = callback; - - // - websocket.connection.send(moduleName); - - } else { - console.log("Websocket not ready yet.", moduleName); - } - - } - // otherwise - else { - - var moduleAddress = 'server/?module=' + moduleName; - - return $http.get(moduleAddress).then(function(response) { - return callback(response.data); - }); - - } - - }; - - }]); - - /** - * Hook to run websocket support check. - */ - angular.module('linuxDash').run(function(server, $location, $rootScope) { - - server.checkIfWebsocketsAreSupported(); - - var currentRoute = $location.path(); - var currentTab = (currentRoute === '/loading')? 'system-status': currentRoute; - localStorage.setItem('currentTab', currentTab); - - $location.path('/loading'); - - }); - - /** - * Sidebar for SPA - */ - angular.module('linuxDash').directive('navBar', function($location) { - return { - restrict: 'E', - templateUrl: 'templates/app/navbar.html', - link: function(scope) { - scope.items = [ - 'system-status', - 'basic-info', - 'network', - 'accounts', - 'apps' - ]; - - scope.getNavItemName = function(url) { - return url.replace('-', ' '); - }; - - scope.isActive = function(route) { - return '/' + route === $location.path(); - }; - } - }; - - }); - - ////////////////////////////////////////////////////////////// - ////////////////// UI Element Directives ////////////////// // - ////////////////////////////////////////////////////////////// - - /** - * Shows loader - */ - angular.module('linuxDash').directive('loader', function() { - return { - restrict: 'E', - scope: { - width: '@' - }, - template: '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' - }; - }); - - /** - * Top Bar for widget - */ - angular.module('linuxDash').directive('topBar', function() { - return { - restrict: 'E', - scope: { - heading: '=', - refresh: '&', - lastUpdated: '=', - info: '=', - }, - templateUrl: 'templates/app/ui-elements/top-bar.html', - link: function(scope, element, attrs) { - var $refreshBtn = element.find('refresh-btn').eq(0); - - if (typeof attrs.noRefreshBtn !== 'undefined') { - $refreshBtn.remove(); - } - } - }; - }); - - /** - * Shows refresh button and calls - * provided expression on-click - */ - angular.module('linuxDash').directive('refreshBtn', function() { - return { - restrict: 'E', - scope: { - refresh: '&' - }, - template: '' - }; - }); - - /** - * Message shown when no data is found from server - */ - angular.module('linuxDash').directive('noData', function() { - return { - restrict: 'E', - template: 'No Data' - }; - }); - - /** - * Displays last updated timestamp for widget - */ - angular.module('linuxDash').directive('lastUpdate', function() { - return { - restrict: 'E', - scope: { - timestamp: '=' - }, - templateUrl: 'templates/app/ui-elements/last-update.html' - }; - }); - - - ////////////////// Plugin Directives ////////////////// - - /** - * Fetches and displays table data - */ - angular.module('linuxDash').directive('tableData', ['server', '$rootScope', function(server, $rootScope) { - return { - restrict: 'E', - scope: { - heading: '@', - info: '@', - moduleName: '@', - width: '@', - height: '@' - }, - templateUrl: 'templates/app/table-data-plugin.html', - link: function(scope, element) { - - scope.sortByColumn = null; - scope.sortReverse = null; - - // set the column to sort by - scope.setSortColumn = function(column) { - - // if the column is already being sorted - // reverse the order - if (column === scope.sortByColumn) { - scope.sortReverse = !scope.sortReverse; - } else { - scope.sortByColumn = column; - } - - scope.sortTableRows(); - }; - - scope.sortTableRows = function() { - scope.tableRows.sort(function(currentRow, nextRow) { - - var sortResult = 0; - - if (currentRow[scope.sortByColumn] < nextRow[scope.sortByColumn]) { - sortResult = -1; - } else if (currentRow[scope.sortByColumn] === nextRow[scope.sortByColumn]) { - sortResult = 0; - } else { - sortResult = 1; - } - - if (scope.sortReverse) { - sortResult = -1 * sortResult; - } - - return sortResult; - }); - }; - - scope.getData = function() { - delete scope.tableRows; - - server.get(scope.moduleName, function(serverResponseData) { - - if (serverResponseData.length > 0) { - scope.tableHeaders = Object.keys(serverResponseData[0]); - } - - scope.tableRows = serverResponseData; - - if (scope.sortByColumn) { - scope.sortTableRows(); - } - - scope.lastGet = new Date().getTime(); - - if (serverResponseData.length < 1) { - scope.emptyResult = true; - } - - if (!scope.$$phase && !$rootScope.$$phase) scope.$digest(); - }); - }; - - scope.getData(); - } - }; - }]); - - /** - * Fetches and displays table data - */ - angular.module('linuxDash').directive('keyValueList', ['server', '$rootScope', function(server, $rootScope) { - return { - restrict: 'E', - scope: { - heading: '@', - info: '@', - moduleName: '@', - }, - templateUrl: 'templates/app/key-value-list-plugin.html', - link: function(scope, element) { - - scope.getData = function() { - delete scope.tableRows; - - server.get(scope.moduleName, function(serverResponseData) { - scope.tableRows = serverResponseData; - scope.lastGet = new Date().getTime(); - - if (Object.keys(serverResponseData).length === 0) { - scope.emptyResult = true; - } - - if (!scope.$$phase && !$rootScope.$$phase) scope.$digest(); - }); - }; - - scope.getData(); - } - }; - }]); - - /** - * Fetches and displays data as line chart at a certain refresh rate - */ - angular.module('linuxDash').directive('lineChartPlugin', ['$interval', '$compile', 'server', '$window', function($interval, $compile, server, $window) { - return { - restrict: 'E', - scope: { - heading: '@', - moduleName: '@', - refreshRate: '=', - maxValue: '=', - minValue: '=', - getDisplayValue: '=', - metrics: '=', - color: '@' - }, - templateUrl: 'templates/app/line-chart-plugin.html', - link: function(scope, element) { - - scope.initializing = true - - if (!scope.color) scope.color = '0, 255, 0'; - - var series, w, h, canvas; - - angular.element($window).bind('resize', function() { - canvas.width = w; - canvas.height = h; - }); - - // smoothieJS - Create new chart - var chart = new SmoothieChart({ - borderVisible: false, - sharpLines: true, - grid: { - fillStyle: '#ffffff', - strokeStyle: 'rgba(232,230,230,0.93)', - sharpLines: true, - millisPerLine: 3000, - borderVisible: false - }, - labels: { - fontSize: 11, - precision: 0, - fillStyle: '#0f0e0e' - }, - maxValue: parseInt(scope.maxValue), - minValue: parseInt(scope.minValue), - horizontalLines: [{ - value: 5, - color: '#eff', - lineWidth: 1 - }] - }); - - // smoothieJS - set up canvas element for chart - canvas = element.find('canvas')[0]; - series = new TimeSeries(); - w = canvas.width; - h = canvas.height; - - chart.addTimeSeries(series, { - strokeStyle: 'rgba(' + scope.color + ', 1)', - fillStyle: 'rgba(' + scope.color + ', 0.2)', - lineWidth: 2 - }); - - chart.streamTo(canvas, 1000); - - var dataCallInProgress = false; - - // update data on chart - scope.getData = function() { - - if(scope.initializing) - scope.initializing = false - - if (dataCallInProgress) return; - - dataCallInProgress = true; - - server.get(scope.moduleName, function(serverResponseData) { - - if (serverResponseData.length < 1) { - scope.emptyResult = true - return - } - - dataCallInProgress = false; - scope.lastGet = new Date().getTime(); - - // change graph colour depending on usage - if (scope.maxValue / 4 * 3 < scope.getDisplayValue(serverResponseData)) { - chart.seriesSet[0].options.strokeStyle = 'rgba(255, 89, 0, 1)'; - chart.seriesSet[0].options.fillStyle = 'rgba(255, 89, 0, 0.2)'; - } else if (scope.maxValue / 3 < scope.getDisplayValue(serverResponseData)) { - chart.seriesSet[0].options.strokeStyle = 'rgba(255, 238, 0, 1)'; - chart.seriesSet[0].options.fillStyle = 'rgba(255, 238, 0, 0.2)'; - } else { - chart.seriesSet[0].options.strokeStyle = 'rgba(' + scope.color + ', 1)'; - chart.seriesSet[0].options.fillStyle = 'rgba(' + scope.color + ', 0.2)'; - } - - // update chart with this response - series.append(scope.lastGet, scope.getDisplayValue(serverResponseData)); - - // update the metrics for this chart - scope.metrics.forEach(function(metricObj) { - metricObj.data = metricObj.generate(serverResponseData); - }); - - }); - }; - - // set the directive-provided interval - // at which to run the chart update - var intervalRef = $interval(scope.getData, scope.refreshRate); - var removeInterval = function() { - $interval.cancel(intervalRef); - }; - - element.on("$destroy", removeInterval); - } - }; - }]); - - /** - * Fetches and displays data as line chart at a certain refresh rate - * - */ - angular.module('linuxDash').directive('multiLineChartPlugin', ['$interval', '$compile', 'server', '$window', function($interval, $compile, server, $window) { - return { - restrict: 'E', - scope: { - heading: '@', - moduleName: '@', - refreshRate: '=', - getDisplayValue: '=', - units: '=', - delay: '=' - }, - templateUrl: 'templates/app/multi-line-chart-plugin.html', - link: function(scope, element) { - - var w, h, canvas; - - angular.element($window).bind('resize', function() { - canvas.width = w; - canvas.height = h; - }); - - // smoothieJS - Create new chart - var chart = new SmoothieChart({ - borderVisible: false, - sharpLines: true, - grid: { - fillStyle: '#ffffff', - strokeStyle: 'rgba(232,230,230,0.93)', - sharpLines: true, - borderVisible: false - }, - labels: { - fontSize: 12, - precision: 0, - fillStyle: '#0f0e0e' - }, - maxValue: 100, - minValue: 0, - horizontalLines: [{ - value: 1, - color: '#ecc', - lineWidth: 1 - }] - }); - - var seriesOptions = [{ - strokeStyle: 'rgba(255, 0, 0, 1)', - lineWidth: 2 - }, { - strokeStyle: 'rgba(0, 255, 0, 1)', - lineWidth: 2 - }, { - strokeStyle: 'rgba(0, 0, 255, 1)', - lineWidth: 2 - }, { - strokeStyle: 'rgba(255, 255, 0, 1)', - lineWidth: 1 - }]; - - // smoothieJS - set up canvas element for chart - var canvas = element.find('canvas')[0]; - w = canvas.width; - h = canvas.height; - scope.seriesArray = []; - scope.metricsArray = []; - - // get the data once to set up # of lines on chart - server.get(scope.moduleName, function(serverResponseData) { - - var numberOfLines = Object.keys(serverResponseData).length; - - for (var x = 0; x < numberOfLines; x++) { - - var keyForThisLine = Object.keys(serverResponseData)[x]; - - scope.seriesArray[x] = new TimeSeries(); - chart.addTimeSeries(scope.seriesArray[x], seriesOptions[x]); - scope.metricsArray[x] = { - name: keyForThisLine, - color: seriesOptions[x].strokeStyle, - }; - } - - }); - - var delay = 1000; - - if (angular.isDefined(scope.delay)) - delay = scope.delay; - - chart.streamTo(canvas, delay); - - var dataCallInProgress = false; - - // update data on chart - scope.getData = function() { - - if (dataCallInProgress) return; - - if (!scope.seriesArray.length) return; - - dataCallInProgress = true; - - server.get(scope.moduleName, function(serverResponseData) { - - dataCallInProgress = false; - scope.lastGet = new Date().getTime(); - var keyCount = 0; - var maxAvg = 100; - - // update chart with current response - for (var key in serverResponseData) { - scope.seriesArray[keyCount].append(scope.lastGet, serverResponseData[key]); - keyCount++; - maxAvg = Math.max(maxAvg, serverResponseData[key]); - } - - // update the metrics for this chart - scope.metricsArray.forEach(function(metricObj) { - metricObj.data = serverResponseData[metricObj.name].toString() + ' ' + scope.units; - }); - - // round up the average and set the maximum scale - var len = parseInt(Math.log(maxAvg) / Math.log(10)); - var div = Math.pow(10, len); - chart.options.maxValue = Math.ceil(maxAvg / div) * div; - - }); - - }; - - var refreshRate = (angular.isDefined(scope.refreshRate)) ? scope.refreshRate : 1000; - var intervalRef = $interval(scope.getData, refreshRate); - var removeInterval = function() { - $interval.cancel(intervalRef); - }; - - element.on("$destroy", removeInterval); - } - }; - }]); - - /** - * Base plugin structure - */ - angular.module('linuxDash').directive('plugin', function() { - return { - restrict: 'E', - transclude: true, - templateUrl: 'templates/app/base-plugin.html' - } - }); - - /** - * Progress bar element - */ - angular.module('linuxDash').directive('progressBarPlugin', function() { - return { - restrict: 'E', - scope: { - width: '@', - moduleName: '@', - name: '@', - value: '@', - max: '@' - }, - templateUrl: 'templates/app/progress-bar-plugin.html' - }; - }); - - - /** - * Theme switcher - */ - angular.module('linuxDash').directive('themeSwitcher', ['$location', function($location) { - return { - restrict: 'E', - templateUrl: 'templates/app/theme-switcher.html', - link: function(scope) { - - // alternate themes available - scope.themes = [{ - name: 'winter', - }, { - name: 'summer', - }, { - name: 'spring', - }, { - name: 'fall', - }, { - name: 'old', - }, ]; - - scope.themeSwitcherOpen = false; - - scope.switchTheme = function(theme) { - - if (theme.selected) { - scope.setDefaultTheme(); - return; - } - - scope.removeExistingThemes(); - theme.selected = true; - document.getElementsByTagName('html')[0].className = theme.name; - localStorage.setItem('theme', theme.name); - }; - - scope.toggleThemeSwitcher = function() { - scope.themeSwitcherOpen = !scope.themeSwitcherOpen; - }; - - scope.removeExistingThemes = function() { - scope.themes.forEach(function(item) { - item.selected = false; - }); - }; - - scope.setDefaultTheme = function() { - scope.removeExistingThemes(); - document.getElementsByTagName('html')[0].className = ''; - localStorage.setItem('theme', null); - }; - - // on load, check if theme was set in localStorage - if (localStorage.getItem('theme')) { - - scope.themes.forEach(function(theme) { - - if (theme.name === localStorage.getItem('theme')) { - scope.switchTheme(theme); - } - - }); - } - } - }; - }]); - -}()); diff --git a/js/modules.js b/js/modules.js deleted file mode 100644 index 8824932f..00000000 --- a/js/modules.js +++ /dev/null @@ -1,323 +0,0 @@ -(function() { - - "use strict"; - - /////////////////////////////////////////////////////////// - ////////////////// Module Directives /////////////////// // - /////////////////////////////////////////////////////////// - - angular.module('linuxDash').directive('diskSpace', ['server', function(server) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'templates/modules/disk-space.html', - link: function(scope) { - - scope.heading = "Disk Partitions"; - - scope.getData = function() { - server.get('disk_partitions', function(serverResponseData) { - scope.diskSpaceData = serverResponseData; - }); - - scope.lastGet = new Date().getTime(); - }; - - scope.getData(); - - scope.getKB = function(stringSize) { - var lastChar = stringSize.slice(-1), - size = parseFloat(stringSize.replace(",", ".")); - - switch (lastChar) { - case 'M': - return size * Math.pow(1024, 1); - case 'G': - return size * Math.pow(1024, 2); - case 'T': - return size * Math.pow(1024, 3); - case 'P': - return size * Math.pow(1024, 4); - case 'E': - return size * Math.pow(1024, 5); - case 'Z': - return size * Math.pow(1024, 6); - case 'Y': - return size * Math.pow(1024, 7); - default: - return size; - } - }; - } - }; - }]); - - angular.module('linuxDash').directive('ramChart', ['server', function(server) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'templates/modules/ram-chart.html', - link: function(scope) { - - // get max ram available on machine before we - // can start charting - server.get('current_ram', function(resp) { - scope.maxRam = resp.total; - scope.minRam = 0; - }); - - scope.ramToDisplay = function(serverResponseData) { - return serverResponseData.used; - }; - - var humanizeRam = function (ramInMB) { - var ram = { - value: parseInt(ramInMB, 10), - unit: 'MB', - }; - - // if ram > 1,000 MB, use GB - if (ram.value > 1000) { - ram = { - value: (ramInMB/1024).toFixed(2), - unit: 'GB', - }; - } - - return ram.value + ' ' + ram.unit; - }; - - scope.ramMetrics = [{ - name: 'Used', - generate: function(serverResponseData) { - var ratio = serverResponseData.used / serverResponseData.total; - var percentage = parseInt(ratio * 100); - - var usedRam = humanizeRam(serverResponseData.used); - return usedRam + ' (' + percentage.toString() + '%)'; - } - }, - { - name: 'Free', - generate: function(serverResponseData) { - - var freeRam = humanizeRam(serverResponseData.free); - var totalRam = humanizeRam(serverResponseData.total); - return freeRam + ' of ' + totalRam; - } - }]; - } - }; - }]); - - angular.module('linuxDash').directive('cpuAvgLoadChart', ['server', function(server) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'templates/modules/cpu-load.html', - link: function(scope) { - scope.units = '%'; - } - }; - }]); - angular.module('linuxDash').directive('cpuTemp', ['server', function(server) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'templates/modules/cpu-temp.html', - link: function(scope) { - scope.min = 0; - scope.max = 100; - - scope.displayValue = function(serverResponseData) { - return serverResponseData; - }; - - scope.utilMetrics = [{ - name: 'Temperature', - generate: function(serverResponseData) { - return serverResponseData + ' °C'; - } - }]; - - } - }; - }]); - - angular.module('linuxDash').directive('cpuUtilizationChart', ['server', function(server) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'templates/modules/cpu-utilization-chart.html', - link: function(scope) { - scope.min = 0; - scope.max = 100; - - scope.displayValue = function(serverResponseData) { - return serverResponseData; - }; - - scope.utilMetrics = [{ - name: 'Usage', - generate: function(serverResponseData) { - return serverResponseData + ' %'; - } - }]; - - } - }; - }]); - - angular.module('linuxDash').directive('uploadTransferRateChart', ['server', function(server) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'templates/modules/upload-transfer-rate.html', - link: function(scope) { - scope.delay = 2000; - scope.units = 'KB/s'; - } - }; - }]); - - angular.module('linuxDash').directive('downloadTransferRateChart', ['server', function(server) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'templates/modules/download-transfer-rate.html', - link: function(scope) { - scope.delay = 2000; - scope.units = 'KB/s'; - } - }; - }]); - - ////////////////////////////////////////////////////////// - /////////////// Table Data Modules //////////////////// // - ////////////////////////////////////////////////////////// - var simpleTableModules = [ - { - name: 'machineInfo', - template: '' - }, - { - name: 'ipAddresses', - template: '' - }, - { - name: 'ramIntensiveProcesses', - template: '' - }, - { - name: 'cpuIntensiveProcesses', - template: '' - }, - { - name: 'dockerProcesses', - template: '' - }, - { - name: 'networkConnections', - template: '' - }, - { - name: 'serverAccounts', - template: '' - }, - { - name: 'loggedInAccounts', - template: '' - }, - { - name: 'recentLogins', - template: '' - }, - { - name: 'arpCacheTable', - template: '' - }, - { - name: 'commonApplications', - template: '' - }, - { - name: 'pingSpeeds', - template: '' - }, - { - name: 'bandwidth', - template: '' - }, - { - name: 'swapUsage', - template: '' - }, - /*{ - name: 'cpuTemp', - template: '' - },*/ - { - name: 'internetSpeed', - template: '' - }, - { - name: 'memcached', - template: '' - }, - { - name: 'redis', - template: '' - }, - { - name: 'pm2', - template: '' - }, - { - name: 'memoryInfo', - template: '' - }, - { - name: 'cpuInfo', - template: '' - }, - { - name: 'ioStats', - template: '' - }, - { - name: 'scheduledCrons', - template: '' - }, - { - name: 'cronHistory', - template: '' - }, - { - name: 'raidStats', - template: '' - }, - ]; - - simpleTableModules.forEach(function(module, key) { - - angular.module('linuxDash').directive(module.name, ['server', function(server) { - - var moduleDirective = { - restrict: 'E', - scope: {} - }; - - if (module.templateUrl) { - moduleDirective['templateUrl'] = 'templates/modules/' + module.templateUrl - } - - if (module.template) { - moduleDirective['template'] = module.template; - } - - return moduleDirective; - }]); - - }); - -}()); diff --git a/js/smoothie.min.js b/js/smoothie.min.js deleted file mode 100644 index 0c7823a2..00000000 --- a/js/smoothie.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e){function n(e){this.options=t.extend({},n.defaultOptions,e);this.clear()}function r(e){this.options=t.extend({},r.defaultChartOptions,e);this.seriesSet=[];this.currentValueRange=1;this.currentVisMinValue=0;this.lastRenderTimeMillis=0}var t={extend:function(){arguments[0]=arguments[0]||{};for(var e=1;ethis.maxValue){this.maxValue=t}if(t=0&&this.data[r][0]>e){r--}if(r===-1){this.data.splice(0,0,[e,t])}else if(this.data.length>0&&this.data[r][0]===e){if(n){this.data[r][1]+=t;t=this.data[r][1]}else{this.data[r][1]=t}}else if(r=t&&this.data[n+1][0]0){e.resetBoundsTimerId=setInterval(function(){e.resetBounds()},e.options.resetBoundsInterval)}};r.prototype.removeTimeSeries=function(e){var t=this.seriesSet.length;for(var n=0;n.1||Math.abs(a)>.1;this.currentValueRange+=e.scaleSmoothing*u;this.currentVisMinValue+=e.scaleSmoothing*a}this.valueRange={min:n,max:t}};r.prototype.render=function(e,t){var n=(new Date).getTime();if(!this.isAnimatingScale){var r=Math.min(1e3/6,this.options.millisPerPixel);if(n-this.lastRenderTimeMillis0){i.beginPath();for(var l=t-t%s.grid.millisPerLine;l>=u;l-=s.grid.millisPerLine){var c=f(l);if(s.grid.sharpLines){c-=.5}i.moveTo(c,0);i.lineTo(c,o.height)}i.stroke();i.closePath()}for(var h=1;h1){if(w.fillStyle){i.lineTo(o.width+w.lineWidth+1,x);i.lineTo(o.width+w.lineWidth+1,o.height+w.lineWidth+1);i.lineTo(E,o.height+w.lineWidth);i.fillStyle=w.fillStyle;i.fill()}if(w.strokeStyle&&w.strokeStyle!=="none"){i.stroke()}i.closePath()}i.restore()}if(!s.labels.disabled&&!isNaN(this.valueRange.min)&&!isNaN(this.valueRange.max)){var k=s.yMaxFormatter(this.valueRange.max,s.labels.precision),L=s.yMinFormatter(this.valueRange.min,s.labels.precision);i.fillStyle=s.labels.fillStyle;i.fillText(k,o.width-i.measureText(k).width-2,s.labels.fontSize);i.fillText(L,o.width-i.measureText(L).width-2,o.height-2)}if(s.timestampFormatter&&s.grid.millisPerLine>0){var A=o.width-i.measureText(L).width+4;for(var l=t-t%s.grid.millisPerLine;l>=u;l-=s.grid.millisPerLine){var c=f(l);if(c -1; - var moduleNameEmpty = !moduleName; - var moduleNotFound = !fs.existsSync(shellFilePath); - var isValid = true; - - if (moduleInvalidName || moduleNameEmpty || moduleNotFound) { - isValid = false; - } - - return isValid; -} - -wsServer.on('request', function(request) { - - var wsClient = request.accept('linux-dash', request.origin); - - wsClient.on('message', function(wsReq) { - - var moduleName = wsReq.utf8Data; - var shellFile = getShellFilePath(moduleName); - - if (!shellPathAndModuleNameAreValid(shellFile, moduleName)) { - return; - } - - var command = spawn(shellFile, [ wsReq.color || '' ]); - var output = []; - - command.stdout.on('data', function(chunk) { - output.push(chunk); - }); - - command.on('close', function(code) { - - if (code === 0) { - - var wsResponse = { - moduleName: moduleName, - output: output.toString(), - }; - - wsClient.sendUTF(JSON.stringify(wsResponse)); - } - - }); - - }); - -}); - -app.get('/server/', function (req, res) { - - var shellFile = getShellFilePath(req.query.module); - - if (!shellPathAndModuleNameAreValid(shellFile, req.query.module)) { - res.sendStatus(406); - return; - } - - var command = spawn(shellFile, [ req.query.color || '' ]); - var output = []; - - command.stdout.on('data', function(chunk) { - output.push(chunk); - }); - - command.on('close', function(code) { - if (code === 0) res.send(output.toString()); - else res.sendStatus(500); - }); - -}); diff --git a/server/modules/python_files/speedtest_cli.py b/server/modules/python_files/speedtest_cli.py deleted file mode 100755 index 105c3906..00000000 --- a/server/modules/python_files/speedtest_cli.py +++ /dev/null @@ -1,694 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright 2012-2014 Matt Martz -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -__version__ = '0.3.1' - -# Some global variables we use -source = None -shutdown_event = None - -import os -import re -import sys -import math -import signal -import socket -import timeit -import threading - -# Used for bound_interface -socket_socket = socket.socket - -try: - import xml.etree.cElementTree as ET -except ImportError: - try: - import xml.etree.ElementTree as ET - except ImportError: - from xml.dom import minidom as DOM - ET = None - -# Begin import game to handle Python 2 and Python 3 -try: - from urllib2 import urlopen, Request, HTTPError, URLError -except ImportError: - from urllib.request import urlopen, Request, HTTPError, URLError - -try: - from httplib import HTTPConnection, HTTPSConnection -except ImportError: - from http.client import HTTPConnection, HTTPSConnection - -try: - from Queue import Queue -except ImportError: - from queue import Queue - -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - -try: - from urlparse import parse_qs -except ImportError: - try: - from urllib.parse import parse_qs - except ImportError: - from cgi import parse_qs - -try: - from hashlib import md5 -except ImportError: - from md5 import md5 - -try: - from argparse import ArgumentParser as ArgParser -except ImportError: - from optparse import OptionParser as ArgParser - -try: - import builtins -except ImportError: - def print_(*args, **kwargs): - """The new-style print function taken from - https://pypi.python.org/pypi/six/ - - """ - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - fp.write(data) - - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -else: - print_ = getattr(builtins, 'print') - del builtins - - -def bound_socket(*args, **kwargs): - """Bind socket to a specified source IP address""" - - global source - sock = socket_socket(*args, **kwargs) - sock.bind((source, 0)) - return sock - - -def distance(origin, destination): - """Determine distance between 2 sets of [lat,lon] in km""" - - lat1, lon1 = origin - lat2, lon2 = destination - radius = 6371 # km - - dlat = math.radians(lat2 - lat1) - dlon = math.radians(lon2 - lon1) - a = (math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos(math.radians(lat1)) - * math.cos(math.radians(lat2)) * math.sin(dlon / 2) - * math.sin(dlon / 2)) - c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) - d = radius * c - - return d - - -class FileGetter(threading.Thread): - """Thread class for retrieving a URL""" - - def __init__(self, url, start): - self.url = url - self.result = None - self.starttime = start - threading.Thread.__init__(self) - - def run(self): - self.result = [0] - try: - if (timeit.default_timer() - self.starttime) <= 10: - f = urlopen(self.url) - while 1 and not shutdown_event.isSet(): - self.result.append(len(f.read(10240))) - if self.result[-1] == 0: - break - f.close() - except IOError: - pass - - -def downloadSpeed(files, quiet=False): - """Function to launch FileGetter threads and calculate download speeds""" - - start = timeit.default_timer() - - def producer(q, files): - for file in files: - thread = FileGetter(file, start) - thread.start() - q.put(thread, True) - if not quiet and not shutdown_event.isSet(): - sys.stdout.write('.') - sys.stdout.flush() - - finished = [] - - def consumer(q, total_files): - while len(finished) < total_files: - thread = q.get(True) - while thread.isAlive(): - thread.join(timeout=0.1) - finished.append(sum(thread.result)) - del thread - - q = Queue(6) - prod_thread = threading.Thread(target=producer, args=(q, files)) - cons_thread = threading.Thread(target=consumer, args=(q, len(files))) - start = timeit.default_timer() - prod_thread.start() - cons_thread.start() - while prod_thread.isAlive(): - prod_thread.join(timeout=0.1) - while cons_thread.isAlive(): - cons_thread.join(timeout=0.1) - return (sum(finished) / (timeit.default_timer() - start)) - - -class FilePutter(threading.Thread): - """Thread class for putting a URL""" - - def __init__(self, url, start, size): - self.url = url - chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - data = chars * (int(round(int(size) / 36.0))) - self.data = ('content1=%s' % data[0:int(size) - 9]).encode() - del data - self.result = None - self.starttime = start - threading.Thread.__init__(self) - - def run(self): - try: - if ((timeit.default_timer() - self.starttime) <= 10 and - not shutdown_event.isSet()): - f = urlopen(self.url, self.data) - f.read(11) - f.close() - self.result = len(self.data) - else: - self.result = 0 - except IOError: - self.result = 0 - - -def uploadSpeed(url, sizes, quiet=False): - """Function to launch FilePutter threads and calculate upload speeds""" - - start = timeit.default_timer() - - def producer(q, sizes): - for size in sizes: - thread = FilePutter(url, start, size) - thread.start() - q.put(thread, True) - if not quiet and not shutdown_event.isSet(): - sys.stdout.write('.') - sys.stdout.flush() - - finished = [] - - def consumer(q, total_sizes): - while len(finished) < total_sizes: - thread = q.get(True) - while thread.isAlive(): - thread.join(timeout=0.1) - finished.append(thread.result) - del thread - - q = Queue(6) - prod_thread = threading.Thread(target=producer, args=(q, sizes)) - cons_thread = threading.Thread(target=consumer, args=(q, len(sizes))) - start = timeit.default_timer() - prod_thread.start() - cons_thread.start() - while prod_thread.isAlive(): - prod_thread.join(timeout=0.1) - while cons_thread.isAlive(): - cons_thread.join(timeout=0.1) - return (sum(finished) / (timeit.default_timer() - start)) - - -def getAttributesByTagName(dom, tagName): - """Retrieve an attribute from an XML document and return it in a - consistent format - - Only used with xml.dom.minidom, which is likely only to be used - with python versions older than 2.5 - """ - elem = dom.getElementsByTagName(tagName)[0] - return dict(list(elem.attributes.items())) - - -def getConfig(): - """Download the speedtest.net configuration and return only the data - we are interested in - """ - - uh = urlopen('http://www.speedtest.net/speedtest-config.php') - configxml = [] - while 1: - configxml.append(uh.read(10240)) - if len(configxml[-1]) == 0: - break - if int(uh.code) != 200: - return None - uh.close() - try: - try: - root = ET.fromstring(''.encode().join(configxml)) - config = { - 'client': root.find('client').attrib, - 'times': root.find('times').attrib, - 'download': root.find('download').attrib, - 'upload': root.find('upload').attrib} - except AttributeError: - root = DOM.parseString(''.join(configxml)) - config = { - 'client': getAttributesByTagName(root, 'client'), - 'times': getAttributesByTagName(root, 'times'), - 'download': getAttributesByTagName(root, 'download'), - 'upload': getAttributesByTagName(root, 'upload')} - except SyntaxError: - print_('Failed to parse speedtest.net configuration') - sys.exit(1) - del root - del configxml - return config - - -def closestServers(client, all=False): - """Determine the 5 closest speedtest.net servers based on geographic - distance - """ - - uh = urlopen('http://www.speedtest.net/speedtest-servers-static.php') - serversxml = [] - while 1: - serversxml.append(uh.read(10240)) - if len(serversxml[-1]) == 0: - break - if int(uh.code) != 200: - return None - uh.close() - try: - try: - root = ET.fromstring(''.encode().join(serversxml)) - elements = root.getiterator('server') - except AttributeError: - root = DOM.parseString(''.join(serversxml)) - elements = root.getElementsByTagName('server') - except SyntaxError: - print_('Failed to parse list of speedtest.net servers') - sys.exit(1) - servers = {} - for server in elements: - try: - attrib = server.attrib - except AttributeError: - attrib = dict(list(server.attributes.items())) - d = distance([float(client['lat']), float(client['lon'])], - [float(attrib.get('lat')), float(attrib.get('lon'))]) - attrib['d'] = d - if d not in servers: - servers[d] = [attrib] - else: - servers[d].append(attrib) - del root - del serversxml - del elements - - closest = [] - for d in sorted(servers.keys()): - for s in servers[d]: - closest.append(s) - if len(closest) == 5 and not all: - break - else: - continue - break - - del servers - return closest - - -def getBestServer(servers): - """Perform a speedtest.net latency request to determine which - speedtest.net server has the lowest latency - """ - - results = {} - for server in servers: - cum = [] - url = '%s/latency.txt' % os.path.dirname(server['url']) - urlparts = urlparse(url) - for i in range(0, 3): - try: - if urlparts[0] == 'https': - h = HTTPSConnection(urlparts[1]) - else: - h = HTTPConnection(urlparts[1]) - start = timeit.default_timer() - h.request("GET", urlparts[2]) - r = h.getresponse() - total = (timeit.default_timer() - start) - except (HTTPError, URLError, socket.error): - cum.append(3600) - continue - text = r.read(9) - if int(r.status) == 200 and text == 'test=test'.encode(): - cum.append(total) - else: - cum.append(3600) - h.close() - avg = round((sum(cum) / 6) * 1000, 3) - results[avg] = server - fastest = sorted(results.keys())[0] - best = results[fastest] - best['latency'] = fastest - - return best - - -def ctrl_c(signum, frame): - """Catch Ctrl-C key sequence and set a shutdown_event for our threaded - operations - """ - - global shutdown_event - shutdown_event.set() - raise SystemExit('\nCancelling...') - - -def version(): - """Print the version""" - - raise SystemExit(__version__) - - -def speedtest(): - """Run the full speedtest.net test""" - - global shutdown_event, source - shutdown_event = threading.Event() - - signal.signal(signal.SIGINT, ctrl_c) - - description = ( - 'Command line interface for testing internet bandwidth using ' - 'speedtest.net.\n' - '------------------------------------------------------------' - '--------------\n' - 'https://github.com/sivel/speedtest-cli') - - parser = ArgParser(description=description) - # Give optparse.OptionParser an `add_argument` method for - # compatibility with argparse.ArgumentParser - try: - parser.add_argument = parser.add_option - except AttributeError: - pass - parser.add_argument('--bytes', dest='units', action='store_const', - const=('bytes', 1), default=('bits', 8), - help='Display values in bytes instead of bits. Does ' - 'not affect the image generated by --share') - parser.add_argument('--share', action='store_true', - help='Generate and provide a URL to the speedtest.net ' - 'share results image') - parser.add_argument('--simple', action='store_true', - help='Suppress verbose output, only show basic ' - 'information') - parser.add_argument('--list', action='store_true', - help='Display a list of speedtest.net servers ' - 'sorted by distance') - parser.add_argument('--server', help='Specify a server ID to test against') - parser.add_argument('--mini', help='URL of the Speedtest Mini server') - parser.add_argument('--source', help='Source IP address to bind to') - parser.add_argument('--version', action='store_true', - help='Show the version number and exit') - - options = parser.parse_args() - if isinstance(options, tuple): - args = options[0] - else: - args = options - del options - - # Print the version and exit - if args.version: - version() - - # If specified bind to a specific IP address - if args.source: - source = args.source - socket.socket = bound_socket - - if not args.simple: - print_('Retrieving speedtest.net configuration...') - try: - config = getConfig() - except URLError: - print_('Cannot retrieve speedtest configuration') - sys.exit(1) - - if not args.simple: - print_('Retrieving speedtest.net server list...') - if args.list or args.server: - servers = closestServers(config['client'], True) - if args.list: - serverList = [] - for server in servers: - line = ('%(id)4s) %(sponsor)s (%(name)s, %(country)s) ' - '[%(d)0.2f km]' % server) - serverList.append(line) - # Python 2.7 and newer seem to be ok with the resultant encoding - # from parsing the XML, but older versions have some issues. - # This block should detect whether we need to encode or not - try: - unicode() - print_('\n'.join(serverList).encode('utf-8', 'ignore')) - except NameError: - print_('\n'.join(serverList)) - except IOError: - pass - sys.exit(0) - else: - servers = closestServers(config['client']) - - if not args.simple: - print_('Testing from %(isp)s (%(ip)s)...' % config['client']) - - if args.server: - try: - best = getBestServer(filter(lambda x: x['id'] == args.server, - servers)) - except IndexError: - print_('Invalid server ID') - sys.exit(1) - elif args.mini: - name, ext = os.path.splitext(args.mini) - if ext: - url = os.path.dirname(args.mini) - else: - url = args.mini - urlparts = urlparse(url) - try: - f = urlopen(args.mini) - except: - print_('Invalid Speedtest Mini URL') - sys.exit(1) - else: - text = f.read() - f.close() - extension = re.findall('upload_extension: "([^"]+)"', text.decode()) - if not extension: - for ext in ['php', 'asp', 'aspx', 'jsp']: - try: - f = urlopen('%s/speedtest/upload.%s' % (args.mini, ext)) - except: - pass - else: - data = f.read().strip() - if (f.code == 200 and - len(data.splitlines()) == 1 and - re.match('size=[0-9]', data)): - extension = [ext] - break - if not urlparts or not extension: - print_('Please provide the full URL of your Speedtest Mini server') - sys.exit(1) - servers = [{ - 'sponsor': 'Speedtest Mini', - 'name': urlparts[1], - 'd': 0, - 'url': '%s/speedtest/upload.%s' % (url.rstrip('/'), extension[0]), - 'latency': 0, - 'id': 0 - }] - try: - best = getBestServer(servers) - except: - best = servers[0] - else: - if not args.simple: - print_('Selecting best server based on latency...') - best = getBestServer(servers) - - if not args.simple: - # Python 2.7 and newer seem to be ok with the resultant encoding - # from parsing the XML, but older versions have some issues. - # This block should detect whether we need to encode or not - try: - unicode() - print_(('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: ' - '%(latency)s ms' % best).encode('utf-8', 'ignore')) - except NameError: - print_('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: ' - '%(latency)s ms' % best) - else: - print_('Ping: %(latency)s ms' % best) - - sizes = [350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000] - urls = [] - for size in sizes: - for i in range(0, 4): - urls.append('%s/random%sx%s.jpg' % - (os.path.dirname(best['url']), size, size)) - if not args.simple: - print_('Testing download speed', end='') - dlspeed = downloadSpeed(urls, args.simple) - if not args.simple: - print_() - print_('Download: %0.2f M%s/s' % - ((dlspeed / 1000 / 1000) * args.units[1], args.units[0])) - - sizesizes = [int(.25 * 1000 * 1000), int(.5 * 1000 * 1000)] - sizes = [] - for size in sizesizes: - for i in range(0, 25): - sizes.append(size) - if not args.simple: - print_('Testing upload speed', end='') - ulspeed = uploadSpeed(best['url'], sizes, args.simple) - if not args.simple: - print_() - print_('Upload: %0.2f M%s/s' % - ((ulspeed / 1000 / 1000) * args.units[1], args.units[0])) - - if args.share and args.mini: - print_('Cannot generate a speedtest.net share results image while ' - 'testing against a Speedtest Mini server') - elif args.share: - dlspeedk = int(round((dlspeed / 1000) * 8, 0)) - ping = int(round(best['latency'], 0)) - ulspeedk = int(round((ulspeed / 1000) * 8, 0)) - - # Build the request to send results back to speedtest.net - # We use a list instead of a dict because the API expects parameters - # in a certain order - apiData = [ - 'download=%s' % dlspeedk, - 'ping=%s' % ping, - 'upload=%s' % ulspeedk, - 'promo=', - 'startmode=%s' % 'pingselect', - 'recommendedserverid=%s' % best['id'], - 'accuracy=%s' % 1, - 'serverid=%s' % best['id'], - 'hash=%s' % md5(('%s-%s-%s-%s' % - (ping, ulspeedk, dlspeedk, '297aae72')) - .encode()).hexdigest()] - - req = Request('http://www.speedtest.net/api/api.php', - data='&'.join(apiData).encode()) - req.add_header('Referer', 'http://c.speedtest.net/flash/speedtest.swf') - f = urlopen(req) - response = f.read() - code = f.code - f.close() - - if int(code) != 200: - print_('Could not submit results to speedtest.net') - sys.exit(1) - - qsargs = parse_qs(response.decode()) - resultid = qsargs.get('resultid') - if not resultid or len(resultid) != 1: - print_('Could not submit results to speedtest.net') - sys.exit(1) - - print_('Share results: http://www.speedtest.net/result/%s.png' % - resultid[0]) - - -def main(): - try: - speedtest() - except KeyboardInterrupt: - print_('\nCancelling...') - - -if __name__ == '__main__': - main() - -# vim:ts=4:sw=4:expandtab diff --git a/server/modules/shell_files/arp_cache.sh b/server/modules/shell_files/arp_cache.sh deleted file mode 100755 index 1555d5bd..00000000 --- a/server/modules/shell_files/arp_cache.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -arpCommand=$(command -v arp) - -result=$($arpCommand | awk 'BEGIN {print "["} NR>1 \ - {print "{ \"address\": \"" $1 "\", " \ - "\"hw_type\": \"" $2 "\", " \ - "\"hw_address\": \"" $3 "\", " \ - "\"flags\": \"" $4 "\", " \ - "\"mask\": \"" $5 "\" }, " \ - } \ - END {print "]"}' \ - | /bin/sed 'N;$s/},/}/;P;D') - -if [ -z "$result" ]; then echo {} -else echo $result -fi \ No newline at end of file diff --git a/server/modules/shell_files/bandwidth.sh b/server/modules/shell_files/bandwidth.sh deleted file mode 100755 index 51a2cd75..00000000 --- a/server/modules/shell_files/bandwidth.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -/bin/cat /proc/net/dev \ -| awk 'BEGIN {print "["} NR>2 {print "{ \"interface\": \"" $1 "\"," \ - " \"tx\": " $2 "," \ - " \"rx\": " $10 " }," } END {print "]"}' \ -| /bin/sed 'N;$s/,\n/\n/;P;D' diff --git a/server/modules/shell_files/common_applications.sh b/server/modules/shell_files/common_applications.sh deleted file mode 100755 index d9e81bd8..00000000 --- a/server/modules/shell_files/common_applications.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -result=$(whereis php node mysql mongo vim python ruby java apache2 nginx openssl vsftpd make \ -| awk -F: '{if(length($2)==0) { installed="false"; } else { installed="true"; } \ - print \ - "{ \ - \"binary\": \""$1"\", \ - \"location\": \""$2"\", \ - \"installed\": "installed" \ - },"}') - -echo "[" ${result%?} "]" \ No newline at end of file diff --git a/server/modules/shell_files/cpu_info.sh b/server/modules/shell_files/cpu_info.sh deleted file mode 100755 index c00b6031..00000000 --- a/server/modules/shell_files/cpu_info.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -result=$(/usr/bin/lscpu \ - | /usr/bin/awk -F: '{print "\""$1"\": \""$2"\"," } '\ - ) - -echo "{" ${result%?} "}" \ No newline at end of file diff --git a/server/modules/shell_files/cpu_intensive_processes.sh b/server/modules/shell_files/cpu_intensive_processes.sh deleted file mode 100755 index a193bb23..00000000 --- a/server/modules/shell_files/cpu_intensive_processes.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -result=$(/bin/ps axo pid,user,pcpu,rss,vsz,comm --sort -pcpu,-rss,-vsz \ - | head -n 15 \ - | /usr/bin/awk 'BEGIN{OFS=":"} NR>1 {print "{ \"pid\": " $1 \ - ", \"user\": \"" $2 "\"" \ - ", \"cpu%\": " $3 \ - ", \"rss\": " $4 \ - ", \"vsz\": " $5 \ - ", \"cmd\": \"" $6 "\"" "},"\ - }') - -echo "[" ${result%?} "]" \ No newline at end of file diff --git a/server/modules/shell_files/cpu_temp.sh b/server/modules/shell_files/cpu_temp.sh deleted file mode 100755 index c25b9718..00000000 --- a/server/modules/shell_files/cpu_temp.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -if [ `which sensors` ]; then - returnString=`sensors` - #amd - if [[ "${returnString/"k10"}" != "${returnString}" ]] ; then - echo ${returnString##*k10} | cut -d ' ' -f 6 | cut -c 2- | cut -c 1-4 - #intel - elif [[ "${returnString/"core"}" != "${returnString}" ]] ; then - fromcore=${returnString##*"coretemp"} - echo ${fromcore##*Core} | cut -d ' ' -f 2 | cut -c 2-5 - fi -else - echo "[]" -fi - diff --git a/server/modules/shell_files/cpu_utilization.sh b/server/modules/shell_files/cpu_utilization.sh deleted file mode 100755 index 7d6c3619..00000000 --- a/server/modules/shell_files/cpu_utilization.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -# by Paul Colby (http://colby.id.au), no rights reserved ;) - -PREV_TOTAL=0 -PREV_IDLE=0 -iteration=0 - -while [[ iteration -lt 2 ]]; do - # Get the total CPU statistics, discarding the 'cpu ' prefix. - CPU=(`sed -n 's/^cpu\s//p' /proc/stat`) - IDLE=${CPU[3]} # Just the idle CPU time. - - # Calculate the total CPU time. - TOTAL=0 - for VALUE in "${CPU[@]}"; do - let "TOTAL=$TOTAL+$VALUE" - done - - # Calculate the CPU usage since we last checked. - let "DIFF_IDLE=$IDLE-$PREV_IDLE" - let "DIFF_TOTAL=$TOTAL-$PREV_TOTAL" - let "DIFF_USAGE=(1000*($DIFF_TOTAL-$DIFF_IDLE)/$DIFF_TOTAL+5)/10" - #echo -en "\rCPU: $DIFF_USAGE% \b\b" - - # Remember the total and idle CPU times for the next check. - PREV_TOTAL="$TOTAL" - PREV_IDLE="$IDLE" - - # Wait before checking again. - sleep 1 - iteration="$iteration+1" -done -echo -en "$DIFF_USAGE" diff --git a/server/modules/shell_files/cron_history.sh b/server/modules/shell_files/cron_history.sh deleted file mode 100755 index e7cd8045..00000000 --- a/server/modules/shell_files/cron_history.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -grepCmd=$(which grep) -cronLog='/var/log/syslog' -numberOfLines='50' - -# Month, Day, Time, Hostname, tag, user, - -result=$($grepCmd -m$numberOfLines CRON $cronLog \ - | awk '{ s = ""; for (i = 6; i <= NF; i++) s = s $i " "; \ - print "{\"time\" : \"" $1" "$2" "$3 "\"," \ - "\"user\" : \"" $6 "\"," \ - "\"message\" : \"" $5" "gensub("\"", "\\\\\"", "g", s) "\"" \ - "}," - }' - ) - -echo [${result%?}] diff --git a/server/modules/shell_files/current_ram.sh b/server/modules/shell_files/current_ram.sh deleted file mode 100755 index 4cbac099..00000000 --- a/server/modules/shell_files/current_ram.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -awkCmd=$(which awk) -catCmd=$(which cat) -grepCmd=$(which grep) -memInfoFile="/proc/meminfo" - -# References: -# Calculations: http://zcentric.com/2012/05/29/mapping-procmeminfo-to-output-of-free-command/ -# Fields: https://www.kernel.org/doc/Documentation/filesystems/proc.txt - -memInfo=$($catCmd $memInfoFile | $grepCmd 'MemTotal\|MemFree\|Buffers\|Cached') - -echo $memInfo | $awkCmd '{print "{ \"total\": " ($2/1024) ", \"used\": " ( ($2-($5+$8+$11))/1024 ) ", \"free\": " (($5+$8+$11)/1024) " }" }' diff --git a/server/modules/shell_files/disk_partitions.sh b/server/modules/shell_files/disk_partitions.sh deleted file mode 100755 index d563d193..00000000 --- a/server/modules/shell_files/disk_partitions.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -result=$(/bin/df -Ph | awk 'NR>1 {print "{\"file_system\": \"" $1 "\", \"size\": \"" $2 "\", \"used\": \"" $3 "\", \"avail\": \"" $4 "\", \"used%\": \"" $5 "\", \"mounted\": \"" $6 "\"},"}') - -echo [ ${result%?} ] \ No newline at end of file diff --git a/server/modules/shell_files/docker_processes.sh b/server/modules/shell_files/docker_processes.sh deleted file mode 100755 index 9ba42cc8..00000000 --- a/server/modules/shell_files/docker_processes.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -result="" -containers="$(docker ps | awk '{if(NR>1) print $NF}')" -for i in $containers; do -result="$result $(/usr/bin/docker top $i axo pid,user,pcpu,pmem,comm --sort -pcpu,-pmem \ - | head -n 15 \ - | /usr/bin/awk -v cnt="$i" 'BEGIN{OFS=":"} NR>1 {print "{ \"cname\": \"" cnt \ - "\", \"pid\": " $1 \ - ", \"user\": \"" $2 "\"" \ - ", \"cpu%\": " $3 \ - ", \"mem%\": " $4 \ - ", \"cmd\": \"" $5 "\"" "},"\ - }')" -done - -echo "[" ${result%?} "]" diff --git a/server/modules/shell_files/download_transfer_rate.sh b/server/modules/shell_files/download_transfer_rate.sh deleted file mode 100755 index 5762d6d4..00000000 --- a/server/modules/shell_files/download_transfer_rate.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -files=(/sys/class/net/*) -pos=$(( ${#files[*]} - 1 )) -last=${files[$pos]} -in1=() - -json_output="{" - -for interface in "${files[@]}" -do - basename=$(basename "$interface") - - # find the number of bytes transfered for this interface - in1+=( $(cat /sys/class/net/"$basename"/statistics/rx_bytes) ) -done - -# wait a moment -sleep 0.5 - -for interface in "${files[@]}" -do - basename=$(basename "$interface") - # check same interface again - in2=$(cat /sys/class/net/"$basename"/statistics/rx_bytes) - - # read and remove first element - in=${in1[0]} - unset in1[0] - in1=( "${in1[@]}" ) - - # get the difference (transfer rate) - in_bytes=$((in2 - in)) - - # convert transfer rate to KB - in_kbytes=$((in_bytes / 1024 * 2)) - - # convert transfer rate to KB - json_output="$json_output \"$basename\": $in_kbytes" - - # if it is not the last line - if [[ ! $interface == $last ]] - then - # add a comma to the line (JSON formatting) - json_output="$json_output," - fi -done - -# close the JSON object & print to screen -echo "$json_output}" diff --git a/server/modules/shell_files/general_info.sh b/server/modules/shell_files/general_info.sh deleted file mode 100755 index a76b20cb..00000000 --- a/server/modules/shell_files/general_info.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -function displaytime { - local T=$1 - local D=$((T/60/60/24)) - local H=$((T/60/60%24)) - local M=$((T/60%60)) - local S=$((T%60)) - [[ $D > 0 ]] && printf '%d days ' $D - [[ $H > 0 ]] && printf '%d hours ' $H - [[ $M > 0 ]] && printf '%d minutes ' $M - [[ $D > 0 || $H > 0 || $M > 0 ]] && printf 'and ' - printf '%d seconds\n' $S -} - -lsbRelease=$(/usr/bin/lsb_release -ds | sed -e 's/^"//' -e 's/"$//') -uname=$(/bin/uname -r | sed -e 's/^"//' -e 's/"$//') -os=`echo $lsbRelease $uname` -hostname=$(/bin/hostname) -uptime_seconds=$(/bin/cat /proc/uptime | awk '{print $1}') -server_time=$(date) - -echo { \ - \"OS\": \"$os\", \ - \"Hostname\": \"$hostname\", \ - \"Uptime\": \" $(displaytime ${uptime_seconds%.*}) \", \ - \"Server Time\": \"$server_time\" \ - } diff --git a/server/modules/shell_files/internet_speed.sh b/server/modules/shell_files/internet_speed.sh deleted file mode 100755 index fa2478a6..00000000 --- a/server/modules/shell_files/internet_speed.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -SCRIPTPATH=`dirname $(readlink -f $0)` -SPEED_TEST_SCRIPT=$SCRIPTPATH"/../python_files/speedtest_cli.py" - -$SPEED_TEST_SCRIPT \ -| grep 'Upload\|Download' \ -| awk 'BEGIN {print "{"} {print "\"" $1 "\": \"" $2 " " $3 "\"," } END {print "}"}' \ -| /bin/sed 'N;$s/",/"/;P;D' \ No newline at end of file diff --git a/server/modules/shell_files/io_stats.sh b/server/modules/shell_files/io_stats.sh deleted file mode 100755 index 0e14d4e6..00000000 --- a/server/modules/shell_files/io_stats.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -result=$(/bin/cat /proc/diskstats | /usr/bin/awk \ - '{ if($4==0 && $8==0 && $12==0 && $13==0) next } \ - {print "{ \"device\": \"" $3 "\", \"reads\": \""$4"\", \"writes\": \"" $8 "\", \"in_progress\": \"" $12 "\", \"time_in_io\": \"" $13 "\"},"}' - ) - -echo [ ${result%?} ] \ No newline at end of file diff --git a/server/modules/shell_files/ip_addresses.sh b/server/modules/shell_files/ip_addresses.sh deleted file mode 100755 index 0018f6f8..00000000 --- a/server/modules/shell_files/ip_addresses.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -awkCmd=`which awk` -grepCmd=`which grep` -sedCmd=`which sed` -ifconfigCmd=`which ifconfig` -trCmd=`which tr` -digCmd=`which dig` - -externalIp=`$digCmd +short myip.opendns.com @resolver1.opendns.com` - -echo -n "[" - -for item in $($ifconfigCmd | $grepCmd -oP "^[a-zA-Z0-9:]*(?=:)") -do - echo -n "{\"interface\" : \""$item"\", \"ip\" : \"$( $ifconfigCmd $item | $grepCmd "inet" | $awkCmd '{match($0,"inet (addr:)?([0-9.]*)",a)}END{ if (NR != 0){print a[2]; exit}{print "none"}}')\"}, " -done - -echo "{ \"interface\": \"external\", \"ip\": \"$externalIp\" } ]" diff --git a/server/modules/shell_files/load_avg.sh b/server/modules/shell_files/load_avg.sh deleted file mode 100755 index 98be035f..00000000 --- a/server/modules/shell_files/load_avg.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -grepCmd=`which grep` -awkCmd=`which awk` -catCmd=`which cat` - -numberOfCores=$($grepCmd -c 'processor' /proc/cpuinfo) - -if [ $numberOfCores -eq 0 ]; then - numberOfCores=1 -fi - -result=$($catCmd /proc/loadavg | $awkCmd '{print "{ \"1_min_avg\": " ($1*100)/'$numberOfCores' ", \"5_min_avg\": " ($2*100)/'$numberOfCores' ", \"15_min_avg\": " ($3*100)/'$numberOfCores' "}," }') - -echo ${result%?} diff --git a/server/modules/shell_files/logged_in_users.sh b/server/modules/shell_files/logged_in_users.sh deleted file mode 100755 index 635163a0..00000000 --- a/server/modules/shell_files/logged_in_users.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -result=$(COLUMNS=300 /usr/bin/w -h | /usr/bin/awk '{print "{\"user\": \"" $1 "\", \"from\": \"" $3 "\", \"when\": \"" $4 "\"},"}') - -echo [ ${result%?} ] \ No newline at end of file diff --git a/server/modules/shell_files/memcached.sh b/server/modules/shell_files/memcached.sh deleted file mode 100755 index 88f8516e..00000000 --- a/server/modules/shell_files/memcached.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -echo "stats" \ - | /bin/nc -w 1 127.0.0.1 11211 \ - | /bin/grep 'bytes' \ - | /usr/bin/awk 'BEGIN {print "{"} {print "\"" $2 "\": " $3 } END {print "}"}' \ - | /usr/bin/tr '\r' ',' \ - | /bin/sed 'N;$s/,\n/\n/;P;D' \ No newline at end of file diff --git a/server/modules/shell_files/memory_info.sh b/server/modules/shell_files/memory_info.sh deleted file mode 100755 index 9b2ce6cd..00000000 --- a/server/modules/shell_files/memory_info.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -/bin/cat /proc/meminfo \ - | /usr/bin/awk -F: 'BEGIN {print "{"} {print "\"" $1 "\": \"" $2 "\"," } END {print "}"}' \ - | /bin/sed 'N;$s/,\n/\n/;P;D' diff --git a/server/modules/shell_files/network_connections.sh b/server/modules/shell_files/network_connections.sh deleted file mode 100755 index 9f952f83..00000000 --- a/server/modules/shell_files/network_connections.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -netstatCmd=`which netstat` -awkCmd=`which awk` -sortCmd=`which sort` -uniqCmd=`which uniq` -sedCmd=`which sed` - -$netstatCmd -ntu \ -| $awkCmd 'NR>2 {print $5}' \ -| $sortCmd \ -| $uniqCmd -c \ -| $awkCmd 'BEGIN {print "["} {print "{ \"connections\": " $1 ", \"address\": \"" $2 "\" }," } END {print "]"}' \ -| $sedCmd 'N;$s/},/}/;P;D' diff --git a/server/modules/shell_files/number_of_cpu_cores.sh b/server/modules/shell_files/number_of_cpu_cores.sh deleted file mode 100755 index 42c20e5a..00000000 --- a/server/modules/shell_files/number_of_cpu_cores.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -numberOfCores=$(/bin/grep -c 'model name' /proc/cpuinfo) - -if [length($numberOfCores)]; then - echo "cannnot be found"; -fi \ No newline at end of file diff --git a/server/modules/shell_files/ping.sh b/server/modules/shell_files/ping.sh deleted file mode 100755 index 32d5e3b8..00000000 --- a/server/modules/shell_files/ping.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# http://askubuntu.com/questions/413367/ping-multiple-ips-using-bash - -# get absolute path to config file -SCRIPTPATH=`dirname $(readlink -f $0)` -CONFIG_PATH=$SCRIPTPATH"/../config/ping_hosts" - -catCmd=`which cat` -pingCmd=`which ping` -awkCmd=`which awk` -sedCmd=`which sed` -numOfLinesInConfig=`$sedCmd -n '$=' $CONFIG_PATH` -result='[' - -$catCmd $CONFIG_PATH \ -| while read output - do - singlePing=$($pingCmd -qc 2 $output \ - | $awkCmd -F/ 'BEGIN { endLine="}," } /^rtt/ { if ('$numOfLinesInConfig'==1){endLine="}"} print "{" "\"host\": \"'$output'\", \"ping\": " $5 " " endLine }' \ - ) - numOfLinesInConfig=$(($numOfLinesInConfig-1)) - result=$result$singlePing - if [ $numOfLinesInConfig -eq 0 ] - then - echo $result"]" - fi - done \ -| $sedCmd 's/\},]/}]/g' diff --git a/server/modules/shell_files/pm2.sh b/server/modules/shell_files/pm2.sh deleted file mode 100755 index 7815f4e6..00000000 --- a/server/modules/shell_files/pm2.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -#get data -command="pm2 list" -data="$($command)" - -#only process data if variable has a length -#this should handle cases where pm2 is not installed -if [ -n "$data" ]; then - - #start processing data on line 4 - #don't process last 2 lines - json=$( echo "$data" | tail -n +4 | head -n +2 \ - | awk '{print "{"}\ - {print "\"appName\":\"" $2 "\","} \ - {print "\"id\":\"" $4 "\","} \ - {print "\"mode\":\"" $6 "\","} \ - {print "\"pid\":\"" $8 "\","}\ - {print "\"status\":\"" $10 "\","}\ - {print "\"restart\":\"" $12 "\","}\ - {print "\"uptime\":\"" $14 "\","}\ - {print "\"memory\":\"" $16 $17 "\","}\ - {print "\"watching\":\"" $19 "\""}\ - {print "},"}') - #make sure to remove last comma and print in array - echo "[" ${json%?} "]" -else - #no data found - echo "{}" -fi - - diff --git a/server/modules/shell_files/raid_status.sh b/server/modules/shell_files/raid_status.sh deleted file mode 100755 index ef0ec2d0..00000000 --- a/server/modules/shell_files/raid_status.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/perl - - -my($devinfo_re, $devstat_re, $action_re) = ( - '(md\d+)\s+:\s+active\s+(\(read-only\)\s+|\(auto-read-only\)\s+|)(\w+)\s+(.*)', - '.*\[(\d+)\/(\d+)]\s+\[(\w+)]', - '.*(reshape|check|resync|recovery)\s*=\s*(\d+\.\d+%|\w+)(.*finish=(.*min))?', -); -# Interestingly, swap is presented as "active (auto-read-only)" -# and mdadm has '--readonly' option to make the array 'active (read-only)' -print "["; - -open(my $mdstat, "/proc/mdstat"); -my(@text) = <$mdstat>; -# contents of <$mdstat> may be changed at next reading, so fetch the contents at a time -close($mdstat); - -my($dev, $ro, $type, $members, $nmem, $nact, $status, $action, $proc, $minute, $idx); -while (@text) { - my $line = shift @text; - if ($line =~ /$devinfo_re/) { - # first line should like "active raid1 sda1[0] sdc1[2] sdb1[1]" - $dev = $1; - $ro = $2 || ''; - $type = $3; - $members = $4; - - $line = shift @text; - if ($line =~ /$devstat_re/) { - # second line should like "123456 blocks super 1.2 [2/2] [UU]" - $nmem = $1; - $nact = $2; - $status = $3; - } - else { - # sencond line did not exist on /proc/mdstat - next; - } - - $line = shift @text; - if ($line =~ /$action_re/) { - # third line should like " [==>..................] check = 10.0% (12345/123456) finish=123min speed=12345/sec" - # this line will appear only when the array is in action - $action = $1; - my $percent = $2; - $minute = $4 || ''; - if ($percent =~ /(\d+\.\d+)%/) { - $proc = $1; - } - else { - # 'resync=DELAYED' or 'resync=PENDING' - $action .= " ($percent)"; - $proc = -1; - } - } - else { - # array is not in action - $action = 'idle'; - $minute = ''; - unshift(@text, $line); - } - } - else { - # skip until first line is found - next; - } - - if ( $ARGV[0] and $ARGV[0] eq "config" ) { - print "$dev.label $dev\n"; - print "$dev.info $type $ro$members\n"; - # 100: means less than 100 - # Because of an unfound bug, sometimes reported as 99.XX even when OS reports 100. - print "$dev.critical 98:\n"; - print $dev, "_rebuild.label $dev reshape/recovery\n"; - print $dev, "_rebuild.info $action $minute\n"; - # Because of an unfound bug, sometimes reported as 99.XX even when OS reports 100. - print $dev, "_rebuild.critical 98:\n"; - print $dev, "_check.label $dev check/resync \n"; - print $dev, "_check.info $action $minute\n"; - } else { - my $pct = 100 * $nact / $nmem; - my $rpct = 100; - my $cpct = 100; - if ($action =~ /reshape|recovery/) { - $rpct = $proc; - $cpct = 0; # check/resync is not done - } - elsif ($action =~ /check|resync/) { - if ($proc < 0) { - # array is on DELAYED or PENDING, further info is unknown - $rpct = 0; - $cpct = 0; - } - else { - # reshape/recovery was done, $rpct => 100 - $cpct = $proc; - } - } - - if($idx > 0) { - print ", \n"; - } - print "{ \"device\": \"$dev\", \"value\": $pct, \"rebuild\": $rpct, \"check\": $cpct }"; - - } - $idx = $idx + 1; - -} -print "]"; diff --git a/server/modules/shell_files/ram_intensive_processes.sh b/server/modules/shell_files/ram_intensive_processes.sh deleted file mode 100755 index a86e120b..00000000 --- a/server/modules/shell_files/ram_intensive_processes.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -result=$(/bin/ps axo pid,user,pmem,rss,vsz,comm --sort -pmem,-rss,-vsz \ - | head -n 15 \ - | /usr/bin/awk 'NR>1 {print "{ \"pid\": " $1 \ - ", \"user\": \"" $2 \ - "\", \"mem%\": " $3 \ - ", \"rss\": " $4 \ - ", \"vsz\": " $5 \ - ", \"cmd\": \"" $6 \ - "\"},"}') - -echo [ ${result%?} ] \ No newline at end of file diff --git a/server/modules/shell_files/recent_account_logins.sh b/server/modules/shell_files/recent_account_logins.sh deleted file mode 100755 index de0b0809..00000000 --- a/server/modules/shell_files/recent_account_logins.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -result=$(/usr/bin/lastlog -t 365 \ - | /usr/bin/awk 'NR>1 {\ - print "{ \ - \"user\": \"" $1 "\", \ - \"ip\": \"" $3 "\","" \ - \"date\": \"" $5" "$6" "$7" "$8" "$9 "\"}," - }' - ) -echo [ ${result%?} ] \ No newline at end of file diff --git a/server/modules/shell_files/redis.sh b/server/modules/shell_files/redis.sh deleted file mode 100755 index 38bcaf43..00000000 --- a/server/modules/shell_files/redis.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -########### Enter Your Redis Password HERE ######### -redisPassword='' -########### Enter Your Redis Password HERE ######### - -redisCommand=$(which redis-cli); - -if [ -n "$redisPassword" ]; then - redisCommand="$redisCommand -a $redisPassword" -fi - -result=$($redisCommand INFO \ - | grep 'redis_version\|connected_clients\|connected_slaves\|used_memory_human\|total_connections_received\|total_commands_processed' \ - | awk -F: '{print "\"" $1 "\":" "\"" $2 }' \ - | tr '\r' '"' | tr '\n' ',' - ) -echo { ${result%?} } \ No newline at end of file diff --git a/server/modules/shell_files/scheduled_crons.sh b/server/modules/shell_files/scheduled_crons.sh deleted file mode 100755 index 293683f0..00000000 --- a/server/modules/shell_files/scheduled_crons.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash -###### -# Credit: http://stackoverflow.com/questions/134906/how-do-i-list-all-cron-jobs-for-all-users#answer-137173 -###### - -catCmd=`which cat` -awkCmd=`which awk` -sedCmd=`which sed` -egrepCmd=`which egrep` -echoCmd=`which echo` -crontabCmd=`which crontab` -trCmd=`which tr` - -# System-wide crontab file and cron job directory. Change these for your system. -CRONTAB='/etc/crontab' -CRONDIR='/etc/cron.d' - -# Single tab character. Annoyingly necessary. -tab=$(echo -en "\t") - -# Given a stream of crontab lines, exclude non-cron job lines, replace -# whitespace characters with a single space, and remove any spaces from the -# beginning of each line. -function clean_cron_lines() { - while read line ; do - $echoCmd "${line}" | - $egrepCmd --invert-match '^($|\s*#|\s*[[:alnum:]_]+=)' | - $sedCmd --regexp-extended "s/\s+/ /g" | - $sedCmd --regexp-extended "s/^ //" - done; -} - -# Given a stream of cleaned crontab lines, $echoCmd any that don't include the -# run-parts command, and for those that do, show each job file in the run-parts -# directory as if it were scheduled explicitly. -function lookup_run_parts() { - while read line ; do - match=$($echoCmd "${line}" | $egrepCmd -o 'run-parts (-{1,2}\S+ )*\S+') - - if [[ -z "${match}" ]] ; then - $echoCmd "${line}" - else - cron_fields=$($echoCmd "${line}" | cut -f1-6 -d' ') - cron_job_dir=$($echoCmd "${match}" | awk '{print $NF}') - - if [[ -d "${cron_job_dir}" ]] ; then - for cron_job_file in "${cron_job_dir}"/* ; do # */ - [[ -f "${cron_job_file}" ]] && $echoCmd "${cron_fields} ${cron_job_file}" - done - fi - fi - done; -} - -# Temporary file for crontab lines. -temp=$(mktemp) || exit 1 - -# Add all of the jobs from the system-wide crontab file. -$catCmd "${CRONTAB}" | clean_cron_lines | lookup_run_parts >"${temp}" - -# Add all of the jobs from the system-wide cron directory. -$catCmd "${CRONDIR}"/* | clean_cron_lines >>"${temp}" # */ - -# Add each user's crontab (if it exists). Insert the user's name between the -# five time fields and the command. -while read user ; do - $crontabCmd -l -u "${user}" 2>/dev/null | - clean_cron_lines | - $sedCmd --regexp-extended "s/^((\S+ +){5})(.+)$/\1${user} \3/" >>"${temp}" -done < <(cut --fields=1 --delimiter=: /etc/passwd) - -# Output the collected crontab lines. - -## Changes: Parses output into JSON - -$catCmd "${temp}" \ - | awk 'BEGIN {print "["} \ - {print "{ \"min(s)\": \"" $1 \ - "\", \"hours(s)\": \"" $2 "\", " \ - " \"day(s)\": \"" $3 "\", " \ - " \"month\": \"" $4 "\", " \ - " \"weekday\": \"" $5 "\", " \ - " \"user\": \"" $6 "\", " \ - " \"command\": \""} \ - {for(i=7;i<=NF;++i) printf("%s ", gensub("\"", "\\\\\"", "g", $i) ) } \ - {print "\" " \ - "}," } \ - END {print "]"}' \ - | $sedCmd 'N;$s/,\n//;P;D' | $trCmd -s '\n' ' ' - -rm --force "${temp}" diff --git a/server/modules/shell_files/swap.sh b/server/modules/shell_files/swap.sh deleted file mode 100755 index 0ba4492a..00000000 --- a/server/modules/shell_files/swap.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -catCmd=`which cat`; -wcCmd=`which wc`; -awkCmd=`which awk` - -swapLineCount=$($catCmd /proc/swaps | $wcCmd -l) - -if [ "$swapLineCount" -gt 1 ]; then - - result=$($catCmd /proc/swaps \ - | $awkCmd 'NR>1 {print "{ \"filename\": \"" $1"\", \"type\": \""$2"\", \"size\": \""$3"\", \"used\": \""$4"\", \"priority\": \""$5"\"}," }' - ) - - echo [ ${result%?} ] - -else - echo [] -fi diff --git a/server/modules/shell_files/upload_transfer_rate.sh b/server/modules/shell_files/upload_transfer_rate.sh deleted file mode 100755 index 1cac77cf..00000000 --- a/server/modules/shell_files/upload_transfer_rate.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -files=(/sys/class/net/*) -pos=$(( ${#files[*]} - 1 )) -last=${files[$pos]} -out1=() - -json_output="{" - -for interface in "${files[@]}" -do - basename=$(basename "$interface") - - # find the number of bytes transfered for this interface - out1+=( $(cat /sys/class/net/"$basename"/statistics/tx_bytes) ) -done - -# wait a moment -sleep 0.5 - -for interface in "${files[@]}" -do - basename=$(basename "$interface") - - # check same interface again - out2=$(cat /sys/class/net/"$basename"/statistics/tx_bytes) - - # read and remove first element - out=${out1[0]} - unset out1[0] - out1=( ${out1[@]} ) - - # get the difference (transfer rate) - out_bytes=$((out2 - out)) - - # convert transfer rate to KB - out_kbytes=$((out_bytes / 1024 * 2)) - - # convert transfer rate to KB - json_output="$json_output \"$basename\": $out_kbytes" - - # if it is not the last line - if [[ ! $interface == $last ]] - then - # add a comma to the line (JSON formatting) - json_output="$json_output," - fi -done - -# close the JSON object & print to screen -echo "$json_output}" diff --git a/server/modules/shell_files/user_accounts.sh b/server/modules/shell_files/user_accounts.sh deleted file mode 100755 index 599d6ba9..00000000 --- a/server/modules/shell_files/user_accounts.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -result=$(/usr/bin/awk -F: '{ \ - if ($3<=499){userType="system";} \ - else {userType="user";} \ - print "{ \"type\": \"" userType "\"" ", \"user\": \"" $1 "\", \"home\": \"" $6 "\" }," }' < /etc/passwd - ) - -length=$(echo ${#result}) - -if [ $length -eq 0 ]; then - result=$(getent passwd | /usr/bin/awk -F: '{ if ($3<=499){userType="system";} else {userType="user";} print "{ \"type\": \"" userType "\"" ", \"user\": \"" $1 "\", \"home\": \"" $6 "\" }," }') -fi - -echo [ ${result%?} ] \ No newline at end of file diff --git a/src/css/README.md b/src/css/README.md new file mode 100644 index 00000000..9f6b785a --- /dev/null +++ b/src/css/README.md @@ -0,0 +1,18 @@ +# Global CSS + +These are CSS files which set global & base element style rules. + +## main.css + +Styles for body & html tags. + +## plugins-container.css + +All plugins rendered on a page are inside of this element. + +## tables.css + +Base table element styling. + +Also contains styling for the `metrics-table` class, which is used to display the tabular data underneath the line and multiline charts. + diff --git a/src/css/buttons.css b/src/css/buttons.css new file mode 100644 index 00000000..296fec1d --- /dev/null +++ b/src/css/buttons.css @@ -0,0 +1,3 @@ +button { + outline: none; +} diff --git a/src/css/main.css b/src/css/main.css new file mode 100644 index 00000000..45c2e3f4 --- /dev/null +++ b/src/css/main.css @@ -0,0 +1,21 @@ +html { + margin-top: 0px; + padding-top: 0; + + -webkit-transition: all 1s ease; + -moz-transition: all 1s ease; + -ms-transition: all 1s ease; + -o-transition: all 1s ease; + transition: all 1s ease; +} + +body { + letter-spacing: .1rem; + font-family: 'Merriweather', Arial, sans-serif; + padding: 0; + margin: 0; +} + +.centered { + margin: 0 auto; +} diff --git a/src/css/plugins-cotnainer.css b/src/css/plugins-cotnainer.css new file mode 100644 index 00000000..f5a8a897 --- /dev/null +++ b/src/css/plugins-cotnainer.css @@ -0,0 +1,23 @@ +#plugins { + text-align: center; + padding: 20px; + padding-top: 0; + margin-top: 0; + border: 1px; +} + +@media (min-width: 1080px) { + #plugins { + float: none; + margin: 0 auto; + clear: both; + } +} + +@media (max-width: 1079px) { + #plugins { + float: none; + margin: 0 auto; + clear: both; + } +} diff --git a/src/css/tables.css b/src/css/tables.css new file mode 100644 index 00000000..5daaa696 --- /dev/null +++ b/src/css/tables.css @@ -0,0 +1,40 @@ +table { + width: 100%; + font-size: 10px; + margin: 0; + border-collapse: collapse; + text-align: left; + table-layout:fixed; +} + +table th, +table td { + padding: 2px; + max-width: 250px; + word-wrap: break-word; +} + +table th{ + font-weight: 600; + text-transform: uppercase; + border-bottom: 1px solid #f1f1f1; +} + +table td { + border-bottom: 1px solid #f1f1f1; + font-family: Arial, sans-serif; + font-size: 10px; + color: rgba(0,0,0,.65); +} + +table tbody tr:hover td { + background-color: #fafafa; +} + +table.metrics-table { + text-align: center; +} + +table.metrics-table tr:last-child td { + border: none; +} diff --git a/src/js/README.md b/src/js/README.md new file mode 100644 index 00000000..ddf12d73 --- /dev/null +++ b/src/js/README.md @@ -0,0 +1,17 @@ +# JS (UI) + +The root directory for _all_ of the UI JavaScript source files which power Linux Dash + +It is an Angular JS (v1.3) SPA + +## core/ + +Contains all of the directives, services, and module declaration for the Angular SPA + +This is where you can find all of the base UI elements that power the UI + +For example, in this directory, you will find the base `line-chart` directive used by several plugins, the global `navbar` for Linux Dash pages, and other base UI elements. + +## plugins/ + +Contains the code for the individual plugins the user interacts with on the Linux Dash UI. diff --git a/src/js/core/app.js b/src/js/core/app.js new file mode 100644 index 00000000..ce263ae8 --- /dev/null +++ b/src/js/core/app.js @@ -0,0 +1,19 @@ +function runFn(server, $location, $rootScope) { + server.checkIfWebsocketsAreSupported() + + $rootScope.$on("$locationChangeSuccess", function(event, next, current) { + var nextRoute = next.split('#')[1] + if (nextRoute !== '/loading') { + localStorage.setItem('currentTab', nextRoute) + } + }); + + $location.path('/loading') +} + +angular + .module('linuxDash', ['ngRoute']) + .run([ 'server', '$location', '$rootScope', runFn]) + .config(['$compileProvider', function ($compileProvider) { + $compileProvider.debugInfoEnabled(false) + }]) diff --git a/src/js/core/features/key-value-list/key-value-list.directive.js b/src/js/core/features/key-value-list/key-value-list.directive.js new file mode 100644 index 00000000..682629e8 --- /dev/null +++ b/src/js/core/features/key-value-list/key-value-list.directive.js @@ -0,0 +1,29 @@ +angular.module('linuxDash').directive('keyValueList', ['server', '$rootScope', function (server, $rootScope) { + return { + scope: { + heading: '@', + info: '@', + moduleName: '@', + }, + templateUrl: 'src/js/core/features/key-value-list/key-value-list.html', + link: function(scope, element) { + + scope.getData = function() { + delete scope.tableRows + + server.get(scope.moduleName, function(serverResponseData) { + scope.tableRows = serverResponseData + scope.lastGet = new Date().getTime() + + if (Object.keys(serverResponseData).length === 0) { + scope.emptyResult = true + } + + if (!scope.$$phase && !$rootScope.$$phase) scope.$digest() + }) + } + + scope.getData() + } + } +}]) diff --git a/src/js/core/features/key-value-list/key-value-list.html b/src/js/core/features/key-value-list/key-value-list.html new file mode 100644 index 00000000..e8e3f502 --- /dev/null +++ b/src/js/core/features/key-value-list/key-value-list.html @@ -0,0 +1,22 @@ + + + + +
+ + + + + + + +
{{ name }}{{ value }}
+ +
+ + No data +
diff --git a/src/js/core/features/line-chart/line-chart-plugin.directive.js b/src/js/core/features/line-chart/line-chart-plugin.directive.js new file mode 100644 index 00000000..4d32ac52 --- /dev/null +++ b/src/js/core/features/line-chart/line-chart-plugin.directive.js @@ -0,0 +1,158 @@ +angular.module('linuxDash').directive('lineChartPlugin', [ + '$interval', '$compile', 'server', '$window', + function ($interval, $compile, server, $window) { + return { + scope: { + heading: '@', + moduleName: '@', + refreshRate: '=', + maxValue: '=', + minValue: '=', + getDisplayValue: '=', + metrics: '=', + color: '@' + }, + templateUrl: 'src/js/core/features/line-chart/line-chart-plugin.html', + link: function(scope, element) { + + scope.initializing = true + + // wrap the entire plugin into an initializing function + var start_rendering_line_chart = function () { + + if (!scope.color) + scope.color = '0, 255, 0' + + var series, w, h, canvas + + angular.element($window).bind('resize', function() { + canvas.width = w + canvas.height = h + }) + + // smoothieJS - Create new chart + var chart = new SmoothieChart({ + borderVisible: false, + sharpLines: true, + grid: { + fillStyle: '#ffffff', + strokeStyle: 'rgba(232,230,230,0.93)', + sharpLines: true, + millisPerLine: 3000, + borderVisible: false + }, + labels: { + fontSize: 11, + precision: 0, + fillStyle: '#0f0e0e' + }, + maxValue: parseInt(scope.maxValue), + minValue: parseInt(scope.minValue), + horizontalLines: [{ + value: 5, + color: '#eff', + lineWidth: 1 + }] + }) + + var initializeChart = function () { + // smoothieJS - set up canvas element for chart + var checkForCanvasReadyState = $interval(function () { + if (element.find('canvas')[0]) { + canvas = element.find('canvas')[0] + series = series || new TimeSeries() + w = canvas.width + h = canvas.height + + if (chart.seriesSet.length > 0) + chart.removeTimeSeries(chart.seriesSet[0].timeSeries) + + chart.addTimeSeries(series, { + strokeStyle: 'rgba(' + scope.color + ', 1)', + fillStyle: 'rgba(' + scope.color + ', 0.2)', + lineWidth: 2 + }) + + chart.streamTo(canvas, 1000) + $interval.cancel(checkForCanvasReadyState) + } + }, 100) + } + + scope.reInitializeChart = function () { + initializeChart() + } + + if (!scope.isHidden) + initializeChart() + + var dataCallInProgress = false + + // update data on chart + scope.getData = function() { + + if(scope.initializing) + scope.initializing = false + + if (dataCallInProgress || !element.find('canvas')[0]) return + + dataCallInProgress = true + + server.get(scope.moduleName, function(serverResponseData) { + + if (serverResponseData.length < 1) { + scope.emptyResult = true + return + } + + dataCallInProgress = false + scope.lastGet = new Date().getTime() + + // change graph colour depending on usage + if (scope.maxValue / 4 * 3 < scope.getDisplayValue(serverResponseData)) { + chart.seriesSet[0].options.strokeStyle = 'rgba(255, 89, 0, 1)' + chart.seriesSet[0].options.fillStyle = 'rgba(255, 89, 0, 0.2)' + } else if (scope.maxValue / 3 < scope.getDisplayValue(serverResponseData)) { + chart.seriesSet[0].options.strokeStyle = 'rgba(255, 238, 0, 1)' + chart.seriesSet[0].options.fillStyle = 'rgba(255, 238, 0, 0.2)' + } else { + chart.seriesSet[0].options.strokeStyle = 'rgba(' + scope.color + ', 1)' + chart.seriesSet[0].options.fillStyle = 'rgba(' + scope.color + ', 0.2)' + } + + scope.newData = scope.getDisplayValue(serverResponseData) + + // update chart with this response + series.append(scope.lastGet, scope.newData) + + // update the metrics for this chart + scope.metrics.forEach(function(metricObj) { + metricObj.data = metricObj.generate(serverResponseData) + }) + + }) + } + + // set the directive-provided interval + // at which to run the chart update + var intervalRef = $interval(scope.getData, scope.refreshRate) + var removeInterval = function() { + $interval.cancel(intervalRef) + } + + element.on("$destroy", removeInterval) + } + + // only start rendering plugin when we know the scale of max/min for the canvas chart (smoothie) + var stopWatching = scope.$watch('maxValue', function (n, o) { + if (n) { + start_rendering_line_chart() + stopWatching() + } + }) + + + } + } + } +]) diff --git a/src/js/core/features/line-chart/line-chart-plugin.html b/src/js/core/features/line-chart/line-chart-plugin.html new file mode 100644 index 00000000..d2eb7118 --- /dev/null +++ b/src/js/core/features/line-chart/line-chart-plugin.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + +
{{ metric.name }}{{ metric.data }}
+ + No data + +
diff --git a/src/js/core/features/loader/loader.css b/src/js/core/features/loader/loader.css new file mode 100644 index 00000000..3d8fbcd6 --- /dev/null +++ b/src/js/core/features/loader/loader.css @@ -0,0 +1,52 @@ +.spinner { + margin: 100px auto; + width: 50px; + height: 30px; + text-align: center; + font-size: 10px; +} + +.spinner > div { + background-color: #009587; + height: 100%; + width: 6px; + display: inline-block; + + -webkit-animation: stretchdelay 1.2s infinite ease-in-out; + animation: stretchdelay 1.2s infinite ease-in-out; +} + +.spinner .rect2 { + -webkit-animation-delay: -1.1s; + animation-delay: -1.1s; +} + +.spinner .rect3 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} + +.spinner .rect4 { + -webkit-animation-delay: -0.9s; + animation-delay: -0.9s; +} + +.spinner .rect5 { + -webkit-animation-delay: -0.8s; + animation-delay: -0.8s; +} + +@-webkit-keyframes stretchdelay { + 0%, 40%, 100% { -webkit-transform: scaleY(0.4) } + 20% { -webkit-transform: scaleY(1.0) } +} + +@keyframes stretchdelay { + 0%, 40%, 100% { + transform: scaleY(0.4); + -webkit-transform: scaleY(0.4); + } 20% { + transform: scaleY(1.0); + -webkit-transform: scaleY(1.0); + } +} diff --git a/src/js/core/features/loader/loader.directive.js b/src/js/core/features/loader/loader.directive.js new file mode 100644 index 00000000..c04f90e9 --- /dev/null +++ b/src/js/core/features/loader/loader.directive.js @@ -0,0 +1,16 @@ +angular.module('linuxDash').directive('loader', function() { + return { + scope: { + width: '@' + }, + template: '\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + ' + } +}) diff --git a/src/js/core/features/multi-line-chart/multi-line-chart-plugin.directive.js b/src/js/core/features/multi-line-chart/multi-line-chart-plugin.directive.js new file mode 100644 index 00000000..81699d20 --- /dev/null +++ b/src/js/core/features/multi-line-chart/multi-line-chart-plugin.directive.js @@ -0,0 +1,167 @@ +angular.module('linuxDash').directive('multiLineChartPlugin', [ + '$interval', '$compile', 'server', '$window', + function ($interval, $compile, server, $window) { + return { + scope: { + heading: '@', + moduleName: '@', + refreshRate: '=', + getDisplayValue: '=', + units: '=', + delay: '=' + }, + templateUrl: 'src/js/core/features/multi-line-chart/multi-line-chart-plugin.html', + link: function(scope, element) { + + var w, h, canvas + + angular.element($window).bind('resize', function() { + canvas.width = w + canvas.height = h + }) + + // smoothieJS - Create new chart + var chart = new SmoothieChart({ + borderVisible: false, + sharpLines: true, + grid: { + fillStyle: '#ffffff', + strokeStyle: 'rgba(232,230,230,0.93)', + sharpLines: true, + borderVisible: false + }, + labels: { + fontSize: 12, + precision: 0, + fillStyle: '#0f0e0e' + }, + maxValue: 100, + minValue: 0, + horizontalLines: [{ + value: 1, + color: '#ecc', + lineWidth: 1 + }] + }) + + var seriesOptions = [ + { + strokeStyle: 'rgba(255, 0, 0, 1)', + lineWidth: 2 + }, + { + strokeStyle: 'rgba(0, 255, 0, 1)', + lineWidth: 2 + }, + { + strokeStyle: 'rgba(0, 0, 255, 1)', + lineWidth: 2 + }, + { + strokeStyle: 'rgba(255, 255, 0, 1)', + lineWidth: 1 + } + ] + + // smoothieJS - set up canvas element for chart + scope.seriesArray = [] + scope.metricsArray = [] + + var delay = 1000 + + if (angular.isDefined(scope.delay)) + delay = scope.delay + + var initializeChart = function () { + // smoothieJS - set up canvas element for chart + var checkForCanvasReadyState = $interval(function () { + if (element.find('canvas')[0]) { + canvas = element.find('canvas')[0] + w = canvas.width + h = canvas.height + + // get the data once to set up # of lines on chart + server.get(scope.moduleName, function(serverResponseData) { + + var numberOfLines = Object.keys(serverResponseData).length + + for (var x = 0; x < numberOfLines; x++) { + + var keyForThisLine = Object.keys(serverResponseData)[x]; + + scope.seriesArray[x] = new TimeSeries(); + chart.addTimeSeries(scope.seriesArray[x], seriesOptions[x]); + scope.metricsArray[x] = { + name: keyForThisLine, + color: seriesOptions[x].strokeStyle, + } + } + + }) + + chart.streamTo(canvas, delay) + $interval.cancel(checkForCanvasReadyState) + } + }, 100) + } + + scope.reInitializeChart = function () { + chart.seriesSet.forEach(function (ts) { + chart.removeTimeSeries(ts.timeSeries) + }) + + initializeChart() + } + + if (!scope.isHidden) + initializeChart() + + var dataCallInProgress = false + + // update data on chart + scope.getData = function() { + + if (dataCallInProgress) return + + if (!scope.seriesArray.length) return + + dataCallInProgress = true + + server.get(scope.moduleName, function(serverResponseData) { + + dataCallInProgress = false + scope.lastGet = new Date().getTime() + var keyCount = 0 + var maxAvg = 100 + + // update chart with current response + for (var key in serverResponseData) { + scope.seriesArray[keyCount].append(scope.lastGet, serverResponseData[key]) + keyCount++ + maxAvg = Math.max(maxAvg, serverResponseData[key]) + } + + // update the metrics for this chart + scope.metricsArray.forEach(function(metricObj) { + metricObj.data = serverResponseData[metricObj.name].toString() + ' ' + scope.units + }) + + // round up the average and set the maximum scale + var len = parseInt(Math.log(maxAvg) / Math.log(10)) + var div = Math.pow(10, len) + chart.options.maxValue = Math.ceil(maxAvg / div) * div + + }) + + } + + var refreshRate = (angular.isDefined(scope.refreshRate)) ? scope.refreshRate : 1000 + var intervalRef = $interval(scope.getData, refreshRate) + var removeInterval = function() { + $interval.cancel(intervalRef) + } + + element.on("$destroy", removeInterval) + } + } +}]) diff --git a/src/js/core/features/multi-line-chart/multi-line-chart-plugin.html b/src/js/core/features/multi-line-chart/multi-line-chart-plugin.html new file mode 100644 index 00000000..ecb27041 --- /dev/null +++ b/src/js/core/features/multi-line-chart/multi-line-chart-plugin.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + +
+
+
+
{{ metric.name }}{{ metric.data }}
+ +
diff --git a/src/js/core/features/navbar/navbar.css b/src/js/core/features/navbar/navbar.css new file mode 100644 index 00000000..b2e374dd --- /dev/null +++ b/src/js/core/features/navbar/navbar.css @@ -0,0 +1,65 @@ +nav-bar { + display: block; + background-color: #f6f8f8; + height: 40px; + padding-left: 25px; + padding-top: 3px; +} + +nav-bar ul { + margin-left: 10px; + padding: 0; + list-style-type: none; + display: inline; +} + +nav-bar ul li a { + font-size: 11px; + text-transform: uppercase; + text-decoration: none; + line-height: 2.3rem; + color: grey; +} + +nav-bar ul li a:hover { + color: rgb(0,0,0); +} + +nav-bar ul li.active { + border-bottom: 2px solid #009587; +} + +nav-bar ul li.active a { + color: #009587; +} + +nav-bar ul li { + margin-left: 20px; + display: inline; + padding-left: 5px; + padding-bottom: 10px; +} + +nav-bar .title { + color: #009587; + font-weight: 300; + font-size: 20px; + letter-spacing: .1rem; + float: left; + padding-top: 5px; +} + +nav-bar .right-content { + float: right; +} + +nav-bar .right-content, +nav-bar .right-content a { + font-size: 11px; + color: grey; + padding-top: 10px; +} + +nav-bar .right-content a:hover { + color: #000; +} diff --git a/src/js/core/features/navbar/navbar.directive.js b/src/js/core/features/navbar/navbar.directive.js new file mode 100644 index 00000000..886c45ea --- /dev/null +++ b/src/js/core/features/navbar/navbar.directive.js @@ -0,0 +1,37 @@ +angular.module('linuxDash').directive('navBar', ['$location', function($location) { + return { + template: '\ + \ + Linux Dash (Demo)\ + \ +
    \ +
  • \ + \ +
  • \ +
\ + \ + Resources:\ + GitHub | \ + Gitter Chat Room | \ + Docs \ + \ + ', + link: function(scope) { + scope.items = [ + 'system-status', + 'basic-info', + 'network', + 'accounts', + 'apps' + ] + + scope.getNavItemName = function(url) { + return url.replace('-', ' ') + } + + scope.isActive = function(route) { + return '/' + route === $location.path() + } + } + } +}]) diff --git a/src/js/core/features/plugin/plugin.css b/src/js/core/features/plugin/plugin.css new file mode 100644 index 00000000..2e56422f --- /dev/null +++ b/src/js/core/features/plugin/plugin.css @@ -0,0 +1,109 @@ + +.plugin { + transition: all .1s linear; + + vertical-align: text-top; + width: 400px; + display: inline-block; + padding: 0; + color: black; + text-align: center; + border-radius: 2px; + box-shadow: 0 1px 10px rgba(0,0,0,.13),0px 5px 10px rgba(0,0,0,.16); + margin-top: 10px; + margin: 0 auto; + margin-top: 20px; + margin-left: 15px; + margin-bottom: 20px; + max-height: 400px; + overflow: hidden; + + resize: both; + padding-bottom: 15px; + background-color: rgba(0,0,0,0.015); +} + +.plugin-hidden { + width: 250px; +} + +.plugin-hidden .top-bar .heading { + color: grey; + font-style: italic; +} + +.plugin-enlarged { + width: 50%; +} + +@media (max-width: 768px) { + .plugin { + max-width: 80%; + float: none; + margin: 0 auto; + margin-bottom: 10px; + } + + .plugin-body { + max-height: 323px; + } + + .plugin-enlarged { + width: 80%; + } +} + +@media (max-width: 950px) { + .plugin-enlarged { + width: 90%; + } +} + +.plugin.chart-plugin { + padding: 0px; + resize: none; + padding-bottom: 0; +} + +.plugin-body { + background-color: #fff; + height: 323px; + font-size: 12px; + padding: 10px; + line-height: 30px; + overflow: auto; + border-top: 1px solid #ececec; +} + +.plugin.chart-plugin, +.plugin.chart-plugin .plugin-body { + overflow: hidden; + padding: 0; +} + +.plugin-body-short { + height: 263px; +} + +.plugin last-update{ + font-size: 11px; + float: left; +} + +.plugin ::-webkit-scrollbar { + width: 8px; + height: 8px; +} +.plugin ::-webkit-scrollbar-track { + background: #eee; + border: thin solid lightgray; + box-shadow: 0px 0px 3px #dfdfdf inset; +} +.plugin ::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0.1); + border: thin solid rgba(0,0,0,0.1); + border-radius: 0; +} +.plugin ::-webkit-scrollbar-thumb:hover { + background: rgba(0,0,0,0.2); +} diff --git a/src/js/core/features/plugin/plugin.directive.js b/src/js/core/features/plugin/plugin.directive.js new file mode 100644 index 00000000..9c5668a1 --- /dev/null +++ b/src/js/core/features/plugin/plugin.directive.js @@ -0,0 +1,41 @@ +angular.module('linuxDash').directive('plugin', ['$rootScope', function($rootScope) { + return { + transclude: true, + templateUrl: 'src/js/core/features/plugin/plugin.html', + link: function (s, el, attr) { + + if (attr.hasOwnProperty('chartPlugin')) + s.isChartPlugin = true + + if ($rootScope.hiddenPlugins.indexOf(s.moduleName) > -1) + s.isHidden = true + + s.toggleWidth = function () { + el.find('div')[0].removeAttribute('style') + s.enlarged = !s.enlarged + } + + var setPluginVisibility = function (shouldShow) { + s.isHidden = !shouldShow + + if (shouldShow) { + $rootScope.$emit('show-plugin', s.moduleName) + if (s.isChartPlugin) s.reInitializeChart() + } else { + $rootScope.$emit('hide-plugin', s.moduleName) + } + } + + s.toggleVisibility = function () { + setPluginVisibility(s.isHidden) + } + + + s.$watch('emptyResult', function (n, o) { + if (n) { + setPluginVisibility(false) + } + }) + } + } +}]) diff --git a/src/js/core/features/plugin/plugin.html b/src/js/core/features/plugin/plugin.html new file mode 100644 index 00000000..0b819851 --- /dev/null +++ b/src/js/core/features/plugin/plugin.html @@ -0,0 +1,28 @@ +
+ + + + +
+
+ +
diff --git a/src/js/core/features/progress-bar/progress-bar-plugin.directive.js b/src/js/core/features/progress-bar/progress-bar-plugin.directive.js new file mode 100644 index 00000000..099be928 --- /dev/null +++ b/src/js/core/features/progress-bar/progress-bar-plugin.directive.js @@ -0,0 +1,18 @@ +angular.module('linuxDash').directive('progressBarPlugin', function() { + return { + scope: { + width: '@', + moduleName: '@', + name: '@', + value: '@', + max: '@' + }, + template: '\ +
\ +
\ +
\ +
\ +
\ + ' + } +}) diff --git a/src/js/core/features/progress-bar/progress-bar.css b/src/js/core/features/progress-bar/progress-bar.css new file mode 100644 index 00000000..b47313cb --- /dev/null +++ b/src/js/core/features/progress-bar/progress-bar.css @@ -0,0 +1,16 @@ +.progress-bar { + background-color: #eec; + border-radius: 10px; + padding: 0px; + clear: both; + display: inline-block; + overflow: hidden; + white-space: nowrap; +} + +.progress-bar > div { + background-color: #1EAEDB; + width: 0%; + height: 5px; + border-radius: 5px; +} diff --git a/src/js/core/features/table-data/table-data.css b/src/js/core/features/table-data/table-data.css new file mode 100644 index 00000000..e676c361 --- /dev/null +++ b/src/js/core/features/table-data/table-data.css @@ -0,0 +1,34 @@ +.table-data-plugin .filter-container { + padding-bottom: 0; + margin: 0; +} + +.table-data-plugin .filter, +.table-data-plugin .filter:focus, +.table-data-plugin .filter:active { + height: 20px; + padding: 5px; + margin: 5px; + border: none; + outline-color: transparent; + background: transparent; + width: 100%; + margin: 0; + text-align: center; + font-size: 15px; +} + +.table-data-plugin .filter:focus { + border-bottom: 1px solid #ff5722; +} + +.table-data-plugin thead tr th a, +.table-data-plugin thead tr th a:visited { + color: black; + text-decoration: none; +} + +.table-data-plugin .column-sort-caret { + font-size: 10px; + color: #1EAEDB; +} diff --git a/src/js/core/features/table-data/table-data.directive.js b/src/js/core/features/table-data/table-data.directive.js new file mode 100644 index 00000000..3c61336d --- /dev/null +++ b/src/js/core/features/table-data/table-data.directive.js @@ -0,0 +1,79 @@ +angular.module('linuxDash').directive('tableData', ['server', '$rootScope', function (server, $rootScope) { + return { + scope: { + heading: '@', + info: '@', + moduleName: '@', + width: '@', + height: '@' + }, + templateUrl: 'src/js/core/features/table-data/table-data.html', + link: function(scope, element) { + + scope.sortByColumn = null + scope.sortReverse = null + + // set the column to sort by + scope.setSortColumn = function(column) { + + // if the column is already being sorted + // reverse the order + if (column === scope.sortByColumn) { + scope.sortReverse = !scope.sortReverse + } else { + scope.sortByColumn = column + } + + scope.sortTableRows() + } + + scope.sortTableRows = function() { + scope.tableRows.sort(function(currentRow, nextRow) { + + var sortResult = 0 + + if (currentRow[scope.sortByColumn] < nextRow[scope.sortByColumn]) { + sortResult = -1 + } else if (currentRow[scope.sortByColumn] === nextRow[scope.sortByColumn]) { + sortResult = 0 + } else { + sortResult = 1 + } + + if (scope.sortReverse) { + sortResult = -1 * sortResult + } + + return sortResult + }) + } + + scope.getData = function() { + delete scope.tableRows + + server.get(scope.moduleName, function(serverResponseData) { + + if (serverResponseData.length > 0) { + scope.tableHeaders = Object.keys(serverResponseData[0]) + } + + scope.tableRows = serverResponseData + + if (scope.sortByColumn) { + scope.sortTableRows() + } + + scope.lastGet = new Date().getTime() + + if (serverResponseData.length < 1) { + scope.emptyResult = true + } + + if (!scope.$$phase && !$rootScope.$$phase) scope.$digest() + }) + } + + scope.getData() + } + } +}]) diff --git a/src/js/core/features/table-data/table-data.html b/src/js/core/features/table-data/table-data.html new file mode 100644 index 00000000..889e3394 --- /dev/null +++ b/src/js/core/features/table-data/table-data.html @@ -0,0 +1,40 @@ + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ {{ header }} + + {{ (header === sortByColumn && !sortReverse) ? '▲': ''; }} + {{ (header === sortByColumn && sortReverse) ? '▼': ''; }} + +
+ {{ row[header] }} +
+ +
+ + No data +
diff --git a/src/js/core/features/top-bar/topbar.css b/src/js/core/features/top-bar/topbar.css new file mode 100644 index 00000000..9784ec32 --- /dev/null +++ b/src/js/core/features/top-bar/topbar.css @@ -0,0 +1,69 @@ +.top-bar { + + height: 15px; + padding: 15px; + + font-size: 13px; + text-transform: capitalize; + color: #009587; + + background-color: #f6f8f8; +} + +.top-bar .heading { + float: left; + + cursor: grab; + cursor: -moz-grab; + cursor: -webkit-grab; +} + +.ld-top-bar-btn { + + float: right; + font-size: 17px; + + color: #009587; + background: #fff; + + border: 1px solid #eee; + border-radius: 50%; + + width: 30px; + height: 30px; + margin-top: -5px; + + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -ms-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + + cursor: pointer; + transition: all 0.5s ease; +} + +.minimize-btn { + font-size: 19px; +} + +.minimize-btn.active { +} + +.ld-refresh-btn:hover { + background-color: #ffeb3b; + color: black; +} + +.ld-refresh-btn:active { + background-color: #0f9d58; +} + +.plugin-hidden .width-toggle-btn { + display: none; +} + +.plugin-hidden .minimize-btn, +.plugin-enlarged .width-toggle-btn { + background: #eee; + color: #000; +} diff --git a/src/js/core/features/top-bar/topbar.directive.js b/src/js/core/features/top-bar/topbar.directive.js new file mode 100644 index 00000000..41f64f5d --- /dev/null +++ b/src/js/core/features/top-bar/topbar.directive.js @@ -0,0 +1,28 @@ +angular.module('linuxDash').directive('topBar', ['$rootScope', function($rootScope) { + return { + scope: { + heading: '=', + refresh: '&', + lastUpdated: '=', + toggleVisibility: '&', + isHidden: '=', + toggleWidth: '&', + isChart: '=', + info: '=', // not being used; needs a good ui solution + }, + template: '\ +
\ + ☰ {{ heading }} \ + \ + \ + \ + \ + \ + \ +
\ + ', + } +}]) diff --git a/src/js/core/rootscope-event-handlers/hide-plugin.run.js b/src/js/core/rootscope-event-handlers/hide-plugin.run.js new file mode 100644 index 00000000..6b77797d --- /dev/null +++ b/src/js/core/rootscope-event-handlers/hide-plugin.run.js @@ -0,0 +1,37 @@ +angular + .module('linuxDash') + .run(['$rootScope', '$location', function ($rootScope, $location) { + + var key = 'hiddenPlugins' + + var getHiddenPlugins = function () { + var hiddenPluginsCSV = localStorage.getItem(key) || '' + return hiddenPluginsCSV.split(',') + } + + var updateHiddenPlugins = function (hiddenPlugins) { + localStorage.setItem(key, hiddenPlugins.join(',')) + } + + $rootScope.$on('hide-plugin', function (e, m) { + var hiddenPlugins = getHiddenPlugins() + + if(hiddenPlugins.indexOf(m) < 0) + hiddenPlugins.push(m) + + updateHiddenPlugins(hiddenPlugins) + }) + + $rootScope.$on('show-plugin', function (e, m) { + var hiddenPlugins = getHiddenPlugins() + var indexOfPlugin = hiddenPlugins.indexOf(m) + + if(indexOfPlugin > -1) + hiddenPlugins.splice(indexOfPlugin, 1) + + updateHiddenPlugins(hiddenPlugins) + }) + + $rootScope.hiddenPlugins = getHiddenPlugins() + + }]) diff --git a/src/js/core/rootscope-event-handlers/make-plugins-draggable.run.js b/src/js/core/rootscope-event-handlers/make-plugins-draggable.run.js new file mode 100644 index 00000000..c84ce8fa --- /dev/null +++ b/src/js/core/rootscope-event-handlers/make-plugins-draggable.run.js @@ -0,0 +1,37 @@ +angular + .module('linuxDash') + .run(['$rootScope', '$location', function ($rootScope, $location) { + + $rootScope.$on('$routeChangeSuccess', function () { + + var intervalId = setInterval(function () { + + var el = document.getElementById('plugins') + + if (el) { + + var sortable = Sortable.create(el, { + group: 'plugin-order-' + $location.path().replace('/', ''), + handle: '.heading', + ghostClass: 'ld-ghost', + chosenClass: 'ld-chosen', + dataIdAttr: 'sortablejs-id', + animation: 1050, + store: { + get: function (sortable) { + var order = localStorage.getItem(sortable.options.group.name); + return order ? order.split('|') : []; + }, + set: function (sortable) { + var order = sortable.toArray(); + localStorage.setItem(sortable.options.group.name, order.join('|')); + } + } + }) + + clearInterval(intervalId) + } + }) + }) + + }]) diff --git a/src/js/core/routes.js b/src/js/core/routes.js new file mode 100644 index 00000000..10bbb8db --- /dev/null +++ b/src/js/core/routes.js @@ -0,0 +1,82 @@ +function appLoadController($scope, $location, $rootScope) { + var loadUrl = localStorage.getItem('currentTab') || 'system-status' + var loadLinuxDash = function () { + $location.path(loadUrl) + } + + $rootScope.$on('start-linux-dash', loadLinuxDash) +} + +function routesFn($routeProvider) { + + $routeProvider + + .when('/loading', { + template: [ + '
', + '', + 'Loading...', + '
', + ].join(''), + controller: ['$scope', '$location', '$rootScope', appLoadController], + }) + + .when('/system-status', { + template: [ + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ].join(''), + }) + + .when('/basic-info', { + template: [ + '', + '', + '', + '', + '', + '', + ].join(''), + }) + + .when('/network', { + template: [ + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ].join(''), + }) + + .when('/accounts', { + template: [ + ' ', + ' ', + ' ', + ].join(''), + }) + + .when('/apps', { + template: [ + '', + '', + '', + '', + ].join(''), + }) + .otherwise({ + redirectTo: '/loading' + }) +} + +angular.module('linuxDash').config(['$routeProvider', routesFn]) diff --git a/src/js/core/server.service.js b/src/js/core/server.service.js new file mode 100644 index 00000000..7c742f62 --- /dev/null +++ b/src/js/core/server.service.js @@ -0,0 +1,144 @@ +angular + .module('linuxDash') + .service('server', [ + '$http', '$rootScope', '$location', + function($http, $rootScope, $location) { + + var websocket = { + connection: null, + onMessageEventHandlers: {} + }; + + /** + * @description: + * Establish a websocket connection with server + * + * @return Null + */ + var establishWebsocketConnection = function() { + + var websocketUrl = (location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.hostname + ':' + window.location.port; + + if (websocket.connection === null) { + + websocket.connection = new WebSocket(websocketUrl); + + websocket.connection.onopen = function() { + $rootScope.$broadcast("start-linux-dash", {}); + $rootScope.$apply(); + console.info('Websocket connection is open'); + }; + + websocket.connection.onmessage = function(event) { + + var response = JSON.parse(event.data); + var moduleName = response.moduleName; + var moduleData = JSON.parse(response.output); + + if (!!websocket.onMessageEventHandlers[moduleName]) { + websocket.onMessageEventHandlers[moduleName](moduleData); + } else { + console.info("Websocket could not find module", moduleName, "in:", websocket.onMessageEventHandlers); + } + + }; + + websocket.connection.onclose = function() { + websocket.connection = null; + } + } + + }; + + /** + * @description: + * Check if websockets are supported + * If so, call establishWebsocketConnection() + * + * @return Null + */ + this.checkIfWebsocketsAreSupported = function() { + + var websocketSupport = { + browser: null, + server: null, + }; + + // does browser support websockets? + if (window.WebSocket) { + + websocketSupport.browser = true; + + // does backend support websockets? + $http.get("/websocket").then(function(response) { + + // if websocket_support property exists and is trurthy + // websocketSupport.server will equal true. + websocketSupport.server = !!response.data["websocket_support"]; + + }).catch(function websocketNotSupportedByServer() { + + websocketSupport.server = false; + $rootScope.$broadcast("start-linux-dash", {}); + + }).then(function finalDecisionOnWebsocket() { + + if (websocketSupport.browser && websocketSupport.server) { + + establishWebsocketConnection(); + + } else { + $rootScope.$broadcast("start-linux-dash", {}); + } + + }); + + } + + }; + + /** + * Handles requests from modules for data from server + * + * @param {String} moduleName + * @param {Function} callback + * @return {[ Null || callback(server response) ]} + */ + this.get = function(moduleName, callback) { + + // if we have a websocket connection + if (websocket.connection) { + + // and the connection is ready + if (websocket.connection.readyState === 1) { + + // set the callback as the event handler + // for server response. + // + // Callback instance needs to be overwritten + // each time for this to work. Not sure why. + websocket.onMessageEventHandlers[moduleName] = callback; + + // + websocket.connection.send(moduleName); + + } else { + console.log("Websocket not ready yet.", moduleName); + } + + } + // otherwise + else { + + var moduleAddress = 'server/?module=' + moduleName; + + return $http.get(moduleAddress).then(function(response) { + return callback(response.data); + }); + + } + + }; + + } +]) diff --git a/src/js/plugins/README.md b/src/js/plugins/README.md new file mode 100644 index 00000000..25366d04 --- /dev/null +++ b/src/js/plugins/README.md @@ -0,0 +1,9 @@ +# Plugins + +These are the individual plugins which display server data to the user in the UI. + +Majority of the implementations of plugins are very simple since they leverage the Linux Dash core (`src/js/core/features`) + +### disk-space + +Shows the disk diff --git a/src/js/plugins/cpu-avg-load-chart.directive.js b/src/js/plugins/cpu-avg-load-chart.directive.js new file mode 100644 index 00000000..e7cb5531 --- /dev/null +++ b/src/js/plugins/cpu-avg-load-chart.directive.js @@ -0,0 +1,16 @@ +angular.module('linuxDash').directive('cpuAvgLoadChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: '\ + \ + \ + ', + link: function(scope) { + scope.units = '%' + } + } +}]) diff --git a/src/js/plugins/cpu-temp.directive.js b/src/js/plugins/cpu-temp.directive.js new file mode 100644 index 00000000..f0e812fb --- /dev/null +++ b/src/js/plugins/cpu-temp.directive.js @@ -0,0 +1,37 @@ +angular.module('linuxDash').directive('cpuTemp', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.min = 0 + scope.max = 100 + + scope.displayValue = function (serverResponseData) { + return serverResponseData + } + + scope.utilMetrics = [{ + name: 'Temprature', + generate: function (serverResponseData) { + return serverResponseData + ' °C' + } + }] + + } + } +}]) diff --git a/src/js/plugins/cpu-utilization-chart.directive.js b/src/js/plugins/cpu-utilization-chart.directive.js new file mode 100644 index 00000000..d9c71601 --- /dev/null +++ b/src/js/plugins/cpu-utilization-chart.directive.js @@ -0,0 +1,37 @@ +angular.module('linuxDash').directive('cpuUtilizationChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.min = 0 + scope.max = 100 + + scope.displayValue = function(serverResponseData) { + return serverResponseData + } + + scope.utilMetrics = [{ + name: 'Usage', + generate: function(serverResponseData) { + return serverResponseData + ' %' + } + }] + + } + } +}]) diff --git a/src/js/plugins/disk-space/disk-space.directive.js b/src/js/plugins/disk-space/disk-space.directive.js new file mode 100644 index 00000000..68ced381 --- /dev/null +++ b/src/js/plugins/disk-space/disk-space.directive.js @@ -0,0 +1,51 @@ +angular.module('linuxDash').directive('diskSpace', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + templateUrl: 'src/js/plugins/disk-space/disk-space.html', + link: function(scope) { + + var getKBMultiplierFn = function (size, power) { + return function () { + return size * Math.pow(1024, power) + } + } + + var kbDictionary = { + 'M': function () { return getKBMultiplierFn(size, 1) }, + 'G': function () { return getKBMultiplierFn(size, 2) }, + 'T': function () { return getKBMultiplierFn(size, 3) }, + 'P': function () { return getKBMultiplierFn(size, 4) }, + 'E': function () { return getKBMultiplierFn(size, 5) }, + 'Z': function () { return getKBMultiplierFn(size, 6) }, + 'Y': function () { return getKBMultiplierFn(size, 7) }, + } + + scope.heading = "Disk Partitions" + scope.moduleName = 'disk_partitions' + + scope.getData = function() { + server.get(scope.moduleName, function(serverResponseData) { + scope.diskSpaceData = serverResponseData + }) + + scope.lastGet = new Date().getTime() + } + + scope.getData() + + scope.getKB = function(stringSize) { + + var lastChar = stringSize.slice(-1) + var size = parseFloat(stringSize.replace(",", ".")) + + try { + return kbDictionary[lastChar](size) + } catch (err) { + return size + } + } + + } + } +}]) diff --git a/src/js/plugins/disk-space/disk-space.html b/src/js/plugins/disk-space/disk-space.html new file mode 100644 index 00000000..d53051c2 --- /dev/null +++ b/src/js/plugins/disk-space/disk-space.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + +
NameStatsUsedMount
{{partition['file_system']}} + + + + {{ partition['used'] }} / {{ partition['size'] }} + + {{ partition['used%'] }} + {{ partition['mounted'] }}
+ +
diff --git a/src/js/plugins/download-transfer-rate-chart.directive.js b/src/js/plugins/download-transfer-rate-chart.directive.js new file mode 100644 index 00000000..e00788f8 --- /dev/null +++ b/src/js/plugins/download-transfer-rate-chart.directive.js @@ -0,0 +1,17 @@ +angular.module('linuxDash').directive('downloadTransferRateChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.delay = 2000 + scope.units = 'KB/s' + } + } +}]) diff --git a/src/js/plugins/ram-chart.directive.js b/src/js/plugins/ram-chart.directive.js new file mode 100644 index 00000000..28a3915f --- /dev/null +++ b/src/js/plugins/ram-chart.directive.js @@ -0,0 +1,71 @@ +angular.module('linuxDash').directive('ramChart', ['server', function (server) { + return { + restrict: 'E', + scope: {}, + template: '\ + \ + \ + ', + link: function(scope) { + + // get max ram available on machine before we + // can start charting + server.get('current_ram', function(resp) { + scope.maxRam = resp.total + scope.minRam = 0 + }) + + scope.ramToDisplay = function(serverResponseData) { + return serverResponseData.used + } + + var humanizeRam = function (ramInMB) { + var ram = { + value: parseInt(ramInMB, 10), + unit: 'MB', + } + + // if ram > 1,000 MB, use GB + if (ram.value > 1000) { + ram = { + value: (ramInMB/1024).toFixed(2), + unit: 'GB', + } + } + + return ram.value + ' ' + ram.unit + } + + scope.ramMetrics = [{ + name: 'Used', + generate: function(serverResponseData) { + var ratio = serverResponseData.used / serverResponseData.total + var percentage = parseInt(ratio * 100) + + var usedRam = humanizeRam(serverResponseData.used) + return usedRam + ' (' + percentage.toString() + '%)' + } + }, + { + name: 'Available', + generate: function(serverResponseData) { + + var availableRam = humanizeRam(serverResponseData.available) + var totalRam = humanizeRam(serverResponseData.total) + return availableRam + ' of ' + totalRam + } + }] + } + } +}]) diff --git a/src/js/plugins/simple-table-data-plugins.directive.js b/src/js/plugins/simple-table-data-plugins.directive.js new file mode 100644 index 00000000..d4fc26f5 --- /dev/null +++ b/src/js/plugins/simple-table-data-plugins.directive.js @@ -0,0 +1,110 @@ +var simpleTableModules = [ + { + name: 'machineInfo', + template: '' + }, + { + name: 'ipAddresses', + template: '' + }, + { + name: 'ramIntensiveProcesses', + template: '' + }, + { + name: 'cpuIntensiveProcesses', + template: '' + }, + { + name: 'dockerProcesses', + template: '' + }, + { + name: 'networkConnections', + template: '' + }, + { + name: 'serverAccounts', + template: '' + }, + { + name: 'loggedInAccounts', + template: '' + }, + { + name: 'recentLogins', + template: '' + }, + { + name: 'arpCacheTable', + template: '' + }, + { + name: 'commonApplications', + template: '' + }, + { + name: 'pingSpeeds', + template: '' + }, + { + name: 'bandwidth', + template: '' + }, + { + name: 'swapUsage', + template: '' + }, + { + name: 'internetSpeed', + template: '' + }, + { + name: 'memcached', + template: '' + }, + { + name: 'redis', + template: '' + }, + { + name: 'pm2', + template: '' + }, + { + name: 'memoryInfo', + template: '' + }, + { + name: 'cpuInfo', + template: '' + }, + { + name: 'ioStats', + template: '' + }, + { + name: 'scheduledCrons', + template: '' + }, + { + name: 'cronHistory', + template: '' + } +] + +simpleTableModules.forEach(function(module, key) { + + angular.module('linuxDash').directive(module.name, ['server', function(server) { + + var moduleDirective = { + restrict: 'E', + scope: {} + } + + moduleDirective['template'] = module.template + + return moduleDirective + }]) + +}) diff --git a/src/js/plugins/upload-transfer-rate-chart.directive.js b/src/js/plugins/upload-transfer-rate-chart.directive.js new file mode 100644 index 00000000..dbf6e931 --- /dev/null +++ b/src/js/plugins/upload-transfer-rate-chart.directive.js @@ -0,0 +1,17 @@ +angular.module('linuxDash').directive('uploadTransferRateChart', ['server', function(server) { + return { + restrict: 'E', + scope: {}, + template: ' \ + \ + \ + ', + link: function(scope) { + scope.delay = 2000 + scope.units = 'KB/s' + } + } +}]) diff --git a/templates/app/base-plugin.html b/templates/app/base-plugin.html deleted file mode 100644 index 1f1a8510..00000000 --- a/templates/app/base-plugin.html +++ /dev/null @@ -1,10 +0,0 @@ -
- - - -
-
\ No newline at end of file diff --git a/templates/app/key-value-list-plugin.html b/templates/app/key-value-list-plugin.html deleted file mode 100644 index a5b312ae..00000000 --- a/templates/app/key-value-list-plugin.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - -
- - - - - - - -
{{ name }}{{ value }}
- -
- - -
diff --git a/templates/app/line-chart-plugin.html b/templates/app/line-chart-plugin.html deleted file mode 100644 index 47427b12..00000000 --- a/templates/app/line-chart-plugin.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - - -
- - - - - - - - - - -
{{ metric.name }}{{ metric.data }}
- - - -
- diff --git a/templates/app/loading.html b/templates/app/loading.html deleted file mode 100644 index e6aeacdd..00000000 --- a/templates/app/loading.html +++ /dev/null @@ -1,4 +0,0 @@ -
- - Loading... -
diff --git a/templates/app/multi-line-chart-plugin.html b/templates/app/multi-line-chart-plugin.html deleted file mode 100644 index 42d9ef2c..00000000 --- a/templates/app/multi-line-chart-plugin.html +++ /dev/null @@ -1,26 +0,0 @@ -
- - -
- - - - - - - - - - - -
-
-
-
{{ metric.name }}{{ metric.data }}
- - -
- - \ No newline at end of file diff --git a/templates/app/navbar.html b/templates/app/navbar.html deleted file mode 100644 index b1d115bd..00000000 --- a/templates/app/navbar.html +++ /dev/null @@ -1,8 +0,0 @@ -
- diff --git a/templates/app/progress-bar-plugin.html b/templates/app/progress-bar-plugin.html deleted file mode 100644 index 85867eff..00000000 --- a/templates/app/progress-bar-plugin.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
-
-
-
\ No newline at end of file diff --git a/templates/app/table-data-plugin.html b/templates/app/table-data-plugin.html deleted file mode 100644 index a521155b..00000000 --- a/templates/app/table-data-plugin.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - -
- - - - - - - - - - - - - - - -
- -
- {{ header }} - - {{ (header === sortByColumn && !sortReverse) ? '▲': ''; }} - {{ (header === sortByColumn && sortReverse) ? '▼': ''; }} - -
- {{ row[header] }} -
- -
- - -
diff --git a/templates/app/theme-switcher.html b/templates/app/theme-switcher.html deleted file mode 100644 index 36ff802d..00000000 --- a/templates/app/theme-switcher.html +++ /dev/null @@ -1,12 +0,0 @@ -
- -
- -
- {{ theme.name }} -
- -
\ No newline at end of file diff --git a/templates/app/ui-elements/last-update.html b/templates/app/ui-elements/last-update.html deleted file mode 100644 index d8d25ccc..00000000 --- a/templates/app/ui-elements/last-update.html +++ /dev/null @@ -1,4 +0,0 @@ -Loading... - - {{ timestamp | date:'hh:mm:ss a' }} - \ No newline at end of file diff --git a/templates/app/ui-elements/top-bar.html b/templates/app/ui-elements/top-bar.html deleted file mode 100644 index 88b5d1d6..00000000 --- a/templates/app/ui-elements/top-bar.html +++ /dev/null @@ -1,10 +0,0 @@ -
- - - {{ heading }} - - {{ info }} - - - -
\ No newline at end of file diff --git a/templates/modules/cpu-load.html b/templates/modules/cpu-load.html deleted file mode 100644 index e59abbc6..00000000 --- a/templates/modules/cpu-load.html +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/templates/modules/cpu-temp.html b/templates/modules/cpu-temp.html deleted file mode 100644 index 911db1b3..00000000 --- a/templates/modules/cpu-temp.html +++ /dev/null @@ -1,13 +0,0 @@ - - diff --git a/templates/modules/cpu-utilization-chart.html b/templates/modules/cpu-utilization-chart.html deleted file mode 100644 index 13a2a8b7..00000000 --- a/templates/modules/cpu-utilization-chart.html +++ /dev/null @@ -1,13 +0,0 @@ - - diff --git a/templates/modules/disk-space.html b/templates/modules/disk-space.html deleted file mode 100644 index 447c739b..00000000 --- a/templates/modules/disk-space.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -
NameStatsUsedMount Path
{{partition['file_system']}} - - - - {{ partition['used'] }} / {{ partition['size'] }} - - {{ partition['used%'] }} - {{ partition['mounted'] }}
- -
diff --git a/templates/modules/download-transfer-rate.html b/templates/modules/download-transfer-rate.html deleted file mode 100644 index 32fe674c..00000000 --- a/templates/modules/download-transfer-rate.html +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/templates/modules/ram-chart.html b/templates/modules/ram-chart.html deleted file mode 100644 index 27a00d97..00000000 --- a/templates/modules/ram-chart.html +++ /dev/null @@ -1,14 +0,0 @@ - - diff --git a/templates/modules/upload-transfer-rate.html b/templates/modules/upload-transfer-rate.html deleted file mode 100644 index 63f267ed..00000000 --- a/templates/modules/upload-transfer-rate.html +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/templates/ping-speeds.html b/templates/ping-speeds.html deleted file mode 100644 index df5a9b8a..00000000 --- a/templates/ping-speeds.html +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/templates/sections/accounts.html b/templates/sections/accounts.html deleted file mode 100644 index a3a7a33b..00000000 --- a/templates/sections/accounts.html +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/templates/sections/applications.html b/templates/sections/applications.html deleted file mode 100644 index c73305be..00000000 --- a/templates/sections/applications.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/templates/sections/basic-info.html b/templates/sections/basic-info.html deleted file mode 100644 index 9c8c7deb..00000000 --- a/templates/sections/basic-info.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/templates/sections/network.html b/templates/sections/network.html deleted file mode 100644 index e7fbe5f5..00000000 --- a/templates/sections/network.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/templates/sections/system-status.html b/templates/sections/system-status.html deleted file mode 100644 index 74100fc3..00000000 --- a/templates/sections/system-status.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - -