diff --git a/interface/package-lock.json b/interface/package-lock.json index 68b45c481..a9ca3a7a6 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -1,11 +1,11 @@ { - "name": "esp8266-react", + "name": "emsesp-react", "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "esp8266-react", + "name": "emsesp-react", "version": "0.1.0", "dependencies": { "@material-ui/core": "^4.11.4", @@ -8860,18 +8860,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -19797,284 +19785,6 @@ "watchpack-chokidar2": "^2.0.1" } }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "node_modules/watchpack-chokidar2/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/watchpack-chokidar2/node_modules/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==", - "optional": true - }, - "node_modules/watchpack-chokidar2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -20376,19 +20086,6 @@ "node": ">=6" } }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 4.0" - } - }, "node_modules/webpack-dev-server/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -29146,12 +28843,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -38130,250 +37821,6 @@ "watchpack-chokidar2": "^2.0.1" } }, - "watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "requires": { - "chokidar": "^2.1.8" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "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==", - "optional": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, "wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -38852,12 +38299,6 @@ "locate-path": "^3.0.0" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", diff --git a/interface/package.json b/interface/package.json index 5e34e6b2a..fd3d99c94 100644 --- a/interface/package.json +++ b/interface/package.json @@ -1,5 +1,5 @@ { - "name": "esp8266-react", + "name": "emsesp-react", "version": "0.1.0", "private": true, "dependencies": { diff --git a/interface/src/api/Env.ts b/interface/src/api/Env.ts index 59a1263ae..3bfef1d74 100644 --- a/interface/src/api/Env.ts +++ b/interface/src/api/Env.ts @@ -3,6 +3,7 @@ export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH!; export const ENDPOINT_ROOT = calculateEndpointRoot('/rest/'); export const WEB_SOCKET_ROOT = calculateWebSocketRoot('/ws/'); +export const EVENT_SOURCE_ROOT = calculateEndpointRoot('/es/'); function calculateEndpointRoot(endpointPath: string) { const httpRoot = process.env.REACT_APP_HTTP_ROOT; diff --git a/interface/src/components/WindowSize.tsx b/interface/src/components/WindowSize.tsx new file mode 100644 index 000000000..d69405ae8 --- /dev/null +++ b/interface/src/components/WindowSize.tsx @@ -0,0 +1,14 @@ +import { useLayoutEffect, useState } from 'react'; + +export function useWindowSize() { + const [size, setSize] = useState([0, 0]); + useLayoutEffect(() => { + function updateSize() { + setSize([window.innerWidth, window.innerHeight]); + } + window.addEventListener('resize', updateSize); + updateSize(); + return () => window.removeEventListener('resize', updateSize); + }, []); + return size; +} diff --git a/interface/src/components/index.ts b/interface/src/components/index.ts index 4a6cc6a7b..47348a94d 100644 --- a/interface/src/components/index.ts +++ b/interface/src/components/index.ts @@ -15,3 +15,5 @@ export * from './RestController'; export * from './WebSocketFormLoader'; export * from './WebSocketController'; + +export * from './WindowSize'; diff --git a/interface/src/project/EMSESPSettingsController.tsx b/interface/src/project/EMSESPSettingsController.tsx index c173e74e6..08a0eba45 100644 --- a/interface/src/project/EMSESPSettingsController.tsx +++ b/interface/src/project/EMSESPSettingsController.tsx @@ -23,14 +23,12 @@ class EMSESPSettingsController extends Component render() { return ( - // } /> - // ); } } diff --git a/interface/src/project/EMSESPStatusController.tsx b/interface/src/project/EMSESPStatusController.tsx index 83ba70b50..837b8db3b 100644 --- a/interface/src/project/EMSESPStatusController.tsx +++ b/interface/src/project/EMSESPStatusController.tsx @@ -21,7 +21,7 @@ class EMSESPStatusController extends Component { render() { return ( - + } diff --git a/interface/src/setupProxy.js b/interface/src/setupProxy.js index 7c34fb4c6..5a8e4ac3d 100644 --- a/interface/src/setupProxy.js +++ b/interface/src/setupProxy.js @@ -9,4 +9,13 @@ module.exports = function (app) { changeOrigin: true }) ); + + app.use( + '/es/*', + createProxyMiddleware({ + target: 'http://localhost:3090', + secure: false, + changeOrigin: true + }) + ); }; diff --git a/interface/src/system/LogEventConsole.tsx b/interface/src/system/LogEventConsole.tsx new file mode 100644 index 000000000..7d250c5bc --- /dev/null +++ b/interface/src/system/LogEventConsole.tsx @@ -0,0 +1,112 @@ +import { FC } from 'react'; + +import { LogEvent, LogLevel } from './types'; +import { Theme, makeStyles, Box } from '@material-ui/core'; +import { useWindowSize } from '../components'; + +interface LogEventConsoleProps { + events: LogEvent[]; +} + +interface Offsets { + topOffset: () => number; + leftOffset: () => number; +} + +const topOffset = () => + document.getElementById('log-window')?.getBoundingClientRect().bottom || 0; + +const leftOffset = () => + document.getElementById('log-window')?.getBoundingClientRect().left || 0; + +const useStyles = makeStyles((theme: Theme) => ({ + console: { + padding: theme.spacing(2), + position: 'absolute', + left: (offsets: Offsets) => offsets.leftOffset(), + right: 24, + top: (offsets: Offsets) => offsets.topOffset(), + bottom: 24, + backgroundColor: 'black', + overflow: 'auto' + }, + entry: { + color: '#bbbbbb', + fontFamily: 'Courier New, monospace', + fontSize: '14px', + letterSpacing: 'normal', + whiteSpace: 'nowrap' + }, + debug: { + color: '#0000ff' + }, + info: { + color: '#00ff00' + }, + notice: { + color: '#ffff00' + }, + err: { + color: '#ff0000' + }, + unknown: { + color: '#ffffff' + } +})); + +const LogEventConsole: FC = (props) => { + useWindowSize(); + const classes = useStyles({ topOffset, leftOffset }); + const { events } = props; + + const styleLevel = (level: LogLevel) => { + switch (level) { + case LogLevel.DEBUG: + return classes.debug; + case LogLevel.INFO: + return classes.info; + case LogLevel.NOTICE: + return classes.notice; + case LogLevel.ERR: + return classes.err; + default: + return classes.unknown; + } + }; + + const levelLabel = (level: LogLevel) => { + switch (level) { + case LogLevel.DEBUG: + return 'DEBUG'; + case LogLevel.INFO: + return 'INFO'; + case LogLevel.ERR: + return 'ERR'; + case LogLevel.NOTICE: + return 'NOTICE'; + default: + return '?'; + } + }; + + const paddedLevelLabel = (level: LogLevel) => { + const label = levelLabel(level); + return label.padStart(7, '\xa0'); + }; + + return ( + + {events.map((e) => ( +
+ {e.time} + + {paddedLevelLabel(e.level)}{' '} + + {e.message} +
+ ))} +
+ ); +}; + +export default LogEventConsole; diff --git a/interface/src/system/LogEventController.tsx b/interface/src/system/LogEventController.tsx new file mode 100644 index 000000000..3a2cb0fd4 --- /dev/null +++ b/interface/src/system/LogEventController.tsx @@ -0,0 +1,109 @@ +import { Component } from 'react'; +import { FormActions, FormButton } from '../components'; + +import { + createStyles, + WithStyles, + withStyles, + Typography, + Theme, + Paper +} from '@material-ui/core'; + +import { LogEvent } from './types'; +import { EVENT_SOURCE_ROOT } from '../api/Env'; +import LogEventConsole from './LogEventConsole'; +import { addAccessTokenParameter } from '../authentication'; + +import SaveIcon from '@material-ui/icons/Save'; + +const LOG_EVENT_EVENT_SOURCE_URL = EVENT_SOURCE_ROOT + 'log'; + +interface LogEventControllerState { + eventSource?: EventSource; + events: LogEvent[]; +} + +const styles = (theme: Theme) => + createStyles({ + content: { + padding: theme.spacing(2), + margin: theme.spacing(3) + } + }); + +type LogEventControllerProps = WithStyles; + +class LogEventController extends Component< + LogEventControllerProps, + LogEventControllerState +> { + eventSource?: EventSource; + reconnectTimeout?: NodeJS.Timeout; + + constructor(props: LogEventControllerProps) { + super(props); + this.state = { + events: [] + }; + } + + componentDidMount() { + this.configureEventSource(); + } + + componentWillUnmount() { + if (this.eventSource) { + this.eventSource.close(); + } + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + } + } + + configureEventSource = () => { + this.eventSource = new EventSource( + addAccessTokenParameter(LOG_EVENT_EVENT_SOURCE_URL) + ); + this.eventSource.onmessage = this.onMessage; + this.eventSource.onerror = this.onError; + }; + + onError = () => { + if (this.eventSource && this.reconnectTimeout) { + this.eventSource.close(); + this.eventSource = undefined; + this.reconnectTimeout = setTimeout(this.configureEventSource, 1000); + } + }; + + onMessage = (event: MessageEvent) => { + const rawData = event.data; + if (typeof rawData === 'string' || rawData instanceof String) { + const event = JSON.parse(rawData as string) as LogEvent; + this.setState((state) => ({ events: [...state.events, event] })); + } + }; + + render() { + const { classes } = this.props; + return ( + + System Log + + } + variant="contained" + color="secondary" + // onClick={this.requestNetworkScan} + > + Save + + + + + ); + } +} + +export default withStyles(styles)(LogEventController); diff --git a/interface/src/system/System.tsx b/interface/src/system/System.tsx index 8b41c5deb..e9bdf0394 100644 --- a/interface/src/system/System.tsx +++ b/interface/src/system/System.tsx @@ -15,6 +15,7 @@ import { MenuAppBar } from '../components'; import SystemStatusController from './SystemStatusController'; import OTASettingsController from './OTASettingsController'; import UploadFirmwareController from './UploadFirmwareController'; +import LogEventController from './LogEventController'; type SystemProps = AuthenticatedContextProps & RouteComponentProps & @@ -35,6 +36,7 @@ class System extends Component { variant="fullWidth" > + {features.ota && ( { path="/system/status" component={SystemStatusController} /> + {features.ota && ( ) When developing and testing the web interface, it's handy not to bother with re-flashing an ESP32 each time. The idea is to mimic the ESP using a mock/stub server that responds to the REST (HTTP POST & GET) and WebSocket calls. @@ -16,7 +15,7 @@ and to run it ```sh % cd interface -% npm run dev +% npm run standalone ``` ## Notes @@ -24,6 +23,18 @@ and to run it - new file `interface/src/setupProxy.js` - new files `mock-api/server.js` with the hardcoded data. Requires its own npm packages for express -## ToDo +## Testing + +```bash +% curl -i http://localhost:3080/rest/emsespSettings +``` + +or from a browser use port 3000 since `setupProxy.js` is redirecting, like http://172.22.227.82:3000/rest/emsespSettings + +http://172.22.227.82:3090/es/log?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInZlcnNpb24iOiIzLjAuMmIwIn0.MsHSgoJKI1lyYz77EiT5ZN3ECMrb4mPv9FNy3udq0TU + +Testing the EventSource/SSE use http://172.22.227.82:3090/es/log + +## To Do - add filter rule to prevent from exposing yourself to malicious attacks when running the dev server() diff --git a/mock-api/server.js b/mock-api/server.js index aac713f1c..3794dd923 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -1,21 +1,23 @@ const express = require('express') const path = require('path') - const msgpack = require('@msgpack/msgpack') -// import { encode } from "@msgpack/msgpack"; +// REST API const app = express() const port = process.env.PORT || 3080 - +const REST_ENDPOINT_ROOT = '/rest/' app.use(express.static(path.join(__dirname, '../interface/build'))) app.use(express.json()) -const ENDPOINT_ROOT = '/rest/' +// ES API +const server = express() +const es_port = 3090 +const ES_ENDPOINT_ROOT = '/es/' // NTP -const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'ntpStatus' -const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'ntpSettings' -const TIME_ENDPOINT = ENDPOINT_ROOT + 'time' +const NTP_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'ntpStatus' +const NTP_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'ntpSettings' +const TIME_ENDPOINT = REST_ENDPOINT_ROOT + 'time' const ntp_settings = { enabled: true, server: 'time.google.com', @@ -31,8 +33,8 @@ const ntp_status = { } // AP -const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'apSettings' -const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'apStatus' +const AP_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'apSettings' +const AP_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'apStatus' const ap_settings = { provision_mode: 1, ssid: 'ems-esp', @@ -49,10 +51,10 @@ const ap_status = { } // NETWORK -const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'networkSettings' -const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + 'networkStatus' -const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'scanNetworks' -const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'listNetworks' +const NETWORK_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'networkSettings' +const NETWORK_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'networkStatus' +const SCAN_NETWORKS_ENDPOINT = REST_ENDPOINT_ROOT + 'scanNetworks' +const LIST_NETWORKS_ENDPOINT = REST_ENDPOINT_ROOT + 'listNetworks' const network_settings = { ssid: 'myWifi', password: 'myPassword', @@ -134,7 +136,7 @@ const list_networks = { } // OTA -const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'otaSettings' +const OTA_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'otaSettings' const ota_settings = { enabled: true, port: 8266, @@ -142,8 +144,8 @@ const ota_settings = { } // MQTT -const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'mqttSettings' -const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + 'mqttStatus' +const MQTT_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'mqttSettings' +const MQTT_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'mqttStatus' const mqtt_settings = { enabled: true, host: '192.168.1.4', @@ -179,15 +181,15 @@ const mqtt_status = { } // SYSTEM -const FEATURES_ENDPOINT = ENDPOINT_ROOT + 'features' -const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + 'verifyAuthorization' -const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + 'systemStatus' -const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'securitySettings' -const RESTART_ENDPOINT = ENDPOINT_ROOT + 'restart' -const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + 'factoryReset' -const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + 'uploadFirmware' -const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + 'signIn' -const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + 'generateToken' +const FEATURES_ENDPOINT = REST_ENDPOINT_ROOT + 'features' +const VERIFY_AUTHORIZATION_ENDPOINT = REST_ENDPOINT_ROOT + 'verifyAuthorization' +const SYSTEM_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'systemStatus' +const SECURITY_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'securitySettings' +const RESTART_ENDPOINT = REST_ENDPOINT_ROOT + 'restart' +const FACTORY_RESET_ENDPOINT = REST_ENDPOINT_ROOT + 'factoryReset' +const UPLOAD_FIRMWARE_ENDPOINT = REST_ENDPOINT_ROOT + 'uploadFirmware' +const SIGN_IN_ENDPOINT = REST_ENDPOINT_ROOT + 'signIn' +const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken' const system_status = { emsesp_version: '3.1 demo', esp_platform: 'ESP32', @@ -226,13 +228,13 @@ const signin = { const generate_token = { token: '1234' } // EMS-ESP Project specific -const EMSESP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'emsespSettings' -const EMSESP_ALLDEVICES_ENDPOINT = ENDPOINT_ROOT + 'allDevices' -const EMSESP_SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + 'scanDevices' -const EMSESP_DEVICEDATA_ENDPOINT = ENDPOINT_ROOT + 'deviceData' -const EMSESP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'emsespStatus' -const EMSESP_BOARDPROFILE_ENDPOINT = ENDPOINT_ROOT + 'boardProfile' -const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + 'writeValue' +const EMSESP_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'emsespSettings' +const EMSESP_ALLDEVICES_ENDPOINT = REST_ENDPOINT_ROOT + 'allDevices' +const EMSESP_SCANDEVICES_ENDPOINT = REST_ENDPOINT_ROOT + 'scanDevices' +const EMSESP_DEVICEDATA_ENDPOINT = REST_ENDPOINT_ROOT + 'deviceData' +const EMSESP_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'emsespStatus' +const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile' +const WRITE_VALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeValue' const emsesp_settings = { tx_mode: 1, tx_delay: 0, @@ -912,5 +914,60 @@ app.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { res.json(data) }) +// create helper middleware so we can reuse server-sent events +const useServerSentEventsMiddleware = (req, res, next) => { + res.setHeader('Content-Type', 'text/event-stream') + res.setHeader('Cache-Control', 'no-cache') + + // only if you want anyone to access this endpoint + res.setHeader('Access-Control-Allow-Origin', '*') + + res.flushHeaders() + + const sendEventStreamData = (data) => { + const sseFormattedResponse = `data: ${JSON.stringify(data)}\n\n` + res.write(sseFormattedResponse) + } + + // we are attaching sendEventStreamData to res, so we can use it later + Object.assign(res, { + sendEventStreamData, + }) + + next() +} + +const streamLog = (req, res) => { + let interval = setInterval(function generateAndSendLog() { + count = count + 1 + + const data = { + time: '000+00:00:00.000', + level: 4, + message: 'this is message #' + count, + } + + res.sendEventStreamData(data) + }, 1000) + + res.on('close', () => { + clearInterval(interval) + res.end() + }) +} + +// event source, server-sent events SSE +const ES_LOG_ENDPOINT = ES_ENDPOINT_ROOT + 'log' +let count = 0 +server.get(ES_LOG_ENDPOINT, useServerSentEventsMiddleware, streamLog) +server.listen(es_port, () => + console.log( + `Mock EventSource server for EMS-ESP listening at http://localhost:${es_port}`, + ), +) + +// rest API app.listen(port) -console.log(`Mock API Server is up and running at: http://localhost:${port}`) +console.log( + `Mock RESTful API server for EMS-ESP is up and running at http://localhost:${port}`, +) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 3435b5844..9fda46f59 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -40,6 +40,7 @@ WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, & WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); WebDevicesService EMSESP::webDevicesService = WebDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp8266React.getSecurityManager()); +WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp8266React.getSecurityManager()); using DeviceFlags = EMSdevice; using DeviceType = EMSdevice::DeviceType; @@ -1209,11 +1210,12 @@ void EMSESP::start() { // main loop calling all services void EMSESP::loop() { - esp8266React.loop(); // web + esp8266React.loop(); // web services system_.loop(); // does LED and checks system health, and syslog service // if we're doing an OTA upload, skip MQTT and EMS if (!system_.upload_status()) { + webLogService.loop(); // log in Web UI rxservice_.loop(); // process any incoming Rx telegrams shower_.loop(); // check for shower on/off dallassensor_.loop(); // read dallas sensor temperatures diff --git a/src/emsesp.h b/src/emsesp.h index 08306d59c..c262663ea 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -35,10 +35,11 @@ #include -#include "WebStatusService.h" -#include "WebDevicesService.h" -#include "WebSettingsService.h" -#include "WebAPIService.h" +#include "web/WebStatusService.h" +#include "web/WebDevicesService.h" +#include "web/WebSettingsService.h" +#include "web/WebAPIService.h" +#include "web/WebLogService.h" #include "emsdevice.h" #include "emsfactory.h" @@ -205,6 +206,7 @@ class EMSESP { static WebStatusService webStatusService; static WebDevicesService webDevicesService; static WebAPIService webAPIService; + static WebLogService webLogService; static uuid::log::Logger logger(); diff --git a/src/system.cpp b/src/system.cpp index 6473b32e0..b35d6d9ce 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -849,6 +849,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & EMSESP::webSettingsService.read([&](WebSettings & settings) { node = json.createNestedObject("Settings"); node["tx_mode"] = settings.tx_mode; + node["tx_delay"] = settings.tx_delay; node["ems_bus_id"] = settings.ems_bus_id; node["syslog_enabled"] = settings.syslog_enabled; node["syslog_level"] = settings.syslog_level; diff --git a/src/version.h b/src/version.h index c3735ba6b..05391a6b1 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.1.1b6" +#define EMSESP_APP_VERSION "3.1.1b7" diff --git a/src/WebAPIService.cpp b/src/web/WebAPIService.cpp similarity index 100% rename from src/WebAPIService.cpp rename to src/web/WebAPIService.cpp diff --git a/src/WebAPIService.h b/src/web/WebAPIService.h similarity index 100% rename from src/WebAPIService.h rename to src/web/WebAPIService.h diff --git a/src/WebDevicesService.cpp b/src/web/WebDevicesService.cpp similarity index 100% rename from src/WebDevicesService.cpp rename to src/web/WebDevicesService.cpp diff --git a/src/WebDevicesService.h b/src/web/WebDevicesService.h similarity index 99% rename from src/WebDevicesService.h rename to src/web/WebDevicesService.h index 010a09bdc..a5bbe87e7 100644 --- a/src/WebDevicesService.h +++ b/src/web/WebDevicesService.h @@ -29,7 +29,6 @@ #define DEVICE_DATA_SERVICE_PATH "/rest/deviceData" #define WRITE_VALUE_SERVICE_PATH "/rest/writeValue" - namespace emsesp { class WebDevicesService { diff --git a/src/web/WebLogService.cpp b/src/web/WebLogService.cpp new file mode 100644 index 000000000..bbdc35249 --- /dev/null +++ b/src/web/WebLogService.cpp @@ -0,0 +1,126 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "emsesp.h" + +using namespace std::placeholders; + +namespace emsesp { + +WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager) + : _events(EVENT_SOURCE_LOG_PATH) { + _events.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); + server->addHandler(&_events); + server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1)); + start(); +} + +void WebLogService::forbidden(AsyncWebServerRequest * request) { + request->send(403); +} + +void WebLogService::start() { + uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // default is INFO +} + +uuid::log::Level WebLogService::log_level() const { + return uuid::log::Logger::get_log_level(this); +} + +void WebLogService::remove_queued_messages(uuid::log::Level level) { + unsigned long offset = 0; + + for (auto it = log_messages_.begin(); it != log_messages_.end();) { + if (it->content_->level > level) { + offset++; + it = log_messages_.erase(it); + } else { + it->id_ -= offset; + it++; + } + } + + log_message_id_ -= offset; +} + +void WebLogService::log_level(uuid::log::Level level) { + uuid::log::Logger::register_handler(this, level); +} + +size_t WebLogService::maximum_log_messages() const { + return maximum_log_messages_; +} + +void WebLogService::maximum_log_messages(size_t count) { + maximum_log_messages_ = std::max((size_t)1, count); + while (log_messages_.size() > maximum_log_messages_) { + log_messages_.pop_front(); + } +} + +WebLogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr && content) + : id_(id) + , content_(std::move(content)) { +} + +void WebLogService::operator<<(std::shared_ptr message) { + if (log_messages_.size() >= maximum_log_messages_) { + log_messages_.pop_front(); + } + log_messages_.emplace_back(log_message_id_++, std::move(message)); +} + +void WebLogService::loop() { + if (!_events.count()) { + return; + } + + while (!log_messages_.empty() && can_transmit()) { + transmit(log_messages_.front()); + log_messages_.pop_front(); + } +} + +bool WebLogService::can_transmit() { + const uint64_t now = uuid::get_uptime_ms(); + if (now < last_transmit_ || now - last_transmit_ < 100) { + return false; + } + return true; +} + +// send to web eventsource +void WebLogService::transmit(const QueuedLogMessage & message) { + DynamicJsonDocument jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_SMALL); + JsonObject logEvent = jsonDocument.to(); + logEvent["time"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3); + logEvent["level"] = message.content_->level; + logEvent["message"] = message.content_->text; + + size_t len = measureJson(jsonDocument); + char * buffer = new char[len + 1]; + if (buffer) { + serializeJson(jsonDocument, buffer, len + 1); + _events.send(buffer, "message", millis()); + } + delete[] buffer; + + last_transmit_ = uuid::get_uptime_ms(); +} + +} // namespace emsesp diff --git a/src/web/WebLogService.h b/src/web/WebLogService.h new file mode 100644 index 000000000..f5b1a9ea8 --- /dev/null +++ b/src/web/WebLogService.h @@ -0,0 +1,74 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WebLogService_h +#define WebLogService_h + +#include +#include +#include +#include + +#include + +#define EVENT_SOURCE_LOG_PATH "/es/log" + +namespace emsesp { + +class WebLogService : public uuid::log::Handler { + public: + static constexpr size_t MAX_LOG_MESSAGES = 50; + + WebLogService(AsyncWebServer * server, SecurityManager * securityManager); + + void start(); + uuid::log::Level log_level() const; + void log_level(uuid::log::Level level); + size_t maximum_log_messages() const; + void maximum_log_messages(size_t count); + void loop(); + + virtual void operator<<(std::shared_ptr message); + + private: + AsyncEventSource _events; + + class QueuedLogMessage { + public: + QueuedLogMessage(unsigned long id, std::shared_ptr && content); + ~QueuedLogMessage() = default; + + unsigned long id_; // Sequential identifier for this log message + struct timeval time_; // Time message was received + const std::shared_ptr content_; // Log message content + }; + + void forbidden(AsyncWebServerRequest * request); + void remove_queued_messages(uuid::log::Level level); + bool can_transmit(); + void transmit(const QueuedLogMessage & message); + + uint64_t last_transmit_ = 0; // Last transmit time + size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output + unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages + std::list log_messages_; // Queued log messages, in the order they were received +}; + +} // namespace emsesp + +#endif diff --git a/src/WebSettingsService.cpp b/src/web/WebSettingsService.cpp similarity index 100% rename from src/WebSettingsService.cpp rename to src/web/WebSettingsService.cpp diff --git a/src/WebSettingsService.h b/src/web/WebSettingsService.h similarity index 98% rename from src/WebSettingsService.h rename to src/web/WebSettingsService.h index e90b30b6b..dccad6961 100644 --- a/src/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -22,7 +22,7 @@ #include #include -#include "default_settings.h" +#include "../default_settings.h" #define EMSESP_SETTINGS_FILE "/config/emsespSettings.json" #define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings" diff --git a/src/WebStatusService.cpp b/src/web/WebStatusService.cpp similarity index 100% rename from src/WebStatusService.cpp rename to src/web/WebStatusService.cpp diff --git a/src/WebStatusService.h b/src/web/WebStatusService.h similarity index 100% rename from src/WebStatusService.h rename to src/web/WebStatusService.h