Browse Source

Upgrade to material ui 4

Add user management and roles - TBA
Menu Label Renames - TBA
master
Rick Watson 5 years ago
parent
commit
0c630f0f93
  1. 553
      interface/package-lock.json
  2. 6
      interface/package.json
  3. 13
      interface/src/App.js
  4. 4
      interface/src/AppRouting.js
  5. 16
      interface/src/components/MenuAppBar.js
  6. 2
      interface/src/components/SectionContent.js
  7. 79
      interface/src/containers/APStatus.js
  8. 27
      interface/src/containers/ManageUsers.js
  9. 98
      interface/src/containers/NTPStatus.js
  10. 6
      interface/src/containers/Security.js
  11. 49
      interface/src/containers/WiFiStatus.js
  12. 226
      interface/src/forms/ManageUsersForm.js
  13. 112
      interface/src/forms/UserForm.js
  14. 7
      platformio.ini
  15. 38
      src/SecurityManager.cpp
  16. 10
      src/SecurityManager.h

553
interface/package-lock.json

@ -890,68 +890,105 @@
"resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz",
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw=="
},
"@emotion/hash": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.1.tgz",
"integrity": "sha512-OYpa/Sg+2GDX+jibUfpZVn1YqSVRpYmTLF2eyAfrFTIJSbwyIrc+YscayoykvaOME/wV4BV0Sa0yqdMrgse6mA=="
},
"@material-ui/core": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz",
"integrity": "sha512-REIj62+zEvTgI/C//YL4fZxrCVIySygmpZglsu/Nl5jPqy3CDjZv1F9ubBYorHqmRgeVPh64EghMMWqk4egmfg==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.0.0.tgz",
"integrity": "sha512-mLEGTuzgUALRKFI3hkRcS0gi/cB3XV0JA4F5PT3rGUt7Dc4liu8/IGiHF7iQh+p337FMk8vkEMxMVdYd9JXKMQ==",
"requires": {
"@babel/runtime": "^7.2.0",
"@material-ui/system": "^3.0.0-alpha.0",
"@material-ui/utils": "^3.0.0-alpha.2",
"@types/jss": "^9.5.6",
"@types/react-transition-group": "^2.0.8",
"brcast": "^3.0.1",
"classnames": "^2.2.5",
"@material-ui/styles": "^4.0.0",
"@material-ui/system": "^4.0.0",
"@material-ui/types": "^4.0.0",
"@material-ui/utils": "^4.0.0",
"@types/react-transition-group": "^2.0.16",
"clsx": "^1.0.2",
"convert-css-length": "^1.0.2",
"csstype": "^2.5.2",
"debounce": "^1.1.0",
"deepmerge": "^3.0.0",
"dom-helpers": "^3.2.1",
"hoist-non-react-statics": "^3.2.1",
"is-plain-object": "^2.0.4",
"jss": "^9.8.7",
"jss-camel-case": "^6.0.0",
"jss-default-unit": "^8.0.2",
"jss-global": "^3.0.0",
"jss-nested": "^6.0.1",
"jss-props-sort": "^6.0.0",
"jss-vendor-prefixer": "^7.0.0",
"normalize-scroll-left": "^0.1.2",
"popper.js": "^1.14.1",
"prop-types": "^15.6.0",
"react-event-listener": "^0.6.2",
"react-transition-group": "^2.2.1",
"recompose": "0.28.0 - 0.30.0",
"prop-types": "^15.7.2",
"react-event-listener": "^0.6.6",
"react-transition-group": "^4.0.0",
"warning": "^4.0.1"
}
},
"@material-ui/icons": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.2.tgz",
"integrity": "sha512-QY/3gJnObZQ3O/e6WjH+0ah2M3MOgLOzCy8HTUoUx9B6dDrS18vP7Ycw3qrDEKlB6q1KNxy6CZHm5FCauWGy2g==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.0.0.tgz",
"integrity": "sha512-hXoKnVLmVer+kic84ypoyG3Amym3a8q3pvDg4KYjeKW9fxGru7x/IkelBJODQL0jO+nAPz1+9RNpFWC75v35dg==",
"requires": {
"@babel/runtime": "^7.2.0"
}
},
"@material-ui/styles": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.0.0.tgz",
"integrity": "sha512-TUpmXlyZDVOl6E2//+UzsZxgi2E+2L753QY02nNkbAC6PPx8FUBqvnjYSGqX0V/BjTJ/fD4CkoS6ZpY3lHf+Gg==",
"requires": {
"@babel/runtime": "^7.2.0",
"recompose": "0.28.0 - 0.30.0"
"@emotion/hash": "^0.7.1",
"@material-ui/types": "^4.0.0",
"@material-ui/utils": "^4.0.0",
"clsx": "^1.0.2",
"deepmerge": "^3.0.0",
"hoist-non-react-statics": "^3.2.1",
"jss": "^10.0.0-alpha.16",
"jss-plugin-camel-case": "^10.0.0-alpha.16",
"jss-plugin-default-unit": "^10.0.0-alpha.16",
"jss-plugin-global": "^10.0.0-alpha.16",
"jss-plugin-nested": "^10.0.0-alpha.16",
"jss-plugin-props-sort": "^10.0.0-alpha.16",
"jss-plugin-rule-value-function": "^10.0.0-alpha.16",
"jss-plugin-vendor-prefixer": "^10.0.0-alpha.16",
"prop-types": "^15.7.2",
"warning": "^4.0.1"
},
"dependencies": {
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"@material-ui/system": {
"version": "3.0.0-alpha.2",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz",
"integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.0.0.tgz",
"integrity": "sha512-SIsqIwjix98Mqw9LVAmRqTs10E4S/SP5n5mlBlhHVHI+2XG2c+MaCPzOF2Zxq0KdqOMgTb7/aevR3mG9UmODxg==",
"requires": {
"@babel/runtime": "^7.2.0",
"deepmerge": "^3.0.0",
"prop-types": "^15.6.0",
"prop-types": "^15.7.2",
"warning": "^4.0.1"
}
},
"@material-ui/types": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-4.0.0.tgz",
"integrity": "sha512-wuiQMo8nSljZR1oWh57UQYssdtFqaU+Cbhr16uLohzzTllpCAK4LkH0slnH3n+5vCa2dgOdNlZTrmsIDDwvRJQ=="
},
"@material-ui/utils": {
"version": "3.0.0-alpha.3",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz",
"integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.0.0.tgz",
"integrity": "sha512-gjz52hO1hkIbKPMng1diQybVgtfgCptOCrulUs4emSCHHKUoR1zfT+IUrjgOaKIpYZNOgS/CI7KDMp689+FzeQ==",
"requires": {
"@babel/runtime": "^7.2.0",
"prop-types": "^15.6.0",
"react-is": "^16.6.3"
"prop-types": "^15.7.2",
"react-is": "^16.8.0"
}
},
"@mrmlnc/readdir-enhanced": {
@ -1107,24 +1144,15 @@
"loader-utils": "^1.1.0"
}
},
"@types/jss": {
"version": "9.5.8",
"resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.8.tgz",
"integrity": "sha512-bBbHvjhm42UKki+wZpR89j73ykSXg99/bhuKuYYePtpma3ZAnmeGnl0WxXiZhPGsIfzKwCUkpPC0jlrVMBfRxA==",
"requires": {
"csstype": "^2.0.0",
"indefinite-observable": "^1.0.1"
}
},
"@types/node": {
"version": "11.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz",
"integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ=="
},
"@types/prop-types": {
"version": "15.7.0",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.0.tgz",
"integrity": "sha512-eItQyV43bj4rR3JPV0Skpl1SncRCdziTEK9/v8VwXmV6d/qOUO8/EuWeHBbCZcsfSHfzI5UyMJLCSXtxxznyZg=="
"version": "15.7.1",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz",
"integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg=="
},
"@types/q": {
"version": "1.5.2",
@ -1132,18 +1160,18 @@
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw=="
},
"@types/react": {
"version": "16.8.13",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.13.tgz",
"integrity": "sha512-otJ4ntMuHGrvm67CdDJMAls4WqotmAmW0g3HmWi9LCjSWXrxoXY/nHXrtmMfvPEEmGFNm6NdgMsJmnfH820Qaw==",
"version": "16.8.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.18.tgz",
"integrity": "sha512-lUXdKzRqWR4FebR5tGHkLCqnvQJS4fdXKCBrNGGbglqZg2gpU+J82pMONevQODUotATs9fc9k66bx3/St8vReg==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-hP7vUaZMVSWKxo133P8U51U6UZ7+pbY+eAQb8+p6SZ2rB1rj3mOTDgTzhhi+R2SCB4S+sWekAAGoxdiZPG0ReQ==",
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.1.tgz",
"integrity": "sha512-1usq4DRUVBFnxc9KGJAlJO9EpQrLZGDDEC8wDOn2+2ODSyudYo8FiIzPDRaX/hfQjHqGeeoNaNdA2bj0l35hZQ==",
"requires": {
"@types/react": "*"
}
@ -2686,11 +2714,6 @@
"repeat-element": "^1.1.2"
}
},
"brcast": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz",
"integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg=="
},
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
@ -2960,11 +2983,6 @@
"supports-color": "^5.3.0"
}
},
"change-emitter": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@ -3616,11 +3634,6 @@
}
}
},
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
"clean-css": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
@ -3679,6 +3692,11 @@
"shallow-clone": "^0.1.2"
}
},
"clsx": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz",
"integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg=="
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -3859,6 +3877,11 @@
"date-now": "^0.1.4"
}
},
"console-polyfill": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/console-polyfill/-/console-polyfill-0.1.2.tgz",
"integrity": "sha1-ls/tUcr3gYn2mVcubxgnHcN8DjA="
},
"constants-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
@ -3879,6 +3902,15 @@
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"convert-css-length": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/convert-css-length/-/convert-css-length-1.0.2.tgz",
"integrity": "sha512-ecV7j3hXyXN1X2XfJBzhMR0o1Obv0v3nHmn0UiS3ACENrzbxE/EknkiunS/fCwQva0U62X1GChi8GaPh4oTlLg==",
"requires": {
"console-polyfill": "^0.1.2",
"parse-unit": "^1.0.1"
}
},
"convert-source-map": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
@ -4257,14 +4289,6 @@
"resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz",
"integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w="
},
"css-vendor": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz",
"integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=",
"requires": {
"is-in-browser": "^1.0.2"
}
},
"css-what": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
@ -4449,9 +4473,9 @@
}
},
"csstype": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.3.tgz",
"integrity": "sha512-rINUZXOkcBmoHWEyu7JdHu5JMzkGRoMX4ov9830WNgxf5UYxcBUO0QTKAqeJ5EZfSdlrcJYkC8WwfVW7JYi4yg=="
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.4.tgz",
"integrity": "sha512-lAJUJP3M6HxFXbqtGRc0iZrdyeN+WzOWeY0q/VnFzI+kqVrYIzC7bWlKqCW7oCIdzoPkvfp82EVvrTlQ8zsWQg=="
},
"cyclist": {
"version": "0.2.2",
@ -7993,14 +8017,6 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
},
"indefinite-observable": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz",
"integrity": "sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA==",
"requires": {
"symbol-observable": "1.2.0"
}
},
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
@ -8230,11 +8246,6 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"is-function": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz",
"integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU="
},
"is-generator-fn": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz",
@ -9195,148 +9206,242 @@
}
},
"jss": {
"version": "9.8.7",
"resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz",
"integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==",
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"symbol-observable": "^1.1.0",
"warning": "^3.0.0"
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-camel-case": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0-alpha.16.tgz",
"integrity": "sha512-nki+smHEsFyoZ0OlOYtaxVqcQA0ZHVJCE1slRnk+1TklbmxbBiO4TwITMTEaNIDv0U0Uyb0Z8wVgFgRwCCIFog==",
"requires": {
"@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3",
"jss": "10.0.0-alpha.16"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"loose-envify": "^1.0.0"
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-camel-case": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz",
"integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==",
"jss-plugin-compose": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-compose/-/jss-plugin-compose-10.0.0-alpha.16.tgz",
"integrity": "sha512-MeOc5RuDSqB3czoUFM32pBq370+sKKjG1K4aamVWpAUWpsphLi/YlotrFOkk/FCb2So1ga4W7/zrCc/50OeRAQ==",
"requires": {
"hyphenate-style-name": "^1.0.2"
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16",
"tiny-warning": "^1.0.2"
}
},
"jss-compose": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jss-compose/-/jss-compose-5.0.0.tgz",
"integrity": "sha512-YofRYuiA0+VbeOw0VjgkyO380sA4+TWDrW52nSluD9n+1FWOlDzNbgpZ/Sb3Y46+DcAbOS21W5jo6SAqUEiuwA==",
"jss-plugin-default-unit": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0-alpha.16.tgz",
"integrity": "sha512-jjGW4F/r9yKvoyUk22M8nWhdMfvoWzJw/oFO2cDRXCk2onnWFiRALfqeUsEDyocwdZbyVF9WhZbSHn4GL03kSw==",
"requires": {
"warning": "^3.0.0"
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"loose-envify": "^1.0.0"
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-default-unit": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz",
"integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg=="
"jss-plugin-expand": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-expand/-/jss-plugin-expand-10.0.0-alpha.16.tgz",
"integrity": "sha512-Q3m0PDWGojfcmWBCkegRJxonq2q9lI6ZfixoFgvTvi+b9zKza0KXkHBUzGjeFyM36U/WRWj43SC33dajcI9jAg==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16"
}
},
"jss-expand": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/jss-expand/-/jss-expand-5.3.0.tgz",
"integrity": "sha512-NiM4TbDVE0ykXSAw6dfFmB1LIqXP/jdd0ZMnlvlGgEMkMt+weJIl8Ynq1DsuBY9WwkNyzWktdqcEW2VN0RAtQg=="
"jss-plugin-extend": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-extend/-/jss-plugin-extend-10.0.0-alpha.16.tgz",
"integrity": "sha512-nJ8H5b/dBZlqaPYCLNmcaHRQgzSlnAwhZUcIo30s0IgvhTtN/TaiRtEbrJZjfXPzatTsnFoRwZzJqs8Sakev+A==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16",
"tiny-warning": "^1.0.2"
}
},
"jss-extend": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jss-extend/-/jss-extend-6.2.0.tgz",
"integrity": "sha512-YszrmcB6o9HOsKPszK7NeDBNNjVyiW864jfoiHoMlgMIg2qlxKw70axZHqgczXHDcoyi/0/ikP1XaHDPRvYtEA==",
"jss-plugin-global": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0-alpha.16.tgz",
"integrity": "sha512-B1mm2ZF9OEsWPmzkG5ZUXqV88smDqpc4unILLXhWVuj0U5JeT0DNitH+QbXFrSueDJzkWVfvqyckvWDR/0qeDg==",
"requires": {
"warning": "^3.0.0"
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"loose-envify": "^1.0.0"
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-global": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz",
"integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q=="
},
"jss-nested": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz",
"integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==",
"jss-plugin-nested": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0-alpha.16.tgz",
"integrity": "sha512-3l/MB6COnIpq4GOXQFae6UydoaIPa81UxhuBTEQuiAojgTeUla9L7nB3h18Q4zAhQQpjxaEsyppAKuEzIP7kPQ==",
"requires": {
"warning": "^3.0.0"
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16",
"tiny-warning": "^1.0.2"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"loose-envify": "^1.0.0"
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-preset-default": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-4.5.0.tgz",
"integrity": "sha512-qZbpRVtHT7hBPpZEBPFfafZKWmq3tA/An5RNqywDsZQGrlinIF/mGD9lmj6jGqu8GrED2SMHZ3pPKLmjCZoiaQ==",
"requires": {
"jss-camel-case": "^6.1.0",
"jss-compose": "^5.0.0",
"jss-default-unit": "^8.0.2",
"jss-expand": "^5.3.0",
"jss-extend": "^6.2.0",
"jss-global": "^3.0.0",
"jss-nested": "^6.0.1",
"jss-props-sort": "^6.0.0",
"jss-template": "^1.0.1",
"jss-vendor-prefixer": "^7.0.0"
}
},
"jss-props-sort": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz",
"integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g=="
"jss-plugin-props-sort": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0-alpha.16.tgz",
"integrity": "sha512-+Yn9nugHAH58nf/d43H2uxMvlCFPDgLKRSmKO4Q4m1IGYjMbHsWt1Rk2HfC9IiCanqcqpc8hstwtzf+HG7PWFQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16"
},
"dependencies": {
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-template": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/jss-template/-/jss-template-1.0.1.tgz",
"integrity": "sha512-m5BqEWha17fmIVXm1z8xbJhY6GFJxNB9H68GVnCWPyGYfxiAgY9WTQyvDAVj+pYRgrXSOfN5V1T4+SzN1sJTeg==",
"jss-plugin-rule-value-function": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0-alpha.16.tgz",
"integrity": "sha512-MQap9ne6ZGZH0NlpSQTMSm6QalBTF0hYpd2uaGQwam+GlT7IKeO+sTjd46I1WgO3kyOmwb0pIY6CnuLQGXKtSA==",
"requires": {
"warning": "^3.0.0"
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"loose-envify": "^1.0.0"
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-vendor-prefixer": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz",
"integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==",
"jss-plugin-rule-value-observable": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-observable/-/jss-plugin-rule-value-observable-10.0.0-alpha.16.tgz",
"integrity": "sha512-Gmj1sVKWM2KVZpG0Wn3Z+SArvskdXEtSCrww43g/OO+j8DN9O+UEV47tM/HYfdiyLICnvKHc2XGmhNz9LHcpNQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16",
"symbol-observable": "^1.2.0"
}
},
"jss-plugin-template": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-template/-/jss-plugin-template-10.0.0-alpha.16.tgz",
"integrity": "sha512-L1epTMTDINJPUZkFuyohCXQtJDTMj1CNTBv9ysqVyMc3qjkifAvPEws6XuoRSC9jy1ZvqDTWlxPfbmoJ2r6BWg==",
"requires": {
"css-vendor": "^0.3.8"
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-vendor-prefixer": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0-alpha.16.tgz",
"integrity": "sha512-70yJ6QE5dN8VlPUGKld5jK2SKyrteheEL/ismexpybIufunMs6iJgkhDndbOfv8ia13yZgUVqeakMdhRKYwK1A==",
"requires": {
"@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.1",
"jss": "10.0.0-alpha.16"
},
"dependencies": {
"css-vendor": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.2.tgz",
"integrity": "sha512-Xn5ZAlI00d8HaQ8/oQ8d+iBzSF//NCc77LPzsucM32X/R/yTqmXy6otVsAM0XleXk6HjPuXoVZwXsayky/fsFQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.0.2"
}
},
"jss": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
"requires": {
"@babel/runtime": "^7.3.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-preset-default": {
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-10.0.0-alpha.16.tgz",
"integrity": "sha512-YBq2XE4iJdl16klxfw0xTaKksfAIXSoC2kPZQ4dmw4n/KMFOz/A26eN30FwWixyObfDMKyZp94vwCKal7711IQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0-alpha.16",
"jss-plugin-camel-case": "10.0.0-alpha.16",
"jss-plugin-compose": "10.0.0-alpha.16",
"jss-plugin-default-unit": "10.0.0-alpha.16",
"jss-plugin-expand": "10.0.0-alpha.16",
"jss-plugin-extend": "10.0.0-alpha.16",
"jss-plugin-global": "10.0.0-alpha.16",
"jss-plugin-nested": "10.0.0-alpha.16",
"jss-plugin-props-sort": "10.0.0-alpha.16",
"jss-plugin-rule-value-function": "10.0.0-alpha.16",
"jss-plugin-rule-value-observable": "10.0.0-alpha.16",
"jss-plugin-template": "10.0.0-alpha.16",
"jss-plugin-vendor-prefixer": "10.0.0-alpha.16"
}
},
"jsx-ast-utils": {
@ -10455,6 +10560,11 @@
"json-parse-better-errors": "^1.0.1"
}
},
"parse-unit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-unit/-/parse-unit-1.0.1.tgz",
"integrity": "sha1-fhu21b7zh0wo45JSaiVBFwKR7s8="
},
"parse5": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
@ -13013,6 +13123,11 @@
}
}
},
"react-display-name": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.4.tgz",
"integrity": "sha512-zvU6iouW+SWwHTyThwxGICjJYCMZFk/6r/+jmOdC7ntQoPlS/Pqb81MkxaMf2bHTSq9TN3K3zX2/ayMW/jCtyA=="
},
"react-dom": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
@ -13054,22 +13169,17 @@
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
},
"react-jss": {
"version": "8.6.1",
"resolved": "https://registry.npmjs.org/react-jss/-/react-jss-8.6.1.tgz",
"integrity": "sha512-SH6XrJDJkAphp602J14JTy3puB2Zxz1FkM3bKVE8wON+va99jnUTKWnzGECb3NfIn9JPR5vHykge7K3/A747xQ==",
"requires": {
"hoist-non-react-statics": "^2.5.0",
"jss": "^9.7.0",
"jss-preset-default": "^4.3.0",
"version": "10.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/react-jss/-/react-jss-10.0.0-alpha.16.tgz",
"integrity": "sha512-nGIerGVDV9V6cpRXhkJZgoV0MsoJbKMdAiCoPzCDnsdR+om6zLyhQEvVHNtd0mB16dO+pzNaovhBvElhdj/3ug==",
"requires": {
"@babel/runtime": "^7.3.1",
"hoist-non-react-statics": "^3.2.0",
"jss": "10.0.0-alpha.16",
"jss-preset-default": "10.0.0-alpha.16",
"prop-types": "^15.6.0",
"theming": "^1.3.0"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
}
"theming": "^3.0.3",
"tiny-warning": "^1.0.2"
}
},
"react-lifecycles-compat": {
@ -13171,14 +13281,13 @@
}
},
"react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.0.1.tgz",
"integrity": "sha512-SsLcBYhO4afXJC9esL8XMxi/y0ZvEc7To0TvtrBELqzpjXQHPZOTxvuPh2/4EhYc0uSMfp2SExIxsyJ0pBdNzg==",
"requires": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
"prop-types": "^15.6.2"
}
},
"read-pkg": {
@ -13514,26 +13623,6 @@
"util.promisify": "^1.0.0"
}
},
"recompose": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz",
"integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==",
"requires": {
"@babel/runtime": "^7.0.0",
"change-emitter": "^0.1.2",
"fbjs": "^0.8.1",
"hoist-non-react-statics": "^2.3.1",
"react-lifecycles-compat": "^3.0.2",
"symbol-observable": "^1.0.4"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
}
}
},
"recursive-readdir": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
@ -15184,14 +15273,14 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
},
"theming": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/theming/-/theming-1.3.0.tgz",
"integrity": "sha512-ya5Ef7XDGbTPBv5ENTwrwkPUexrlPeiAg/EI9kdlUAZhNlRbCdhMKRgjNX1IcmsmiPcqDQZE6BpSaH+cr31FKw==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/theming/-/theming-3.2.0.tgz",
"integrity": "sha512-n0fSNYXkX63rcFBBeAthy14IcgPZLHp0OGkGZheaj64j7cBoP7INLd6+7HIXqWVjFn1M5cYSiZ1nszi+jo/Szg==",
"requires": {
"brcast": "^3.0.1",
"is-function": "^1.0.1",
"is-plain-object": "^2.0.1",
"prop-types": "^15.5.8"
"hoist-non-react-statics": "^3.3.0",
"prop-types": "^15.5.8",
"react-display-name": "^0.2.4",
"tiny-warning": "^1.0.2"
}
},
"throat": {

6
interface/package.json

@ -3,8 +3,8 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^3.9.3",
"@material-ui/icons": "^3.0.2",
"@material-ui/core": "^4.0.0",
"@material-ui/icons": "^4.0.0",
"compression-webpack-plugin": "^2.0.0",
"jwt-decode": "^2.2.0",
"moment": "^2.24.0",
@ -12,7 +12,7 @@
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-form-validator-core": "^0.6.2",
"react-jss": "^8.6.1",
"react-jss": "^10.0.0-alpha.16",
"react-material-ui-form-validator": "^2.0.7",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",

13
interface/src/App.js

@ -10,14 +10,12 @@ import orange from '@material-ui/core/colors/orange';
import red from '@material-ui/core/colors/red';
import green from '@material-ui/core/colors/green';
import JssProvider from 'react-jss/lib/JssProvider';
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import {
MuiThemeProvider,
createMuiTheme,
createGenerateClassName,
jssPreset,
createMuiTheme
} from '@material-ui/core/styles';
// Our theme
@ -35,20 +33,17 @@ const theme = createMuiTheme({
// JSS instance
const jss = create(jssPreset());
// Class name generator.
const generateClassName = createGenerateClassName();
class App extends Component {
render() {
return (
<JssProvider jss={jss} generateClassName={generateClassName}>
<StylesProvider jss={jss}>
<MuiThemeProvider theme={theme}>
<SnackbarNotification>
<CssBaseline />
<AppRouting />
</SnackbarNotification>
</MuiThemeProvider>
</JssProvider>
</StylesProvider>
)
}
}

4
interface/src/AppRouting.js

@ -14,7 +14,7 @@ import NTPConfiguration from './containers/NTPConfiguration';
import OTAConfiguration from './containers/OTAConfiguration';
import APConfiguration from './containers/APConfiguration';
import SignInPage from './containers/SignInPage';
import UserConfiguration from './containers/UserConfiguration';
import Security from './containers/Security';
class AppRouting extends Component {
@ -31,7 +31,7 @@ class AppRouting extends Component {
<AuthenticatedRoute exact path="/ap-configuration" component={APConfiguration} />
<AuthenticatedRoute exact path="/ntp-configuration" component={NTPConfiguration} />
<AuthenticatedRoute exact path="/ota-configuration" component={OTAConfiguration} />
<AuthenticatedRoute exact path="/user-configuration" component={UserConfiguration} />
<AuthenticatedRoute exact path="/security" component={Security} />
<Redirect to="/" />
</Switch>
</AuthenticationWrapper>

16
interface/src/components/MenuAppBar.js

@ -22,7 +22,7 @@ import SystemUpdateIcon from '@material-ui/icons/SystemUpdate';
import AccessTimeIcon from '@material-ui/icons/AccessTime';
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
import PeopleIcon from '@material-ui/icons/People';
import LockIcon from '@material-ui/icons/Lock';
import { APP_NAME } from '../constants/App';
import { withAuthenticationContext } from '../authentication/Context.js';
@ -112,31 +112,31 @@ class MenuAppBar extends React.Component {
<ListItemIcon>
<WifiIcon />
</ListItemIcon>
<ListItemText primary="WiFi Configuration" />
<ListItemText primary="WiFi Connection" />
</ListItem>
<ListItem button component={Link} to='/ap-configuration'>
<ListItemIcon>
<SettingsInputAntennaIcon />
</ListItemIcon>
<ListItemText primary="AP Configuration" />
<ListItemText primary="Access Point" />
</ListItem>
<ListItem button component={Link} to='/ntp-configuration'>
<ListItemIcon>
<AccessTimeIcon />
</ListItemIcon>
<ListItemText primary="NTP Configuration" />
<ListItemText primary="Network Time" />
</ListItem>
<ListItem button component={Link} to='/ota-configuration'>
<ListItemIcon>
<SystemUpdateIcon />
</ListItemIcon>
<ListItemText primary="OTA Configuration" />
<ListItemText primary="OTA Updates" />
</ListItem>
<ListItem button component={Link} to='/user-configuration'>
<ListItem button component={Link} to='/security'>
<ListItemIcon>
<PeopleIcon />
<LockIcon />
</ListItemIcon>
<ListItemText primary="User Configuration" />
<ListItemText primary="Security" />
</ListItem>
<Divider />
<ListItem button onClick={authenticationContext.signOut}>

2
interface/src/components/SectionContent.js

@ -16,7 +16,7 @@ function SectionContent(props) {
const { children, classes, title } = props;
return (
<Paper className={classes.content}>
<Typography variant="h4">
<Typography variant="h6">
{title}
</Typography>
{children}

79
interface/src/containers/APStatus.js

@ -7,17 +7,18 @@ import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import Divider from '@material-ui/core/Divider';
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
import DeviceHubIcon from '@material-ui/icons/DeviceHub';
import ComputerIcon from '@material-ui/icons/Computer';
import {restComponent} from '../components/RestComponent';
import { restComponent } from '../components/RestComponent';
import SectionContent from '../components/SectionContent'
import * as Highlight from '../constants/Highlight';
import { AP_STATUS_ENDPOINT } from '../constants/Endpoints';
import { AP_STATUS_ENDPOINT } from '../constants/Endpoints';
const styles = theme => ({
["apStatus_" + Highlight.SUCCESS]: {
@ -42,40 +43,48 @@ class APStatus extends Component {
this.props.loadData();
}
apStatusHighlight(data){
apStatusHighlight(data) {
return data.active ? Highlight.SUCCESS : Highlight.IDLE;
}
apStatus(data){
apStatus(data) {
return data.active ? "Active" : "Inactive";
}
createListItems(data, classes){
createListItems(data, classes) {
return (
<Fragment>
<ListItem>
<Avatar className={classes["apStatus_" + this.apStatusHighlight(data)]}>
<SettingsInputAntennaIcon />
</Avatar>
<ListItemAvatar>
<Avatar className={classes["apStatus_" + this.apStatusHighlight(data)]}>
<SettingsInputAntennaIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Status" secondary={this.apStatus(data)} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>IP</Avatar>
<ListItemAvatar>
<Avatar>IP</Avatar>
</ListItemAvatar>
<ListItemText primary="IP Address" secondary={data.ip_address} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<DeviceHubIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="MAC Address" secondary={data.mac_address} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<ComputerIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<ComputerIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="AP Clients" secondary={data.station_num} />
</ListItem>
<Divider variant="inset" component="li" />
@ -83,8 +92,8 @@ class APStatus extends Component {
);
}
renderAPStatus(data, classes){
return (
renderAPStatus(data, classes) {
return (
<div>
<List>
<Fragment>
@ -99,30 +108,30 @@ class APStatus extends Component {
}
render() {
const { data, fetched, errorMessage, classes } = this.props;
const { data, fetched, errorMessage, classes } = this.props;
return (
<SectionContent title="AP Status">
{
!fetched ?
<div>
<LinearProgress className={classes.fetching}/>
<Typography variant="h4" className={classes.fetching}>
Loading...
!fetched ?
<div>
<LinearProgress className={classes.fetching} />
<Typography variant="h4" className={classes.fetching}>
Loading...
</Typography>
</div>
:
data ? this.renderAPStatus(data, classes)
:
<div>
<Typography variant="h4" className={classes.fetching}>
{errorMessage}
</Typography>
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</div>
:
data ? this.renderAPStatus(data, classes)
:
<div>
<Typography variant="h4" className={classes.fetching}>
{errorMessage}
</Typography>
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
}
</div>
}
</SectionContent>
)
}

27
interface/src/containers/ManageUsers.js

@ -1,30 +1,27 @@
import React, { Component } from 'react';
import { USERS_ENDPOINT } from '../constants/Endpoints';
import {restComponent} from '../components/RestComponent';
import SectionContent from '../components/SectionContent';
import { USERS_ENDPOINT } from '../constants/Endpoints';
import { restComponent } from '../components/RestComponent';
import ManageUsersForm from '../forms/ManageUsersForm';
class ManageUsers extends Component {
componentDidMount() {
this.props.loadData();
this.props.loadData();
}
render() {
const { data, fetched, errorMessage } = this.props;
return (
<SectionContent title="Manage Users">
<ManageUsersForm
users={data}
usersFetched={fetched}
errorMessage={errorMessage}
onSubmit={this.props.saveData}
onReset={this.props.loadData}
handleValueChange={this.props.handleValueChange}
handleCheckboxChange={this.props.handleCheckboxChange}
/>
</SectionContent>
<ManageUsersForm
userData={data}
userDataFetched={fetched}
errorMessage={errorMessage}
onSubmit={this.props.saveData}
onReset={this.props.loadData}
setData={this.props.setData}
handleValueChange={this.props.handleValueChange}
/>
)
}

98
interface/src/containers/NTPStatus.js

@ -6,6 +6,7 @@ import LinearProgress from '@material-ui/core/LinearProgress';
import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import ListItemText from '@material-ui/core/ListItemText';
import Avatar from '@material-ui/core/Avatar';
import Divider from '@material-ui/core/Divider';
@ -16,10 +17,10 @@ import TimerIcon from '@material-ui/icons/Timer';
import UpdateIcon from '@material-ui/icons/Update';
import AvTimerIcon from '@material-ui/icons/AvTimer';
import { isSynchronized, ntpStatusHighlight, ntpStatus } from '../constants/NTPStatus';
import { isSynchronized, ntpStatusHighlight, ntpStatus } from '../constants/NTPStatus';
import * as Highlight from '../constants/Highlight';
import { unixTimeToTimeAndDate } from '../constants/TimeFormat';
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
import { restComponent } from '../components/RestComponent';
import SectionContent from '../components/SectionContent';
@ -51,52 +52,61 @@ class NTPStatus extends Component {
this.props.loadData();
}
createListItems(data, classes){
createListItems(data, classes) {
return (
<Fragment>
<ListItem>
<Avatar className={classes["ntpStatus_" + ntpStatusHighlight(data)]}>
<UpdateIcon />
</Avatar>
<ListItem >
<ListItemAvatar>
<Avatar className={classes["ntpStatus_" + ntpStatusHighlight(data)]}>
<UpdateIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Status" secondary={ntpStatus(data)} />
</ListItem>
<Divider variant="inset" component="li" />
{ isSynchronized(data) &&
{isSynchronized(data) &&
<Fragment>
<ListItem>
<Avatar>
<AccessTimeIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<AccessTimeIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Time Now" secondary={unixTimeToTimeAndDate(data.now)} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<SwapVerticalCircleIcon />
</Avatar>
<ListItemText primary="Last Sync" secondary={data.last_sync > 0 ? unixTimeToTimeAndDate(data.last_sync) : "never" } />
<ListItemAvatar>
<Avatar>
<SwapVerticalCircleIcon />
</Avatar></ListItemAvatar>
<ListItemText primary="Last Sync" secondary={data.last_sync > 0 ? unixTimeToTimeAndDate(data.last_sync) : "never"} />
</ListItem>
<Divider variant="inset" component="li" />
</Fragment>
}
<ListItem>
<Avatar>
<DNSIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<DNSIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="NTP Server" secondary={data.server} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<TimerIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<TimerIcon />
</Avatar></ListItemAvatar>
<ListItemText primary="Sync Interval" secondary={moment.duration(data.interval, 'seconds').humanize()} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<AvTimerIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<AvTimerIcon />
</Avatar></ListItemAvatar>
<ListItemText primary="Uptime" secondary={moment.duration(data.uptime, 'seconds').humanize()} />
</ListItem>
<Divider variant="inset" component="li" />
@ -104,8 +114,8 @@ class NTPStatus extends Component {
);
}
renderNTPStatus(data, classes){
return (
renderNTPStatus(data, classes) {
return (
<div>
<List>
{this.createListItems(data, classes)}
@ -118,30 +128,30 @@ class NTPStatus extends Component {
}
render() {
const { data, fetched, errorMessage, classes } = this.props;
const { data, fetched, errorMessage, classes } = this.props;
return (
<SectionContent title="NTP Status">
{
!fetched ?
<div>
<LinearProgress className={classes.fetching}/>
<Typography variant="h4" className={classes.fetching}>
Loading...
!fetched ?
<div>
<LinearProgress className={classes.fetching} />
<Typography variant="h4" className={classes.fetching}>
Loading...
</Typography>
</div>
:
data ? this.renderNTPStatus(data, classes)
:
<div>
<Typography variant="h4" className={classes.fetching}>
{errorMessage}
</Typography>
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</div>
:
data ? this.renderNTPStatus(data, classes)
:
<div>
<Typography variant="h4" className={classes.fetching}>
{errorMessage}
</Typography>
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
}
</div>
}
</SectionContent>
)
}

6
interface/src/containers/UserConfiguration.js → interface/src/containers/Security.js

@ -2,14 +2,14 @@ import React, { Component } from 'react';
import MenuAppBar from '../components/MenuAppBar';
import ManageUsers from './ManageUsers';
class UserConfiguration extends Component {
class Security extends Component {
render() {
return (
<MenuAppBar sectionTitle="User Configuration">
<MenuAppBar sectionTitle="Security">
<ManageUsers />
</MenuAppBar>
)
}
}
export default UserConfiguration
export default Security

49
interface/src/containers/WiFiStatus.js

@ -8,6 +8,7 @@ import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import Divider from '@material-ui/core/Divider';
@ -63,9 +64,11 @@ class WiFiStatus extends Component {
return (
<Fragment>
<ListItem>
<Avatar className={classes["wifiStatus_" + connectionStatusHighlight(data)]}>
<WifiIcon />
</Avatar>
<ListItemAvatar>
<Avatar className={classes["wifiStatus_" + connectionStatusHighlight(data)]}>
<WifiIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Connection Status" secondary={connectionStatus(data)} />
</ListItem>
<Divider variant="inset" component="li" />
@ -73,40 +76,52 @@ class WiFiStatus extends Component {
isConnected(data) &&
<Fragment>
<ListItem>
<Avatar>
<SettingsInputAntennaIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<SettingsInputAntennaIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="SSID" secondary={data.ssid} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>IP</Avatar>
<ListItemAvatar>
<Avatar>IP</Avatar>
</ListItemAvatar>
<ListItemText primary="IP Address" secondary={data.local_ip} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<DeviceHubIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="MAC Address" secondary={data.mac_address} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>#</Avatar>
<ListItemAvatar>
<Avatar>#</Avatar>
</ListItemAvatar>
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<SettingsInputComponentIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<SettingsInputComponentIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Gateway IP" secondary={data.gateway_ip ? data.gateway_ip : "none"} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Avatar>
<DNSIcon />
</Avatar>
<ListItemAvatar>
<Avatar>
<DNSIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(data)} />
</ListItem>
<Divider variant="inset" component="li" />

226
interface/src/forms/ManageUsersForm.js

@ -1,23 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ValidatorForm } from 'react-material-ui-form-validator';
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import LinearProgress from '@material-ui/core/LinearProgress';
import Typography from '@material-ui/core/Typography';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableFooter from '@material-ui/core/TableFooter';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import IconButton from '@material-ui/core/IconButton';
import Chip from '@material-ui/core/Chip';
import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete';
import Chip from '@material-ui/core/Chip';
import IconButton from '@material-ui/core/IconButton';
import SectionContent from '../components/SectionContent';
import UserForm from './UserForm';
const styles = theme => ({
loadingSettings: {
@ -43,95 +46,198 @@ const styles = theme => ({
margin: theme.spacing.unit,
},
table: {
'& td, & th': {padding: theme.spacing.unit}
}
'& td, & th': { padding: theme.spacing.unit }
},
actions: {
color: theme.palette.text.secondary,
}
});
function compareUsers(a, b) {
if (a.username < b.username) {
return -1;
}
if (a.username > b.username) {
return 1;
}
return 0;
}
class ManageUsersForm extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
createUser = () => {
this.setState({
creating: true,
user: {
username: "",
password: "",
roles: []
}
});
};
uniqueUsername = username => {
return !this.props.userData.users.find(u => u.username === username);
}
usersValid = username => {
return !!this.props.userData.users.find(u => u.roles.includes("admin"));
}
startEditingUser = user => {
this.setState({
creating: false,
user
});
};
cancelEditingUser = () => {
this.setState({
user: undefined
});
}
sortedUsers(users) {
return users.sort(compareUsers);
}
doneEditingUser = () => {
const { user } = this.state;
const { userData } = this.props;
let { users } = userData;
users = users.filter(u => u.username !== user.username);
users.push(user);
this.props.setData({ ...userData, users });
this.setState({
user: undefined
});
};
handleUserValueChange = name => event => {
const { user } = this.state;
if (user) {
this.setState({
user: {
...user, [name]: event.target.value
}
});
}
};
render() {
const { classes, users, usersFetched, errorMessage, onSubmit, onReset } = this.props;
const { classes, userData, userDataFetched, errorMessage, onSubmit, onReset, handleValueChange } = this.props;
const { user, creating } = this.state;
return (
<div>
<SectionContent title="Manage Users">
{
!usersFetched ?
!userDataFetched ?
<div className={classes.loadingSettings}>
<LinearProgress className={classes.loadingSettingsDetails} />
<Typography variant="h4" className={classes.loadingSettingsDetails}>
Loading...
</Typography>
</div>
: users ?
<ValidatorForm onSubmit={onSubmit}>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell>Username</TableCell>
<TableCell>Password</TableCell>
<TableCell align="center">Role(s)</TableCell>
<TableCell align="center">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.users.map(user => (
<TableRow key={user.username}>
<TableCell component="th" scope="row">
{user.username}
</TableCell>
<TableCell>{user.password}</TableCell>
<TableCell align="center">
<Chip label={user.role} className={classes.chip} />
:
userData ?
user ?
<UserForm
user={user}
creating={creating}
roles={userData.roles}
onDoneEditing={this.doneEditingUser}
onCancelEditing={this.cancelEditingUser}
handleValueChange={this.handleUserValueChange}
uniqueUsername={this.uniqueUsername}
/>
:
<ValidatorForm onSubmit={onSubmit}>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell>Username</TableCell>
<TableCell align="center">Role(s)</TableCell>
<TableCell align="center">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.sortedUsers(userData.users).map(user => (
<TableRow key={user.username}>
<TableCell component="th" scope="row">
{user.username}
</TableCell>
<TableCell align="center">
{
user.roles.map(role => (
<Chip label={role} className={classes.chip} />
))
}
</TableCell>
<TableCell align="center">
<IconButton aria-label="Delete">
<DeleteIcon />
</IconButton>
<IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}>
<EditIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>
{
!this.usersValid() &&
<Typography variant="body1" color="error">
You must have at least one admin user configured.
</Typography>
}
</TableCell>
<TableCell align="center">
<IconButton aria-label="Delete">
<DeleteIcon />
</IconButton>
<IconButton aria-label="Edit">
<EditIcon />
</IconButton>
<Button variant="contained" color="secondary" className={classes.button} onClick={this.createUser}>
Add User
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Button variant="contained" color="primary" className={classes.button} type="submit">
Save
</Button>
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
Reset
</Button>
</ValidatorForm>
</TableFooter>
</Table>
<Button variant="contained" color="primary" className={classes.button} type="submit">
Save
</Button>
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
Reset
</Button>
</ValidatorForm>
:
<div className={classes.loadingSettings}>
<SectionContent title="Manage Users">
<Typography variant="h4" className={classes.loadingSettingsDetails}>
{errorMessage}
</Typography>
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
Reset
</Button>
</div>
</Button>
</SectionContent>
}
</div>
</SectionContent>
);
}
}
ManageUsersForm.propTypes = {
classes: PropTypes.object.isRequired,
users: PropTypes.object,
usersFetched: PropTypes.bool.isRequired,
userData: PropTypes.object,
userDataFetched: PropTypes.bool.isRequired,
errorMessage: PropTypes.string,
onSubmit: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
handleValueChange: PropTypes.func.isRequired,
handleCheckboxChange: PropTypes.func.isRequired,
setData: PropTypes.func.isRequired,
handleValueChange: PropTypes.func.isRequired
};
export default withStyles(styles)(ManageUsersForm);

112
interface/src/forms/UserForm.js

@ -0,0 +1,112 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import PasswordValidator from '../components/PasswordValidator';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import Chip from '@material-ui/core/Chip';
const styles = theme => ({
textField: {
width: "100%"
},
checkboxControl: {
width: "100%"
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
marginRight: theme.spacing.unit,
},
button: {
marginRight: theme.spacing.unit * 2,
marginTop: theme.spacing.unit * 2,
}
});
class UserForm extends React.Component {
componentWillMount() {
ValidatorForm.addValidationRule('uniqueUsername', this.props.uniqueUsername);
}
render() {
const { classes, user, roles, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props;
return (
<ValidatorForm onSubmit={onDoneEditing}>
<TextValidator
validators={creating ? ['required', 'uniqueUsername', 'matchRegexp:^[a-zA-Z0-9_\\.]{1,24}$'] : []}
errorMessages={creating ? ['Username is required', "That username already exists", "Must be 1-24 characters: alpha numberic, '_' or '.'"] : []}
name="username"
label="Username"
className={classes.textField}
value={user.username}
disabled={!creating}
onChange={handleValueChange('username')}
margin="normal"
/>
<PasswordValidator
validators={['required', 'matchRegexp:^.{0,64}$']}
errorMessages={['Password is required', 'Password must be 64 characters or less']}
name="password"
label="Password"
className={classes.textField}
value={user.password}
onChange={handleValueChange('password')}
margin="normal"
/>
<FormControl className={classes.textField}>
<InputLabel htmlFor="roles">Roles</InputLabel>
<Select
multiple
value={user.roles}
onChange={handleValueChange('roles')}
input={<Input id="roles" />}
renderValue={selected => (
<div className={classes.chips}>
{selected.map(value => (
<Chip key={value} label={value} className={classes.chip} />
))}
</div>
)}
>
{roles.map(name => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
<Button variant="contained" color="primary" className={classes.button} type="submit">
Save
</Button>
<Button variant="contained" color="secondary" className={classes.button} onClick={onCancelEditing}>
Back
</Button>
</ValidatorForm>
);
}
}
UserForm.propTypes = {
classes: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
creating: PropTypes.bool.isRequired,
roles: PropTypes.array.isRequired,
onDoneEditing: PropTypes.func.isRequired,
onCancelEditing: PropTypes.func.isRequired,
uniqueUsername: PropTypes.func.isRequired,
handleValueChange: PropTypes.func.isRequired
};
export default withStyles(styles)(UserForm);

7
platformio.ini

@ -7,10 +7,9 @@
;
; Please visit documentation for the other options and examples
; http://docs.platformio.org/page/projectconf.html
[env:esp12e]
platform = espressif8266
board = esp12e
board_build.f_cpu = 160000000L
[env:node32s]
platform = espressif32
board = node32s
framework = arduino
;upload_flags = --port=8266 --auth=esp-react

38
src/SecurityManager.cpp

@ -1,9 +1,6 @@
#include <SecurityManager.h>
SecurityManager::SecurityManager(AsyncWebServer* server, FS* fs) : SettingsPersistence(fs, SECURITY_SETTINGS_FILE) {
server->on(USERS_PATH, HTTP_GET, std::bind(&SecurityManager::fetchUsers, this, std::placeholders::_1));
}
SecurityManager::SecurityManager(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, USERS_PATH, SECURITY_SETTINGS_FILE) {}
SecurityManager::~SecurityManager() {}
void SecurityManager::readFromJsonObject(JsonObject& root) {
@ -22,16 +19,19 @@ void SecurityManager::readFromJsonObject(JsonObject& root) {
// users
_users.clear();
if (root["users"].is<JsonArray>()) {
JsonArray users = root["users"];
for (JsonVariant user : users) {
String username = user["username"];
String password = user["password"];
String role = user["role"];
_users.push_back(User(username, password, role));
for (JsonVariant user : root["users"].as<JsonArray>()) {
std::list<String> roles;
if (user["roles"].is<JsonArray>()) {
for (JsonVariant role : user["roles"].as<JsonArray>()) {
roles.push_back(role.as<String>());
}
}
_users.push_back(User(user["username"], user["password"], roles));
}
}
}
void SecurityManager::writeToJsonObject(JsonObject& root) {
// secret
root["jwt_secret"] = _jwtSecret;
@ -48,18 +48,13 @@ void SecurityManager::writeToJsonObject(JsonObject& root) {
JsonObject user = users.createNestedObject();
user["username"] = _user.getUsername();
user["password"] = _user.getPassword();
user["role"] = _user.getRole();
JsonArray roles = user.createNestedArray("roles");
for (String _role : _user.getRoles()){
roles.add(_role);
}
}
}
void SecurityManager::fetchUsers(AsyncWebServerRequest *request) {
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE);
JsonObject jsonObject = response->getRoot();
writeToJsonObject(jsonObject);
response->setLength();
request->send(response);
}
void SecurityManager::begin() {
// read config
readFromFS();
@ -105,7 +100,10 @@ Authentication SecurityManager::authenticate(String username, String password) {
inline void populateJWTPayload(JsonObject &payload, User *user) {
payload["username"] = user->getUsername();
payload["role"] = user->getRole();
JsonArray roles = payload.createNestedArray("roles");
for (String _role : user->getRoles()){
roles.add(_role);
}
}
boolean SecurityManager::validatePayload(JsonObject &parsedPayload, User *user) {

10
src/SecurityManager.h

@ -28,17 +28,17 @@ class User {
private:
String _username;
String _password;
String _role;
std::list<String> _roles;
public:
User(String username, String password, String role): _username(username), _password(password), _role(role) {}
User(String username, String password, std::list<String> roles): _username(username), _password(password), _roles(roles) {}
String getUsername() {
return _username;
}
String getPassword() {
return _password;
}
String getRole() {
return _role;
std::list<String> getRoles() {
return _roles;
}
};
@ -62,7 +62,7 @@ class Authentication {
}
};
class SecurityManager : public SettingsPersistence {
class SecurityManager : public SettingsService {
public:

Loading…
Cancel
Save