commit c22caed44619cc92e1604b85d9e726953a76b7e5 Author: Evert Prants Date: Fri Mar 1 21:44:08 2019 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a66bf95 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,363 @@ +{ + "name": "quickstatus", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "~1.38.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "node-os-utils": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/node-os-utils/-/node-os-utils-1.0.7.tgz", + "integrity": "sha1-Q6+sBpSwTRRoIN8EkEWPzdBxBAM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..dcdaf3f --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "quickstatus", + "version": "1.0.0", + "description": "Get system status in JSON format", + "main": "server.js", + "scripts": { + "start": "node server.js", + "output": "node system.js" + }, + "private": true, + "license": "CC0", + "dependencies": { + "express": "^4.16.4", + "node-os-utils": "^1.0.7" + } +} diff --git a/public/dark-theme.css b/public/dark-theme.css new file mode 100644 index 0000000..64838a7 --- /dev/null +++ b/public/dark-theme.css @@ -0,0 +1,20 @@ +body { + background-color: #00060e; +} +.infobox { + color: #ddd; +} +.infobox .header { + background-color: #393939; + border: 1px solid #454545; +} +.infobox .content { + background-color: #242424; + border: 1px solid #454545; +} +.bar-inner { + background-color: #181818; +} +.bar-progress { + background-color: #00626e; +} diff --git a/public/index.css b/public/index.css new file mode 100644 index 0000000..b188b52 --- /dev/null +++ b/public/index.css @@ -0,0 +1,87 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif, sans; + background-color: #f0f0f0; +} +#app { + margin: 10px 120px; +} +h1,h2,h3,h4 { + margin: 0; +} +.infobox { + margin: 5px; +} +.infobox .header { + background-color: #f3f3f3; + padding: 15px; + /*border-radius: 10px 10px 0 0;*/ + border: 1px solid #ddd; + border-bottom: 0; +} +.infobox .content { + padding: 10px; + background-color: #ececec; + border: 1px solid #ddd; +} +.infobox-row { + display: flex; + flex-direction: row; +} +.infobox-row .infobox { + flex-grow: 1; +} +.row { + margin-bottom: 5px; +} +.row label { + min-width: 50%; + display: inline-block; +} +.bar-inner { + height: 40px; + background-color: #ddd; + border-radius: 5px; +} +.bar-progress { + height: 40px; + background-color: #00c9e1; + border-radius: 5px; +} +.bar-stats { + display: flex; + flex-direction: row; + margin-top: 5px; +} +.bar-stats span { + flex-grow: 1; + color: #9c9c9c; +} +.bar-stats .stat-now { + text-align: center; +} +.bar-stats .stat-high { + text-align: right; +} +.ip-addr { + display: block; +} +.sec-info { + margin-top: 20px; + margin-bottom: 10px; +} +.disks .disk { + margin-bottom: 20px; +} +@media only screen and (max-width: 800px) { + #app { + margin: 15px; + } + .infobox-row { + display: block; + } + .infobox { + width: 100%; + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..6b8b99c --- /dev/null +++ b/public/index.html @@ -0,0 +1,16 @@ + + + + + + + + Please Wait.. + + +
+

Gathering the latest information, please wait..

+
+ + + diff --git a/public/index.js b/public/index.js new file mode 100644 index 0000000..894ca48 --- /dev/null +++ b/public/index.js @@ -0,0 +1,203 @@ +(function () { + let app = document.getElementById('app') + + function httpGet (url) { + let request = new XMLHttpRequest() + return new Promise(function (resolve, reject) { + request.onreadystatechange = function () { + if (this.readyState === 4) { + if (this.status === 200) { + resolve(this.responseText) + } else if (this.response == null && this.status === 0) { + reject(new Error('The status server appears to be offile.')) + } + } + } + + request.open("GET", url, true) + request.send(null) + }) + } + + function bytesToSize (bytes) { + let sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] + if (bytes == 0) return '0 Byte' + let i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))) + return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i] + } + + // Add a zero in front of single-digit numbers + function zf (v) { + if (v > 9) { + return '' + v + } else { + return '0' + v + } + } + + // Convert seconds into years days hours minutes seconds(.milliseconds) + function readableTime (timems, ignoreMs) { + var time = timems | 0 + var ms = ignoreMs ? '' : '.' + zf((timems * 100) % 100 | 0) + if (time < 60) return zf(time) + ms + 's' + else if (time < 3600) return zf(time / 60 | 0) + 'm ' + zf(time % 60) + ms + 's' + else if (time < 86400) return zf(time / 3600 | 0) + 'h ' + zf((time % 3600) / 60 | 0) + 'm ' + zf((time % 3600) % 60) + ms + 's' + else if (time < 31536000) return (time / 86400 | 0) + 'd ' + zf((time % 86400) / 3600 | 0) + 'h ' + zf((time % 3600) / 60 | 0) + 'm ' + zf((time % 3600) % 60) + 's' + else return (time / 31536000 | 0) + 'y ' + zf((time % 31536000) / 86400 | 0) + 'd ' + zf((time % 86400) / 3600 | 0) + 'h ' + zf((time % 3600) / 60 | 0) + 'm ' + zf((time % 3600) % 60) + 's' + } + + function e (element, content, cl) { + let el = document.createElement(element) + if (cl) el.className = cl + el.innerHTML = content + return el.outerHTML + } + + function box (type, content) { + return e('div', content, type) + } + + function bar (metric, value, percentage, low, high) { + return box('bar-outer', + e('span', metric, 'bar-name') + + box('bar-inner', + '
' + ) + + box('bar-stats', + e('span', low, 'stat-low') + + e('span', value, 'stat-now') + + e('span', high, 'stat-high') + ) + ) + } + + function renderPage (data) { + let title = 'Welcome to ' + data.hostname + ' on ' + data.kernel + document.title = title + + app.innerHTML = '' + app.innerHTML += box('infobox-row', + box('general infobox', + box('header', + e('h2', 'General') + ) + + box('content', + box('row', + e('label', 'Hostname') + + e('span', data.hostname) + ) + box('row', + e('label', 'Uptime') + + e('span', readableTime(data.uptime)) + ) + box('row', + e('label', 'Kernel version') + + e('span', data.kernel) + ) + box('row', + e('label', 'Processes') + + e('span', data.cpu.processes) + ) + ) + ) + + + // CPU usage + box('cpu infobox', + box('header', + e('h2', 'CPU') + ) + + box('content', + box('row', + e('label', 'Num. cores') + + e('span', data.cpu.count) + ) + + box('row', + e('label', 'Usage') + + e('span', Math.floor(data.cpu.usage) + ' %') + ) + box('row', + e('label', 'Load averages') + + e('span', data.cpu.loadAverage[0].toFixed(2) + ', ' + data.cpu.loadAverage[1].toFixed(2) + ', ' + data.cpu.loadAverage[2].toFixed(2)) + ) + ) + )) + + // Memory usage + let memuse = (data.memory.total - data.memory.free) + app.innerHTML += box('memory infobox', + box('header', + e('h2', 'Memory') + ) + + box('content', + bar('Memory usage', bytesToSize(memuse), memuse * 100 / data.memory.total, '0 Bytes', bytesToSize(data.memory.total)) + ) + ) + + // Network interfaces + let interfaces = '' + for (let int in data.network) { + if (int === 'lo') continue + let iface = data.network[int] + let ips = [] + for (let ip in iface.addresses) { + ips.push(e('span', iface.addresses[ip], 'ip-addr')) + } + interfaces += box('interface', + e('h3', int) + + ips.join('') + + e('h4', 'Bandwidth', 'sec-info') + + box('row', + e('label', 'Download') + + e('span', bytesToSize(iface.download)) + ) + box('row', + e('label', 'Upload') + + e('span', bytesToSize(iface.upload)) + ) + ) + } + + // Disks + let disks = '' + for (let mount in data.disk) { + let disk = data.disk[mount] + disks += box('disk', + e('h3', 'Mounted on ' + mount) + + box('row', + e('label', 'Free space') + + e('span', disk.freeGb + ' GB') + ) + + bar('Disk usage', disk.usedGb + ' GB', disk.usedPercentage, '0 GB', disk.totalGb + ' GB') + ) + } + + app.innerHTML += box('infobox-row', + box('network infobox', + box('header', + e('h2', 'Network Interfaces') + ) + + box('content', + box('interfaces', interfaces) + ) + )+ + + box('disk infobox', + box('header', + e('h2', 'Disk Drives') + ) + + box('content', + box('disks', disks) + ) + ) + ) + } + + function update () { + httpGet('/status').then(function (data) { + try { + renderPage(JSON.parse(data)) + } catch (e) { + console.error(e) + } + }, function (e) { + console.error(e) + }) + } + + update() +})() diff --git a/server.js b/server.js new file mode 100644 index 0000000..4303d77 --- /dev/null +++ b/server.js @@ -0,0 +1,41 @@ +const express = require('express') +const cprog = require('child_process') +const path = require('path') + +const app = express() +const port = process.env.PORT || 8432 + +/* Information segment */ + +let info = {} +let infoTimer + +async function requestInfo () { + return new Promise((resolve, reject) => { + let proc = cprog.exec('node ' + path.join(__dirname, 'system.js'), (err, stdout) => { + if (err) return reject(err) + resolve(JSON.parse(stdout)) + }) + }) +} + +function update () { + requestInfo().then((newInfo) => { + info = newInfo + }).catch(e => console.error(e)) +} + +infoTimer = setInterval(update, 8000) +update() + +/* Web server segment */ + +app.use('/status', function (req, res) { + res.jsonp(info) +}) + +app.use('/', express.static(path.join(__dirname, 'public'))) + +app.listen(port, function (argument) { + console.log('Listening on', port) +}) diff --git a/system.js b/system.js new file mode 100644 index 0000000..69aedf4 --- /dev/null +++ b/system.js @@ -0,0 +1,86 @@ +const fs = require('fs') +const util = require('util') +const os = require('os') +const osu = require('node-os-utils') + +let disks = ['/'] + +// Get disks from environment variable +let denv = process.env.SHOW_DISKS +if (denv) { + try { + disks = JSON.parse(denv) + } catch (e) { + console.warn(e) + } +} + +async function diskInfo () { + let result = {} + for (let i in disks) { + let info = await osu.drive.info(disks[i]) + result[disks[i]] = info + } + return result +} + +async function networkInfo () { + let interfaces = {} + let all = os.networkInterfaces() + for (let iface in all) { + let ips = all[iface] + interfaces[iface] = { + addresses: [], + upload: null, + download: null, + } + for (let i in ips) { + interfaces[iface].addresses.push(ips[i].address) + } + } + + // Interface usage statistics + let data = await osu.netstat.stats() + for (let p in data) { + let ifa = data[p] + let dt = interfaces[ifa.interface] + if (!dt) continue + dt.upload = ifa.outputBytes + dt.download = ifa.inputBytes + } + + return interfaces +} + +async function cpuInfo () { + return { + usage: await osu.cpu.usage(), + loadAverage: os.loadavg(), + count: os.cpus().length, + processes: await osu.proc.totalProcesses() + } +} + +async function memInfo () { + let free = os.freemem() + let total = os.totalmem() + return { + free: free, + total: total, + usage: (total - free) / total * 100 + } +} + +async function all () { + return { + hostname: os.hostname(), + kernel: os.release(), + uptime: os.uptime(), + cpu: await cpuInfo(), + memory: await memInfo(), + network: await networkInfo(), + disk: await diskInfo() + } +} + +all().then((a) => console.log(JSON.stringify(a)))