summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json203
-rw-r--r--package.json1
-rw-r--r--src/assets/styles/_form-components.scss29
-rw-r--r--src/assets/styles/_modal.scss7
-rw-r--r--src/assets/styles/_obmc-custom.scss6
-rw-r--r--src/assets/styles/_table.scss21
-rw-r--r--src/components/Mixins/VuelidateMixin.js10
-rw-r--r--src/main.js10
-rw-r--r--src/store/modules/Authentication/AuthenticanStore.js17
-rw-r--r--src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue51
-rw-r--r--src/views/AccessControl/LocalUserManagement/ModalUser.vue272
-rw-r--r--src/views/AccessControl/LocalUserManagement/TableRoles.vue2
-rw-r--r--src/views/Login/Login.vue101
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(