diff options
author | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-01-06 18:36:16 +0300 |
---|---|---|
committer | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-01-31 01:33:12 +0300 |
commit | 4b0fc1dbb3f60a485d3ba7ec27d7654a8ea0d382 (patch) | |
tree | 0b2c344755c9c0c58681b95c4d452d661267fbda | |
parent | 6109113cc9e7e481b6f37b25c329ac64fcb7dbfc (diff) | |
download | webui-vue-4b0fc1dbb3f60a485d3ba7ec27d7654a8ea0d382.tar.xz |
Update local user layout and styles
Resubmitting after reverted–original commit here
https://gerrit.openbmc-project.xyz/c/openbmc/webui-vue/+/28790
- Add BVConfig plugin to modify boostrap component
defaults
- Add vuelidate
- Add package and basic validations to user form
- Add all user form validations
- Add checks for edit user
- Create VuelidateMixin for shared methods
- Update Login to use Vuelidate
Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Signed-off-by: Derick Montague <derick.montague@ibm.com>
Change-Id: Ib50ee4d1fb5f14637c9460e77f0682869a86ac8a
-rw-r--r-- | package-lock.json | 203 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/assets/styles/_form-components.scss | 29 | ||||
-rw-r--r-- | src/assets/styles/_modal.scss | 7 | ||||
-rw-r--r-- | src/assets/styles/_obmc-custom.scss | 6 | ||||
-rw-r--r-- | src/assets/styles/_table.scss | 21 | ||||
-rw-r--r-- | src/components/Mixins/VuelidateMixin.js | 10 | ||||
-rw-r--r-- | src/main.js | 10 | ||||
-rw-r--r-- | src/store/modules/Authentication/AuthenticanStore.js | 17 | ||||
-rw-r--r-- | src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue | 51 | ||||
-rw-r--r-- | src/views/AccessControl/LocalUserManagement/ModalUser.vue | 272 | ||||
-rw-r--r-- | src/views/AccessControl/LocalUserManagement/TableRoles.vue | 2 | ||||
-rw-r--r-- | src/views/Login/Login.vue | 101 |
13 files changed, 540 insertions, 190 deletions
diff --git a/package-lock.json b/package-lock.json index b5906307..3c0106b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6431,25 +6431,29 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, "requires": { @@ -6459,13 +6463,15 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, "requires": { @@ -6475,37 +6481,43 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, "requires": { @@ -6514,25 +6526,29 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, "requires": { @@ -6541,13 +6557,15 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, "requires": { @@ -6563,7 +6581,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, "requires": { @@ -6577,13 +6596,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, "requires": { @@ -6592,7 +6613,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, "requires": { @@ -6601,7 +6623,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, "requires": { @@ -6611,19 +6634,22 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, "requires": { @@ -6632,13 +6658,15 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, "requires": { @@ -6647,13 +6675,15 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, "requires": { @@ -6663,7 +6693,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, "requires": { @@ -6672,7 +6703,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, "requires": { @@ -6681,13 +6713,15 @@ }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, "requires": { @@ -6698,7 +6732,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, "requires": { @@ -6716,7 +6751,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -6726,13 +6762,15 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, "requires": { @@ -6742,7 +6780,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, "requires": { @@ -6754,19 +6793,22 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, "requires": { @@ -6775,19 +6817,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, "requires": { @@ -6797,19 +6842,22 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, "requires": { @@ -6821,7 +6869,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -6829,7 +6878,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, "requires": { @@ -6844,7 +6894,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, "requires": { @@ -6853,43 +6904,50 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, "requires": { @@ -6900,7 +6958,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -6909,7 +6968,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, "requires": { @@ -6918,13 +6978,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, "requires": { @@ -6939,13 +7001,15 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, "requires": { @@ -6954,13 +7018,15 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true } @@ -14963,6 +15029,11 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vuelidate": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/vuelidate/-/vuelidate-0.7.5.tgz", + "integrity": "sha512-GAAG8QAFVp7BFeQlNaThpTbimq3+HypBPNwdkCkHZZeVaD5zmXXfhp357dcUJXHXTZjSln0PvP6wiwLZXkFTwg==" + }, "vuex": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.0.1.tgz", diff --git a/package.json b/package.json index 565f09a1..2cefe6f5 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "vue": "2.6.10", "vue-date-fns": "^1.1.0", "vue-router": "3.1.3", + "vuelidate": "^0.7.4", "vuex": "3.0.1" }, "devDependencies": { diff --git a/src/assets/styles/_form-components.scss b/src/assets/styles/_form-components.scss new file mode 100644 index 00000000..41b291b2 --- /dev/null +++ b/src/assets/styles/_form-components.scss @@ -0,0 +1,29 @@ +.form-text { + margin-top: -$spacer / 4; + margin-bottom: $spacer / 2; + color: $gray-800; +} + +.col-form-label { + color: $gray-800; + font-size: 14px; +} + +.form-group { + margin-bottom: $spacer * 2; +} + +.custom-select, +.custom-control-label, +.form-control { + //important needed to override validation colors on radio labels + color: $gray-900!important; + border-color: $gray-400!important; + &::before { + border-color: $primary; + } + &.is-invalid, + &:invalid { + border-bottom: 2px solid $danger!important; + } +}
\ No newline at end of file diff --git a/src/assets/styles/_modal.scss b/src/assets/styles/_modal.scss new file mode 100644 index 00000000..b20327e7 --- /dev/null +++ b/src/assets/styles/_modal.scss @@ -0,0 +1,7 @@ +.modal-header { + .close { + font-weight: normal; + color: $gray-900; + opacity: 1; + } +}
\ No newline at end of file diff --git a/src/assets/styles/_obmc-custom.scss b/src/assets/styles/_obmc-custom.scss index e87e01bb..d20e64e4 100644 --- a/src/assets/styles/_obmc-custom.scss +++ b/src/assets/styles/_obmc-custom.scss @@ -1,4 +1,5 @@ $enable-rounded: false; +$enable-validation-icons: false; // Required @import "~bootstrap/scss/functions"; @@ -52,4 +53,7 @@ $colors: map-remove($theme-colors, "light", "dark"); @import "~bootstrap-vue/src/index.scss"; -@import "./buttons";
\ No newline at end of file +@import "./buttons"; +@import "./form-components"; +@import "./modal"; +@import "./table";
\ No newline at end of file diff --git a/src/assets/styles/_table.scss b/src/assets/styles/_table.scss new file mode 100644 index 00000000..ff1ed302 --- /dev/null +++ b/src/assets/styles/_table.scss @@ -0,0 +1,21 @@ +.table-light { + td { + border-top: none; + border-bottom: 1px solid $gray-300; + } +} + +.thead-light.thead-light { + th { + border: none; + color: $gray-900; + } +} + +.table-cell__actions { + text-align: right; + .btn { + padding-top: 0; + padding-bottom: 0; + } +} diff --git a/src/components/Mixins/VuelidateMixin.js b/src/components/Mixins/VuelidateMixin.js new file mode 100644 index 00000000..8c617791 --- /dev/null +++ b/src/components/Mixins/VuelidateMixin.js @@ -0,0 +1,10 @@ +const VuelidateMixin = { + methods: { + getValidationState(model) { + const { $dirty, $error } = model; + return $dirty ? !$error : null; + } + } +}; + +export default VuelidateMixin; diff --git a/src/main.js b/src/main.js index b69c6591..e32a56be 100644 --- a/src/main.js +++ b/src/main.js @@ -7,6 +7,7 @@ import { AlertPlugin, BadgePlugin, ButtonPlugin, + BVConfigPlugin, CollapsePlugin, FormPlugin, FormCheckboxPlugin, @@ -22,12 +23,20 @@ import { NavPlugin, TablePlugin } from 'bootstrap-vue'; +import Vuelidate from 'vuelidate'; Vue.filter('date', dateFilter); Vue.use(AlertPlugin); Vue.use(BadgePlugin); Vue.use(ButtonPlugin); +Vue.use(BVConfigPlugin, { + BFormText: { textVariant: 'black' }, + BTable: { + headVariant: 'light', + footVariant: 'light' + } +}); Vue.use(CollapsePlugin); Vue.use(FormPlugin); Vue.use(FormCheckboxPlugin); @@ -43,6 +52,7 @@ Vue.use(ModalPlugin); Vue.use(NavbarPlugin); Vue.use(NavPlugin); Vue.use(TablePlugin); +Vue.use(Vuelidate); new Vue({ router, diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js index 3a554b6b..8d8898ef 100644 --- a/src/store/modules/Authentication/AuthenticanStore.js +++ b/src/store/modules/Authentication/AuthenticanStore.js @@ -4,35 +4,28 @@ import Cookies from 'js-cookie'; const AuthenticationStore = { namespaced: true, state: { - status: '', + authError: false, cookie: Cookies.get('XSRF-TOKEN') }, getters: { - authStatus: state => state.status, + authError: state => state.authError, isLoggedIn: state => !!state.cookie }, mutations: { - authRequest(state) { - state.status = 'processing'; - }, authSuccess(state) { - state.status = 'authenticated'; + state.authError = false; state.cookie = Cookies.get('XSRF-TOKEN'); }, authError(state) { - state.status = 'error'; - }, - authReset(state) { - state.status = ''; + state.authError = true; }, logout(state) { - state.status = ''; + state.authError = false; Cookies.remove('XSRF-TOKEN'); } }, actions: { login({ commit }, auth) { - commit('authRequest'); return api .post('/login', { data: auth }) .then(() => commit('authSuccess')) diff --git a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue index 6ca43f32..b81dba67 100644 --- a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue +++ b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue @@ -2,8 +2,8 @@ <b-container class="ml-0"> <PageTitle /> <b-row> - <b-col lg="10"> - <b-button @click="initModalSettings" variant="link"> + <b-col lg="10" class="text-right"> + <b-button variant="link" @click="initModalSettings"> Account policy settings <icon-settings /> </b-button> @@ -15,11 +15,11 @@ </b-row> <b-row> <b-col lg="10"> - <b-table bordered show-empty head-variant="dark" :items="tableItems"> - <template v-slot:head(actions)="data"></template> + <b-table show-empty :fields="fields" :items="tableItems"> <template v-slot:cell(actions)="data"> <b-button aria-label="Edit user" + title="Edit user" variant="link" :disabled="!data.value.edit" @click="initModalUser(data.item)" @@ -28,6 +28,7 @@ </b-button> <b-button aria-label="Delete user" + title="Delete user" variant="link" :disabled="!data.value.delete" @click="initModalDelete(data.item)" @@ -42,6 +43,7 @@ <b-col lg="8"> <b-button v-b-toggle.collapse-role-table variant="link" class="mt-3"> View privilege role descriptions + <icon-chevron /> </b-button> <b-collapse id="collapse-role-table" class="mt-3"> <table-roles /> @@ -49,12 +51,8 @@ </b-col> </b-row> <!-- Modals --> - <modal-settings v-bind:settings="settings"></modal-settings> - <modal-user - v-bind:user="activeUser" - @ok="saveUser" - @hidden="clearActiveUser" - ></modal-user> + <modal-settings :settings="settings"></modal-settings> + <modal-user :user="activeUser" @ok="saveUser"></modal-user> </b-container> </template> @@ -63,6 +61,7 @@ import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; import IconEdit from '@carbon/icons-vue/es/edit/20'; import IconAdd from '@carbon/icons-vue/es/add--alt/20'; import IconSettings from '@carbon/icons-vue/es/settings/20'; +import IconChevron from '@carbon/icons-vue/es/chevron--up/20'; import TableRoles from './TableRoles'; import ModalUser from './ModalUser'; @@ -73,6 +72,7 @@ export default { name: 'local-users', components: { IconAdd, + IconChevron, IconEdit, IconSettings, IconTrashcan, @@ -84,7 +84,17 @@ export default { data() { return { activeUser: null, - settings: null + settings: null, + fields: [ + 'username', + 'privilege', + 'status', + { + key: 'actions', + label: '', + tdClass: 'table-cell__actions' + } + ] }; }, created() { @@ -108,7 +118,8 @@ export default { actions: { edit: true, delete: user.UserName === 'root' ? false : true - } + }, + ...user }; }); } @@ -143,18 +154,15 @@ export default { // fetch settings then show modal } }, - saveUser({ newUser, form }) { - if (newUser) { - this.$store.dispatch('localUsers/createUser', form); + saveUser({ isNewUser, userData }) { + if (isNewUser) { + this.$store.dispatch('localUsers/createUser', userData); } else { - this.$store.dispatch('localUsers/updateUser', form); + this.$store.dispatch('localUsers/updateUser', userData); } }, deleteUser({ username }) { this.$store.dispatch('localUsers/deleteUser', username); - }, - clearActiveUser() { - this.activeUser = null; } } }; @@ -164,4 +172,9 @@ export default { h1 { margin-bottom: 2rem; } +.btn.collapsed { + svg { + transform: rotate(180deg); + } +} </style> diff --git a/src/views/AccessControl/LocalUserManagement/ModalUser.vue b/src/views/AccessControl/LocalUserManagement/ModalUser.vue index 73aa164c..e3ceb7df 100644 --- a/src/views/AccessControl/LocalUserManagement/ModalUser.vue +++ b/src/views/AccessControl/LocalUserManagement/ModalUser.vue @@ -1,9 +1,5 @@ <template> - <b-modal - id="modal-user" - @ok="$emit('ok', { newUser, form })" - @hidden="$emit('hidden')" - > + <b-modal id="modal-user" ref="modal" @ok="onOk" @hidden="resetForm"> <template v-slot:modal-title> <template v-if="newUser"> Add user @@ -12,27 +8,121 @@ Edit user </template> </template> - <b-form> - <b-form-group label="Account status"> - <b-form-radio v-model="form.status" name="user-status" value="true" - >Enabled</b-form-radio - > - <b-form-radio v-model="form.status" name="user-status" value="false" - >Disabled</b-form-radio - > - </b-form-group> - <b-form-group label="Username"> - <b-form-input type="text" v-model="form.username" /> - </b-form-group> - <b-form-group label="Privilege"> - <b-form-select - v-model="form.privilege" - :options="privilegeTypes" - ></b-form-select> - </b-form-group> - <b-form-group label="Password"> - <b-form-input type="password" v-model="form.password" /> - </b-form-group> + <b-form novalidate @submit="handleSubmit"> + <b-container> + <b-row> + <b-col> + <b-form-group label="Account status"> + <b-form-radio + v-model="form.status" + name="user-status" + :value="true" + @input="$v.form.status.$touch()" + > + Enabled + </b-form-radio> + <b-form-radio + v-model="form.status" + name="user-status" + :value="false" + @input="$v.form.status.$touch()" + > + Disabled + </b-form-radio> + </b-form-group> + <b-form-group label="Username" label-for="username"> + <b-form-text id="username-help-block"> + Cannot start with a number + <br /> + No special characters except underscore + </b-form-text> + <b-form-input + v-model="form.username" + type="text" + id="username" + aria-describedby="username-help-block" + :state="getValidationState($v.form.username)" + :disabled="!newUser && originalUsername === 'root'" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.username.required"> + Field required + </template> + <template v-else-if="!$v.form.username.maxLength"> + Length must be between 1 – 16 characters + </template> + <template v-else-if="!$v.form.username.pattern"> + Invalid format + </template> + </b-form-invalid-feedback> + </b-form-group> + <b-form-group label="Privilege"> + <b-form-select + v-model="form.privilege" + :options="privilegeTypes" + :state="getValidationState($v.form.privilege)" + @input="$v.form.privilege.$touch()" + > + </b-form-select> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.privilege.required"> + Field required + </template> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + <b-col> + <b-form-group label="User password" label-for="password"> + <b-form-text id="password-help-block" text-variant="black"> + <!-- TODO: Should be dynamic values --> + Password must between 8 – 20 characters + </b-form-text> + <b-form-input + v-model="form.password" + type="password" + id="password" + aria-describedby="password-help-block" + :state="getValidationState($v.form.password)" + @input="$v.form.password.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.password.required"> + Field required + </template> + <template + v-if=" + !$v.form.password.minLength || !$v.form.password.maxLength + " + > + Length must be between 8 – 20 characters + </template> + </b-form-invalid-feedback> + </b-form-group> + <b-form-group + label="Confirm user password" + label-for="password-confirmation" + > + <b-form-input + v-model="form.passwordConfirmation" + type="password" + id="password-confirmation" + :state="getValidationState($v.form.passwordConfirmation)" + @input="$v.form.passwordConfirmation.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.passwordConfirmation.required"> + Field required + </template> + <template + v-else-if="!$v.form.passwordConfirmation.sameAsPassword" + > + Passwords do not match + </template> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + </b-row> + </b-container> </b-form> <template v-slot:modal-ok> <template v-if="newUser"> @@ -46,29 +136,133 @@ </template> <script> +import { + required, + maxLength, + minLength, + sameAs, + helpers, + requiredIf +} from 'vuelidate/lib/validators'; +import VuelidateMixin from '../../../components/Mixins/VuelidateMixin.js'; + export default { props: ['user'], + mixins: [VuelidateMixin], data() { return { - privilegeTypes: ['Administrator', 'Operator', 'ReadOnly', 'NoAccess'] + privilegeTypes: ['Administrator', 'Operator', 'ReadOnly', 'NoAccess'], + originalUsername: '', + form: { + status: true, + username: '', + privilege: '', + password: '', + passwordConfirmation: '' + } }; }, computed: { newUser() { return this.user ? false : true; + } + }, + watch: { + user: function(value) { + if (value === null) return; + this.originalUsername = value.username; + this.form.username = value.username; + this.form.status = value.Enabled; + this.form.privilege = value.privilege; + } + }, + validations: { + form: { + status: { + required + }, + username: { + required, + maxLength: maxLength(16), + pattern: helpers.regex('pattern', /^([a-zA-Z_][a-zA-Z0-9_]*)/) + }, + privilege: { + required + }, + password: { + required: requiredIf(function() { + return this.requirePassword(); + }), + minLength: minLength(8), //TODO: Update to dynamic backend values + maxLength: maxLength(20) //TODO: UPdate to dynamic backend values + }, + passwordConfirmation: { + required: requiredIf(function() { + return this.requirePassword(); + }), + sameAsPassword: sameAs('password') + } + } + }, + methods: { + handleSubmit() { + let userData = {}; + + if (this.newUser) { + this.$v.$touch(); + if (this.$v.$invalid) return; + userData.username = this.form.username; + userData.status = this.form.status; + userData.privilege = this.form.privilege; + userData.password = this.form.password; + } else { + if (this.$v.$invalid) return; + userData.originalUsername = this.originalUsername; + if (this.$v.form.status.$dirty) { + userData.status = this.form.status; + } + if (this.$v.form.username.$dirty) { + userData.username = this.form.username; + } + if (this.$v.form.privilege.$dirty) { + userData.privilege = this.form.privilege; + } + if (this.$v.form.password.$dirty) { + userData.password = this.form.password; + } + if (Object.entries(userData).length === 1) { + this.closeModal(); + return; + } + } + + this.$emit('ok', { isNewUser: this.newUser, userData }); + this.closeModal(); + }, + closeModal() { + this.$nextTick(() => { + this.$refs.modal.hide(); + }); + }, + resetForm() { + this.form.originalUsername = ''; + this.form.status = true; + this.form.username = ''; + this.form.privilege = ''; + this.form.password = ''; + this.form.passwordConfirmation = ''; + this.$v.$reset(); + }, + requirePassword() { + if (this.newUser) return true; + if (this.$v.form.password.$dirty) return true; + if (this.$v.form.passwordConfirmation.$dirty) return true; + return false; }, - form() { - return { - originalUsername: this.newUser ? null : this.user.username, - status: this.newUser - ? true - : this.user.status === 'Enabled' - ? true - : false, - username: this.newUser ? '' : this.user.username, - privilege: this.newUser ? '' : this.user.privilege, - password: '' - }; + onOk(bvModalEvt) { + // prevent modal close + bvModalEvt.preventDefault(); + this.handleSubmit(); } } }; diff --git a/src/views/AccessControl/LocalUserManagement/TableRoles.vue b/src/views/AccessControl/LocalUserManagement/TableRoles.vue index b4019664..7ea89da9 100644 --- a/src/views/AccessControl/LocalUserManagement/TableRoles.vue +++ b/src/views/AccessControl/LocalUserManagement/TableRoles.vue @@ -1,5 +1,5 @@ <template> - <b-table bordered small head-variant="dark" :items="items" :fields="fields"> + <b-table small :items="items" :fields="fields"> <template v-slot:cell(administrator)="data"> <template v-if="data.value"> <Checkmark20 /> diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue index 706d3ecc..4270b3fd 100644 --- a/src/views/Login/Login.vue +++ b/src/views/Login/Login.vue @@ -16,15 +16,10 @@ <b-col md="6"> <b-form class="login-form" @submit.prevent="login" novalidate> - <b-alert - class="login-error" - v-if="authStatus == 'error'" - show - variant="danger" - > + <b-alert class="login-error" :show="authError" variant="danger"> <p id="login-error-alert"> <strong>{{ errorMsg.title }}</strong> - <span v-if="errorMsg.action">{{ errorMsg.action }}</span> + <span>{{ errorMsg.action }}</span> </p> </b-alert> <div class="login-form__section"> @@ -32,14 +27,18 @@ <b-form-input id="username" v-model="userInfo.username" - :aria-describedby=" - authStatus == 'error' ? 'login-error-alert' : '' - " + :aria-describedby="authError ? 'login-error-alert' : ''" + :state="getValidationState($v.userInfo.username)" type="text" - required autofocus="autofocus" + @input="$v.userInfo.username.$touch()" > </b-form-input> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.userInfo.username.required"> + Field required + </template> + </b-form-invalid-feedback> </div> <div class="login-form__section"> @@ -47,19 +46,25 @@ <b-form-input id="password" v-model="userInfo.password" - :aria-describedby=" - authStatus == 'error' ? 'login-error-alert' : '' - " + :aria-describedby="authError ? 'login-error-alert' : ''" + :state="getValidationState($v.userInfo.password)" type="password" - required + @input="$v.userInfo.password.$touch()" > </b-form-input> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.userInfo.password.required"> + Field required + </template> + </b-form-invalid-feedback> </div> <b-button + block + class="mt-5" type="submit" variant="primary" - :disabled="authStatus == 'processing'" + :disabled="disableSubmitButton" >Log in</b-button > </b-form> @@ -70,18 +75,22 @@ </template> <script> +import { required } from 'vuelidate/lib/validators'; +import VuelidateMixin from '../../components/Mixins/VuelidateMixin.js'; + export default { name: 'Login', + mixins: [VuelidateMixin], computed: { - authStatus() { - return this.$store.getters['authentication/authStatus']; + authError() { + return this.$store.getters['authentication/authError']; } }, data() { return { errorMsg: { - title: null, - action: null + title: 'Invalid username or password.', + action: 'Please try again.' }, userInfo: { username: null, @@ -90,46 +99,34 @@ export default { disableSubmitButton: false }; }, - methods: { - resetState: function() { - this.errorMsg.title = null; - this.errorMsg.action = null; - this.$store.commit('authentication/authReset'); - }, - validateRequiredFields: function() { - if (!this.userInfo.username || !this.userInfo.password) { - this.$store.commit('authentication/authError'); + validations: { + userInfo: { + username: { + required + }, + password: { + required } - }, + } + }, + methods: { login: function() { - this.resetState(); - this.validateRequiredFields(); - if (this.authStatus !== 'error') { - const username = this.userInfo.username; - const password = this.userInfo.password; - this.$store - .dispatch('authentication/login', [username, password]) - .then(() => { - this.$router.push('/'); - }) - .catch(error => { - this.errorMsg.title = 'Invalid username or password.'; - this.errorMsg.action = 'Please try again.'; - console.log(error); - }); - } else { - this.errorMsg.title = 'Username and password required.'; - } + this.$v.$touch(); + if (this.$v.$invalid) return; + this.disableSubmitButton = true; + const username = this.userInfo.username; + const password = this.userInfo.password; + this.$store + .dispatch('authentication/login', [username, password]) + .then(() => this.$router.push('/')) + .catch(error => console.log(error)) + .finally(() => (this.disableSubmitButton = false)); } } }; </script> <style lang="scss" scoped> -@import '~bootstrap/scss/functions'; -@import '~bootstrap/scss/variables'; -@import '~bootstrap/scss/mixins'; - .login-container { @include media-breakpoint-up(md) { background: linear-gradient( |