mirror of
https://github.com/fleetbase/fleetbase.git
synced 2026-01-10 16:29:07 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5535067d87 | ||
|
|
aab4e9f1f6 | ||
|
|
43329a0ed2 | ||
|
|
427816d2dd | ||
|
|
3d9d5cbe9d | ||
|
|
3fc5bdb32e | ||
|
|
a60f75655d | ||
|
|
0984af400a | ||
|
|
7f900fe218 | ||
|
|
3aaf382384 | ||
|
|
f589b1cf0a | ||
|
|
bce1cbf22d | ||
|
|
6327603857 | ||
|
|
7204ddb3a9 | ||
|
|
2bc9745b95 | ||
|
|
4a2b3e094f | ||
|
|
313906d36a | ||
|
|
d1d5b87c21 | ||
|
|
8dc7d9ff87 | ||
|
|
008faa1ca7 | ||
|
|
6aab30b358 | ||
|
|
30ab3aab09 | ||
|
|
df3c93803d | ||
|
|
3d1e20439f | ||
|
|
646f10713f | ||
|
|
5db723b003 | ||
|
|
74770883fe | ||
|
|
3ebedab90a | ||
|
|
d0864e822b | ||
|
|
f8b564acc9 | ||
|
|
fd604c99e0 | ||
|
|
09996a4398 | ||
|
|
22043a53e3 | ||
|
|
acde54c6ae | ||
|
|
e6961089ca | ||
|
|
880facdaef | ||
|
|
87c1a56fd1 | ||
|
|
dba816463f | ||
|
|
270371ce9c | ||
|
|
9f37b15d8a | ||
|
|
3ac3ad97f3 | ||
|
|
ad067bd32c | ||
|
|
9bd0cc17d9 | ||
|
|
c5ac611000 | ||
|
|
863e27bd7b | ||
|
|
63c9bc0880 | ||
|
|
6183378717 | ||
|
|
ea35a6f81f | ||
|
|
5fb4851544 | ||
|
|
e348b02611 | ||
|
|
3f779969c3 | ||
|
|
9a23c64dde | ||
|
|
b2098d0cb8 | ||
|
|
850cc1e20d | ||
|
|
6dd7ca401c | ||
|
|
fa51d03cfd | ||
|
|
092ebf0589 | ||
|
|
d314964776 | ||
|
|
9c1655167d | ||
|
|
16e59f3465 | ||
|
|
f9d362b1be |
@@ -7,7 +7,11 @@ routes:
|
||||
headers:
|
||||
Cache-Control: "max-age=600, no-transform, public"
|
||||
gzip: false
|
||||
- route: "^.+\\.(html|xml|json)$"
|
||||
- route: "^.+\\.(xml|json)$"
|
||||
headers:
|
||||
Cache-Control: "max-age=600, no-transform, public"
|
||||
gzip: true
|
||||
- route: "^.+\\.(html)$"
|
||||
headers:
|
||||
Cache-Control: "public, max-age=0, must-revalidate"
|
||||
gzip: true
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0",
|
||||
"fleetbase/core-api": "^1.3.7",
|
||||
"fleetbase/fleetops-api": "^0.3.9",
|
||||
"fleetbase/storefront-api": "^0.2.6",
|
||||
"fleetbase/core-api": "^1.3.12",
|
||||
"fleetbase/fleetops-api": "^0.4.1",
|
||||
"fleetbase/storefront-api": "^0.2.8",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"laravel/framework": "^8.75",
|
||||
|
||||
126
api/composer.lock
generated
126
api/composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "364ff14c7487716ed6feabec0132f8d7",
|
||||
"content-hash": "f05cf921b043d22c7a75ee5f991a72d9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aloha/twilio",
|
||||
@@ -192,16 +192,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.296.4",
|
||||
"version": "3.296.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "2efd701a5e5f50560319ffe0669942dc8d3a4a54"
|
||||
"reference": "f84fe470709e5ab9515649a1a0891e279693d1b3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2efd701a5e5f50560319ffe0669942dc8d3a4a54",
|
||||
"reference": "2efd701a5e5f50560319ffe0669942dc8d3a4a54",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f84fe470709e5ab9515649a1a0891e279693d1b3",
|
||||
"reference": "f84fe470709e5ab9515649a1a0891e279693d1b3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -281,9 +281,9 @@
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.296.4"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.296.8"
|
||||
},
|
||||
"time": "2024-01-17T23:34:36+00:00"
|
||||
"time": "2024-01-23T20:32:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php-laravel",
|
||||
@@ -2071,16 +2071,16 @@
|
||||
},
|
||||
{
|
||||
"name": "fleetbase/core-api",
|
||||
"version": "1.3.7",
|
||||
"version": "1.3.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fleetbase/core-api.git",
|
||||
"reference": "7faca511a792c0f657cdf897517b06295ef823e7"
|
||||
"reference": "b1a6b5f4614bad34591336a3aa0d41679085d8b8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/fleetbase/core-api/zipball/7faca511a792c0f657cdf897517b06295ef823e7",
|
||||
"reference": "7faca511a792c0f657cdf897517b06295ef823e7",
|
||||
"url": "https://api.github.com/repos/fleetbase/core-api/zipball/b1a6b5f4614bad34591336a3aa0d41679085d8b8",
|
||||
"reference": "b1a6b5f4614bad34591336a3aa0d41679085d8b8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2156,29 +2156,29 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/fleetbase/core-api/issues",
|
||||
"source": "https://github.com/fleetbase/core-api/tree/v1.3.7"
|
||||
"source": "https://github.com/fleetbase/core-api/tree/v1.3.12"
|
||||
},
|
||||
"time": "2024-01-18T10:26:36+00:00"
|
||||
"time": "2024-01-24T07:56:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fleetbase/fleetops-api",
|
||||
"version": "0.3.9",
|
||||
"version": "0.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fleetbase/fleetops.git",
|
||||
"reference": "687c82780e359fb69dddc19e198c5d82308aabe0"
|
||||
"reference": "5881b878ce039551dd6a46e50c2e6bc805bcc226"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/fleetbase/fleetops/zipball/687c82780e359fb69dddc19e198c5d82308aabe0",
|
||||
"reference": "687c82780e359fb69dddc19e198c5d82308aabe0",
|
||||
"url": "https://api.github.com/repos/fleetbase/fleetops/zipball/5881b878ce039551dd6a46e50c2e6bc805bcc226",
|
||||
"reference": "5881b878ce039551dd6a46e50c2e6bc805bcc226",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"barryvdh/laravel-dompdf": "^2.0",
|
||||
"brick/geo": "0.7.2",
|
||||
"cknow/laravel-money": "^7.1",
|
||||
"fleetbase/core-api": "^1.3.7",
|
||||
"fleetbase/core-api": "^1.3.12",
|
||||
"geocoder-php/google-maps-places-provider": "^1.4",
|
||||
"giggsey/libphonenumber-for-php": "^8.13",
|
||||
"league/geotools": "^1.1.0",
|
||||
@@ -2244,27 +2244,27 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/fleetbase/fleetops/issues",
|
||||
"source": "https://github.com/fleetbase/fleetops/tree/v0.3.9"
|
||||
"source": "https://github.com/fleetbase/fleetops/tree/v0.4.1"
|
||||
},
|
||||
"time": "2024-01-18T10:51:28+00:00"
|
||||
"time": "2024-01-24T09:39:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fleetbase/storefront-api",
|
||||
"version": "0.2.6",
|
||||
"version": "0.2.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fleetbase/storefront.git",
|
||||
"reference": "1afef1deb719248a817aea23ac5434ff4a7dd1e1"
|
||||
"reference": "cd1bb7f73ec7b91a1448c08ade311343686ed66a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/fleetbase/storefront/zipball/1afef1deb719248a817aea23ac5434ff4a7dd1e1",
|
||||
"reference": "1afef1deb719248a817aea23ac5434ff4a7dd1e1",
|
||||
"url": "https://api.github.com/repos/fleetbase/storefront/zipball/cd1bb7f73ec7b91a1448c08ade311343686ed66a",
|
||||
"reference": "cd1bb7f73ec7b91a1448c08ade311343686ed66a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"fleetbase/core-api": "^1.3.6",
|
||||
"fleetbase/fleetops-api": "^0.3.8",
|
||||
"fleetbase/core-api": "^1.3.12",
|
||||
"fleetbase/fleetops-api": "^0.4.1",
|
||||
"geocoder-php/google-maps-places-provider": "^1.4",
|
||||
"laravel-notification-channels/apn": "^3.8",
|
||||
"laravel-notification-channels/fcm": "^2.7",
|
||||
@@ -2325,9 +2325,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/fleetbase/storefront/issues",
|
||||
"source": "https://github.com/fleetbase/storefront/tree/v0.2.6"
|
||||
"source": "https://github.com/fleetbase/storefront/tree/v0.2.8"
|
||||
},
|
||||
"time": "2024-01-12T10:15:42+00:00"
|
||||
"time": "2024-01-24T09:47:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fruitcake/laravel-cors",
|
||||
@@ -2993,16 +2993,16 @@
|
||||
},
|
||||
{
|
||||
"name": "google/cloud-core",
|
||||
"version": "v1.53.0",
|
||||
"version": "v1.53.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/google-cloud-php-core.git",
|
||||
"reference": "1899ea5306a20c4fb64f00f542e8895e7aa0e8cd"
|
||||
"reference": "4bb40e0cc1839054ea5010321e1105107a3413ce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/google-cloud-php-core/zipball/1899ea5306a20c4fb64f00f542e8895e7aa0e8cd",
|
||||
"reference": "1899ea5306a20c4fb64f00f542e8895e7aa0e8cd",
|
||||
"url": "https://api.github.com/repos/googleapis/google-cloud-php-core/zipball/4bb40e0cc1839054ea5010321e1105107a3413ce",
|
||||
"reference": "4bb40e0cc1839054ea5010321e1105107a3413ce",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3017,7 +3017,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"erusev/parsedown": "^1.6",
|
||||
"google/cloud-common-protos": "^0.4",
|
||||
"google/cloud-common-protos": "^0.5",
|
||||
"google/gax": "^1.26.0",
|
||||
"opis/closure": "^3",
|
||||
"phpdocumentor/reflection": "^5.0",
|
||||
@@ -3052,9 +3052,9 @@
|
||||
],
|
||||
"description": "Google Cloud PHP shared dependency, providing functionality useful to all components.",
|
||||
"support": {
|
||||
"source": "https://github.com/googleapis/google-cloud-php-core/tree/v1.53.0"
|
||||
"source": "https://github.com/googleapis/google-cloud-php-core/tree/v1.53.1"
|
||||
},
|
||||
"time": "2024-01-12T19:18:38+00:00"
|
||||
"time": "2024-01-19T15:59:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/cloud-storage",
|
||||
@@ -6195,16 +6195,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "2.72.1",
|
||||
"version": "2.72.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||
"reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78"
|
||||
"reference": "3e7edc41b58d65509baeb0d4a14c8fa41d627130"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78",
|
||||
"reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/3e7edc41b58d65509baeb0d4a14c8fa41d627130",
|
||||
"reference": "3e7edc41b58d65509baeb0d4a14c8fa41d627130",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6298,7 +6298,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-08T23:47:49+00:00"
|
||||
"time": "2024-01-19T00:21:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/caching",
|
||||
@@ -9213,16 +9213,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sentry/sentry",
|
||||
"version": "4.3.1",
|
||||
"version": "4.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/getsentry/sentry-php.git",
|
||||
"reference": "cd89f230bda0833cb9992ebe9a1b7d24d6ee245b"
|
||||
"reference": "95a428a59ebddf786a27f09d19ec395a32f62082"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/cd89f230bda0833cb9992ebe9a1b7d24d6ee245b",
|
||||
"reference": "cd89f230bda0833cb9992ebe9a1b7d24d6ee245b",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/95a428a59ebddf786a27f09d19ec395a32f62082",
|
||||
"reference": "95a428a59ebddf786a27f09d19ec395a32f62082",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9286,7 +9286,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getsentry/sentry-php/issues",
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.3.1"
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9298,7 +9298,7 @@
|
||||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-22T18:46:49+00:00"
|
||||
"time": "2024-01-23T09:49:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sentry/sentry-laravel",
|
||||
@@ -13927,16 +13927,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "0ac3c270590e54910715e9a1a044cc368df282b2"
|
||||
"reference": "ce594cbc39a4866c544f1a970d285ff0548221ad"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/0ac3c270590e54910715e9a1a044cc368df282b2",
|
||||
"reference": "0ac3c270590e54910715e9a1a044cc368df282b2",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/ce594cbc39a4866c544f1a970d285ff0548221ad",
|
||||
"reference": "ce594cbc39a4866c544f1a970d285ff0548221ad",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -13952,14 +13952,14 @@
|
||||
"doctrine/coding-standard": "12.0.0",
|
||||
"fig/log-test": "^1",
|
||||
"jetbrains/phpstorm-stubs": "2023.1",
|
||||
"phpstan/phpstan": "1.10.42",
|
||||
"phpstan/phpstan": "1.10.56",
|
||||
"phpstan/phpstan-strict-rules": "^1.5",
|
||||
"phpunit/phpunit": "9.6.13",
|
||||
"phpunit/phpunit": "9.6.15",
|
||||
"psalm/plugin-phpunit": "0.18.4",
|
||||
"slevomat/coding-standard": "8.13.1",
|
||||
"squizlabs/php_codesniffer": "3.7.2",
|
||||
"symfony/cache": "^5.4|^6.0",
|
||||
"symfony/console": "^4.4|^5.4|^6.0",
|
||||
"squizlabs/php_codesniffer": "3.8.1",
|
||||
"symfony/cache": "^5.4|^6.0|^7.0",
|
||||
"symfony/console": "^4.4|^5.4|^6.0|^7.0",
|
||||
"vimeo/psalm": "4.30.0"
|
||||
},
|
||||
"suggest": {
|
||||
@@ -14020,7 +14020,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.7.2"
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.7.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -14036,7 +14036,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-19T08:06:58+00:00"
|
||||
"time": "2024-01-21T07:53:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
@@ -15424,16 +15424,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.6.15",
|
||||
"version": "9.6.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "05017b80304e0eb3f31d90194a563fd53a6021f1"
|
||||
"reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1",
|
||||
"reference": "05017b80304e0eb3f31d90194a563fd53a6021f1",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f",
|
||||
"reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -15507,7 +15507,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -15523,7 +15523,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-01T16:55:19+00:00"
|
||||
"time": "2024-01-19T07:03:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
||||
5622
api/pnpm-lock.yaml
generated
Normal file
5622
api/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
console/.gitignore
vendored
1
console/.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules/
|
||||
/scripts/node_modules/
|
||||
|
||||
# misc
|
||||
/.env*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ---- Build Stage ----
|
||||
FROM node:16.20-alpine AS builder
|
||||
FROM node:18.15.0-alpine AS builder
|
||||
|
||||
# Set the working directory in the container to /app
|
||||
WORKDIR /app
|
||||
|
||||
1
console/app/components/configure/2fa.hbs
Normal file
1
console/app/components/configure/2fa.hbs
Normal file
@@ -0,0 +1 @@
|
||||
{{yield}}
|
||||
3
console/app/components/configure/2fa.js
Normal file
3
console/app/components/configure/2fa.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
export default class Configure2faComponent extends Component {}
|
||||
30
console/app/components/locale-selector.hbs
Normal file
30
console/app/components/locale-selector.hbs
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="next-user-button" ...attributes>
|
||||
<BasicDropdown @defaultClass={{@wrapperClass}} @onOpen={{@onOpen}} @onClose={{@onClose}} @verticalPosition={{@verticalPosition}} @horizontalPosition={{@horizontalPosition}} @renderInPlace={{or @renderInPlace true}} @initiallyOpened={{@initiallyOpened}} as |dd|>
|
||||
<dd.Trigger class={{@triggerClass}}>
|
||||
<div class="next-org-button-trigger flex-shrink-0 {{if dd.isOpen 'is-open'}}">
|
||||
<FaIcon @icon="globe" @size="sm" />
|
||||
</div>
|
||||
</dd.Trigger>
|
||||
<dd.Content class={{@contentClass}}>
|
||||
<div class="next-dd-menu {{@dropdownMenuClass}} {{if dd.isOpen 'is-open'}}">
|
||||
{{#each-in this.availableLocales as |key country|}}
|
||||
<div class="px-1">
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (fn this.changeLocale key)}}>
|
||||
<div class="flex flex-row items-center justify-between w-full">
|
||||
<div class="flex-1">
|
||||
<span class="mr-1">{{country.emoji}}</span>
|
||||
<span>{{country.language}}</span>
|
||||
</div>
|
||||
{{#if (eq this.currentLocale key)}}
|
||||
<div>
|
||||
<FaIcon @icon="check" class="text-green-400" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{/each-in}}
|
||||
</div>
|
||||
</dd.Content>
|
||||
</BasicDropdown>
|
||||
</div>
|
||||
144
console/app/components/locale-selector.js
Normal file
144
console/app/components/locale-selector.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
|
||||
export default class LocaleSelectorComponent extends Component {
|
||||
/**
|
||||
* Inject the intl service.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the intl service.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Tracks all the available locales.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked locales = [];
|
||||
|
||||
/**
|
||||
* All available countries data.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked countries = [];
|
||||
|
||||
/**
|
||||
* The current locale in use.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked currentLocale;
|
||||
|
||||
/**
|
||||
* Creates an instance of LocaleSelectorComponent.
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.locales = this.intl.locales;
|
||||
this.currentLocale = this.intl.primaryLocale;
|
||||
this.loadAvailableCountries.perform();
|
||||
|
||||
// Check for locale change
|
||||
this.intl.onLocaleChanged(() => {
|
||||
this.currentLocale = this.intl.primaryLocale;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of locale.
|
||||
* @param {string} selectedLocale - The selected locale.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method changeLocale
|
||||
* @instance
|
||||
* @action
|
||||
*/
|
||||
@action changeLocale(selectedLocale) {
|
||||
this.currentLocale = selectedLocale;
|
||||
this.intl.setLocale(selectedLocale);
|
||||
// Persist to server
|
||||
this.saveUserLocale.perform(selectedLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads available countries asynchronously.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method loadAvailableCountries
|
||||
* @instance
|
||||
* @task
|
||||
* @generator
|
||||
*/
|
||||
@task *loadAvailableCountries() {
|
||||
this.countries = yield this.fetch.get('lookup/countries', { columns: ['name', 'cca2', 'flag', 'emoji', 'languages'] });
|
||||
this.availableLocales = this._createAvailableLocaleMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the user's selected locale to the server.
|
||||
* @param {string} locale - The user's selected locale.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method saveUserLocale
|
||||
* @instance
|
||||
* @task
|
||||
* @generator
|
||||
*/
|
||||
@task *saveUserLocale(locale) {
|
||||
yield this.fetch.post('users/locale', { locale });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a map of available locales.
|
||||
* @private
|
||||
* @returns {Object} - The map of available locales.
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method _createAvailableLocaleMap
|
||||
* @instance
|
||||
*/
|
||||
_createAvailableLocaleMap() {
|
||||
const localeMap = {};
|
||||
|
||||
for (let i = 0; i < this.locales.length; i++) {
|
||||
const locale = this.locales.objectAt(i);
|
||||
|
||||
localeMap[locale] = this._findCountryDataForLocale(locale);
|
||||
}
|
||||
|
||||
return localeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds country data for a given locale.
|
||||
* @private
|
||||
* @param {string} locale - The locale to find country data for.
|
||||
* @returns {Object|null} - The country data or null if not found.
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method _findCountryDataForLocale
|
||||
* @instance
|
||||
*/
|
||||
_findCountryDataForLocale(locale) {
|
||||
const localeCountry = locale.split('-')[1];
|
||||
const country = this.countries.find((country) => country.cca2.toLowerCase() === localeCountry);
|
||||
|
||||
if (country) {
|
||||
// get the language
|
||||
country.language = Object.values(country.languages)[0];
|
||||
}
|
||||
|
||||
return country;
|
||||
}
|
||||
}
|
||||
14
console/app/components/two-fa-enforcement-alert.hbs
Normal file
14
console/app/components/two-fa-enforcement-alert.hbs
Normal file
@@ -0,0 +1,14 @@
|
||||
{{#if this.shouldRender}}
|
||||
<InfoBlock @icon="triangle-exclamation" class="two-fa-enforcement-alert bg-yellow-100 border-2 border-yellow-600 dark:border-yellow-500 rounded-lg py-3.5 px-5">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex-1 pr-2">
|
||||
<p class="text-sm dark:text-yellow-900 mb-2">
|
||||
{{t "component.two-fa-enforcement-alert.message"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<Button id="two-fa-setup-button" @text={{t "component.two-fa-enforcement-alert.button-text"}} @icon="shield-halved" @type="warning" @buttonType="button" @onClick={{this.transitionToTwoFactorSettings}} />
|
||||
</div>
|
||||
</div>
|
||||
</InfoBlock>
|
||||
{{/if}}
|
||||
74
console/app/components/two-fa-enforcement-alert.js
Normal file
74
console/app/components/two-fa-enforcement-alert.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
* Glimmer component for handling notification enforcement.
|
||||
*
|
||||
* @class EnforceNotificationComponent
|
||||
* @extends Component
|
||||
*/
|
||||
export default class TwoFaEnforcementAlertComponent extends Component {
|
||||
/**
|
||||
* Flag to determine whether the component should render or not.
|
||||
*
|
||||
* @property {boolean} shouldRender
|
||||
* @default false
|
||||
* @tracked
|
||||
*/
|
||||
@tracked shouldRender = false;
|
||||
|
||||
/**
|
||||
* Ember Router service for transitioning between routes.
|
||||
*
|
||||
* @type {RouterService}
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Fetch service for making HTTP requests.
|
||||
*
|
||||
* @property {FetchService} fetch
|
||||
* @inject
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Constructor method for the ConsoleAccountAuthController.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.checkTwoFactorEnforcement.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to the users auth page.
|
||||
*
|
||||
* @method transitionToTwoFa
|
||||
* @memberof ConsoleHomeController
|
||||
*/
|
||||
@action transitionToTwoFactorSettings() {
|
||||
this.router.transitionTo('console.account.auth');
|
||||
}
|
||||
|
||||
@task *checkTwoFactorEnforcement() {
|
||||
const shouldRender = yield this.fetch.get('two-fa/enforce').catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
|
||||
/**
|
||||
* Task to check whether two-factor authentication enforcement is required.
|
||||
*
|
||||
* @method checkTwoFactorEnforcement
|
||||
* @memberof TwoFaEnforcementAlertComponent
|
||||
* @task
|
||||
*/
|
||||
if (shouldRender) {
|
||||
this.shouldRender = shouldRender.shouldEnforce;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
console/app/components/two-fa-settings.hbs
Normal file
31
console/app/components/two-fa-settings.hbs
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="text-base font-medium">Enable Two-Factor Authentication</label>
|
||||
<Toggle @isToggled={{this.isTwoFaEnabled}} @onToggle={{this.onTwoFaToggled}} />
|
||||
</div>
|
||||
|
||||
{{#if this.isTwoFaEnabled}}
|
||||
<div class="mt-6">
|
||||
{{#if this.showEnforceOption}}
|
||||
<div class="flex items-center space-x-4 mb-6">
|
||||
<label class="text-base font-medium">Require Users to Set-Up 2FA</label>
|
||||
<Toggle @isToggled={{this.isTwoFaEnforced}} @onToggle={{this.onTwoFaEnforcedToggled}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.showMethodSelection}}
|
||||
<label class="text-base font-medium">Choose an authentication method</label>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-300 mt-1">In addition to your username and password, you'll have to enter a code (delivered via app or SMS) to sign in to your account</p>
|
||||
{{#each @twoFaMethods as |method|}}
|
||||
<div class="border rounded-lg px-4 py-3 mt-2 transition duration-300 {{if (eq this.selectedTwoFaMethod method.key) 'border-blue-500' 'border-gray-200 dark:border-gray-700'}}">
|
||||
<input type="radio" name="2fa-method" id="{{method.name}}" checked={{eq this.selectedTwoFaMethod method.key}} {{on "change" (fn this.onTwoFaSelected method.key)}} />
|
||||
<label for="{{method.name}}">
|
||||
{{method.name}}
|
||||
{{#if method.recommended}}
|
||||
<span class="bg-blue-500 rounded-xl text-white px-2 py-1 ml-2 text-xs font-semibold">Recommended</span>
|
||||
{{/if}}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-300 mt-1">{{method.description}}</p>
|
||||
</label>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
169
console/app/components/two-fa-settings.js
Normal file
169
console/app/components/two-fa-settings.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
/**
|
||||
* Default Two-Factor Authentication method when not explicitly selected.
|
||||
*
|
||||
* @property {string} DEFAULT_2FA_METHOD
|
||||
* @private
|
||||
*/
|
||||
const DEFAULT_2FA_METHOD = 'email';
|
||||
|
||||
/**
|
||||
* Glimmer component for managing Two-Factor Authentication settings.
|
||||
*
|
||||
* @class TwoFaSettingsComponent
|
||||
* @extends Component
|
||||
*/
|
||||
export default class TwoFaSettingsComponent extends Component {
|
||||
/**
|
||||
* The fetch service for making HTTP requests.
|
||||
*
|
||||
* @property {Service} fetch
|
||||
* @public
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* The notifications service for displaying user notifications.
|
||||
*
|
||||
* @property {Service} notifications
|
||||
* @public
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* The currently selected Two-Factor Authentication method.
|
||||
*
|
||||
* @property {string} selectedTwoFaMethod
|
||||
* @public
|
||||
*/
|
||||
@tracked selectedTwoFaMethod;
|
||||
|
||||
/**
|
||||
* Indicates whether Two-Factor Authentication is currently enabled.
|
||||
*
|
||||
* @property {boolean} isTwoFaEnabled
|
||||
* @public
|
||||
*/
|
||||
@tracked isTwoFaEnabled;
|
||||
|
||||
/**
|
||||
* Indicates whether Two-Factor Authentication is required for all users.
|
||||
*
|
||||
* @property {boolean} isTwoFaEnforced
|
||||
* @public
|
||||
*/
|
||||
@tracked isTwoFaEnforced;
|
||||
|
||||
/**
|
||||
* Indicates whether the settings should render an option to `enforce`
|
||||
* Enforce is a flag that indicates that users either under a company or system must setup 2FA.
|
||||
*
|
||||
* @property {boolean} showEnforceOption
|
||||
* @public
|
||||
*/
|
||||
@tracked showEnforceOption;
|
||||
|
||||
/**
|
||||
* Indicates whether the settings should render an option to select 2fa `mn=ethod`
|
||||
* Method is a flag that indicates which method users can receive a 2FA code from.
|
||||
*
|
||||
* @property {boolean} showEnforceOption
|
||||
* @public
|
||||
*/
|
||||
@tracked showMethodSelection;
|
||||
|
||||
/**
|
||||
* Class constructor to initialize the component.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} owner - The owner of the component.
|
||||
* @param {Object} options - Options passed during component instantiation.
|
||||
* @param {Object} options.twoFaSettings - The current Two-Factor Authentication settings.
|
||||
* @param {Array} options.twoFaMethods - Available Two-Factor Authentication methods.
|
||||
*/
|
||||
constructor(owner, { twoFaSettings, twoFaMethods, showEnforceOption, showMethodSelection = true }) {
|
||||
super(...arguments);
|
||||
|
||||
const userSelectedMethod = isArray(twoFaMethods) ? twoFaMethods.find(({ key }) => key === twoFaSettings.method) : null;
|
||||
|
||||
this.showMethodSelection = showMethodSelection === true;
|
||||
this.showEnforceOption = showEnforceOption === true;
|
||||
this.isTwoFaEnabled = twoFaSettings.enabled === true;
|
||||
this.isTwoFaEnforced = twoFaSettings.enforced === true;
|
||||
this.selectedTwoFaMethod = userSelectedMethod ? userSelectedMethod.key : DEFAULT_2FA_METHOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action handler for toggling Two-Factor Authentication.
|
||||
*
|
||||
* @method onTwoFaToggled
|
||||
* @param {boolean} isTwoFaEnabled - Indicates whether Two-Factor Authentication is enabled.
|
||||
* @return {void}
|
||||
* @public
|
||||
*/
|
||||
@action onTwoFaToggled(isTwoFaEnabled) {
|
||||
this.isTwoFaEnabled = isTwoFaEnabled;
|
||||
|
||||
if (isTwoFaEnabled) {
|
||||
const recommendedMethod = isArray(this.args.twoFaMethods) ? this.args.twoFaMethods.find((method) => method.recommended) : null;
|
||||
if (recommendedMethod) {
|
||||
this.selectedTwoFaMethod = recommendedMethod.key;
|
||||
}
|
||||
} else {
|
||||
this.selectedTwoFaMethod = null;
|
||||
}
|
||||
|
||||
if (typeof this.args.onTwoFaToggled === 'function') {
|
||||
this.args.onTwoFaToggled(...arguments);
|
||||
}
|
||||
|
||||
if (typeof this.args.onTwoFaMethodSelected === 'function') {
|
||||
this.args.onTwoFaMethodSelected(this.selectedTwoFaMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action handler for toggling Two-Factor Authentication.
|
||||
*
|
||||
* @method onTwoFaEnforcedToggled
|
||||
* @param {boolean} isTwoFaEnforced - Indicates whether Two-Factor Authentication is enabled.
|
||||
* @return {void}
|
||||
* @public
|
||||
*/
|
||||
@action onTwoFaEnforcedToggled(isTwoFaEnforced) {
|
||||
this.isTwoFaEnforced = isTwoFaEnforced;
|
||||
|
||||
if (typeof this.args.onTwoFaEnforcedToggled === 'function') {
|
||||
this.args.onTwoFaEnforcedToggled(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action handler for selecting a Two-Factor Authentication method.
|
||||
*
|
||||
* @method onTwoFaSelected
|
||||
* @param {string} method - The selected Two-Factor Authentication method.
|
||||
* @return {void}
|
||||
* @public
|
||||
*/
|
||||
@action onTwoFaSelected(method) {
|
||||
this.selectedTwoFaMethod = method;
|
||||
|
||||
if (typeof this.args.onTwoFaMethodSelected === 'function') {
|
||||
this.args.onTwoFaMethodSelected(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@action onRequireUsersToSetUpToggled(isTwoFaEnforced) {
|
||||
this.isTwoFaEnforced = isTwoFaEnforced;
|
||||
|
||||
if (typeof this.args.onTwoFaEnforcedToggled === 'function') {
|
||||
this.args.onTwoFaEnforcedToggled(isTwoFaEnforced);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,13 @@ export default class AuthForgotPasswordController extends Controller {
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @memberof AuthForgotPasswordController
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* The email variable
|
||||
*
|
||||
@@ -55,7 +62,7 @@ export default class AuthForgotPasswordController extends Controller {
|
||||
this.fetch
|
||||
.post('auth/get-magic-reset-link', { email })
|
||||
.then(() => {
|
||||
this.notifications.success('Check your email to continue!');
|
||||
this.notifications.success(this.intl.t('auth.forgot-password.success-message'));
|
||||
this.isSent = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -40,6 +40,13 @@ export default class AuthLoginController extends Controller {
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the `fetch` service
|
||||
*
|
||||
@@ -103,42 +110,60 @@ export default class AuthLoginController extends Controller {
|
||||
*/
|
||||
@tracked failedAttempts = 0;
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @void
|
||||
*/
|
||||
@tracked token;
|
||||
|
||||
@action async login(event) {
|
||||
// firefox patch
|
||||
event.preventDefault();
|
||||
|
||||
// get user credentials
|
||||
const { email, password, rememberMe } = this;
|
||||
const { identity, password, rememberMe } = this;
|
||||
|
||||
// If no password error
|
||||
if (!email) {
|
||||
return this.notifications.warning('Did you forget to enter your email?');
|
||||
if (!identity) {
|
||||
return this.notifications.warning(this.intl.t('auth.login.no-identity-notification'));
|
||||
}
|
||||
|
||||
// If no password error
|
||||
if (!password) {
|
||||
return this.notifications.warning('Did you forget to enter your password?');
|
||||
return this.notifications.warning(this.intl.t('auth.login.no-identity-notification'));
|
||||
}
|
||||
|
||||
// start loader
|
||||
this.set('isLoading', true);
|
||||
|
||||
// set where to redirect on login
|
||||
this.setRedirect();
|
||||
|
||||
// send request to check for 2fa
|
||||
try {
|
||||
await this.session.authenticate('authenticator:fleetbase', { email, password }, rememberMe);
|
||||
let { twoFaSession, isTwoFaEnabled } = await this.session.checkForTwoFactor(identity);
|
||||
|
||||
if (isTwoFaEnabled) {
|
||||
return this.session.store
|
||||
.persist({ identity })
|
||||
.then(() => {
|
||||
return this.router.transitionTo('auth.two-fa', { queryParams: { token: twoFaSession } }).then(() => {
|
||||
this.reset('success');
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
this.reset('error');
|
||||
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return this.notifications.serverError(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.session.authenticate('authenticator:fleetbase', { identity, password }, rememberMe);
|
||||
} catch (error) {
|
||||
this.failedAttempts++;
|
||||
|
||||
// Handle unverified user
|
||||
if (error.toString().includes('not verified')) {
|
||||
return this.sendUserForEmailVerification(email);
|
||||
return this.sendUserForEmailVerification(identity);
|
||||
}
|
||||
|
||||
return this.failure(error);
|
||||
@@ -171,13 +196,13 @@ export default class AuthLoginController extends Controller {
|
||||
* Creates an email verification session and transitions user to verification route.
|
||||
*
|
||||
* @param {String} email
|
||||
* @return {Promise<Transition>}
|
||||
* @return {Promise<Transition>}
|
||||
* @memberof AuthLoginController
|
||||
*/
|
||||
sendUserForEmailVerification(email) {
|
||||
return this.fetch.post('auth/create-verification-session', { email, send: true }).then(({ token }) => {
|
||||
return this.session.store.persist({ email }).then(() => {
|
||||
this.notifications.warning('Your account needs to be verified to proceed.');
|
||||
this.notifications.warning(this.intl.t('auth.login.unverified-notification'));
|
||||
return this.router
|
||||
.transitionTo('auth.verification', {
|
||||
queryParams: {
|
||||
@@ -230,7 +255,7 @@ export default class AuthLoginController extends Controller {
|
||||
* @void
|
||||
*/
|
||||
slowConnection() {
|
||||
this.notifications.error('Experiencing connectivity issues.');
|
||||
this.notifications.error(this.intl.t('auth.login.slow-connection-message'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,13 @@ export default class AuthResetPasswordController extends Controller {
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* The code param.
|
||||
*
|
||||
@@ -70,7 +77,7 @@ export default class AuthResetPasswordController extends Controller {
|
||||
this.fetch
|
||||
.post('auth/reset-password', { link: id, code, password, password_confirmation })
|
||||
.then(() => {
|
||||
this.notifications.success('Your password has been reset! Login to continue.');
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
|
||||
return this.router.transitionTo('auth.login');
|
||||
})
|
||||
|
||||
271
console/app/controllers/auth/two-fa.js
Normal file
271
console/app/controllers/auth/two-fa.js
Normal file
@@ -0,0 +1,271 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
/**
|
||||
* Controller responsible for handling two-factor authentication.
|
||||
* @class AuthTwoFaController
|
||||
* @extends Controller
|
||||
*/
|
||||
export default class AuthTwoFaController extends Controller {
|
||||
/**
|
||||
* Router service.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Fetch service for making HTTP requests.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Notifications service for handling notifications.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Session service for managing user sessions.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service session;
|
||||
|
||||
/**
|
||||
* Internationalization service.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Tracked property for storing the verification token.
|
||||
*
|
||||
* @property {string} token
|
||||
* @tracked
|
||||
*/
|
||||
@tracked token;
|
||||
|
||||
/**
|
||||
* The current 2FA identity in memory
|
||||
*/
|
||||
@tracked identity;
|
||||
|
||||
/**
|
||||
* Tracked property representing the client token from the validated 2fa session.
|
||||
*
|
||||
* @property {number} clientToken
|
||||
* @tracked
|
||||
* @default null
|
||||
*/
|
||||
@tracked clientToken;
|
||||
|
||||
/**
|
||||
* Tracked property for storing the verification code.
|
||||
*
|
||||
* @property {string} verificationCode
|
||||
* @tracked
|
||||
*/
|
||||
@tracked verificationCode = '';
|
||||
|
||||
/**
|
||||
* Tracked property for storing the verification code.
|
||||
*
|
||||
* @property {string} verificationCode
|
||||
* @tracked
|
||||
*/
|
||||
@tracked otpValue = '';
|
||||
|
||||
/**
|
||||
* Tracked property representing the date the 2fa session will expire
|
||||
* @property {Date|null} twoFactorSessionExpiresAfter
|
||||
* @tracked
|
||||
* @default null
|
||||
*/
|
||||
@tracked twoFactorSessionExpiresAfter;
|
||||
|
||||
/**
|
||||
* Tracked property representing when the countdown is ready to start.
|
||||
*
|
||||
* @property {Boolean} countdownReady
|
||||
* @tracked
|
||||
* @default false
|
||||
*/
|
||||
@tracked countdownReady = false;
|
||||
|
||||
/**
|
||||
* Tracked property representing when verification code has expired.
|
||||
*
|
||||
* @property {Boolean} isCodeExpired
|
||||
* @tracked
|
||||
* @default false
|
||||
*/
|
||||
@tracked isCodeExpired = false;
|
||||
|
||||
/**
|
||||
* Query parameters for the controller.
|
||||
*
|
||||
* @property {Array} queryParams
|
||||
*/
|
||||
queryParams = ['token', 'clientToken'];
|
||||
|
||||
/**
|
||||
* Action method for verifying the entered verification code.
|
||||
*
|
||||
* @method verifyCode
|
||||
* @action
|
||||
*/
|
||||
@action async verifyCode(event) {
|
||||
// prevent form default behaviour
|
||||
if (event && typeof event.preventDefault === 'function') {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
try {
|
||||
const { token, verificationCode, clientToken, identity } = this;
|
||||
|
||||
if (!clientToken) {
|
||||
this.notifications.error(this.intl.t('auth.two-fa.verify-code.invalid-session-error-notification'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the backend API to verify the entered verification code
|
||||
const { authToken } = await this.fetch.post('two-fa/verify', {
|
||||
token,
|
||||
code: verificationCode,
|
||||
clientToken,
|
||||
identity,
|
||||
});
|
||||
|
||||
// If verification is successful, transition to the desired route
|
||||
this.notifications.success(this.intl.t('auth.two-fa.verify-code.verification-successful-notification'));
|
||||
|
||||
// authenticate user
|
||||
return this.session.authenticate('authenticator:fleetbase', { authToken }).then(() => {
|
||||
return this.router.transitionTo('console');
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes('Verification code has expired')) {
|
||||
this.notifications.info(this.intl.t('auth.two-fa.verify-code.verification-code-expired-notification'));
|
||||
} else {
|
||||
this.notifications.error(this.intl.t('auth.two-fa.verify-code.verification-code-failed-notification'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resends the verification code for Two-Factor Authentication.
|
||||
* Disables the countdown timer while processing and handles success or error notifications.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @action
|
||||
*/
|
||||
@action async resendCode() {
|
||||
// disable countdown timer
|
||||
this.countdownReady = false;
|
||||
|
||||
try {
|
||||
const { identity, token } = this;
|
||||
const { clientToken } = await this.fetch.post('two-fa/resend', {
|
||||
identity,
|
||||
token,
|
||||
});
|
||||
|
||||
if (clientToken) {
|
||||
this.clientToken = clientToken;
|
||||
this.twoFactorSessionExpiresAfter = this.getExpirationDateFromClientToken(clientToken);
|
||||
this.countdownReady = true;
|
||||
this.isCodeExpired = false;
|
||||
this.notifications.success(this.intl.t('auth.two-fa.resend-code.verification-code-resent-notification'));
|
||||
} else {
|
||||
this.notifications.error(this.intl.t('auth.two-fa.resend-code.verification-code-resent-error-notification'));
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle errors, show error notifications, etc.
|
||||
this.notifications.error(this.intl.t('auth.two-fa.resend-code.verification-code-resent-error-notification'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the current Two-Fa session and redirects to login screen.
|
||||
*
|
||||
* @returns {Promise<Transition>}
|
||||
* @memberof AuthTwoFaController
|
||||
*/
|
||||
@action cancelTwoFactor() {
|
||||
return this.fetch
|
||||
.post('two-fa/invalidate', {
|
||||
identity: this.identity,
|
||||
token: this.token,
|
||||
})
|
||||
.then(() => {
|
||||
return this.router.transitionTo('auth.login');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that the verification code has expired and allow user to resend.
|
||||
*
|
||||
* @memberof AuthTwoFaController
|
||||
*/
|
||||
@action handleCodeExpired() {
|
||||
this.isCodeExpired = true;
|
||||
this.countdownReady = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the input of the OTP (One-Time Password) and triggers the verification process.
|
||||
*
|
||||
* @param {string} otpValue - The OTP value entered by the user.
|
||||
* @returns {void}
|
||||
* @action
|
||||
*/
|
||||
@action handleOtpInput(otpValue) {
|
||||
this.verificationCode = otpValue;
|
||||
this.verifyCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a base64 encoded client token to a Date representing the expiration date.
|
||||
*
|
||||
* @method getExpirationDateFromClientToken
|
||||
* @param {string} clientToken - Base64 encoded client token.
|
||||
* @returns {Date|null} - Date representing the expiration date, or null if invalid.
|
||||
*/
|
||||
getExpirationDateFromClientToken(clientToken) {
|
||||
const decoder = new TextDecoder();
|
||||
const binString = atob(clientToken);
|
||||
const bytes = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
||||
const decodedString = decoder.decode(bytes);
|
||||
|
||||
if (typeof decodedString === 'string' && decodedString.includes('|')) {
|
||||
const parts = decodedString.split('|');
|
||||
const expiresAt = this.convertUtcToClientTime(parts[0]);
|
||||
|
||||
if (expiresAt instanceof Date) {
|
||||
return expiresAt;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UTC date-time string to client time zone.
|
||||
*
|
||||
* @method convertUtcToClientTime
|
||||
* @param {string} utcDateTimeString - UTC date-time string.
|
||||
* @returns {Date} - Date in client time zone.
|
||||
*/
|
||||
convertUtcToClientTime(utcDateTimeString) {
|
||||
const utcDate = new Date(utcDateTimeString);
|
||||
const clientTimezoneOffset = new Date().getTimezoneOffset();
|
||||
const clientDate = new Date(utcDate.getTime() - clientTimezoneOffset * 60 * 1000);
|
||||
return clientDate;
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export default class AuthVerificationController extends Controller {
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked waitTimeout = 800 * 60 * 2;
|
||||
@tracked waitTimeout = 1000 * 60 * 1.25;
|
||||
|
||||
/**
|
||||
* Determines if Fleetbase is still awaiting verification after a certain time.
|
||||
@@ -190,9 +190,14 @@ export default class AuthVerificationController extends Controller {
|
||||
modal.startLoading();
|
||||
const phone = modal.getOption('phone');
|
||||
|
||||
return this.fetch.post('onboard/send-verification-sms', { phone }).then(() => {
|
||||
this.notifications.success('Verification code SMS sent!');
|
||||
});
|
||||
return this.fetch
|
||||
.post('onboard/send-verification-sms', { phone, session: this.hello })
|
||||
.then(() => {
|
||||
this.notifications.success('Verification code SMS sent!');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -211,9 +216,14 @@ export default class AuthVerificationController extends Controller {
|
||||
modal.startLoading();
|
||||
const email = modal.getOption('email');
|
||||
|
||||
return this.fetch.post('onboard/send-verification-email', { email }).then(() => {
|
||||
this.notifications.success('Verification code email sent!');
|
||||
});
|
||||
return this.fetch
|
||||
.post('onboard/send-verification-email', { email, session: this.hello })
|
||||
.then(() => {
|
||||
this.notifications.success('Verification code email sent!');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ export default class ConsoleController extends Controller {
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.router.on('routeDidChange', (transition) => {
|
||||
if (this.sidebarContext) {
|
||||
// Determine if the new route should hide the sidebar
|
||||
|
||||
248
console/app/controllers/console/account/auth.js
Normal file
248
console/app/controllers/console/account/auth.js
Normal file
@@ -0,0 +1,248 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
import getTwoFaMethods from '@fleetbase/console/utils/get-two-fa-methods';
|
||||
|
||||
/**
|
||||
* Controller for managing user authentication and password-related actions in the console.
|
||||
*
|
||||
* @class ConsoleAccountAuthController
|
||||
* @extends Controller
|
||||
*/
|
||||
export default class ConsoleAccountAuthController extends Controller {
|
||||
/**
|
||||
* Service for handling data fetching.
|
||||
*
|
||||
* @type {fetch}
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Service for displaying notifications.
|
||||
*
|
||||
* @type {notifications}
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Service for managing application routing.
|
||||
*
|
||||
* @type {router}
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* The user's current password.
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked password;
|
||||
|
||||
/**
|
||||
* The user's confirmation of the new password.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked confirmPassword;
|
||||
|
||||
/**
|
||||
* The new password the user intends to set.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked newPassword;
|
||||
|
||||
/**
|
||||
* The user's confirmation of the new password.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked newConfirmPassword;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the current password has been validated.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isPasswordValidated = false;
|
||||
|
||||
/**
|
||||
* System-wide two-factor authentication configuration.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked twoFaConfig = {};
|
||||
|
||||
/**
|
||||
* User-specific two-factor authentication settings.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked twoFaSettings = {};
|
||||
|
||||
/**
|
||||
* Flag indicating whether system-wide two-factor authentication is enabled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isSystemTwoFaEnabled = false;
|
||||
|
||||
/**
|
||||
* Available two-factor authentication methods.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
@tracked methods = getTwoFaMethods();
|
||||
|
||||
/**
|
||||
* Constructor method for the ConsoleAccountAuthController.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.loadSystemTwoFaConfig.perform();
|
||||
this.loadUserTwoFaSettings.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the user's current password.
|
||||
*
|
||||
* @method validatePassword
|
||||
* @param {Event} event - The event object triggering the action.
|
||||
*/
|
||||
@action validatePassword(event) {
|
||||
event.preventDefault();
|
||||
this.validatePasswordTask.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changeUserPasswordTask
|
||||
* @param {Event} event - The event object triggering the action.
|
||||
*/
|
||||
@action changeUserPassword(event) {
|
||||
event.preventDefault();
|
||||
this.changeUserPasswordTask.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when two-factor authentication is toggled.
|
||||
*
|
||||
* @method onTwoFaToggled
|
||||
* @param {boolean} enabled - Whether two-factor authentication is enabled or not.
|
||||
*/
|
||||
@action onTwoFaToggled(enabled) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when a two-factor authentication method is selected.
|
||||
*
|
||||
* @method onTwoFaMethodSelected
|
||||
* @param {string} method - The selected two-factor authentication method.
|
||||
*/
|
||||
@action onTwoFaMethodSelected(method) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
method,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method saveTwoFactorAuthSettings
|
||||
*/
|
||||
@action saveTwoFactorAuthSettings() {
|
||||
this.saveUserTwoFaSettings.perform(this.twoFaSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method saveUserTwoFaSettings
|
||||
* @param {Object} twoFaSettings - User-specific two-factor authentication settings.
|
||||
*/
|
||||
@task *saveUserTwoFaSettings(twoFaSettings = {}) {
|
||||
yield this.fetch
|
||||
.post('users/two-fa', { twoFaSettings })
|
||||
.then(() => {
|
||||
this.notifications.success('2FA Settings saved successfully.');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to load user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method loadUserTwoFaSettings
|
||||
*/
|
||||
@task *loadUserTwoFaSettings() {
|
||||
const twoFaSettings = yield this.fetch.get('users/two-fa');
|
||||
|
||||
if (twoFaSettings) {
|
||||
this.isUserTwoFaEnabled = twoFaSettings.enabled;
|
||||
this.twoFaSettings = twoFaSettings;
|
||||
}
|
||||
return twoFaSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to load system-wide two-factor authentication configuration asynchronously.
|
||||
*
|
||||
* @method loadSystemTwoFaConfig
|
||||
*/
|
||||
@task *loadSystemTwoFaConfig() {
|
||||
const twoFaConfig = yield this.fetch.get('two-fa/config');
|
||||
|
||||
if (twoFaConfig) {
|
||||
this.isSystemTwoFaEnabled = twoFaConfig.enabled;
|
||||
this.twoFaConfig = twoFaConfig;
|
||||
}
|
||||
return twoFaConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to validate the user's current password asynchronously.
|
||||
*
|
||||
* @method validatePasswordTask
|
||||
*/
|
||||
@task *validatePasswordTask() {
|
||||
try {
|
||||
yield this.fetch.post('users/validate-password', {
|
||||
password: this.password,
|
||||
password_confirmation: this.confirmPassword,
|
||||
});
|
||||
|
||||
this.isPasswordValidated = true;
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Invalid current password.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changeUserPasswordTask
|
||||
*/
|
||||
@task *changeUserPasswordTask() {
|
||||
try {
|
||||
yield this.fetch.post('users/change-password', {
|
||||
password: this.newPassword,
|
||||
password_confirmation: this.newConfirmPassword,
|
||||
});
|
||||
|
||||
this.notifications.success('Password change successfully.');
|
||||
} catch (error) {
|
||||
this.notifications.error('Failed to change password');
|
||||
}
|
||||
}
|
||||
}
|
||||
172
console/app/controllers/console/admin/two-fa-settings.js
Normal file
172
console/app/controllers/console/admin/two-fa-settings.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
import getTwoFaMethods from '@fleetbase/console/utils/get-two-fa-methods';
|
||||
|
||||
/**
|
||||
* Controller responsible for handling Two-Factor Authentication settings in the admin console.
|
||||
*
|
||||
* @class ConsoleAdminTwoFaSettingsController
|
||||
* @extends Controller
|
||||
*/
|
||||
export default class ConsoleAdminTwoFaSettingsController extends Controller {
|
||||
/**
|
||||
* Service for handling data fetching.
|
||||
*
|
||||
* @type {fetch}
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Service for displaying notifications.
|
||||
*
|
||||
* @type {notifications}
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* System-wide two-factor authentication configuration.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked twoFaConfig = {};
|
||||
|
||||
/**
|
||||
* User-specific two-factor authentication settings.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked twoFaSettings = {};
|
||||
|
||||
/**
|
||||
* Flag indicating whether system-wide two-factor authentication is enabled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isSystemTwoFaEnabled = false;
|
||||
|
||||
/**
|
||||
* Flag indicating whether 2FA enforcement is required.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isTwoFaEnforced = false;
|
||||
|
||||
/**
|
||||
* Available two-factor authentication methods.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
@tracked methods = getTwoFaMethods();
|
||||
|
||||
/**
|
||||
* Tracked property for the loading state
|
||||
*
|
||||
* @memberof ConsoleAdminTwoFaSettingsController
|
||||
* @var {Boolean}
|
||||
*/
|
||||
@tracked isLoading = false;
|
||||
|
||||
/**
|
||||
* Constructor method for the ConsoleAccountAuthController.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.loadSystemTwoFaConfig.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when two-factor authentication is toggled.
|
||||
*
|
||||
* @method onTwoFaToggled
|
||||
* @param {boolean} enabled - Whether two-factor authentication is enabled or not.
|
||||
*/
|
||||
@action onTwoFaToggled(enabled) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when a two-factor authentication method is selected.
|
||||
*
|
||||
* @method onTwoFaMethodSelected
|
||||
* @param {string} method - The selected two-factor authentication method.
|
||||
*/
|
||||
@action onTwoFaMethodSelected(method) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
method,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when two-factor authentication is toggled.
|
||||
*
|
||||
* @method onTwoFaToggled
|
||||
* @param {boolean} enabled - Whether two-factor authentication is enforced or not.
|
||||
*/
|
||||
@action onTwoFaEnforceToggled(enforced) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
enforced,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when 2FA enforcement is toggled.
|
||||
*
|
||||
* @method onTwoFaEnforceToggled
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method saveTwoFactorAuthSettings
|
||||
*/
|
||||
@action saveSettings() {
|
||||
this.saveTwoFactorSettingsForAdmin.perform(this.twoFaSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to load system-wide two-factor authentication configuration asynchronously.
|
||||
*
|
||||
* @method loadSystemTwoFaConfig
|
||||
*/
|
||||
|
||||
@task *loadSystemTwoFaConfig() {
|
||||
const twoFaSettings = yield this.fetch.get('two-fa/config').catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
if (twoFaSettings) {
|
||||
this.twoFaSettings = twoFaSettings;
|
||||
}
|
||||
|
||||
return twoFaSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method saveTwoFactorSettingsForAdmin
|
||||
* @param {Object} twoFaSettings - User-specific two-factor authentication settings.
|
||||
*/
|
||||
@task *saveTwoFactorSettingsForAdmin(twoFaSettings = {}) {
|
||||
yield this.fetch
|
||||
.post('two-fa/config', { twoFaSettings })
|
||||
.then(() => {
|
||||
this.notifications.success('2FA Settings saved for admin successfully.');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
3
console/app/controllers/console/settings/auth.js
Normal file
3
console/app/controllers/console/settings/auth.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default class ConsoleSettingsAuthController extends Controller {}
|
||||
184
console/app/controllers/console/settings/two-fa.js
Normal file
184
console/app/controllers/console/settings/two-fa.js
Normal file
@@ -0,0 +1,184 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
import getTwoFaMethods from '@fleetbase/console/utils/get-two-fa-methods';
|
||||
|
||||
export default class ConsoleSettingsTwoFaController extends Controller {
|
||||
/**
|
||||
* Service for handling data fetching.
|
||||
*
|
||||
* @type {fetch}
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Service for displaying notifications.
|
||||
*
|
||||
* @type {notifications}
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* System-wide two-factor authentication configuration.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked twoFaConfig = {};
|
||||
|
||||
/**
|
||||
* User-specific two-factor authentication settings.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked twoFaSettings = {};
|
||||
|
||||
/**
|
||||
* Flag indicating whether system-wide two-factor authentication is enabled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isSystemTwoFaEnabled = false;
|
||||
|
||||
/**
|
||||
* Flag indicating whether system-wide two-factor authentication is enabled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isUserTwoFaEnabled = false;
|
||||
|
||||
/**
|
||||
* Flag indicating whether 2FA enforcement is required.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isTwoFaEnforced = false;
|
||||
|
||||
/**
|
||||
* Available two-factor authentication methods.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
@tracked methods = getTwoFaMethods();
|
||||
|
||||
/**
|
||||
* Constructor method for the ConsoleAccountAuthController.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.loadSystemTwoFaConfig.perform();
|
||||
this.loadCompanyTwoFaSettings.perform();
|
||||
this.loadUserTwoFaSettings.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when two-factor authentication is toggled.
|
||||
*
|
||||
* @method onTwoFaToggled
|
||||
* @param {boolean} enabled - Whether two-factor authentication is enabled or not.
|
||||
*/
|
||||
@action onTwoFaToggled(enabled) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when a two-factor authentication method is selected.
|
||||
*
|
||||
* @method onTwoFaMethodSelected
|
||||
* @param {string} method - The selected two-factor authentication method.
|
||||
*/
|
||||
@action onTwoFaMethodSelected(method) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
method,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when two-factor authentication is toggled.
|
||||
*
|
||||
* @method onTwoFaToggled
|
||||
* @param {boolean} enabled - Whether two-factor authentication is enforced or not.
|
||||
*/
|
||||
@action onTwoFaEnforceToggled(enforced) {
|
||||
this.twoFaSettings = {
|
||||
...this.twoFaSettings,
|
||||
enforced,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method saveTwoFactor
|
||||
*/
|
||||
@action saveTwoFactor() {
|
||||
this.saveTwoFactorSettingsForCompany.perform(this.twoFaSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to load user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method loadUserTwoFaSettings
|
||||
*/
|
||||
@task *loadCompanyTwoFaSettings() {
|
||||
const twoFaSettings = yield this.fetch.get('companies/two-fa');
|
||||
if (twoFaSettings) {
|
||||
this.twoFaSettings = twoFaSettings;
|
||||
this.isTwoFaEnforced = twoFaSettings.enforced;
|
||||
}
|
||||
return twoFaSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to load system-wide two-factor authentication configuration asynchronously.
|
||||
*
|
||||
* @method loadSystemTwoFaConfig
|
||||
*/
|
||||
@task *loadSystemTwoFaConfig() {
|
||||
const twoFaConfig = yield this.fetch.get('two-fa/config');
|
||||
|
||||
if (twoFaConfig) {
|
||||
this.isSystemTwoFaEnabled = twoFaConfig.enabled;
|
||||
this.twoFaConfig = twoFaConfig;
|
||||
}
|
||||
return twoFaConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to load user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
* @method loadUserTwoFaSettings
|
||||
*/
|
||||
@task *loadUserTwoFaSettings() {
|
||||
const twoFaSettings = yield this.fetch.get('users/two-fa');
|
||||
if (twoFaSettings) {
|
||||
this.isUserTwoFaEnabled = twoFaSettings.enabled;
|
||||
this.twoFaSettings = twoFaSettings;
|
||||
}
|
||||
return twoFaSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings for the company asynchronously.
|
||||
*
|
||||
* @method saveTwoFactorSettingsForCompany
|
||||
* @param {Object} twoFaSettings - User-specific two-factor authentication settings.
|
||||
*/
|
||||
@task *saveTwoFactorSettingsForCompany(twoFaSettings = {}) {
|
||||
yield this.fetch
|
||||
.post('companies/two-fa', { twoFaSettings })
|
||||
.then(() => {
|
||||
this.notifications.success('2FA Settings saved for organization successfully.');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,11 @@ export default class UserModel extends Model {
|
||||
@not('isEmailVerified') emailIsNotVerified;
|
||||
@not('isPhoneVerified') phoneIsNotVerified;
|
||||
|
||||
/** @computed */
|
||||
@computed('meta.two_factor_enabled') get isTwoFactorEnabled() {
|
||||
return this.meta && this.meta.two_factor_enabled;
|
||||
}
|
||||
|
||||
@computed('types') get typesList() {
|
||||
const types = Array.from(this.types);
|
||||
return types.join(', ');
|
||||
|
||||
@@ -11,6 +11,7 @@ Router.map(function () {
|
||||
this.route('login', { path: '/' });
|
||||
this.route('forgot-password');
|
||||
this.route('reset-password');
|
||||
this.route('two-fa');
|
||||
this.route('verification');
|
||||
});
|
||||
this.route('onboard', function () {
|
||||
@@ -26,9 +27,11 @@ Router.map(function () {
|
||||
this.route('notifications');
|
||||
this.route('account', function () {
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
this.route('auth');
|
||||
});
|
||||
this.route('settings', function () {
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
this.route('two-fa');
|
||||
});
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
this.route('admin', function () {
|
||||
@@ -44,8 +47,29 @@ Router.map(function () {
|
||||
});
|
||||
this.route('branding');
|
||||
this.route('notifications');
|
||||
this.route('two-fa-settings');
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
});
|
||||
|
||||
this.mount('@fleetbase/dev-engine', {
|
||||
as: 'developers',
|
||||
path: 'developers'
|
||||
});
|
||||
|
||||
this.mount('@fleetbase/fleetops-engine', {
|
||||
as: 'fleet-ops',
|
||||
path: 'fleet-ops'
|
||||
});
|
||||
|
||||
this.mount('@fleetbase/iam-engine', {
|
||||
as: 'iam',
|
||||
path: 'iam'
|
||||
});
|
||||
|
||||
this.mount('@fleetbase/storefront-engine', {
|
||||
as: 'storefront',
|
||||
path: 'storefront'
|
||||
});
|
||||
});
|
||||
this.route('install');
|
||||
});
|
||||
|
||||
114
console/app/routes/auth/two-fa.js
Normal file
114
console/app/routes/auth/two-fa.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class AuthTwoFaRoute extends Route {
|
||||
/**
|
||||
* Fetch service for making HTTP requests.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Notifications service for handling notifications.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Router service.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Session service for managing user sessions.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service session;
|
||||
|
||||
/**
|
||||
* Query parameters for the route.
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
queryParams = {
|
||||
token: {
|
||||
refreshModel: false,
|
||||
replace: true,
|
||||
},
|
||||
clientToken: {
|
||||
refreshModel: false,
|
||||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes before the model is loaded, used for validating 2FA session with the server.
|
||||
*
|
||||
* @param {Object} transition - The transition object representing the route transition.
|
||||
* @return {Promise} A promise that resolves if the 2FA session is valid, and rejects with an error otherwise.
|
||||
*/
|
||||
beforeModel(transition) {
|
||||
// validate 2fa session with server
|
||||
let { token, clientToken } = transition.to.queryParams;
|
||||
|
||||
return this.session.store.restore().then(({ identity }) => {
|
||||
if (!identity) {
|
||||
this.notifications.error('2FA failed to initialize.');
|
||||
return this.router.transitionTo('auth.login');
|
||||
}
|
||||
|
||||
return this.fetch
|
||||
.post('two-fa/validate', { token, identity, clientToken })
|
||||
.then(({ clientToken, expired }) => {
|
||||
// handle when code expired
|
||||
if (expired === true) {
|
||||
return this.invalidateTwoFaSession(token, identity);
|
||||
}
|
||||
|
||||
// clear session data after validated 2fa session
|
||||
this.session.store.persist({
|
||||
identity,
|
||||
token,
|
||||
clientToken,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
return this.router.transitionTo('auth.login');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the controller, including client token and session expiration details.
|
||||
*
|
||||
* @param {Object} controller - The controller for the route.
|
||||
*/
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
|
||||
this.session.store.restore().then(({ clientToken, identity }) => {
|
||||
controller.clientToken = clientToken;
|
||||
controller.identity = identity;
|
||||
controller.twoFactorSessionExpiresAfter = controller.getExpirationDateFromClientToken(clientToken);
|
||||
controller.countdownReady = true;
|
||||
});
|
||||
}
|
||||
|
||||
invalidateTwoFaSession(token, identity) {
|
||||
this.notifications.error('2FA authentication session has expired.');
|
||||
return this.fetch
|
||||
.post('two-fa/invalidate', {
|
||||
token,
|
||||
identity,
|
||||
})
|
||||
.then(() => {
|
||||
return this.router.transitionTo('auth.login');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,13 @@ export default class ConsoleRoute extends Route {
|
||||
*/
|
||||
@service session;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the `currentUser` service
|
||||
*
|
||||
@@ -72,6 +79,12 @@ export default class ConsoleRoute extends Route {
|
||||
@action setupController(controller, model) {
|
||||
super.setupController(controller, model);
|
||||
|
||||
// Get and set user locale
|
||||
this.fetch.get('users/locale').then(({ locale }) => {
|
||||
this.intl.setLocale(locale);
|
||||
});
|
||||
|
||||
// Get user organizations
|
||||
this.fetch.get('auth/organizations').then((organizations) => {
|
||||
this.currentUser.setOption('organizations', organizations);
|
||||
controller.organizations = organizations;
|
||||
|
||||
3
console/app/routes/console/account/auth.js
Normal file
3
console/app/routes/console/account/auth.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleAccountAuthRoute extends Route {}
|
||||
3
console/app/routes/console/admin/two-fa-settings.js
Normal file
3
console/app/routes/console/admin/two-fa-settings.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleAdminTwoFaSettingsRoute extends Route {}
|
||||
3
console/app/routes/console/settings/auth.js
Normal file
3
console/app/routes/console/settings/auth.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleSettingsAuthRoute extends Route {}
|
||||
3
console/app/routes/console/settings/two-fa.js
Normal file
3
console/app/routes/console/settings/two-fa.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleSettingsTwoFaRoute extends Route {}
|
||||
4
console/app/serializers/two-fa-settings.js
Normal file
4
console/app/serializers/two-fa-settings.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class TwoFaSettingsSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {}
|
||||
@@ -2,3 +2,4 @@
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import 'inter-ui/inter.css';
|
||||
@import 'console.css';
|
||||
|
||||
13
console/app/styles/console.css
Normal file
13
console/app/styles/console.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.two-fa-enforcement-alert svg.fa-triangle-exclamation {
|
||||
font-size: 2.25rem;
|
||||
padding-right: 0.5rem;
|
||||
color: rgb(202 138 4);
|
||||
}
|
||||
|
||||
.two-fa-enforcement-alert button#two-fa-setup-button.btn.btn-warning,
|
||||
body[data-theme='dark'] .two-fa-enforcement-alert button#two-fa-setup-button.btn.btn-warning {
|
||||
background-color: rgb(202 138 4);
|
||||
border-color: rgb(161 98 7);
|
||||
color: rgb(254 249 195);
|
||||
cursor: default;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="mb-4">
|
||||
<Image src={{@model.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} width="160" height="56" class="w-40 h-14 mx-auto" />
|
||||
<h2 class="text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{if this.isSent "Almost done!" "Forgot your password?"}}
|
||||
{{if this.isSent (t "auth.forgot-password.is-sent.title") (t "auth.forgot-password.not-sent.title")}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<FaIcon @icon="check-circle" @size="lg" class="text-green-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-green-900 dark:text-green-900">
|
||||
<strong>Check your email!</strong><br> We've sent you a magic link to your email which will allow you to reset your password. The link expires in 15 minutes.
|
||||
{{t "auth.forgot-password.is-sent.message" htmlSafe=true}}
|
||||
</p>
|
||||
</div>
|
||||
{{else}}
|
||||
@@ -21,23 +21,23 @@
|
||||
<FaIcon @icon="info-circle" @size="lg" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
<strong>Don't worry, we've got your back.</strong><br> Enter the email you use to login to {{t "app.name"}} and we will send you a secure link to reset your password.
|
||||
{{t "auth.forgot-password.not-sent.message" htmlSafe=true appName=(t "app.name")}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="space-y-6" {{on "submit" this.sendSecureLink}}>
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-50">
|
||||
Your email address
|
||||
{{t "auth.forgot-password.form.email-label"}}
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<Input @value={{this.email}} @type="email" id="email" name="email" required class="form-input form-input-lg w-full" placeholder="Your email" />
|
||||
<Input @value={{this.email}} @type="email" id="email" name="email" required class="form-input form-input-lg w-full" placeholder={{t "auth.forgot-password.form.email-label"}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row space-x-2">
|
||||
<Button @icon="magic" @type="primary" @buttonType="submit" @text="OK, Send me a magic link!" @onClick={{this.sendSecureLink}} @isLoading={{this.isLoading}} />
|
||||
<Button @buttonType="button" @text="Nevermind" @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
|
||||
<Button @icon="magic" @type="primary" @buttonType="submit" @text={{t "auth.forgot-password.form.submit-button"}} @onClick={{this.sendSecureLink}} @isLoading={{this.isLoading}} />
|
||||
<Button @buttonType="button" @text={{t "auth.forgot-password.form.nevermind-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<LogoIcon @url={{@brand.icon_url}} @size="12" class="mx-auto" />
|
||||
</div>
|
||||
<h2 class="mt-6 mb-3 text-3xl font-extrabold leading-9 text-center text-gray-900 dark:text-gray-100">
|
||||
Sign in to your account
|
||||
{{t "auth.login.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
<FaIcon @icon="exclamation-triangle" @size="lg" class="text-yellow-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-yellow-900 dark:yellow-red-900">
|
||||
<strong>Forgot your password?</strong><br> Click the button below to reset your password.
|
||||
{{t "auth.login.failed-attempt.message" htmlSafe=true}}
|
||||
</p>
|
||||
</div>
|
||||
<Button @text="Ok, help me reset!" @type="warning" @onClick={{this.forgotPassword}} />
|
||||
<Button @text={{t "auth.login.failed-attempt.button-text"}} @type="warning" @onClick={{this.forgotPassword}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -25,10 +25,30 @@
|
||||
<input type="hidden" name="remember" value="true" />
|
||||
<div class="rounded-md shadow-sm">
|
||||
<div>
|
||||
<Input @value={{this.email}} aria-label="Email address" name="email" @type="email" autocomplete="username" required class="relative block w-full px-3 py-2 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-none appearance-none rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm sm:leading-5 dark:text-white dark:bg-gray-700 dark:border-gray-900" placeholder="Email address" disabled={{this.isLoading}} />
|
||||
<Input
|
||||
@value={{this.identity}}
|
||||
aria-label={{t "auth.login.form.email-label"}}
|
||||
name="email"
|
||||
@type="email"
|
||||
autocomplete="username"
|
||||
required
|
||||
class="relative block w-full px-3 py-2 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-none appearance-none rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm sm:leading-5 dark:text-white dark:bg-gray-700 dark:border-gray-900"
|
||||
placeholder={{t "auth.login.form.email-label"}}
|
||||
disabled={{this.isLoading}}
|
||||
/>
|
||||
</div>
|
||||
<div class="-mt-px">
|
||||
<Input @value={{this.password}} aria-label="Password" name="password" @type="password" autocomplete="current-password" required class="relative block w-full px-3 py-2 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-none appearance-none rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm sm:leading-5 dark:text-white dark:bg-gray-700 dark:border-gray-900" placeholder="Password" disabled={{this.isLoading}} />
|
||||
<Input
|
||||
@value={{this.password}}
|
||||
aria-label={{t "auth.login.form.password-label"}}
|
||||
name="password"
|
||||
@type="password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="relative block w-full px-3 py-2 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-none appearance-none rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm sm:leading-5 dark:text-white dark:bg-gray-700 dark:border-gray-900"
|
||||
placeholder={{t "auth.login.form.password-label"}}
|
||||
disabled={{this.isLoading}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,22 +56,27 @@
|
||||
<div class="flex items-center">
|
||||
<Input id="rememberMe" @type="checkbox" @checked={{this.rememberMe}} disabled={{this.isLoading}} class="w-4 h-4 transition duration-150 ease-in-out form-checkbox text-sky-500" />
|
||||
<label for="rememberMe" class="block ml-2 text-sm leading-5 text-gray-900 dark:text-gray-100">
|
||||
Remember me
|
||||
{{t "auth.login.form.remember-me-label"}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-sm leading-5">
|
||||
<a href="javascript:;" {{on "click" this.forgotPassword}} disabled={{this.isLoading}} class="font-medium transition duration-150 ease-in-out text-sky-600 hover:text-sky-500 focus:outline-none focus:underline">
|
||||
Forgot your password?
|
||||
<a
|
||||
href="javascript:;"
|
||||
{{on "click" this.forgotPassword}}
|
||||
disabled={{this.isLoading}}
|
||||
class="font-medium transition duration-150 ease-in-out text-sky-600 hover:text-sky-500 focus:outline-none focus:underline"
|
||||
>
|
||||
{{t "auth.login.form.forgot-password-label"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<Button @buttonType="submit" @type="primary" @text="Sign In" @icon="lock" @wrapperClass="btn-block" @isLoading={{this.isLoading}} @onClick={{this.login}} />
|
||||
<Button @buttonType="submit" @type="primary" @text={{t "auth.login.form.sign-in-button"}} @icon="lock" @wrapperClass="btn-block" @isLoading={{this.isLoading}} @onClick={{this.login}} />
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<Button @text="Create a new Account" @wrapperClass="btn-block" @disabled={{this.isLoading}} @onClick={{fn (transition-to "onboard")}} />
|
||||
<Button @text={{t "auth.login.form.create-account-button"}} @wrapperClass="btn-block" @disabled={{this.isLoading}} @onClick={{fn (transition-to "onboard")}} />
|
||||
</div>
|
||||
</form>
|
||||
@@ -2,18 +2,18 @@
|
||||
<div class="mb-4">
|
||||
<Image src={{@model.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} width="160" height="56" class="w-40 h-14 mx-auto" />
|
||||
<h2 class="text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
Reset your password
|
||||
{{t "auth.reset-password.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form class="space-y-6" {{on "submit" this.resetPassword}}>
|
||||
<InputGroup @name="Your reset code" @value={{this.code}} @inputClass="form-input-lg" @helpText="The verification code you received in your email." />
|
||||
<InputGroup @name="New Password" @value={{this.password}} @type="password" @inputClass="form-input-lg" @helpText="Enter a password at-least 6 characters to continue." />
|
||||
<InputGroup @name="Confirm new Password" @value={{this.password_confirmation}} @type="password" @inputClass="form-input-lg" @helpText="Enter a password at-least 6 characters to continue." />
|
||||
<InputGroup @name={{t "auth.reset-password.form.code.label"}} @value={{this.code}} @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.code.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.password.label"}} @value={{this.password}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.password.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.confirm-password.label"}} @value={{this.password_confirmation}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.confirm-password.help-text"}} />
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text="Reset Password" @onClick={{this.resetPassword}} @isLoading={{this.isLoading}} />
|
||||
<Button @size="lg" @buttonType="button" @text="Back" @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "auth.reset-password.form.submit-button"}} @onClick={{this.resetPassword}} @isLoading={{this.isLoading}} />
|
||||
<Button @size="lg" @buttonType="button" @text={{t "auth.reset-password.form.back-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
47
console/app/templates/auth/two-fa.hbs
Normal file
47
console/app/templates/auth/two-fa.hbs
Normal file
@@ -0,0 +1,47 @@
|
||||
<div class="mb-8 text-center">
|
||||
<Image src={{@model.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} width="160" height="56" class="w-40 h-14 mx-auto" />
|
||||
<h2 class="text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{if this.isSent "Verification Code"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex px-3 py-2 mb-4 rounded-md shadow-sm bg-green-200">
|
||||
<div>
|
||||
<FaIcon @icon="check-circle" @size="lg" class="text-green-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-green-900 dark:text-green-900">
|
||||
<strong>Check your {{this.selectedMethod}}</strong><br />
|
||||
We've sent you a verification code. Enter the code below to complete the login process.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="mt-8" {{on "submit" this.verifyCode}}>
|
||||
<div class="flex items-center justify-between my-6">
|
||||
<OtpInput @onInputCompleted={{this.handleOtpInput}} />
|
||||
</div>
|
||||
|
||||
<div id="otp-countdown-container" class="otp-countdown-container flex items-center justify-center min-h-12">
|
||||
{{#if this.countdownReady}}
|
||||
<Countdown @expiry={{this.twoFactorSessionExpiresAfter}} @countdownClass="text-lg" @onCountdownEnd={{this.handleCodeExpired}} />
|
||||
{{/if}}
|
||||
{{#if this.isCodeExpired}}
|
||||
<InfoBlock>
|
||||
<div>Your 2FA authentication code has expired. You can request another code if you need more time.</div>
|
||||
<Button @type="primary" @wrapperClass="mt-2" @text="Resend Code" @icon="arrow-rotate-right" @onClick={{this.resendCode}} />
|
||||
</InfoBlock>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<Button @buttonType="submit" @type="primary" @text="Verify Code" @icon="check-circle" @wrapperClass="btn-block" @isLoading={{this.isLoading}} />
|
||||
</div>
|
||||
|
||||
<div class="text-center flex flex-row items-center justify-center space-x-4 mt-3.5">
|
||||
<a href="#" class="text-sm text-blue-500 hover:underline inline-block" {{on "click" this.resendCode}}>
|
||||
Resend Code
|
||||
</a>
|
||||
<a href="#" class="text-sm text-danger hover:underline inline-block" {{on "click" this.cancelTwoFactor}}>
|
||||
Cancel Two-Factor
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,10 +1,11 @@
|
||||
{{page-title "Console"}}
|
||||
{{page-title (t "app.name")}}
|
||||
<Layout::Container>
|
||||
<Layout::Header @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} @showSidebarToggle={{true}} @sidebarToggleEnabled={{this.sidebarToggleEnabled}} @onSidebarToggle={{this.onSidebarToggle}} />
|
||||
<Layout::Main>
|
||||
<Layout::Sidebar @onSetup={{this.setSidebarContext}}>
|
||||
<div class="next-sidebar-content-inner">
|
||||
<div role="menu" id="sidebar-menu-items"></div>
|
||||
<div role="menu" id="sidebar-menu-items">
|
||||
</div>
|
||||
</div>
|
||||
</Layout::Sidebar>
|
||||
<Layout::Section>
|
||||
@@ -12,4 +13,9 @@
|
||||
</Layout::Section>
|
||||
</Layout::Main>
|
||||
<Layout::MobileNavbar @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} />
|
||||
</Layout::Container>
|
||||
</Layout::Container>
|
||||
|
||||
{{!-- Add Locale Selector to Header --}}
|
||||
<EmberWormhole @to="view-header-actions">
|
||||
<LocaleSelector class="mr-0.5" />
|
||||
</EmberWormhole>
|
||||
@@ -1,7 +1,8 @@
|
||||
{{page-title "Account"}}
|
||||
<EmberWormhole @to="sidebar-menu-items">
|
||||
<Layout::Sidebar::Panel @open={{true}} @title="Account">
|
||||
<Layout::Sidebar::Panel @open={{true}} @title={{t "common.account"}}>
|
||||
<Layout::Sidebar::Item @route="console.account.index" @icon="user">Profile</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.account.auth" @icon="key">Auth</Layout::Sidebar::Item>
|
||||
{{#each this.universe.accountMenuItems as |menuItem|}}
|
||||
<Layout::Sidebar::Item @onClick={{fn this.universe.transitionMenuItem "console.account.virtual" menuItem}} @item={{menuItem}} @icon={{menuItem.icon}}>{{menuItem.title}}</Layout::Sidebar::Item>
|
||||
{{/each}}
|
||||
|
||||
57
console/app/templates/console/account/auth.hbs
Normal file
57
console/app/templates/console/account/auth.hbs
Normal file
@@ -0,0 +1,57 @@
|
||||
{{page-title "Account Auth"}}
|
||||
<Layout::Section::Header @title="Account Auth" />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title="Change Password" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#if this.isPasswordValidated}}
|
||||
<form id="change-password-form" aria-label="change-password" {{on "submit" this.changeUserPassword}}>
|
||||
<legend class="mb-3">Change Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Enter new Password" @type="password" @value={{this.newPassword}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.newConfirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Confirm & Change Password" @icon="save" {{on "click" this.changeUserPassword}} />
|
||||
</form>
|
||||
{{else}}
|
||||
<form id="validate-password-form" aria-label="validate-password" {{on "submit" this.validatePassword}}>
|
||||
<legend class="mb-3">Validate Current Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Password" @type="password" @value={{this.password}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.confirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Continue" @icon="check" {{on "click" this.validatePassword}} />
|
||||
</form>
|
||||
{{/if}}
|
||||
</ContentPanel>
|
||||
|
||||
{{#if this.isSystemTwoFaEnabled}}
|
||||
<ContentPanel @title="2FA Settings" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<div class="mb-3">
|
||||
{{#if this.loadUserTwoFaSettings.isIdle}}
|
||||
<TwoFaSettings
|
||||
@twoFaMethods={{this.methods}}
|
||||
@twoFaSettings={{this.twoFaSettings}}
|
||||
@onTwoFaToggled={{this.onTwoFaToggled}}
|
||||
@onTwoFaMethodSelected={{this.onTwoFaMethodSelected}}
|
||||
/>
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<Spinner @loadingMessage="Loading User 2FA Settings..." @wrapperClass="flex flex-row" @iconClass="mr-2" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<Button
|
||||
@type="primary"
|
||||
@buttonType="submit"
|
||||
@text="Save 2FA Settings"
|
||||
@icon="save"
|
||||
@onClick={{this.saveTwoFactorAuthSettings}}
|
||||
@isLoading={{this.saveUserTwoFaSettings.isRunning}}
|
||||
/>
|
||||
</ContentPanel>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</Layout::Section::Body>
|
||||
@@ -1,25 +1,25 @@
|
||||
<Layout::Section::Header @title="Profile" />
|
||||
<Layout::Section::Header @title={{t "common.profile"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="max-w-3xl my-10 mx-auto">
|
||||
<ContentPanel @title="Your Profile" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<ContentPanel @title={{t "common.your-profile"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" this.saveProfile}}>
|
||||
<div class="w-32 flex flex-col justify-center mb-6 mr-6">
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md" />
|
||||
<FileUpload @name="photos" @accept="image/*" @onFileAdded={{this.uploadNewPhoto}} @labelClass="flex flex-row items-center justify-center" as |queue|>
|
||||
<FileUpload @name={{t "console.account.index.photos"}} @accept="image/*" @onFileAdded={{this.uploadNewPhoto}} @labelClass="flex flex-row items-center justify-center" as |queue|>
|
||||
<a tabindex={{0}} class="flex items-center px-0 mt-2 text-xs no-underline truncate btn btn-sm btn-default" disabled={{queue.files.length}}>
|
||||
{{#if queue.files.length}}
|
||||
<div class="mr-1.5">
|
||||
<Spinner />
|
||||
</div>
|
||||
<span>
|
||||
Uploading...
|
||||
{{t "common.uploading"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FaIcon @icon="image" class="mr-1.5" />
|
||||
<span>
|
||||
Upload new
|
||||
<span>
|
||||
{{t "console.account.index.upload-new"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
@@ -27,15 +27,15 @@
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
|
||||
<InputGroup @name="Name" @value={{this.user.name}} />
|
||||
<InputGroup @name="Email" @type="email" @value={{this.user.email}} />
|
||||
<InputGroup @name="Your phone number" @helpText="Your phone number.">
|
||||
<InputGroup @name={{t "common.name"}} @value={{this.user.name}} />
|
||||
<InputGroup @name={{t "common.email"}} @type="email" @value={{this.user.email}} />
|
||||
<InputGroup @name={{t "common.phone"}} @helpText={{t "console.account.index.phone"}}>
|
||||
<PhoneInput @value={{this.user.phone}} @onInput={{fn (mut this.user.phone)}} class="form-input input-lg w-full" />
|
||||
</InputGroup>
|
||||
<InputGroup @name="Date of Birth" @type="date" @value={{this.user.date_of_birth}} />
|
||||
<InputGroup @name={{t "common.date-of-birth"}} @type="date" @value={{this.user.date_of_birth}} />
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-end">
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.saveProfile}} @isLoading={{this.isLoading}} />
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{this.saveProfile}} @isLoading={{this.isLoading}} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{{page-title "Admin"}}
|
||||
|
||||
<EmberWormhole @to="sidebar-menu-items">
|
||||
<Layout::Sidebar::Item @route="console.admin.index" @icon="rectangle-list">Overview</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.branding" @icon="palette">Branding</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.notifications" @icon="bell">Notifications</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.index" @icon="rectangle-list">{{t "common.overview"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.branding" @icon="palette">{{t "common.branding"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.notifications" @icon="bell">{{t "common.notifications"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.two-fa-settings" @icon="shield-halved">{{t "common.2fa-config"}}</Layout::Sidebar::Item>
|
||||
{{#each this.universe.adminMenuItems as |menuItem|}}
|
||||
<Layout::Sidebar::Item @onClick={{fn this.universe.transitionMenuItem "console.admin.virtual" menuItem}} @item={{menuItem}} @icon={{menuItem.icon}}>{{menuItem.title}}</Layout::Sidebar::Item>
|
||||
{{/each}}
|
||||
@@ -15,12 +16,12 @@
|
||||
</Layout::Sidebar::Panel>
|
||||
{{/each}}
|
||||
<Layout::Sidebar::Panel @open={{true}} @title="System Config">
|
||||
<Layout::Sidebar::Item @route="console.admin.config.services" @icon="bell-concierge">Services</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.mail" @icon="envelope">Mail</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.filesystem" @icon="hard-drive">Filesystem</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.queue" @icon="layer-group">Queue</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.socket" @icon="plug">Socket</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.notification-channels" @icon="tower-broadcast">Notification Channels</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.services" @icon="bell-concierge">{{t "common.services"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.mail" @icon="envelope">{{t "common.mail"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.filesystem" @icon="hard-drive">{{t "common.filesystem"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.queue" @icon="layer-group">{{t "common.queue"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.socket" @icon="plug">{{t "common.socket"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.config.notification-channels" @icon="tower-broadcast">{{t "common.notification-channels"}}</Layout::Sidebar::Item>
|
||||
</Layout::Sidebar::Panel>
|
||||
</EmberWormhole>
|
||||
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
{{page-title "Branding"}}
|
||||
<Layout::Section::Header @title="Branding">
|
||||
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
|
||||
{{page-title (t "console.admin.branding.title")}}
|
||||
<Layout::Section::Header @title={{t "console.admin.branding.title"}}>
|
||||
<Button @type="primary" @size="sm" @icon="save" @text={{t "common.save-button-text"}} @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 300}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-">
|
||||
<ContentPanel @title="Branding" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title={{t "console.admin.branding.title"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form class="flex flex-col" {{on "submit" this.save}}>
|
||||
<div class="input-group">
|
||||
<label>Icon</label>
|
||||
<label>{{t "console.admin.branding.icon-text"}}</label>
|
||||
<div class="flex flex-row items-center space-x-2">
|
||||
<Image src={{@model.icon_url}} @fallbackSrc="/images/icon.png" alt={{t "app.name"}} width="32" height="32" class="w-8 h-8 shadow-sm" />
|
||||
<FileUpload @name="icon" @accept="image/*" @onFileAdded={{this.uploadIcon}} @labelClass="flex flex-row items-center justify-center mb-0i" as |queue|>
|
||||
<FileUpload @name={{t "console.admin.branding.icon-text"}} @accept="image/*" @onFileAdded={{this.uploadIcon}} @labelClass="flex flex-row items-center justify-center mb-0i" as |queue|>
|
||||
<a tabindex={{0}} class="flex items-center px-0 text-xs no-underline truncate btn btn-sm btn-default" disabled={{queue.files.length}}>
|
||||
{{#if queue.files.length}}
|
||||
<div class="mr-1.5">
|
||||
<Spinner />
|
||||
</div>
|
||||
<span>
|
||||
Uploading...
|
||||
{{t "common.uploading"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FaIcon @icon="image" class="mr-1.5" />
|
||||
<span>
|
||||
Upload new
|
||||
{{t "console.admin.branding.upload-new"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
</FileUpload>
|
||||
</div>
|
||||
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" this.unsetIcon}}>Reset to default</a>
|
||||
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" this.unsetIcon}}>{{t "console.admin.branding.reset-default"}}</a>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Logo</label>
|
||||
<label>{{t "console.admin.branding.logo-text"}}</label>
|
||||
<div class="flex flex-row items-center space-x-2">
|
||||
<Image src={{@model.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} width="160" height="56" class="w-40 h-14" />
|
||||
<FileUpload @name="logo" @accept="image/*" @onFileAdded={{this.uploadLogo}} @labelClass="flex flex-row items-center justify-center mb-0i" as |queue|>
|
||||
<FileUpload @name={{t "console.admin.branding.logo-text"}} @accept="image/*" @onFileAdded={{this.uploadLogo}} @labelClass="flex flex-row items-center justify-center mb-0i" as |queue|>
|
||||
<a tabindex={{0}} class="flex items-center px-0 text-xs no-underline truncate btn btn-sm btn-default" disabled={{queue.files.length}}>
|
||||
{{#if queue.files.length}}
|
||||
<div class="mr-1.5">
|
||||
<Spinner />
|
||||
</div>
|
||||
<span>
|
||||
Uploading...
|
||||
{{t "common.uploading"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FaIcon @icon="image" class="mr-1.5" />
|
||||
<span>
|
||||
Upload new
|
||||
{{t "console.admin.branding.upload-new"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
</FileUpload>
|
||||
</div>
|
||||
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" this.unsetLogo}}>Reset to default</a>
|
||||
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" this.unsetLogo}}>{{t "console.admin.branding.reset-default"}}</a>
|
||||
</div>
|
||||
<InputGroup @name="Default Theme">
|
||||
<InputGroup @name={{t "console.admin.branding.theme"}}>
|
||||
<Select @value={{@model.default_theme}} @onSelect={{this.setTheme}} @options={{this.themeOptions}} />
|
||||
</InputGroup>
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title "Database Configuration"}}
|
||||
<Layout::Section::Header @title="Database Configuration" />
|
||||
<Layout::Section::Header @title={{t "console.admin.config.database.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 800}}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title "Filesystem Configuration"}}
|
||||
<Layout::Section::Header @title="Filesystem Configuration" />
|
||||
<Layout::Section::Header @title={{t "console.admin.config.filesystem.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 800}}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title "Mail Configuration"}}
|
||||
<Layout::Section::Header @title="Mail Configuration" />
|
||||
<Layout::Section::Header @title={{t "console.admin.config.mail.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 800}}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title "Notification Channels Configuration"}}
|
||||
<Layout::Section::Header @title="Notification Channels Configuration" />
|
||||
<Layout::Section::Header @title={{t "console.admin.config.notification-channels.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 800}}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title "Queue Configuration"}}
|
||||
<Layout::Section::Header @title="Queue Configuration" />
|
||||
<Layout::Section::Header @title={{t "console.admin.config.queue.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 800}}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title "Services Configuration"}}
|
||||
<Layout::Section::Header @title="Services Configuration" />
|
||||
<Layout::Section::Header @title={{t "console.admin.config.services.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 900}}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title "Socket Configuration"}}
|
||||
<Layout::Section::Header @title="Socket Configuration" />
|
||||
<Layout::Section::Header @title={{t "console.admin.config.socket.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 900}}>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{{page-title "Overview"}}
|
||||
<Layout::Section::Header @title="Overview" />
|
||||
<Layout::Section::Header @title={{t "common.overview"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 800}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<div class="grid grid-cols-3 xs:grid-cols-1 gap-4">
|
||||
<StatWidget @title="Total Users" @value={{@model.total_users}} />
|
||||
<StatWidget @title="Total Organizations" @value={{@model.total_organizations}} />
|
||||
<StatWidget @title="Total Transactions" @value={{@model.total_transactions}} />
|
||||
<StatWidget @title={{t "console.admin.index.total-users"}} @value={{@model.total_users}} />
|
||||
<StatWidget @title={{t "console.admin.index.total-organizations"}} @value={{@model.total_organizations}} />
|
||||
<StatWidget @title={{t "console.admin.index.total-transactions"}} @value={{@model.total_transactions}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{{page-title "Notifications"}}
|
||||
<Layout::Section::Header @title="Notifications">
|
||||
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.saveSettings}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
|
||||
<Layout::Section::Header @title={{t "console.admin.notifications.title"}}>
|
||||
<Button @type="primary" @size="sm" @icon="save" @text={{t "common.save-button-text"}} @onClick={{this.saveSettings}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 1200}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-4">
|
||||
{{#each-in this.groupedNotifications as |groupName notifications|}}
|
||||
<ContentPanel @title={{concat (smart-humanize groupName) " Notification Settings"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<ContentPanel @title={{concat (smart-humanize groupName) (t "console.admin.notifications.notification-settings") }} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#each notifications as |notification|}}
|
||||
<InputGroup @name={{notification.name}} @helpText={{notification.description}}>
|
||||
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
||||
|
||||
28
console/app/templates/console/admin/two-fa-settings.hbs
Normal file
28
console/app/templates/console/admin/two-fa-settings.hbs
Normal file
@@ -0,0 +1,28 @@
|
||||
{{page-title "2FA Config"}}
|
||||
<Layout::Section::Header @title="2FA Config">
|
||||
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.saveSettings}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 1200}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-4">
|
||||
<ContentPanel @title="2FA Config" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#if this.loadSystemTwoFaConfig.isIdle}}
|
||||
<TwoFaSettings
|
||||
@showEnforceOption={{true}}
|
||||
@twoFaMethods={{this.methods}}
|
||||
@twoFaSettings={{this.twoFaSettings}}
|
||||
@onTwoFaToggled={{this.onTwoFaToggled}}
|
||||
@onTwoFaMethodSelected={{this.onTwoFaMethodSelected}}
|
||||
@onTwoFaEnforcedToggled={{this.onTwoFaEnforceToggled}}
|
||||
@isLoading={{this.isLoading}}
|
||||
/>
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<Spinner @loadingMessage="Loading 2FA Config..." @wrapperClass="flex flex-row" @iconClass="mr-2" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
</Layout::Section::Body>
|
||||
@@ -3,9 +3,9 @@
|
||||
<div class="container mx-auto h-screen space-y-4">
|
||||
<div class="flex flex-col items-center justify-center pt-14 px-40">
|
||||
<FaIcon @icon="shapes" @size="4x" class="mb-6 text-blue-500" />
|
||||
<h1 class="dark:text-gray-100 text-black text-4xl font-bold mb-4">Extensions are coming soon!</h1>
|
||||
<h1 class="dark:text-gray-100 text-black text-4xl font-bold mb-4">{{t "console.extensions.title"}}</h1>
|
||||
<p class="dark:text-gray-300 text-black text-lg">
|
||||
Please check back in the upcoming versions as we prepare to launch the Extensions repository and marketplace.
|
||||
{{t "console.extensions.message"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{{page-title "Dashboard"}}
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full pt-6">
|
||||
<div class="console-home-container mx-auto h-screen space-y-4" {{increase-height-by 300}}>
|
||||
<TwoFaEnforcementAlert />
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-4">
|
||||
<GithubCard class="lg:col-span-4" />
|
||||
<FleetbaseBlog class="lg:col-span-8" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Layout::Section::Header @title="Notifications">
|
||||
<Button @icon="check-square" @type="default" @text="Select All" {{on "click" this.selectAll}} class="mr-2" />
|
||||
<Button @icon="envelope" @type="primary" @text="Mark as Read" {{on "click" this.read}} class="mr-2" />
|
||||
<Button @icon="trash" @type="danger" @text="Delete" {{on "click" this.delete}} />
|
||||
<Layout::Section::Header @title={{t "common.notifications"}}>
|
||||
<Button @icon="check-square" @type="default" @text={{t "console.notifications.select-all"}} {{on "click" this.selectAll}} class="mr-2" />
|
||||
<Button @icon="envelope" @type="primary" @text={{t "console.notifications.mark-as-read"}} {{on "click" this.read}} class="mr-2" />
|
||||
<Button @icon="trash" @type="danger" @text={{t "common.delete"}} {{on "click" this.delete}} />
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="h-full w-full">
|
||||
@@ -26,7 +26,7 @@
|
||||
<h1 class="text-sm font-semibold antialiased leading-4">{{notification.data.subject}}</h1>
|
||||
<div class="text-xs antialiased text-gray-900 dark:text-gray-200">- {{notification.data.message}}</div>
|
||||
</div>
|
||||
<div class="text-gray-300 text-xs antialiased mt-1">Received: {{notification.createdAgo}}</div>
|
||||
<div class="text-gray-300 text-xs antialiased mt-1">{{t "console.notifications.received"}} {{notification.createdAgo}}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div>
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center h-full w-full">
|
||||
<p class="text-base text-gray-800 dark:text-gray-300 italic">No notifications to display.</p>
|
||||
<p class="text-base text-gray-800 dark:text-gray-300 italic">{{t "console.notifications.message"}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
{{page-title "Settings"}}
|
||||
{{page-title (t "common.settings")}}
|
||||
<EmberWormhole @to="sidebar-menu-items">
|
||||
<Layout::Sidebar::Panel @open={{true}} @title="Settings">
|
||||
<Layout::Sidebar::Item @route="console.settings.index" @icon="cog">Organization</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Panel @open={{true}} @title={{t "common.settings"}}>
|
||||
<Layout::Sidebar::Item @route="console.settings.index" @icon="cog">{{t "common.organization"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.settings.two-fa" @icon="shield-halved">{{t "common.two-factor"}}</Layout::Sidebar::Item>
|
||||
{{#each this.universe.settingsMenuItems as |menuItem|}}
|
||||
<Layout::Sidebar::Item @onClick={{fn this.universe.transitionMenuItem "console.settings.virtual" menuItem}} @item={{menuItem}} @icon={{menuItem.icon}}>{{menuItem.title}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item
|
||||
@onClick={{fn this.universe.transitionMenuItem "console.settings.virtual" menuItem}}
|
||||
@item={{menuItem}}
|
||||
@icon={{menuItem.icon}}
|
||||
>{{menuItem.title}}</Layout::Sidebar::Item>
|
||||
{{/each}}
|
||||
</Layout::Sidebar::Panel>
|
||||
{{#each this.universe.settingsMenuPanels as |menuPanel|}}
|
||||
<Layout::Sidebar::Panel @open={{menuPanel.open}} @title={{menuPanel.title}}>
|
||||
{{#each menuPanel.items as |menuItem|}}
|
||||
<Layout::Sidebar::Item @onClick={{fn this.universe.transitionMenuItem "console.settings.virtual" menuItem}} @item={{menuItem}} @icon={{menuItem.icon}}>{{menuItem.title}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item
|
||||
@onClick={{fn this.universe.transitionMenuItem "console.settings.virtual" menuItem}}
|
||||
@item={{menuItem}}
|
||||
@icon={{menuItem.icon}}
|
||||
>{{menuItem.title}}</Layout::Sidebar::Item>
|
||||
{{/each}}
|
||||
</Layout::Sidebar::Panel>
|
||||
{{/each}}
|
||||
|
||||
</EmberWormhole>
|
||||
|
||||
{{outlet}}
|
||||
0
console/app/templates/console/settings/auth.hbs
Normal file
0
console/app/templates/console/settings/auth.hbs
Normal file
@@ -1,40 +1,40 @@
|
||||
<Layout::Section::Header @title="Organization Settings" />
|
||||
<Layout::Section::Header @title={{t "console.settings.index.title"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title="Organization Settings" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<ContentPanel @title={{t "console.settings.index.title"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form {{on "submit" this.saveSettings}}>
|
||||
<InputGroup @name="Organization name" @value={{@model.name}} />
|
||||
<InputGroup @name="Organization description" @value={{@model.description}} />
|
||||
<InputGroup @name="Organization phone number">
|
||||
<InputGroup @name={{t "console.settings.index.organization-name"}} @value={{@model.name}} />
|
||||
<InputGroup @name={{t "console.settings.index.organization-description"}} @value={{@model.description}} />
|
||||
<InputGroup @name={{t "console.settings.index.organization-phone"}}>
|
||||
<PhoneInput @value={{@model.phone}} @onInput={{fn (mut @model.phone)}} class="form-input w-full" />
|
||||
</InputGroup>
|
||||
<InputGroup @name="Organization currency">
|
||||
<InputGroup @name={{t "console.settings.index.organization-currency"}}>
|
||||
<CurrencySelect @value={{@model.currency}} @onSelect={{fn (mut @model.currency)}} @triggerClass="w-full form-select" />
|
||||
</InputGroup>
|
||||
<InputGroup @name="Organization ID" @value={{@model.public_id}} @disabled={{true}} />
|
||||
<InputGroup @name={{t "console.settings.index.organization-id"}} @value={{@model.public_id}} @disabled={{true}} />
|
||||
<div class="mt-3 flex items-center justify-end">
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text="Save Changes" @isLoading={{this.isLoading}} />
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text="{{t "common.save-button-text"}}" @isLoading={{this.isLoading}} />
|
||||
</div>
|
||||
</form>
|
||||
</ContentPanel>
|
||||
|
||||
<ContentPanel @title="Organization Branding" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<InputGroup @name="Logo" @helpText="Logo for your organization.">
|
||||
<ContentPanel @title={{t "console.settings.index.organization-branding"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<InputGroup @name={{t "console.settings.index.logo"}} @helpText={{t "console.settings.index.logo-help-text"}}>
|
||||
<div class="flex flex-row items-center">
|
||||
<Image src={{@model.logo_url}} @fallbackSrc={{config "defaultValues.placeholderImage"}} alt={{concat @model.name " logo"}} class="h-20 w-64 border dark:border-gray-900 rounded-md mr-4" />
|
||||
<FileUpload @name="logo" @accept="image/*" @onFileAdded={{fn this.uploadFile "logo"}} as |queue|>
|
||||
<FileUpload @name={{t "console.settings.index.logo"}} @accept="image/*" @onFileAdded={{fn this.uploadFile "logo"}} as |queue|>
|
||||
<a tabindex={{0}} class="flex items-center px-0 mt-2 text-xs no-underline truncate btn btn-sm btn-default">
|
||||
{{#if queue.files.length}}
|
||||
<Spinner class="mr-1" />
|
||||
<span>
|
||||
Uploading...
|
||||
{{t "common.uploading"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FaIcon @icon="image" class="mr-1" />
|
||||
<span>
|
||||
Upload new logo
|
||||
{{t "console.settings.index.upload-new-logo"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
@@ -42,20 +42,20 @@
|
||||
</div>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup @name="Backdrop" @helpText="Optional banner or background image for your organization.">
|
||||
<InputGroup @name={{t "console.settings.index.backdrop"}} @helpText={{t "console.settings.index.backdrop-help-text"}}>
|
||||
<div class="flex flex-row items-center">
|
||||
<Image src={{@model.backdrop_url}} @fallbackSrc={{config "defaultValues.placeholderImage"}} alt={{concat @model.name " backdrop"}} class="h-20 w-64 border dark:border-gray-900 rounded-md mr-4" />
|
||||
<FileUpload @name="backdrop" @accept="image/*" @onFileAdded={{fn this.uploadFile "backdrop"}} as |queue|>
|
||||
<FileUpload @name={{t "console.settings.index.backdrop"}} @accept="image/*" @onFileAdded={{fn this.uploadFile "backdrop"}} as |queue|>
|
||||
<a tabindex={{0}} class="flex items-center px-0 mt-2 text-xs no-underline truncate btn btn-sm btn-default">
|
||||
{{#if queue.files.length}}
|
||||
<Spinner class="mr-1" />
|
||||
<span>
|
||||
Uploading...
|
||||
{{t "common.uploading"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FaIcon @icon="image" class="mr-1" />
|
||||
<span>
|
||||
Upload new backdrop
|
||||
{{t "console.settings.index.upload-new-backdrop"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
|
||||
36
console/app/templates/console/settings/two-fa.hbs
Normal file
36
console/app/templates/console/settings/two-fa.hbs
Normal file
@@ -0,0 +1,36 @@
|
||||
{{page-title "TwoFa"}}
|
||||
<Layout::Section::Header @title="2FA" />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title="2FA Settings" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<div class="mb-3">
|
||||
{{#if this.loadCompanyTwoFaSettings.isIdle}}
|
||||
<TwoFaSettings
|
||||
@showEnforceOption={{true}}
|
||||
@showMethodSelection={{false}}
|
||||
@twoFaMethods={{this.methods}}
|
||||
@twoFaSettings={{this.twoFaSettings}}
|
||||
@onTwoFaToggled={{this.onTwoFaToggled}}
|
||||
@onTwoFaMethodSelected={{this.onTwoFaMethodSelected}}
|
||||
@onTwoFaEnforcedToggled={{this.onTwoFaEnforceToggled}}
|
||||
/>
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<Spinner @loadingMessage="Loading User 2FA Settings..." @wrapperClass="flex flex-row" @iconClass="mr-2" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<Button
|
||||
@type="primary"
|
||||
@buttonType="submit"
|
||||
@text="Save 2FA Settings"
|
||||
@icon="save"
|
||||
@onClick={{this.saveTwoFactor}}
|
||||
@isLoading={{this.enforceTwoFaForCompanyUsers.isRunning}}
|
||||
/>
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
</Layout::Section::Body>
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="border border-black bg-gray-900 shadow-md rounded-md p-5 mt-12">
|
||||
<div class="w-full flex-col items-center justify-center text-center mb-6">
|
||||
<img src="/images/icon.png" alt={{concat (t "app.name") " Install"}} class="w-12 h-12 mx-auto" width="48" height="48" />
|
||||
<h3 class="mt-2 text-gray-50 font-bold">{{t "app.name"}} Installer</h3>
|
||||
<h3 class="mt-2 text-gray-50 font-bold">{{t "app.name"}} {{t "install.installer-header"}}</h3>
|
||||
</div>
|
||||
<div class="space-y-4 mb-5">
|
||||
{{#each this.steps as |step|}}
|
||||
@@ -24,10 +24,10 @@
|
||||
{{#if this.error}}
|
||||
<div class="flex items-center border border-red-900 bg-red-800 text-red-100 px-4 py-1.5 rounded-lg mb-3 shadow-md">
|
||||
<FaIcon @icon="triangle-exclamation" class="text-red-100 mr-2" />
|
||||
<span>The install failed! Click the button below to retry the install.</span>
|
||||
<span>{{t "install.failed-message-sent"}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
<Button @type="primary" @icon="play" @size="lg" @text={{if this.error "Retry Install" "Start Install"}} @wrapperClass="flex-1" class="w-full" @onClick={{this.startInstall}} @isLoading={{this.install.isRunning}} @disabled={{this.install.isRunning}} />
|
||||
<Button @type="primary" @icon="play" @size="lg" @text={{if this.error (t "install.retry-install") (t "install.start-install") }} @wrapperClass="flex-1" class="w-full" @onClick={{this.startInstall}} @isLoading={{this.install.isRunning}} @disabled={{this.install.isRunning}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="mb-8">
|
||||
<img class="mx-auto h-12 w-auto" src={{@brand.icon_url}} alt={{t "app.name"}}>
|
||||
<h2 class="mt-6 text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
You've been invited to join {{@model.name}}
|
||||
{{t "invite.for-users.invitation-message" companyName=@model.name}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -13,22 +13,22 @@
|
||||
<FaIcon @icon="info-circle" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
You've been invited to join the {{@model.name}} organization on {{t "app.name"}}. To accept this invitation, input your invitation code received by email and click continue.
|
||||
{{t "invite.invitation-sent-message" htnmlSafe=true companyName=@model.name appName=(t "app.name")}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="space-y-6" {{on "submit" this.acceptInvite}}>
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-medium text-gray-700 dark:text-gray-50">
|
||||
Your invitiation code
|
||||
{{t "invite.for-users.invitation-code-sent-text"}}
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<Input @value={{this.code}} id="code" name="code" @type="code" required class="form-input form-input-lg w-full" placeholder="Your invitiation code" />
|
||||
<Input @value={{this.code}} id="code" name="code" @type="code" required class="form-input form-input-lg w-full" placeholder={{t "invite.for-users.invitation-code-sent-text"}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text="Accept Invitation" @onClick={{this.acceptInvite}} @isLoading={{this.isLoading}} />
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "invite.for-users.accept-invitation-text"}} @onClick={{this.acceptInvite}} @isLoading={{this.isLoading}} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="mb-4">
|
||||
<Image src={{@model.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} width="160" height="56" class="w-40 h-14 mx-auto" />
|
||||
<h2 class="text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
Create your account
|
||||
{{t "onboard.index.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<FaIcon @icon="hand-spock" @size="lg" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
<strong>Welcome to {{t "app.name"}}!</strong><br />
|
||||
Complete the details required below to get started.
|
||||
{{t "onboard.index.welcome-title" htnmlSafe=true companyName=(t "app.name")}}
|
||||
{{t "onboard.index.welcome-text"}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -20,17 +20,17 @@
|
||||
{{#if this.error}}
|
||||
<InfoBlock @icon="exclamation-triangle" @text={{this.error}} class="mb-6 px-3 py-2 bg-red-300 text-red-900" @textClass="text-red-900" />
|
||||
{{/if}}
|
||||
<InputGroup @name="Your full name" @value={{this.name}} @helpText="Your full name." @inputClass="input-lg" />
|
||||
<InputGroup @name="Your email address" @type="email" @value={{this.email}} @helpText="Your email address." @inputClass="input-lg" />
|
||||
<InputGroup @name="Your phone number" @helpText="Your phone number.">
|
||||
<InputGroup @name={{t "onboard.index.full-name"}} @value={{this.name}} @helpText={{t "onboard.index.full-name"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.your-email"}} @type="email" @value={{this.email}} @helpText={{t "onboard.index.your-email"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "common.phone"}} @helpText={{t "onboard.index.phone"}}>
|
||||
<PhoneInput @onInput={{fn (mut this.phone)}} class="form-input input-lg w-full" />
|
||||
</InputGroup>
|
||||
<InputGroup @name="Organization name" @value={{this.organization_name}} @helpText="Your organization name, all your services and resources will be managed under this organization, later you can create as many organizations as you want or need." @inputClass="input-lg" />
|
||||
<InputGroup @name="Enter a password" @value={{this.password}} @type="password" @helpText="Your password, make sure it's a good one." @inputClass="input-lg" />
|
||||
<InputGroup @name="Confirm your password" @value={{this.password_confirmation}} @type="password" @helpText="Just to confirm the password you entered above." @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.organization-name"}} @value={{this.organization_name}} @helpText={{t "onboard.index.organization-name"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.password"}} @value={{this.password}} @type="password" @helpText={{t "onboard.index.password"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.confirm-password"}} @value={{this.password_confirmation}} @type="password" @helpText={{t "onboard.index.confirm-password"}} @inputClass="input-lg" />
|
||||
|
||||
<div class="flex items-center justify-end mt-5">
|
||||
<Button @icon="check" @iconPrefix="fas" @type="primary" @size="lg" @text="Continue" @isLoading={{this.isLoading}} @disabled={{this.readyToSubmit}} @onClick={{this.startOnboard}} />
|
||||
<Button @icon="check" @iconPrefix="fas" @type="primary" @size="lg" @text={{t "onboard.index.continue-button"}} @isLoading={{this.isLoading}} @disabled={{this.readyToSubmit}} @onClick={{this.startOnboard}} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,9 +1,9 @@
|
||||
{{page-title "Account Verification"}}
|
||||
<div class="bg-white dark:bg-gray-800 py-8 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-8">
|
||||
<img class="mx-auto h-12 w-auto " src="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}}>
|
||||
<img class="mx-auto h-12 w-auto" src="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}}>
|
||||
<h2 class="mt-6 text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
Verify your email address
|
||||
{{t "onboard.verify-email.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
<FaIcon @icon="shield-check" @size="lg" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
<strong>Almost done!</strong><br> Check your email for a verification code.
|
||||
{{t "onboard.verify-email.message-text" htnmlSafe=true}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="mt-8 space-y-6" {{on "submit" this.verifyCode}}>
|
||||
<InputGroup @type="tel" @name="Verification Code" @value={{this.code}} @helpText="Enter the verification code you received via email." @inputClass="input-lg" {{on "input" this.validateInput}} />
|
||||
<InputGroup @type="tel" @name="Verification Code" @value={{this.code}} @helpText={{t "onboard.verify-email.verification-code-text"}} @inputClass="input-lg" {{on "input" this.validateInput}} />
|
||||
|
||||
<div>
|
||||
<Button @icon="check" @iconPrefix="fas" @buttonType="submit" @type="primary" @size="lg" @text="Verify & Continue" @isLoading={{this.isLoading}} @disabled={{this.isNotReadyToSubmit}} @onClick={{this.verifyCode}} />
|
||||
<Button @icon="check" @iconPrefix="fas" @buttonType="submit" @type="primary" @size="lg" @text={{t "onboard.verify-email.verify-button-text"}} @isLoading={{this.isLoading}} @disabled={{this.isNotReadyToSubmit}} @onClick={{this.verifyCode}} />
|
||||
</div>
|
||||
|
||||
{{#if this.stillWaiting}}
|
||||
@@ -30,14 +30,14 @@
|
||||
<FaIcon @icon="exclamation-triangle" @size="lg" class="text-yellow-400" />
|
||||
</div>
|
||||
<div class="ml-3 flex items-center">
|
||||
<span class="text-lg font-extrabold text-yellow-800">Didn't receive an email yet?</span>
|
||||
<span class="text-lg font-extrabold text-yellow-800">{{t "onboard.verify-email.not-sent.message"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-3">
|
||||
<p class="text-yellow-700">Use alternaitve options below to verify your account.</p>
|
||||
<p class="text-yellow-700">{{t "onboard.verify-email.not-sent.alternative-choice"}}</p>
|
||||
<div class="flex items-center mt-3">
|
||||
<Button @type="default" class="mr-2" @onClick={{this.resendEmail}}>Resend Email</Button>
|
||||
<Button @type="default" @onClick={{this.resendBySms}}>Send by SMS</Button>
|
||||
<Button @type="default" class="mr-2" @onClick={{this.resendEmail}}>{{t "onboard.verify-email.not-sent.resent-button-text"}}</Button>
|
||||
<Button @type="default" @onClick={{this.resendBySms}}>{{t "onboard.verify-email.not-sent.sms-button-text"}}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
12
console/app/utils/get-two-fa-methods.js
Normal file
12
console/app/utils/get-two-fa-methods.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function getTwoFaMethods() {
|
||||
return [
|
||||
// {
|
||||
// key: 'authenticator_app',
|
||||
// name: 'Authenticator App',
|
||||
// description: 'Get codes from an app like Authy, 1Password, Microsoft Authenticator, or Google Authenticator',
|
||||
// recommended: true,
|
||||
// },
|
||||
{ key: 'sms', name: 'SMS', description: 'Receive a unique code via SMS' },
|
||||
{ key: 'email', name: 'Email', description: 'Receive a unique code via Email' },
|
||||
];
|
||||
}
|
||||
@@ -14,7 +14,7 @@ module.exports = function (/* environment */) {
|
||||
* @type {String?}
|
||||
* @default "null"
|
||||
*/
|
||||
fallbackLocale: null,
|
||||
fallbackLocale: 'en-us',
|
||||
|
||||
/**
|
||||
* Path where translations are stored. This is relative to the project root.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fleetbase/console",
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.8",
|
||||
"private": true,
|
||||
"description": "Fleetbase Console",
|
||||
"repository": "https://github.com/fleetbase/fleetbase",
|
||||
@@ -21,6 +21,7 @@
|
||||
"lint:hbs:fix": "ember-template-lint . --fix",
|
||||
"lint:js": "eslint . --cache",
|
||||
"lint:js:fix": "eslint . --fix",
|
||||
"lint:intl": "fleetbase-intl-lint",
|
||||
"start": "pnpm run prebuild && ember serve",
|
||||
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
|
||||
"test:ember": "ember test"
|
||||
@@ -28,11 +29,11 @@
|
||||
"dependencies": {
|
||||
"@fleetbase/ember-core": "^0.2.0",
|
||||
"@fleetbase/ember-ui": "^0.2.9",
|
||||
"@fleetbase/storefront-engine": "^0.2.6",
|
||||
"@fleetbase/fleetops-engine": "^0.3.9",
|
||||
"@fleetbase/storefront-engine": "^0.2.8",
|
||||
"@fleetbase/fleetops-engine": "^0.4.1",
|
||||
"@fleetbase/fleetops-data": "^0.1.7",
|
||||
"@fleetbase/dev-engine": "^0.2.0",
|
||||
"@fleetbase/iam-engine": "^0.0.8",
|
||||
"@fleetbase/dev-engine": "^0.2.1",
|
||||
"@fleetbase/iam-engine": "^0.0.9",
|
||||
"@fleetbase/leaflet-routing-machine": "^3.2.16",
|
||||
"@ember/legacy-built-in-components": "^0.4.1",
|
||||
"@fortawesome/ember-fontawesome": "^0.4.1",
|
||||
@@ -53,6 +54,7 @@
|
||||
"postcss-nth-list": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fleetbase/intl-lint": "^0.0.1",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/eslint-parser": "^7.22.15",
|
||||
"@babel/plugin-proposal-decorators": "^7.23.2",
|
||||
|
||||
2945
console/pnpm-lock.yaml
generated
2945
console/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ Router.map(function () {
|
||||
this.route('login', { path: '/' });
|
||||
this.route('forgot-password');
|
||||
this.route('reset-password');
|
||||
this.route('two-fa');
|
||||
this.route('verification');
|
||||
});
|
||||
this.route('onboard', function () {
|
||||
@@ -26,9 +27,11 @@ Router.map(function () {
|
||||
this.route('notifications');
|
||||
this.route('account', function () {
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
this.route('auth');
|
||||
});
|
||||
this.route('settings', function () {
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
this.route('two-fa');
|
||||
});
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
this.route('admin', function () {
|
||||
@@ -44,6 +47,7 @@ Router.map(function () {
|
||||
});
|
||||
this.route('branding');
|
||||
this.route('notifications');
|
||||
this.route('two-fa-settings');
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
});
|
||||
});
|
||||
|
||||
26
console/tests/integration/components/configure/2fa-test.js
Normal file
26
console/tests/integration/components/configure/2fa-test.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | configure/2fa', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<Configure::2fa />`);
|
||||
|
||||
assert.dom(this.element).hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<Configure::2fa>
|
||||
template block text
|
||||
</Configure::2fa>
|
||||
`);
|
||||
|
||||
assert.dom(this.element).hasText('template block text');
|
||||
});
|
||||
});
|
||||
26
console/tests/integration/components/locale-selector-test.js
Normal file
26
console/tests/integration/components/locale-selector-test.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | locale-selector', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<LocaleSelector />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<LocaleSelector>
|
||||
template block text
|
||||
</LocaleSelector>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | two-fa-enforcement-alert', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<TwoFaEnforcementAlert />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<TwoFaEnforcementAlert>
|
||||
template block text
|
||||
</TwoFaEnforcementAlert>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
});
|
||||
});
|
||||
26
console/tests/integration/components/two-fa-settings-test.js
Normal file
26
console/tests/integration/components/two-fa-settings-test.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | two-fa-settings', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<TwoFaSettings />`);
|
||||
|
||||
assert.dom(this.element).hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<TwoFaSettings>
|
||||
template block text
|
||||
</TwoFaSettings>
|
||||
`);
|
||||
|
||||
assert.dom(this.element).hasText('template block text');
|
||||
});
|
||||
});
|
||||
12
console/tests/unit/controllers/auth/two-fa-test.js
Normal file
12
console/tests/unit/controllers/auth/two-fa-test.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Controller | auth/two-fa', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup('controller:auth/two-fa');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
||||
12
console/tests/unit/controllers/console/account/auth-test.js
Normal file
12
console/tests/unit/controllers/console/account/auth-test.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Controller | console/account/auth', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup('controller:console/account/auth');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Controller | console/admin/two-fa-settings', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup('controller:console/admin/two-fa-settings');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
||||
12
console/tests/unit/controllers/console/settings/auth-test.js
Normal file
12
console/tests/unit/controllers/console/settings/auth-test.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Controller | console/settings/auth', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup('controller:console/settings/auth');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Controller | console/settings/two-fa', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup('controller:console/settings/two-fa');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
||||
11
console/tests/unit/routes/auth/two-fa-test.js
Normal file
11
console/tests/unit/routes/auth/two-fa-test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | auth/two-fa', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:auth/two-fa');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
11
console/tests/unit/routes/console/account/auth-test.js
Normal file
11
console/tests/unit/routes/console/account/auth-test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/account/auth', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/account/auth');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/admin/two-fa-settings', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/admin/two-fa-settings');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
11
console/tests/unit/routes/console/settings/auth-test.js
Normal file
11
console/tests/unit/routes/console/settings/auth-test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/settings/auth', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/settings/auth');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
11
console/tests/unit/routes/console/settings/two-fa-test.js
Normal file
11
console/tests/unit/routes/console/settings/two-fa-test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/settings/two-fa', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/settings/two-fa');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
23
console/tests/unit/serializers/two-fa-settings-test.js
Normal file
23
console/tests/unit/serializers/two-fa-settings-test.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | two fa settings', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('two-fa-settings');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('two-fa-settings', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
10
console/tests/unit/utils/get-two-fa-methods-test.js
Normal file
10
console/tests/unit/utils/get-two-fa-methods-test.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import getTwoFaMethods from '@fleetbase/console/utils/get-two-fa-methods';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | get-two-fa-methods', function () {
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it works', function (assert) {
|
||||
let result = getTwoFaMethods();
|
||||
assert.ok(result);
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,5 @@
|
||||
app:
|
||||
name: Fleetbase
|
||||
|
||||
layout:
|
||||
header:
|
||||
menus:
|
||||
organization:
|
||||
settings: Organization settings
|
||||
create-or-join: Create or join organizations
|
||||
explore-extensions: Explore extensions
|
||||
user:
|
||||
view-profile: View profile
|
||||
keyboard-shortcuts: Show keyboard shortcuts
|
||||
changelog: Changelog
|
||||
|
||||
terms:
|
||||
new: New
|
||||
sort: Sort
|
||||
@@ -25,9 +12,198 @@ terms:
|
||||
dashboard: Dashboard
|
||||
search: Search
|
||||
search-input: Search Input
|
||||
|
||||
common:
|
||||
search: Search
|
||||
common:
|
||||
2fa-config: 2FA Config
|
||||
account: Account
|
||||
admin: Admin
|
||||
branding: Branding
|
||||
columns: Columns
|
||||
dashboard: Dashboard
|
||||
date-of-birth: Date of Birth
|
||||
delete: Delete
|
||||
email: Email
|
||||
filesystem: Filesystem
|
||||
filter: Filer
|
||||
home: Home
|
||||
logout: Logout
|
||||
mail: Mail
|
||||
name: Name
|
||||
new: New
|
||||
notification-channels: Notification Channels
|
||||
notifications: Notifications
|
||||
organization: Organization
|
||||
overview: Overview
|
||||
phone: Your phone number
|
||||
profile: Profile
|
||||
queue: Queue
|
||||
save-button-text: Save Changes
|
||||
search-input: Search Input
|
||||
|
||||
|
||||
search: Search
|
||||
services: Services
|
||||
settings: Settings
|
||||
socket: Socket
|
||||
sort: Sort
|
||||
two-factor: Two Factor
|
||||
uploading: Uploading...
|
||||
your-profile: Your Profile
|
||||
component:
|
||||
two-fa-enforcement-alert:
|
||||
message: To enhance the security of your account, your organization requires Two-Factor Authentication (2FA). Enable 2FA in your account settings for an additional layer of protection.
|
||||
button-text: Setup 2FA
|
||||
auth:
|
||||
two-fa:
|
||||
verify-code:
|
||||
invalid-session-error-notification: Invalid session. Please try again.
|
||||
verification-successful-notification: Verification successful!
|
||||
verification-code-expired-notification: Verification code has expired. Please request a new one.
|
||||
verification-code-failed-notification: Verification failed. Please try again.
|
||||
resend-code:
|
||||
verification-code-resent-notification: New verification code sent.
|
||||
verification-code-resent-error-notification: Error resending verification code. Please try again.
|
||||
forgot-password:
|
||||
success-message: Check your email to continue!
|
||||
is-sent:
|
||||
title: Almost Done!
|
||||
message: <strong>Check your email!</strong><br> We've sent you a magic link to your email which will allow you to reset your password. The link expires in 15 minutes.
|
||||
not-sent:
|
||||
title: Forgot your password?
|
||||
message: <strong>Don't worry, we've got your back.</strong><br> Enter the email you use to login to {appName} and we will send you a secure link to reset your password.
|
||||
form:
|
||||
email-label: Your email address
|
||||
submit-button: OK, Send me a magic link!
|
||||
nevermind-button: Nevermind
|
||||
login:
|
||||
title: Sign in to your account
|
||||
no-identity-notification: Did you forget to enter your email?
|
||||
no-password-notification: Did you forget to enter your password?
|
||||
unverified-notification: Your account needs to be verified to proceed.
|
||||
failed-attempt:
|
||||
message: <strong>Forgot your password?</strong><br> Click the button below to reset your password.
|
||||
button-text: Ok, help me reset!
|
||||
form:
|
||||
email-label: Email address
|
||||
password-label: Password
|
||||
remember-me-label: Remember me
|
||||
forgot-password-label: Forgot your password?
|
||||
sign-in-button: Sign in
|
||||
create-account-button: Create a new Account
|
||||
slow-connection-message: Experiencing connectivity issues.
|
||||
reset-password:
|
||||
success-message: Your password has been reset! Login to continue.
|
||||
title: Reset your password
|
||||
form:
|
||||
code:
|
||||
label: Your reset code
|
||||
help-text: The verification code you received in your email.
|
||||
password:
|
||||
label: New Password
|
||||
help-text: Enter a password at-least 6 characters to continue.
|
||||
confirm-password:
|
||||
label: Confirm new Password
|
||||
help-text: Enter a password at-least 6 characters to continue.
|
||||
submit-button: Reset Password
|
||||
back-button: Back
|
||||
console:
|
||||
account:
|
||||
index:
|
||||
upload-new: Upload new
|
||||
phone: Your phone number.
|
||||
photos: photos
|
||||
admin:
|
||||
config:
|
||||
database:
|
||||
title: Database Configuration
|
||||
filesystem:
|
||||
title: Filesystem Configuration
|
||||
mail:
|
||||
title: Mail Configuration
|
||||
notification-channels:
|
||||
title: Notification Channels Configuration
|
||||
queue:
|
||||
title: Queue Configuration
|
||||
services:
|
||||
title: Services Configuration
|
||||
socket:
|
||||
title: Socket Configuration
|
||||
branding:
|
||||
title: Branding
|
||||
icon-text: Icon
|
||||
upload-new: Upload new
|
||||
reset-default: Reset to default
|
||||
logo-text: Logo
|
||||
theme: Default Theme
|
||||
index:
|
||||
total-users: Total Users
|
||||
total-organizations: Total Organizations
|
||||
total-transactions: Total Transactions
|
||||
notifications:
|
||||
title: Notifications
|
||||
notification-settings: Notification Settings
|
||||
settings:
|
||||
index:
|
||||
title: Organization Settings
|
||||
organization-name: Organization name
|
||||
organization-description: Organization description
|
||||
organization-phone: Organization phone number
|
||||
organization-currency: Organization currency
|
||||
organization-id: Organization ID
|
||||
organization-branding: Organization Branding
|
||||
logo: Logo
|
||||
logo-help-text: Logo for your organization.
|
||||
upload-new-logo: Upload new logo
|
||||
backdrop: Backdrop
|
||||
backdrop-help-text: Optional banner or background image for your organization.
|
||||
upload-new-backdrop: Upload new backdrop
|
||||
extensions:
|
||||
title: Extensions are coming soon!
|
||||
message: Please check back in the upcoming versions as we prepare to launch the Extensions repository and marketplace.
|
||||
notifications:
|
||||
select-all: Select All
|
||||
mark-as-read: Mark as Read
|
||||
received: >-
|
||||
Received:
|
||||
message: No notifications to display.
|
||||
invite:
|
||||
for-users:
|
||||
invitation-message: You've been invited to join {companyName}
|
||||
invitation-sent-message: You've been invited to join the {companyName} organization on {appName}. To accept this invitation, input your invitation code received by email and click continue.
|
||||
invitation-code-sent-text: Your invitiation code
|
||||
accept-invitation-text: Accept Invitation
|
||||
onboard:
|
||||
index:
|
||||
title: Create your account
|
||||
welcome-title: <strong>Welcome to {companyName}!</strong><br />
|
||||
welcome-text: Complete the details required below to get started.
|
||||
full-name: Your full name.
|
||||
your-email: Your email address.
|
||||
phone: Your phone number.
|
||||
organization-name: Your organization name, all your services and resources will be managed under this organization, later you can create as many organizations as you want or need.
|
||||
password: Your password, make sure it's a good one.
|
||||
confirm-password: Just to confirm the password you entered above.
|
||||
continue-button: Continue
|
||||
verify-email:
|
||||
title: Verify your email address
|
||||
message-text: <strong>Almost done!</strong><br> Check your email for a verification code.
|
||||
verification-code-text: Enter the verification code you received via email.
|
||||
verify-button-text: Verify & Continue
|
||||
not-sent:
|
||||
message: Didn't receive an email yet?
|
||||
alternative-choice: Use alternaitve options below to verify your account.
|
||||
resent-button-text: Resend Email
|
||||
sms-button-text: Send by SMS
|
||||
install:
|
||||
installer-header: Installer
|
||||
failed-message-sent: The install failed! Click the button below to retry the install.
|
||||
retry-install: Retry Install
|
||||
start-install: Start Install
|
||||
layout:
|
||||
header:
|
||||
menus:
|
||||
organization:
|
||||
settings: Organization settings
|
||||
create-or-join: Create or join organizations
|
||||
explore-extensions: Explore extensions
|
||||
user:
|
||||
view-profile: View profile
|
||||
keyboard-shortcuts: Show keyboard shortcuts
|
||||
changelog: Changelog
|
||||
|
||||
@@ -37,6 +37,8 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
# Set some build ENV variables
|
||||
ENV LOG_CHANNEL=stdout
|
||||
ENV CACHE_DRIVER=null
|
||||
ENV BROADCAST_DRIVER=socketcluster
|
||||
ENV QUEUE_CONNECTION=redis
|
||||
|
||||
# For development only
|
||||
WORKDIR /var/www/html
|
||||
|
||||
Submodule packages/core-api updated: 7faca511a7...b1a6b5f461
Submodule packages/dev-engine updated: 916a0d1ad3...64a379ce12
Submodule packages/ember-core updated: c5e746370f...4282fa8828
Submodule packages/ember-ui updated: c6694206e9...1ee3606ea5
Submodule packages/fleetops updated: 687c82780e...5881b878ce
Submodule packages/fleetops-data updated: 08520b3981...5ad2d8cfcb
Submodule packages/iam-engine updated: ff3b4bd67e...99698152e2
Submodule packages/pallet updated: bdfd1c9f5a...9e7592eddb
Submodule packages/storefront updated: 1afef1deb7...cd1bb7f73e
Reference in New Issue
Block a user