mirror of
https://github.com/fleetbase/fleetbase.git
synced 2025-12-27 17:47:07 +00:00
Compare commits
3 Commits
v0.4.4
...
feature/so
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
435552f332 | ||
|
|
e0615c5a9b | ||
|
|
a830e190fa |
@@ -14,7 +14,6 @@ concourse/
|
||||
infra/*
|
||||
vagrant/*
|
||||
docker/Dockerfile
|
||||
docker/database/
|
||||
deploy/*
|
||||
media/*
|
||||
data/*
|
||||
@@ -24,4 +23,4 @@ docker-compose-prod.yml
|
||||
docker-compose.yml
|
||||
$virtualenv.tar.gz
|
||||
$node_modules.tar.gz
|
||||
docker-compose.override.yml
|
||||
docker-compose.override.yml
|
||||
190
.github/workflows/cd.yml
vendored
190
.github/workflows/cd.yml
vendored
@@ -2,15 +2,14 @@ name: Fleetbase CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["deploy/*"]
|
||||
branches: [ "deploy/*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PROJECT: ${{ secrets.PROJECT }}
|
||||
GITHUB_AUTH_KEY: ${{ secrets._GITHUB_AUTH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build_service:
|
||||
@@ -18,52 +17,59 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
contents: read # This is required for actions/checkout
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Set Dynamic ENV Vars
|
||||
run: |
|
||||
- name: Set Dynamic ENV Vars
|
||||
run: |
|
||||
SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c -8)
|
||||
echo "VERSION=${SHORT_COMMIT}" >> $GITHUB_ENV
|
||||
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_NUMBER }}:role/${{ env.PROJECT }}-${{ env.STACK }}-deployer
|
||||
role-session-name: github
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_NUMBER }}:role/${{ env.PROJECT }}-${{ env.STACK }}-deployer
|
||||
role-session-name: github
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v1
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v1
|
||||
|
||||
- name: Build and Release
|
||||
uses: docker/bake-action@v2
|
||||
env:
|
||||
REGISTRY: ${{ steps.login-ecr.outputs.registry }}/${{ env.PROJECT }}-${{ env.STACK }}
|
||||
VERSION: ${{ env.VERSION }}
|
||||
GITHUB_AUTH_KEY: ${{ env.GITHUB_AUTH_KEY }}
|
||||
CACHE: type=gha
|
||||
with:
|
||||
push: true
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
- name: Prepare Composer Auth Secret
|
||||
run: |
|
||||
if [[ -n "${{ secrets._GITHUB_AUTH_TOKEN }}" ]]; then
|
||||
echo '{"github-oauth": {"github.com": "'${{ secrets._GITHUB_AUTH_TOKEN }}'"}}' > composer-auth.json
|
||||
else
|
||||
echo '{}' > composer-auth.json
|
||||
fi
|
||||
|
||||
- name: Download ecs-tool
|
||||
run: |
|
||||
- name: Build and Release
|
||||
uses: docker/bake-action@v2
|
||||
env:
|
||||
REGISTRY: ${{ steps.login-ecr.outputs.registry }}/${{ env.PROJECT }}-${{ env.STACK }}
|
||||
VERSION: ${{ env.VERSION }}
|
||||
CACHE: type=gha
|
||||
with:
|
||||
push: true
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
|
||||
- name: Download ecs-tool
|
||||
run: |
|
||||
wget -O ecs-tool.tar.gz https://github.com/springload/ecs-tool/releases/download/1.9.6/ecs-tool_1.9.6_linux_amd64.tar.gz && tar -xvf ecs-tool.tar.gz ecs-tool
|
||||
|
||||
- name: Deploy the images 🚀
|
||||
run: |-
|
||||
|
||||
- name: Deploy the images 🚀
|
||||
run: |-
|
||||
set -eu
|
||||
# run deploy.sh script before deployments
|
||||
env "ECS_RUN.SERVICE=app" "ECS_RUN.LAUNCH_TYPE=FARGATE" ./ecs-tool run -l "ecs-tool" --image_tag '{container_name}-${{ env.VERSION }}' --cluster ${{ env.PROJECT }}-${{ env.STACK }} --task_definition ${{ env.PROJECT }}-${{ env.STACK }}-app --container_name app ./deploy.sh
|
||||
@@ -75,29 +81,29 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
contents: read # This is required for actions/checkout
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set Dynamic ENV Vars
|
||||
run: |
|
||||
- name: Set Dynamic ENV Vars
|
||||
run: |
|
||||
SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c -8)
|
||||
echo "VERSION=${SHORT_COMMIT}" >> $GITHUB_ENV
|
||||
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_NUMBER }}:role/${{ env.PROJECT }}-${{ env.STACK }}-deployer
|
||||
role-session-name: github
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_NUMBER }}:role/${{ env.PROJECT }}-${{ env.STACK }}-deployer
|
||||
role-session-name: github
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Get infra-provided configuration
|
||||
run: |
|
||||
- name: Get infra-provided configuration
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
wget -O- https://github.com/springload/ssm-parent/releases/download/1.8.0/ssm-parent_1.8.0_linux_amd64.tar.gz | tar xvzf - ssm-parent
|
||||
@@ -106,52 +112,52 @@ jobs:
|
||||
# remove double quotes and pipe into the env
|
||||
cat /tmp/dotenv.file | sed -e 's/"//g' >> $GITHUB_ENV
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm Store Directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- name: Get pnpm Store Directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm Cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm Cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Check for _GITHUB_AUTH_TOKEN and create .npmrc
|
||||
run: |
|
||||
if [[ -n "${{ secrets._GITHUB_AUTH_TOKEN }}" ]]; then
|
||||
echo "//npm.pkg.github.com/:_authToken=${{ secrets._GITHUB_AUTH_TOKEN }}" > .npmrc
|
||||
fi
|
||||
working-directory: ./console
|
||||
- name: Check for _GITHUB_AUTH_TOKEN and create .npmrc
|
||||
run: |
|
||||
if [[ -n "${{ secrets._GITHUB_AUTH_TOKEN }}" ]]; then
|
||||
echo "//npm.pkg.github.com/:_authToken=${{ secrets._GITHUB_AUTH_TOKEN }}" > .npmrc
|
||||
fi
|
||||
working-directory: ./console
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./console
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./console
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
set -eu
|
||||
- name: Build
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
pnpm build
|
||||
working-directory: ./console
|
||||
|
||||
- name: Deploy Console 🚀
|
||||
run: |
|
||||
pnpm build
|
||||
working-directory: ./console
|
||||
|
||||
- name: Deploy Console 🚀
|
||||
run: |
|
||||
set -u
|
||||
|
||||
DEPLOY_BUCKET=${STATIC_DEPLOY_BUCKET:-${{ env.PROJECT }}-${{ env.STACK }}}
|
||||
|
||||
185
.github/workflows/gcp-cd.yml
vendored
185
.github/workflows/gcp-cd.yml
vendored
@@ -1,185 +0,0 @@
|
||||
name: Fleetbase CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "gcpdeploy/*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PROJECT: ${{ vars.PROJECT }}
|
||||
REGISTRY: ${{ vars.REGISTRY }}
|
||||
SOCKETCLUSTER_HOST: ${{ vars.SOCKETCLUSTER_HOST }}
|
||||
API_HOST: ${{ vars.API_HOST }}
|
||||
K8S_CLUSTER_NAME: ${{ vars.K8S_CLUSTER_NAME }}
|
||||
K8S_CLUSTER_LOCATION: ${{ vars.K8S_CLUSTER_LOCATION }}
|
||||
GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
GCP_SERVICE_ACCOUNT: ${{ vars.GCP_SERVICE_ACCOUNT }}
|
||||
GCP: "True" # switches docker builds to GCP-style registry
|
||||
|
||||
jobs:
|
||||
build_service:
|
||||
name: Build and Deploy the Service
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Set Dynamic ENV Vars
|
||||
run: |
|
||||
SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c -8)
|
||||
echo "VERSION=${SHORT_COMMIT}" >> $GITHUB_ENV
|
||||
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
|
||||
echo "REGISTRY_HOST=$(dirname $(dirname $REGISTRY))" >> $GITHUB_ENV
|
||||
|
||||
- id: 'auth'
|
||||
name: 'Authenticate to Google Cloud'
|
||||
uses: 'google-github-actions/auth@v1'
|
||||
with:
|
||||
token_format: "access_token"
|
||||
create_credentials_file: true
|
||||
workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
service_account: ${{ env.GCP_SERVICE_ACCOUNT }}
|
||||
|
||||
- name: 'Set up Cloud SDK'
|
||||
uses: 'google-github-actions/setup-gcloud@v1'
|
||||
|
||||
- id: 'get-credentials'
|
||||
uses: 'google-github-actions/get-gke-credentials@v1'
|
||||
with:
|
||||
cluster_name: ${{ env.K8S_CLUSTER_NAME }}
|
||||
location: ${{ env.K8S_CLUSTER_LOCATION }}
|
||||
|
||||
- uses: 'docker/login-action@v3'
|
||||
with:
|
||||
registry: ${{ env.REGISTRY_HOST }}
|
||||
username: 'oauth2accesstoken'
|
||||
password: '${{ steps.auth.outputs.access_token }}'
|
||||
|
||||
- name: Prepare Composer Auth Secret
|
||||
run: |
|
||||
if [[ -n "${{ secrets._GITHUB_AUTH_TOKEN }}" ]]; then
|
||||
echo '{"github-oauth": {"github.com": "'${{ secrets._GITHUB_AUTH_TOKEN }}'"}}' > composer-auth.json
|
||||
else
|
||||
echo '{}' > composer-auth.json
|
||||
fi
|
||||
|
||||
- name: nullify ssm-parent config
|
||||
run: |
|
||||
# this is needed to disable ssm-parent, which is used on AWS
|
||||
echo > api/.ssm-parent.yaml
|
||||
|
||||
- name: Build and Release
|
||||
uses: docker/bake-action@v2
|
||||
env:
|
||||
REGISTRY: ${{ env.REGISTRY }}
|
||||
VERSION: ${{ env.VERSION }}
|
||||
CACHE: type=gha
|
||||
with:
|
||||
push: true
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
|
||||
- name: deploy with helm
|
||||
run: |
|
||||
helm upgrade -i fleetbase infra/helm -n ${{ env.PROJECT }}-${{ env.STACK }} --set image.repository=${{ env.REGISTRY }} --set image.tag=${{ env.VERSION }} --set 'api_host=${{ env.API_HOST }}' --set 'socketcluster_host=${{ env.SOCKETCLUSTER_HOST }}' --set 'ingress.annotations.kubernetes\.io/ingress\.global-static-ip-name=${{ env.PROJECT }}-${{ env.STACK }}'
|
||||
|
||||
build_frontend:
|
||||
name: Build and Deploy the Console
|
||||
needs: [build_service]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set Dynamic ENV Vars
|
||||
run: |
|
||||
SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c -8)
|
||||
echo "VERSION=${SHORT_COMMIT}" >> $GITHUB_ENV
|
||||
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
|
||||
|
||||
- id: 'auth'
|
||||
name: 'Authenticate to Google Cloud'
|
||||
uses: 'google-github-actions/auth@v1'
|
||||
with:
|
||||
token_format: "access_token"
|
||||
create_credentials_file: true
|
||||
workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
service_account: ${{ env.GCP_SERVICE_ACCOUNT }}
|
||||
|
||||
- name: 'Set up Cloud SDK'
|
||||
uses: 'google-github-actions/setup-gcloud@v1'
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm Store Directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm Cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Check for _GITHUB_AUTH_TOKEN and create .npmrc
|
||||
run: |
|
||||
if [[ -n "${{ secrets._GITHUB_AUTH_TOKEN }}" ]]; then
|
||||
echo "//npm.pkg.github.com/:_authToken=${{ secrets._GITHUB_AUTH_TOKEN }}" > .npmrc
|
||||
fi
|
||||
working-directory: ./console
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./console
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
SOCKETCLUSTER_HOST: ${{ env.SOCKETCLUSTER_HOST }}
|
||||
SOCKETCLUSTER_SECURE: "true"
|
||||
SOCKETCLUSTER_PORT: "443"
|
||||
API_HOST: ${{ env.API_HOST }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
pnpm build
|
||||
working-directory: ./console
|
||||
|
||||
- name: Deploy Console 🚀
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
gcloud app deploy --appyaml console/app.yaml console/dist
|
||||
# leave 2 versions
|
||||
gcloud app versions list --filter="traffic_split=0" --sort-by '~version' --format 'value(version.id)' | sed '1d' | xargs -r gcloud app versions delete
|
||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -17,19 +17,10 @@ api/composer-install-dev.sh
|
||||
api/auth.json
|
||||
act.sh
|
||||
composer-auth.json
|
||||
docker/database/*
|
||||
docker/database/mysql/*
|
||||
.talismanrc
|
||||
verdaccio/minio
|
||||
verdaccio/config/htpasswd
|
||||
verdaccio/storage
|
||||
# private packages
|
||||
packages/billing
|
||||
packages/flespi
|
||||
packages/loconav
|
||||
packages/internals
|
||||
packages/billing-api
|
||||
packages/billing-engine
|
||||
packages/flespi-engine
|
||||
packages/flespi-integration
|
||||
packages/projectargus-engine
|
||||
# wip
|
||||
packages/solid
|
||||
solid
|
||||
verdaccio
|
||||
docker/database/*
|
||||
docker/database/mysql/*
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -39,3 +39,6 @@
|
||||
[submodule "docs/api-reference"]
|
||||
path = docs/api-reference
|
||||
url = git@github.com:fleetbase/api-reference.git
|
||||
[submodule "packages/solid"]
|
||||
path = packages/solid
|
||||
url = git@github.com:fleetbase/solid.git
|
||||
|
||||
@@ -7,11 +7,7 @@ routes:
|
||||
headers:
|
||||
Cache-Control: "max-age=600, no-transform, public"
|
||||
gzip: false
|
||||
- route: "^.+\\.(xml|json)$"
|
||||
headers:
|
||||
Cache-Control: "max-age=600, no-transform, public"
|
||||
gzip: true
|
||||
- route: "^.+\\.(html)$"
|
||||
- route: "^.+\\.(html|xml|json)$"
|
||||
headers:
|
||||
Cache-Control: "public, max-age=0, must-revalidate"
|
||||
gzip: true
|
||||
|
||||
12
Caddyfile
12
Caddyfile
@@ -1,12 +0,0 @@
|
||||
{
|
||||
frankenphp
|
||||
order php_server before file_server
|
||||
}
|
||||
|
||||
http://:8000 {
|
||||
root * /fleetbase/api/public
|
||||
encode zstd gzip
|
||||
php_server {
|
||||
resolve_root_symlink
|
||||
}
|
||||
}
|
||||
5
api/.gitignore
vendored
5
api/.gitignore
vendored
@@ -13,7 +13,4 @@ npm-debug.log
|
||||
yarn-error.log
|
||||
/.idea
|
||||
/.vscode
|
||||
.composer.dev.json
|
||||
/caddy
|
||||
frankenphp
|
||||
frankenphp-worker.php
|
||||
.composer.dev.json
|
||||
@@ -16,7 +16,7 @@ class Kernel extends HttpKernel
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\Fruitcake\Cors\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
|
||||
@@ -8,30 +8,37 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"fleetbase/core-api": "^1.4.4",
|
||||
"fleetbase/fleetops-api": "^0.4.10",
|
||||
"fleetbase/storefront-api": "^0.3.0",
|
||||
"php": "^7.3|^8.0",
|
||||
"fleetbase/core-api": "^1.3.2",
|
||||
"fleetbase/fleetops-api": "^0.3.5",
|
||||
"fleetbase/storefront-api": "^0.2.4",
|
||||
"fleetbase/solid-api": "^0.0.1",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/octane": "^2.3",
|
||||
"laravel/tinker": "^2.9",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"laravel/framework": "^8.75",
|
||||
"laravel/sanctum": "^2.11",
|
||||
"laravel/tinker": "^2.5",
|
||||
"league/flysystem-aws-s3-v3": "^1.0",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"phpoffice/phpspreadsheet": "^1.28",
|
||||
"predis/predis": "^2.1",
|
||||
"psr/http-factory-implementation": "*",
|
||||
"s-ichikawa/laravel-sendgrid-driver": "^4.0"
|
||||
"psr/http-factory-implementation": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"facade/ignition": "^2.5",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"kitloong/laravel-migrations-generator": "^6.10",
|
||||
"laravel/sail": "^1.0.1",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.0"
|
||||
"nunomaduro/collision": "^5.10",
|
||||
"phpunit/phpunit": "^9.5.10"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "../packages/solid"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
@@ -91,6 +98,6 @@
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
|
||||
7168
api/composer.lock
generated
7168
api/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Fleetbase'),
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Fleetbase\Support\Utils;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@@ -21,7 +19,7 @@ return [
|
||||
|
||||
'allowed_methods' => ['*'],
|
||||
|
||||
'allowed_origins' => array_filter(['http://localhost:4200', env('CONSOLE_HOST'), Utils::addWwwToUrl(env('CONSOLE_HOST'))]),
|
||||
'allowed_origins' => ['http://localhost:4200', env('CONSOLE_HOST')],
|
||||
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DRIVER', 'public'),
|
||||
'default' => env('FILESYSTEM_DRIVER', 'local'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -32,13 +32,13 @@ return [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app')
|
||||
'root' => storage_path('app'),
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL') . '/storage',
|
||||
'url' => env('APP_URL').'/storage',
|
||||
],
|
||||
|
||||
's3' => [
|
||||
@@ -51,14 +51,6 @@ return [
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
],
|
||||
|
||||
'gcs' => [
|
||||
'driver' => 'gcs',
|
||||
'project_id' => env('GOOGLE_CLOUD_PROJECT_ID', 'your-project-id'),
|
||||
'key_file' => env('GOOGLE_CLOUD_KEY_FILE', null),
|
||||
'bucket' => env('GOOGLE_CLOUD_STORAGE_BUCKET', env('AWS_BUCKET')),
|
||||
'path_prefix' => env('GOOGLE_CLOUD_STORAGE_PATH_PREFIX', null),
|
||||
'storage_api_uri' => env('GOOGLE_CLOUD_STORAGE_API_URI', env('AWS_URL')),
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -73,7 +65,8 @@ return [
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public')
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
public_path('uploads') => storage_path('app/uploads'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -60,10 +60,6 @@ return [
|
||||
'transport' => 'postmark',
|
||||
],
|
||||
|
||||
'sendgrid' => [
|
||||
'transport' => 'sendgrid',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -t -i'),
|
||||
@@ -100,7 +96,7 @@ return [
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@fleetbase.io'),
|
||||
'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Fleetbase')),
|
||||
'name' => env('MAIL_FROM_NAME', 'Fleetbase'),
|
||||
],
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Octane\Contracts\OperationTerminated;
|
||||
use Laravel\Octane\Events\RequestHandled;
|
||||
use Laravel\Octane\Events\RequestReceived;
|
||||
use Laravel\Octane\Events\RequestTerminated;
|
||||
use Laravel\Octane\Events\TaskReceived;
|
||||
use Laravel\Octane\Events\TaskTerminated;
|
||||
use Laravel\Octane\Events\TickReceived;
|
||||
use Laravel\Octane\Events\TickTerminated;
|
||||
use Laravel\Octane\Events\WorkerErrorOccurred;
|
||||
use Laravel\Octane\Events\WorkerStarting;
|
||||
use Laravel\Octane\Events\WorkerStopping;
|
||||
use Laravel\Octane\Listeners\CollectGarbage;
|
||||
use Laravel\Octane\Listeners\DisconnectFromDatabases;
|
||||
use Laravel\Octane\Listeners\EnsureUploadedFilesAreValid;
|
||||
use Laravel\Octane\Listeners\EnsureUploadedFilesCanBeMoved;
|
||||
use Laravel\Octane\Listeners\FlushOnce;
|
||||
use Laravel\Octane\Listeners\FlushTemporaryContainerInstances;
|
||||
use Laravel\Octane\Listeners\FlushUploadedFiles;
|
||||
use Laravel\Octane\Listeners\ReportException;
|
||||
use Laravel\Octane\Listeners\StopWorkerIfNecessary;
|
||||
use Laravel\Octane\Octane;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Server
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the default "server" that will be used by Octane
|
||||
| when starting, restarting, or stopping your server via the CLI. You
|
||||
| are free to change this to the supported server of your choosing.
|
||||
|
|
||||
| Supported: "roadrunner", "swoole", "frankenphp"
|
||||
|
|
||||
*/
|
||||
|
||||
'server' => env('OCTANE_SERVER', 'frankenphp'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Force HTTPS
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When this configuration value is set to "true", Octane will inform the
|
||||
| framework that all absolute links must be generated using the HTTPS
|
||||
| protocol. Otherwise your links may be generated using plain HTTP.
|
||||
|
|
||||
*/
|
||||
|
||||
'https' => env('OCTANE_HTTPS', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Listeners
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All of the event listeners for Octane's events are defined below. These
|
||||
| listeners are responsible for resetting your application's state for
|
||||
| the next request. You may even add your own listeners to the list.
|
||||
|
|
||||
*/
|
||||
|
||||
'listeners' => [
|
||||
WorkerStarting::class => [
|
||||
EnsureUploadedFilesAreValid::class,
|
||||
EnsureUploadedFilesCanBeMoved::class,
|
||||
],
|
||||
|
||||
RequestReceived::class => [
|
||||
...Octane::prepareApplicationForNextOperation(),
|
||||
...Octane::prepareApplicationForNextRequest(),
|
||||
//
|
||||
],
|
||||
|
||||
RequestHandled::class => [
|
||||
//
|
||||
],
|
||||
|
||||
RequestTerminated::class => [
|
||||
// FlushUploadedFiles::class,
|
||||
],
|
||||
|
||||
TaskReceived::class => [
|
||||
...Octane::prepareApplicationForNextOperation(),
|
||||
//
|
||||
],
|
||||
|
||||
TaskTerminated::class => [
|
||||
//
|
||||
],
|
||||
|
||||
TickReceived::class => [
|
||||
...Octane::prepareApplicationForNextOperation(),
|
||||
//
|
||||
],
|
||||
|
||||
TickTerminated::class => [
|
||||
//
|
||||
],
|
||||
|
||||
OperationTerminated::class => [
|
||||
FlushOnce::class,
|
||||
FlushTemporaryContainerInstances::class,
|
||||
// DisconnectFromDatabases::class,
|
||||
// CollectGarbage::class,
|
||||
],
|
||||
|
||||
WorkerErrorOccurred::class => [
|
||||
ReportException::class,
|
||||
StopWorkerIfNecessary::class,
|
||||
],
|
||||
|
||||
WorkerStopping::class => [
|
||||
//
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Warm / Flush Bindings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The bindings listed below will either be pre-warmed when a worker boots
|
||||
| or they will be flushed before every new request. Flushing a binding
|
||||
| will force the container to resolve that binding again when asked.
|
||||
|
|
||||
*/
|
||||
|
||||
'warm' => [
|
||||
...Octane::defaultServicesToWarm(),
|
||||
],
|
||||
|
||||
'flush' => [
|
||||
//
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Swoole Tables
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While using Swoole, you may define additional tables as required by the
|
||||
| application. These tables can be used to store data that needs to be
|
||||
| quickly accessed by other workers on the particular Swoole server.
|
||||
|
|
||||
*/
|
||||
|
||||
'tables' => [
|
||||
'example:1000' => [
|
||||
'name' => 'string:1000',
|
||||
'votes' => 'int',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Swoole Cache Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While using Swoole, you may leverage the Octane cache, which is powered
|
||||
| by a Swoole table. You may set the maximum number of rows as well as
|
||||
| the number of bytes per row using the configuration options below.
|
||||
|
|
||||
*/
|
||||
|
||||
'cache' => [
|
||||
'rows' => 1000,
|
||||
'bytes' => 10000,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| File Watching
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following list of files and directories will be watched when using
|
||||
| the --watch option offered by Octane. If any of the directories and
|
||||
| files are changed, Octane will automatically reload your workers.
|
||||
|
|
||||
*/
|
||||
|
||||
'watch' => [
|
||||
'app',
|
||||
'bootstrap',
|
||||
'config',
|
||||
'database',
|
||||
'public/**/*.php',
|
||||
'resources/**/*.php',
|
||||
'routes',
|
||||
'composer.lock',
|
||||
'.env',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Garbage Collection Threshold
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When executing long-lived PHP scripts such as Octane, memory can build
|
||||
| up before being cleared by PHP. You can force Octane to run garbage
|
||||
| collection if your application consumes this amount of megabytes.
|
||||
|
|
||||
*/
|
||||
|
||||
'garbage' => 50,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maximum Execution Time
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following setting configures the maximum execution time for requests
|
||||
| being handled by Octane. You may set this value to 0 to indicate that
|
||||
| there isn't a specific time limit on Octane request execution time.
|
||||
|
|
||||
*/
|
||||
|
||||
'max_execution_time' => 30,
|
||||
|
||||
];
|
||||
@@ -30,8 +30,4 @@ return [
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'sendgrid' => [
|
||||
'api_key' => env('SENDGRID_API_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
# Exit the script as soon as a command fails
|
||||
set -e
|
||||
@@ -13,7 +13,4 @@ php artisan migrate --force
|
||||
php artisan sandbox:migrate --force
|
||||
|
||||
# Seed database
|
||||
php artisan fleetbase:seed
|
||||
|
||||
# Restart queue
|
||||
php artisan queue:restart
|
||||
php artisan fleetbase:seed
|
||||
5622
api/pnpm-lock.yaml
generated
5622
api/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
api/public/uploads
Symbolic link
1
api/public/uploads
Symbolic link
@@ -0,0 +1 @@
|
||||
/var/www/html/api/storage/app/uploads
|
||||
@@ -1,7 +1,15 @@
|
||||
{
|
||||
/**
|
||||
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
||||
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
||||
Ember CLI sends analytics information by default. The data is completely
|
||||
anonymous, but there are times when you might want to disable this behavior.
|
||||
|
||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
||||
*/
|
||||
"disableAnalytics": false,
|
||||
|
||||
/**
|
||||
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
||||
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
||||
*/
|
||||
"isTypeScriptProject": false
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
# unconventional js
|
||||
/blueprints/*/files/
|
||||
/vendor/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
|
||||
# misc
|
||||
/coverage/
|
||||
!.*
|
||||
.*/
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/npm-shrinkwrap.json.ember-try
|
||||
/package.json.ember-try
|
||||
/package-lock.json.ember-try
|
||||
/yarn.lock.ember-try
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@babel/eslint-parser',
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
requireConfigFile: false,
|
||||
babelOptions: {
|
||||
plugins: [['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }]],
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true,
|
||||
},
|
||||
},
|
||||
plugins: ['ember'],
|
||||
@@ -30,7 +29,7 @@ module.exports = {
|
||||
'ember/no-empty-glimmer-component-classes': 'off',
|
||||
'ember/no-get': 'off',
|
||||
'ember/classic-decorator-no-classic-methods': 'off',
|
||||
'n/no-unpublished-require': [
|
||||
'node/no-unpublished-require': [
|
||||
'error',
|
||||
{
|
||||
allowModules: [
|
||||
@@ -51,18 +50,18 @@ module.exports = {
|
||||
'no-prototype-builtins': 'off',
|
||||
},
|
||||
overrides: [
|
||||
// node files
|
||||
{
|
||||
files: [
|
||||
'./.eslintrc.js',
|
||||
'./.prettierrc.js',
|
||||
'./.stylelintrc.js',
|
||||
'./.template-lintrc.js',
|
||||
'./ember-cli-build.js',
|
||||
'./index.js',
|
||||
'./testem.js',
|
||||
'./blueprints/*/index.js',
|
||||
'./config/**/*.js',
|
||||
'./lib/*/index.js',
|
||||
'./server/**/*.js',
|
||||
'./tests/dummy/config/**/*.js',
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
@@ -71,7 +70,13 @@ module.exports = {
|
||||
browser: false,
|
||||
node: true,
|
||||
},
|
||||
extends: ['plugin:n/recommended'],
|
||||
plugins: ['node'],
|
||||
extends: ['plugin:node/recommended'],
|
||||
},
|
||||
{
|
||||
// test files
|
||||
files: ['tests/**/*-test.{js,ts}'],
|
||||
extends: ['plugin:qunit/recommended'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
4
console/.github/workflows/ci.yml
vendored
4
console/.github/workflows/ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x] # Build on Node.js 18
|
||||
node-version: [16.x] # Build on Node.js 16
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -57,4 +57,4 @@ jobs:
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Build
|
||||
run: npx ember build --environment production
|
||||
run: npx ember build --environment production
|
||||
|
||||
10
console/.gitignore
vendored
10
console/.gitignore
vendored
@@ -1,17 +1,22 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/declarations/
|
||||
/tmp/
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
/scripts/node_modules/
|
||||
|
||||
# misc
|
||||
/.env*
|
||||
/environments/.env*
|
||||
/.pnp*
|
||||
/.sass-cache
|
||||
/.eslintcache
|
||||
/connect.lock
|
||||
/coverage/
|
||||
/libpeerconnection.log
|
||||
/npm-debug.log*
|
||||
/testem.log
|
||||
/yarn-error.log
|
||||
@@ -19,6 +24,7 @@
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/npm-shrinkwrap.json.ember-try
|
||||
/package.json.ember-try
|
||||
/package-lock.json.ember-try
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
# unconventional js
|
||||
/blueprints/*/files/
|
||||
/vendor/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
|
||||
# misc
|
||||
/coverage/
|
||||
!.*
|
||||
.*/
|
||||
.eslintcache
|
||||
.lint-todo/
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/npm-shrinkwrap.json.ember-try
|
||||
/package.json.ember-try
|
||||
/package-lock.json.ember-try
|
||||
/yarn.lock.ember-try
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
printWidth: 190,
|
||||
overrides: [
|
||||
{
|
||||
files: '*.{hbs,js,ts}',
|
||||
files: '*.hbs',
|
||||
options: {
|
||||
singleQuote: false,
|
||||
},
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# unconventional files
|
||||
/blueprints/*/files/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
|
||||
# addons
|
||||
/.node_modules.ember-try/
|
||||
@@ -1,8 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
|
||||
rules: {
|
||||
'import-notation': null,
|
||||
},
|
||||
};
|
||||
@@ -6,6 +6,7 @@ module.exports = {
|
||||
'no-bare-strings': 'off',
|
||||
'no-invalid-interactive': 'off',
|
||||
'no-yield-only': 'off',
|
||||
'no-down-event-binding': 'off',
|
||||
'table-groups': 'off',
|
||||
'link-href-attributes': 'off',
|
||||
'require-input-label': 'off',
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"ignore_dirs": ["dist"]
|
||||
"ignore_dirs": ["tmp", "dist"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ---- Build Stage ----
|
||||
FROM node:18.15.0-alpine AS builder
|
||||
FROM node:16.20-alpine AS builder
|
||||
|
||||
# Set the working directory in the container to /app
|
||||
WORKDIR /app
|
||||
@@ -8,9 +8,6 @@ WORKDIR /app
|
||||
RUN mkdir -p ~/.pnpm
|
||||
ENV PNPM_HOME /root/.pnpm
|
||||
|
||||
# Set environment
|
||||
ARG ENVIRONMENT=production
|
||||
|
||||
# Add the pnpm global bin to the PATH
|
||||
ENV PATH /root/.pnpm/bin:$PATH
|
||||
|
||||
@@ -36,7 +33,7 @@ RUN pnpm install
|
||||
COPY console .
|
||||
|
||||
# Build the application
|
||||
RUN pnpm build --environment $ENVIRONMENT
|
||||
RUN pnpm build
|
||||
|
||||
# ---- Serve Stage ----
|
||||
FROM nginx:alpine
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
runtime: python312
|
||||
|
||||
handlers:
|
||||
- url: /(.*\..+)$
|
||||
static_files: \1
|
||||
upload: (.+)
|
||||
secure: always
|
||||
expiration: 1h
|
||||
- url: /.*
|
||||
static_files: index.html
|
||||
upload: index.html
|
||||
secure: always
|
||||
@@ -1 +0,0 @@
|
||||
{{yield}}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
export default class Configure2faComponent extends Component {}
|
||||
@@ -1,99 +1,7 @@
|
||||
<div class="fleetbase-dashboard-grid flex items-center justify-between mb-4 mt-6 px-14">
|
||||
<div class="left-section">
|
||||
<h1 class="text-lg font-bold">{{this.dashboard.currentDashboard.name}}</h1>
|
||||
<div class="fleetbase-dashboard-grid">
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-4">
|
||||
{{#each this.dashboards as |dashboard index|}}
|
||||
<Dashboard::Create @index={{index}} @dashboard={{dashboard}} />
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="fleetbase-dashboard-actions right-section ml-4 flex items-center">
|
||||
<div class="fleetbase-model-select fleetbase-power-select ember-model-select h-10">
|
||||
|
||||
<DropdownButton
|
||||
class="h-10"
|
||||
@text={{if this.dashboard.currentDashboard.name this.dashboard.currentDashboard.name (t "component.dashboard.select-dashboard")}}
|
||||
@textClass="text-sm mr-2"
|
||||
@buttonClass="flex-row-reverse w-44 justify-between"
|
||||
@icon="caret-down"
|
||||
@iconClass="mr-0i"
|
||||
@size="sm"
|
||||
@iconPrefix="fas"
|
||||
@triggerClass="hidden md:flex"
|
||||
as |dd|
|
||||
>
|
||||
<div class="next-dd-menu mt-1 mx-0" aria-labelledby="user-menu">
|
||||
<div class="p-1">
|
||||
{{#each this.dashboard.dashboards as |dashboard|}}
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.selectDashboard dashboard)}}>
|
||||
<div class="flex-1 flex flex-row items-center">
|
||||
<div class="w-6">
|
||||
<FaIcon @icon="desktop" />
|
||||
</div>
|
||||
<span>{{dashboard.name}}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{#if (eq this.dashboard.currentDashboard.id dashboard.id)}}
|
||||
<FaIcon @icon="check" class="text-green-500" />
|
||||
{{/if}}
|
||||
</div>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
|
||||
<div class="ml-2 relative h-10">
|
||||
<DropdownButton class="h-10" @icon="ellipsis-h" @size="sm" @iconPrefix="fas" @triggerClass="hidden md:flex" as |dd|>
|
||||
<div class="next-dd-menu mt-1 mx-0" aria-labelledby="user-menu">
|
||||
<div class="p-1">
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.createDashboard)}}>
|
||||
<div class="w-6">
|
||||
<FaIcon @icon="add" />
|
||||
</div>
|
||||
<span>{{t "component.dashboard.create-new-dashboard"}}</span>
|
||||
</a>
|
||||
|
||||
{{#unless (eq this.dashboard.currentDashboard.user_uuid "system")}}
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.onChangeEdit true)}}>
|
||||
<div class="w-6">
|
||||
<FaIcon @icon="edit" />
|
||||
</div>
|
||||
<span>{{t "component.dashboard.edit-layout"}}</span>
|
||||
</a>
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.onAddingWidget true)}}>
|
||||
<div class="w-6">
|
||||
<FaIcon @icon="add" />
|
||||
</div>
|
||||
<span>{{t "component.dashboard.add-widgets"}}</span>
|
||||
</a>
|
||||
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.deleteDashboard this.dashboard.currentDashboard)}}>
|
||||
<div class="w-6">
|
||||
<FaIcon @icon="trash" />
|
||||
</div>
|
||||
<span>{{t "component.dashboard.delete-dashboard"}}</span>
|
||||
</a>
|
||||
{{/unless}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
{{#if this.dashboard.isEditingDashboard}}
|
||||
<div class="ml-2 h-10">
|
||||
<Button @type="magic" @icon="save" @helpText={{t "component.dashboard.save-dashboard"}} @onClick={{fn this.onChangeEdit false}} class="h-10" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-10">
|
||||
<Dashboard::Create @isEdit={{this.dashboard.isEditingDashboard}} @isAddingWidget={{this.dashboard.isAddingWidget}} @dashboard={{this.dashboard.currentDashboard}} />
|
||||
{{#if this.dashboard.isAddingWidget}}
|
||||
<EmberWormhole @to="console-home-wormhole">
|
||||
<Dashboard::WidgetPanel
|
||||
@isOpen={{this.dashboard.isAddingWidget}}
|
||||
@onLoad={{this.setWidgetSelectorPanelContext}}
|
||||
@dashboard={{this.dashboard.currentDashboard}}
|
||||
@onClose={{fn this.onAddingWidget false}}
|
||||
/>
|
||||
</EmberWormhole>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -1,140 +1,48 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import loadExtensions from '@fleetbase/ember-core/utils/load-extensions';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
|
||||
/**
|
||||
* DashboardComponent for managing dashboards in an Ember application.
|
||||
* This component handles actions such as selecting, creating, deleting dashboards,
|
||||
* and managing widget selectors and dashboard editing states.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class DashboardComponent extends Component {
|
||||
/**
|
||||
* Ember Data store service.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service store;
|
||||
|
||||
/**
|
||||
* Internationalization service for managing translations.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Notifications service for displaying alerts or confirmations.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Modals manager service for handling modal dialogs.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* Fetch service for handling HTTP requests.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service fetch;
|
||||
@tracked extensions;
|
||||
@tracked dashboards = [];
|
||||
@tracked isLoading;
|
||||
|
||||
/**
|
||||
* Dashboard service for business logic related to dashboards.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service dashboard;
|
||||
|
||||
/**
|
||||
* Creates an instance of DashboardComponent.
|
||||
* @memberof DashboardComponent
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.dashboard.loadDashboards.perform();
|
||||
this.loadExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to select a dashboard.
|
||||
* @param {Object} dashboard - The dashboard to be selected.
|
||||
*/
|
||||
@action selectDashboard(dashboard) {
|
||||
this.dashboard.selectDashboard.perform(dashboard);
|
||||
@action async loadExtensions() {
|
||||
this.extensions = await loadExtensions();
|
||||
this.loadDashboardBuilds.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the context for the widget selector panel.
|
||||
* @param {Object} widgetSelectorContext - The context object for the widget selector.
|
||||
*/
|
||||
@action setWidgetSelectorPanelContext(widgetSelectorContext) {
|
||||
this.widgetSelectorContext = widgetSelectorContext;
|
||||
}
|
||||
@task *loadDashboard(extension) {
|
||||
this.isLoading = extension.extension;
|
||||
let dashboardBuild;
|
||||
|
||||
/**
|
||||
* Creates a new dashboard.
|
||||
* @param {Object} dashboard - The dashboard to be created.
|
||||
* @param {Object} [options={}] - Optional parameters for dashboard creation.
|
||||
*/
|
||||
@action createDashboard(dashboard, options = {}) {
|
||||
this.modalsManager.show('modals/create-dashboard', {
|
||||
title: this.intl.t('component.dashboard.create-a-new-dashboard'),
|
||||
acceptButtonText: this.intl.t('component.dashboard.confirm-create-dashboard'),
|
||||
confirm: async (modal, done) => {
|
||||
modal.startLoading();
|
||||
|
||||
// Get the name from the modal options
|
||||
const { name } = modal.getOptions();
|
||||
|
||||
await this.dashboard.createDashboard.perform(name);
|
||||
done();
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a dashboard.
|
||||
* @param {Object} dashboard - The dashboard to be deleted.
|
||||
* @param {Object} [options={}] - Optional parameters for dashboard deletion.
|
||||
*/
|
||||
@action deleteDashboard(dashboard, options = {}) {
|
||||
if (this.dashboard.dashboards?.length === 1) {
|
||||
return this.notifications.error(this.intl.t('component.dashboard.you-cannot-delete-this-dashboard'));
|
||||
try {
|
||||
dashboardBuild = yield this.fetch.get(extension.fleetbase.dashboard, {}, { namespace: '' });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalsManager.confirm({
|
||||
title: this.intl.t('component.dashboard.are-you-sure-you-want-delete-dashboard', { dashboardName: dashboard.name }),
|
||||
confirm: async (modal, done) => {
|
||||
modal.startLoading();
|
||||
await this.dashboard.deleteDashboard.perform(dashboard);
|
||||
done();
|
||||
},
|
||||
...options,
|
||||
});
|
||||
if (isArray(dashboardBuild)) {
|
||||
this.dashboards = [...this.dashboards, ...dashboardBuild.map((build) => ({ ...build, extension }))];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to handle the addition of a widget.
|
||||
* @param {boolean} [state=true] - The state to set for adding a widget.
|
||||
*/
|
||||
@action onAddingWidget(state = true) {
|
||||
this.dashboard.onAddingWidget(state);
|
||||
}
|
||||
@task({ enqueue: true, maxConcurrency: 1 }) *loadDashboardBuilds() {
|
||||
const extensionsWithDashboards = this.extensions.filter((extension) => typeof extension.fleetbase?.dashboard === 'string');
|
||||
|
||||
/**
|
||||
* Sets the current dashboard.
|
||||
* @param {Object} dashboard - The dashboard to be set as current.
|
||||
*/
|
||||
@action setCurrentDashboard(dashboard) {
|
||||
this.dashboard.setCurrentDashboard.perform(dashboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the editing state of the dashboard.
|
||||
* @param {boolean} [state=true] - The state to set for editing the dashboard.
|
||||
*/
|
||||
@action onChangeEdit(state = true) {
|
||||
this.dashboard.onChangeEdit(state);
|
||||
for (let i = 0; i < extensionsWithDashboards.length; i++) {
|
||||
const extension = extensionsWithDashboards[i];
|
||||
yield this.loadDashboard.perform(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="dashboard-component-count lg:col-span-2 h-full {{@options.wrapperClass}}">
|
||||
<h3 class="text-sm dark:text-gray-100 text-black mb-4 {{@options.titleClass}}">{{this.title}}</h3>
|
||||
<h1 class="text-3xl font-bold dark:text-gray-100 text-black {{@options.valueClass}}">
|
||||
{{this.value}}
|
||||
<div class="dashboard-component-count lg:col-span-2">
|
||||
<h3 class="text-sm dark:text-gray-100 text-black mb-4">{{@options.title}}</h3>
|
||||
<h1 class="text-3xl font-bold dark:text-gray-100 text-black mb-4">
|
||||
{{this.displayValue}}
|
||||
</h1>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { computed } from '@ember/object';
|
||||
import formatCurrency from '@fleetbase/ember-ui/utils/format-currency';
|
||||
import formatMeters from '@fleetbase/ember-ui/utils/format-meters';
|
||||
import formatBytes from '@fleetbase/ember-ui/utils/format-bytes';
|
||||
@@ -7,40 +7,11 @@ import formatDuration from '@fleetbase/ember-ui/utils/format-duration';
|
||||
import formatDate from '@fleetbase/ember-ui/utils/format-date';
|
||||
|
||||
export default class DashboardCountComponent extends Component {
|
||||
/**
|
||||
* The title of the metric count.
|
||||
*
|
||||
* @memberof WidgetKeyMetricsCountComponent
|
||||
*/
|
||||
@tracked title;
|
||||
|
||||
/**
|
||||
* The value to render
|
||||
*
|
||||
* @memberof WidgetKeyMetricsCountComponent
|
||||
*/
|
||||
@tracked value;
|
||||
|
||||
/**
|
||||
* Creates an instance of WidgetKeyMetricsCountComponent.
|
||||
* @param {EngineInstance} owner
|
||||
* @param {Object} { options }
|
||||
* @memberof WidgetKeyMetricsCountComponent
|
||||
*/
|
||||
constructor(owner, { options, title }) {
|
||||
super(...arguments);
|
||||
this.title = title;
|
||||
this.createRenderValueFromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the value to render using the options provided.
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* @memberof WidgetKeyMetricsCountComponent
|
||||
*/
|
||||
createRenderValueFromOptions(options = {}) {
|
||||
let { format, currency, dateFormat, value } = options;
|
||||
@computed('args.options.{currency,dateFormat,format,value}') get displayValue() {
|
||||
let format = this.args.options?.format;
|
||||
let currency = this.args.options?.currency;
|
||||
let dateFormat = this.args.options?.dateFormat;
|
||||
let value = this.args.options?.value;
|
||||
|
||||
switch (format) {
|
||||
case 'money':
|
||||
@@ -67,6 +38,6 @@ export default class DashboardCountComponent extends Component {
|
||||
break;
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<div class="fleetbase-dashboard-grid" ...attributes>
|
||||
<GridStack @options={{this.gridOptions}} @onChange={{this.onChangeGrid}}>
|
||||
{{#each @dashboard.widgets as |widget|}}
|
||||
<GridStackItem id={{widget.id}} @options={{spread-widget-options (hash id=widget.id options=widget.grid_options)}} class="relative">
|
||||
{{component widget.component options=widget.options}}
|
||||
{{#if @isEdit}}
|
||||
<div class="absolute top-2 right-2">
|
||||
<Button @type="default" @icon="trash" @helpText={{"Remove widget from the dashboard"}} @onClick={{fn this.removeWidget widget}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
</GridStackItem>
|
||||
<div class="col-span-{{or @dashboard.size 12}}">
|
||||
<div class="dashboard-title flex flex-col lg:flex-row lg:items-center">
|
||||
<div class="flex flex-row items-center mb-2 lg:mb-0">
|
||||
{{#if this.isLoading}}
|
||||
<Spinner class="mr-2i" />
|
||||
{{/if}}
|
||||
<h2 class="text-sm font-bold dark:text-gray-100 text-black">{{@dashboard.title}}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<Dashboard::QueryParams @params={{@dashboard.queryParams}} @onChange={{this.onQueryParamsChanged}} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 lg:grid-cols-12 gap-4">
|
||||
{{#each this.dashboard.widgets as |widget|}}
|
||||
{{component (concat "dashboard/" widget.component) options=widget.options}}
|
||||
{{/each}}
|
||||
</GridStack>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,99 +1,41 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action, computed } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
|
||||
/**
|
||||
* Component responsible for creating and managing the dashboard layout.
|
||||
* Provides functionalities such as toggling widget float, changing grid layout, and removing widgets.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class DashboardCreateComponent extends Component {
|
||||
/**
|
||||
* Notifications service for displaying alerts or errors.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service notifications;
|
||||
@service fetch;
|
||||
@tracked isLoading = false;
|
||||
@tracked dashboard;
|
||||
|
||||
/**
|
||||
* Tracked array to keep track of widgets that have been updated.
|
||||
* @type {Array}
|
||||
*/
|
||||
@tracked updatedWidgets = [];
|
||||
|
||||
/**
|
||||
* Action to toggle the floating state of widgets on the grid.
|
||||
*/
|
||||
@action toggleFloat() {
|
||||
this.shouldFloat = !this.shouldFloat;
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.dashboard = this.args.dashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes to the grid layout, such as repositioning or resizing widgets.
|
||||
* Iterates over each widget event detail and updates the corresponding widget's properties if necessary.
|
||||
*
|
||||
* @param {Event} event - Event containing details about the grid change.
|
||||
* @action
|
||||
*/
|
||||
@action onChangeGrid(event) {
|
||||
const { dashboard } = this.args;
|
||||
|
||||
event.detail.forEach((currentWidgetEvent) => {
|
||||
const alreadyUpdated = this.updatedWidgets.find((item) => item.id === currentWidgetEvent.id);
|
||||
if (alreadyUpdated || !this.dashboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changedWidget = dashboard.widgets.find((widget) => widget.id === currentWidgetEvent.id);
|
||||
if (!changedWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y, w, h } = currentWidgetEvent;
|
||||
const response = changedWidget.updateProperties({
|
||||
grid_options: { x, y, w, h },
|
||||
});
|
||||
if (response) {
|
||||
this.updatedWidgets.push(changedWidget);
|
||||
}
|
||||
});
|
||||
@action onQueryParamsChanged(changedParams) {
|
||||
this.reloadDashboard.perform(changedParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specified widget from the dashboard.
|
||||
* Performs a removal operation on the dashboard and handles any errors that occur during the process.
|
||||
*
|
||||
* @param {Object} widget - The widget object to be removed.
|
||||
* @action
|
||||
*/
|
||||
@action removeWidget(widget) {
|
||||
const { dashboard } = this.args;
|
||||
@task *reloadDashboard(params) {
|
||||
const { extension } = this.args.dashboard;
|
||||
const index = this.args.index;
|
||||
let dashboards = [];
|
||||
|
||||
if (dashboard) {
|
||||
dashboard.removeWidget(widget.id).catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
dashboards = yield this.fetch.get(extension.fleetbase.dashboard, params, { namespace: '' });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
if (isArray(dashboards)) {
|
||||
this.dashboard = dashboards.objectAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed property that returns grid options based on the current edit state.
|
||||
* Configures grid behavior such as floating, animation, and drag and resize capabilities.
|
||||
*
|
||||
* @computed
|
||||
* @returns {Object} An object containing grid configuration options.
|
||||
*/
|
||||
@computed('args.isEdit') get gridOptions() {
|
||||
return {
|
||||
float: true,
|
||||
animate: true,
|
||||
acceptWidgets: true,
|
||||
alwaysShowResizeHandle: this.args.isEdit,
|
||||
disableDrag: !this.args.isEdit,
|
||||
disableResize: !this.args.isEdit,
|
||||
resizable: { handles: 'all' },
|
||||
cellHeight: 30,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<div class="col-span-{{or @dashboard.size 12}}">
|
||||
<div class="dashboard-title flex flex-col lg:flex-row lg:items-center">
|
||||
<div class="flex flex-row items-center mb-2 lg:mb-0">
|
||||
{{#if this.isLoading}}
|
||||
<Spinner class="mr-2i" />
|
||||
{{/if}}
|
||||
<h2 class="text-sm font-bold dark:text-gray-100 text-black">{{this.dashboard.title}}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<Dashboard::QueryParams @params={{this.dashboard.queryParams}} @onChange={{this.onQueryParamsChanged}} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 lg:grid-cols-12 gap-4">
|
||||
{{#each this.dashboard.widgets as |widget|}}
|
||||
{{component (concat "dashboard/" widget.component) options=widget.options}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,39 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
|
||||
export default class MetricComponent extends Component {
|
||||
@service fetch;
|
||||
@tracked isLoading = false;
|
||||
@tracked dashboard;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.loadDashboard.perform();
|
||||
}
|
||||
|
||||
@action onQueryParamsChanged(changedParams) {
|
||||
this.loadDashboard.perform(changedParams);
|
||||
}
|
||||
|
||||
@task *loadDashboard(params) {
|
||||
let dashboards = [];
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
dashboards = yield this.fetch.get(this.args.options.endpoint, params, { namespace: '' });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
if (isArray(dashboards)) {
|
||||
this.dashboard = dashboards.objectAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<Overlay @isOpen={{@isOpen}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width={{or this.width @width "400px"}}>
|
||||
<Overlay::Header @title={{t "component.dashboard-widget-panel.select-widgets"}} @hideStatusDot={{true}} @titleWrapperClass="leading-5">
|
||||
<div class="flex flex-1 justify-end">
|
||||
<Button @type="default" @icon="times" @helpText={{t "component.dashboard-widget-panel.close-and-save"}} @onClick={{this.onPressClose}} />
|
||||
</div>
|
||||
</Overlay::Header>
|
||||
|
||||
<Overlay::Body @wrapperClass="new-service-rate-overlay-body px-4 space-y-4 pt-4">
|
||||
<div class="grid grid-cols-1 gap-4 text-xs dark:text-gray-100">
|
||||
{{#each this.availableWidgets as |widget|}}
|
||||
<div
|
||||
class="rounded-lg border border-gray-300 bg-white dark:border-gray-700 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-300 ease-in-out shadow-md px-4 py-2 cursor-pointer"
|
||||
{{on "click" (fn this.addWidgetToDashboard widget)}}
|
||||
>
|
||||
<div class="flex flex-row items-center leading-6 mb-2.5">
|
||||
<div class="w-8 flex items-center justify-start">
|
||||
<FaIcon @icon={{widget.icon}} class="text-lg text-gray-600 dark:text-gray-300" />
|
||||
</div>
|
||||
<p class="text-sm truncate font-semibold dark:text-gray-100 text-gray-800">
|
||||
{{t "component.dashboard-widget-panel.widget-name" widgetName=widget.name}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs dark:text-gray-100 text-gray-800">{{widget.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Overlay::Body>
|
||||
</Overlay>
|
||||
@@ -1,60 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class DashboardWidgetPanelComponent extends Component {
|
||||
@service universe;
|
||||
@tracked availableWidgets = [];
|
||||
@tracked dashboard;
|
||||
@tracked isOpen = true;
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Constructs the component and applies initial state.
|
||||
*/
|
||||
constructor(owner, { dashboard }) {
|
||||
super(...arguments);
|
||||
|
||||
this.availableWidgets = this.universe.getDashboardWidgets();
|
||||
this.dashboard = dashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the overlay context.
|
||||
*
|
||||
* @action
|
||||
* @param {OverlayContextObject} overlayContext
|
||||
*/
|
||||
@action setOverlayContext(overlayContext) {
|
||||
this.context = overlayContext;
|
||||
|
||||
if (typeof this.args.onLoad === 'function') {
|
||||
this.args.onLoad(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@action addWidgetToDashboard(widget) {
|
||||
// If widget is a component definition/class
|
||||
if (typeof widget.component === 'function') {
|
||||
widget.component = widget.component.name;
|
||||
}
|
||||
|
||||
this.args.dashboard.addWidget(widget).catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles cancel button press.
|
||||
*
|
||||
* @action
|
||||
*/
|
||||
@action onPressClose() {
|
||||
this.isOpen = false;
|
||||
|
||||
if (typeof this.args.onClose === 'function') {
|
||||
this.args.onClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
<Spinner />
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="flex flex-row p-3 border-b dark:border-gray-700 border-gray-200">
|
||||
<div class="flex flex-row p-4 border-b dark:border-gray-700 border-gray-200">
|
||||
<div class="w-12 flex-shrink-0"><img src={{this.data.owner.avatar_url}} alt="fleetbase/fleetbase" class="rounded-full w-8 h-8" width="32" height="32" /></div>
|
||||
<div class="flex-1 -mt-2">
|
||||
<div class="flex flex-1 flex-row items-center justify-between mb-2">
|
||||
<a href={{this.data.html_url}} target="_github" class="dark:text-gray-100 text-black text-base font-semibold">{{this.data.full_name}}</a>
|
||||
<a href={{this.data.html_url}} target="_github" class="dark:text-gray-100 text-black text-lg font-semibold">{{this.data.full_name}}</a>
|
||||
<a href={{this.data.html_url}} target="_github" class="btn btn-xs btn-default">
|
||||
<FaIcon @icon="star" class="text-yellow-400" />
|
||||
<span class="hidden truncate lg:flex ml-2.5">Star on Github</span>
|
||||
<span class="hidden lg:flex ml-2.5">Star on Github</span>
|
||||
</a>
|
||||
</div>
|
||||
<p class="dark:text-gray-100 text-black text-sm">{{this.data.description}}</p>
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="col-span-3 dark:text-gray-100 text-black text-xs flex flex-row">
|
||||
<span class="font-bold mr-1">{{this.data.open_issues_count}}</span>
|
||||
<span class="truncate"><span class="hidden lg:inline-flex">Open</span> Issues</span>
|
||||
<span><span class="hidden lg:inline-flex">Open</span> Issues</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<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>
|
||||
@@ -1,144 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
||||
<div class="modal-body-container">
|
||||
<InputGroup @name="Dashboard name" @value={{@options.name}} @helpText="Enter the name of your dashboard" />
|
||||
</div>
|
||||
</Modal::Default>
|
||||
@@ -1,14 +0,0 @@
|
||||
{{#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}}
|
||||
@@ -1,74 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<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}}
|
||||
@@ -1,169 +0,0 @@
|
||||
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,13 +18,6 @@ export default class AuthForgotPasswordController extends Controller {
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @memberof AuthForgotPasswordController
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* The email variable
|
||||
*
|
||||
@@ -62,7 +55,7 @@ export default class AuthForgotPasswordController extends Controller {
|
||||
this.fetch
|
||||
.post('auth/get-magic-reset-link', { email })
|
||||
.then(() => {
|
||||
this.notifications.success(this.intl.t('auth.forgot-password.success-message'));
|
||||
this.notifications.success('Check your email to continue!');
|
||||
this.isSent = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -33,27 +33,6 @@ export default class AuthLoginController extends Controller {
|
||||
*/
|
||||
@service session;
|
||||
|
||||
/**
|
||||
* Inject the `router` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the `fetch` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Whether or not to remember the users session
|
||||
*
|
||||
@@ -110,62 +89,29 @@ export default class AuthLoginController extends Controller {
|
||||
*/
|
||||
@tracked failedAttempts = 0;
|
||||
|
||||
@tracked token;
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @void
|
||||
*/
|
||||
@action async login(event) {
|
||||
// firefox patch
|
||||
event.preventDefault();
|
||||
|
||||
// get user credentials
|
||||
const { identity, password, rememberMe } = this;
|
||||
|
||||
// If no password error
|
||||
if (!identity) {
|
||||
return this.notifications.warning(this.intl.t('auth.login.no-identity-notification'));
|
||||
}
|
||||
|
||||
// If no password error
|
||||
if (!password) {
|
||||
return this.notifications.warning(this.intl.t('auth.login.no-identity-notification'));
|
||||
}
|
||||
const { email, password, rememberMe } = this;
|
||||
|
||||
// start loader
|
||||
this.set('isLoading', true);
|
||||
|
||||
// set where to redirect on login
|
||||
this.setRedirect();
|
||||
|
||||
// send request to check for 2fa
|
||||
try {
|
||||
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);
|
||||
await this.session.authenticate('authenticator:fleetbase', { email, password }, rememberMe);
|
||||
} catch (error) {
|
||||
this.failedAttempts++;
|
||||
|
||||
// Handle unverified user
|
||||
if (error.toString().includes('not verified')) {
|
||||
return this.sendUserForEmailVerification(identity);
|
||||
}
|
||||
|
||||
return this.failure(error);
|
||||
}
|
||||
|
||||
@@ -178,44 +124,20 @@ export default class AuthLoginController extends Controller {
|
||||
* Transition user to onboarding screen
|
||||
*/
|
||||
@action transitionToOnboard() {
|
||||
return this.router.transitionTo('onboard');
|
||||
return this.transitionToRoute('onboard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to forgot password screen, if email is set - set it.
|
||||
*/
|
||||
@action forgotPassword() {
|
||||
return this.router.transitionTo('auth.forgot-password').then(() => {
|
||||
return this.transitionToRoute('auth.forgot-password').then(() => {
|
||||
if (this.email) {
|
||||
this.forgotPasswordController.email = this.email;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an email verification session and transitions user to verification route.
|
||||
*
|
||||
* @param {String} email
|
||||
* @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(this.intl.t('auth.login.unverified-notification'));
|
||||
return this.router
|
||||
.transitionTo('auth.verification', {
|
||||
queryParams: {
|
||||
token,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.reset('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets correct route to send user to after login.
|
||||
*
|
||||
@@ -255,7 +177,7 @@ export default class AuthLoginController extends Controller {
|
||||
* @void
|
||||
*/
|
||||
slowConnection() {
|
||||
this.notifications.error(this.intl.t('auth.login.slow-connection-message'));
|
||||
this.notifications.error('Experiencing connectivity issues.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,20 +18,6 @@ export default class AuthResetPasswordController extends Controller {
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Inject the `router` service
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* The code param.
|
||||
*
|
||||
@@ -77,9 +63,9 @@ export default class AuthResetPasswordController extends Controller {
|
||||
this.fetch
|
||||
.post('auth/reset-password', { link: id, code, password, password_confirmation })
|
||||
.then(() => {
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
this.notifications.success('Your password has been reset! Login to continue.');
|
||||
|
||||
return this.router.transitionTo('auth.login');
|
||||
return this.transitionToRoute('auth.login');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
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
|
||||
* @property {string} identity
|
||||
* @tracked
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { later } from '@ember/runloop';
|
||||
import { not } from '@ember/object/computed';
|
||||
|
||||
export default class AuthVerificationController extends Controller {
|
||||
/**
|
||||
* Inject the `fetch` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Inject the `notifications` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Inject the `modalsManager` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* Inject the `currentUser` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service currentUser;
|
||||
|
||||
/**
|
||||
* Inject the `router` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `session` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service session;
|
||||
|
||||
/**
|
||||
* The session paramerer.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked hello;
|
||||
|
||||
/**
|
||||
* The token paramerer.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked token;
|
||||
|
||||
/**
|
||||
* The loading state of the verification request.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked isLoading = false;
|
||||
|
||||
/**
|
||||
* Validation state tracker.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked isReadyToSubmit = false;
|
||||
|
||||
/**
|
||||
* The request timeout to trigger alternative verification options.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked waitTimeout = 1000 * 60 * 1.25;
|
||||
|
||||
/**
|
||||
* Determines if Fleetbase is still awaiting verification after a certain time.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked stillWaiting = false;
|
||||
|
||||
/**
|
||||
* the input code.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked code;
|
||||
|
||||
/**
|
||||
* The query param for the session token.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked queryParams = ['hello', 'token'];
|
||||
|
||||
/**
|
||||
* The boolean opposite of `isReadyToSubmit`
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@not('isReadyToSubmit') isNotReadyToSubmit;
|
||||
|
||||
/**
|
||||
* Creates an instance of OnboardVerifyEmailController.
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
later(
|
||||
this,
|
||||
() => {
|
||||
this.stillWaiting = true;
|
||||
},
|
||||
this.waitTimeout
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow user to manually trigger no code received prompt.
|
||||
*
|
||||
* @memberof AuthVerificationController
|
||||
*/
|
||||
@action onDidntReceiveCode() {
|
||||
this.stillWaiting = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the input
|
||||
*
|
||||
* @param {InputEvent} { target: { value } }
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action validateInput({ target: { value } }) {
|
||||
if (value.length > 5) {
|
||||
this.isReadyToSubmit = true;
|
||||
} else {
|
||||
this.isReadyToSubmit = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits to verify code.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action verifyCode() {
|
||||
const { token, code, email } = this;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return this.fetch
|
||||
.post('auth/verify-email', { token, code, email, authenticate: true })
|
||||
.then(({ status, token }) => {
|
||||
if (status === 'ok') {
|
||||
this.notifications.success('Email successfully verified!');
|
||||
|
||||
if (token) {
|
||||
this.notifications.info('Welcome to Fleetbase!');
|
||||
this.session.manuallyAuthenticate(token);
|
||||
|
||||
return this.router.transitionTo('console');
|
||||
}
|
||||
|
||||
return this.router.transitionTo('auth.login');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to resend verification code by SMS.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action resendBySms() {
|
||||
this.modalsManager.show('modals/verify-by-sms', {
|
||||
title: 'Verify Account by Phone',
|
||||
acceptButtonText: 'Send',
|
||||
phone: this.currentUser.phone,
|
||||
confirm: (modal) => {
|
||||
modal.startLoading();
|
||||
const phone = modal.getOption('phone');
|
||||
|
||||
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);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to resend verification code by email.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action resendEmail() {
|
||||
this.modalsManager.show('modals/resend-verification-email', {
|
||||
title: 'Resend Verification Code',
|
||||
acceptButtonText: 'Send',
|
||||
email: this.currentUser.email,
|
||||
confirm: (modal) => {
|
||||
modal.startLoading();
|
||||
const email = modal.getOption('email');
|
||||
|
||||
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);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -51,13 +51,6 @@ export default class ConsoleController extends Controller {
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service.
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the `universe` service.
|
||||
*
|
||||
@@ -79,20 +72,6 @@ export default class ConsoleController extends Controller {
|
||||
*/
|
||||
@tracked sidebarContext;
|
||||
|
||||
/**
|
||||
* State of sidebar toggle icon
|
||||
*
|
||||
* @var {SidebarContext}
|
||||
*/
|
||||
@tracked sidebarToggleEnabled = true;
|
||||
|
||||
/**
|
||||
* The sidebar toggle state.
|
||||
*
|
||||
* @var {SidebarContext}
|
||||
*/
|
||||
@tracked sidebarToggleState = {};
|
||||
|
||||
/**
|
||||
* Routes which should hide the sidebar menu.
|
||||
*
|
||||
@@ -122,49 +101,18 @@ 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
|
||||
const shouldHideSidebar = this.hiddenSidebarRoutes.includes(transition.to.name);
|
||||
|
||||
// Check if the sidebar was manually toggled and is currently closed
|
||||
const isSidebarManuallyClosed = this.sidebarToggleState.clicked && !this.sidebarToggleState.isOpen;
|
||||
|
||||
// Hide the sidebar if the current route is in hiddenSidebarRoutes
|
||||
if (shouldHideSidebar) {
|
||||
this.sidebarContext.hideNow();
|
||||
this.sidebarToggleEnabled = false;
|
||||
return; // Exit early as no further action is required
|
||||
}
|
||||
|
||||
// If the sidebar was manually closed and not on a hidden route, keep it closed
|
||||
if (isSidebarManuallyClosed) {
|
||||
if (this.hiddenSidebarRoutes.includes(transition.to.name)) {
|
||||
this.sidebarContext.hideNow();
|
||||
} else {
|
||||
// Otherwise, show the sidebar
|
||||
this.sidebarContext.show();
|
||||
}
|
||||
|
||||
// Ensure toggle is enabled unless on a hidden route
|
||||
this.sidebarToggleEnabled = !shouldHideSidebar;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When sidebar is manually toggled
|
||||
*
|
||||
* @param {SidebarContext} sidebar
|
||||
* @param {boolean} isOpen
|
||||
* @memberof ConsoleController
|
||||
*/
|
||||
@action onSidebarToggle(sidebar, isOpen) {
|
||||
this.sidebarToggleState = {
|
||||
clicked: true,
|
||||
isOpen,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sidebar context
|
||||
*
|
||||
@@ -177,7 +125,6 @@ export default class ConsoleController extends Controller {
|
||||
|
||||
if (this.hiddenSidebarRoutes.includes(this.router.currentRouteName)) {
|
||||
this.sidebarContext.hideNow();
|
||||
this.sidebarToggleEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +159,8 @@ export default class ConsoleController extends Controller {
|
||||
const country = this.currentUser.country;
|
||||
|
||||
this.modalsManager.show('modals/create-or-join-org', {
|
||||
title: this.intl.t('console.create-or-join-organization.modal-title'),
|
||||
acceptButtonText: this.intl.t('common.confirm'),
|
||||
title: 'Create or join a organization',
|
||||
acceptButtonText: 'Confirm',
|
||||
acceptButtonIcon: 'check',
|
||||
acceptButtonIconPrefix: 'fas',
|
||||
action: 'join',
|
||||
@@ -233,22 +180,13 @@ export default class ConsoleController extends Controller {
|
||||
const { action, next, name, description, phone, currency, country, timezone } = modal.getOptions();
|
||||
|
||||
if (action === 'join') {
|
||||
return this.fetch
|
||||
.post('auth/join-organization', { next })
|
||||
.then(() => {
|
||||
this.fetch.flushRequestCache('auth/organizations');
|
||||
this.notifications.success(this.intl.t('console.create-or-join-organization.join-success-notification'));
|
||||
later(
|
||||
this,
|
||||
() => {
|
||||
window.location.reload();
|
||||
},
|
||||
900
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
return this.fetch.post('auth/join-organization', { next }).then(() => {
|
||||
this.fetch.flushRequestCache('auth/organizations');
|
||||
this.notifications.success('You have joined a new organization!');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 900);
|
||||
});
|
||||
}
|
||||
|
||||
return this.fetch
|
||||
@@ -262,7 +200,7 @@ export default class ConsoleController extends Controller {
|
||||
})
|
||||
.then(() => {
|
||||
this.fetch.flushRequestCache('auth/organizations');
|
||||
this.notifications.success(this.intl.t('console.create-or-join-organization.create-success-notification'));
|
||||
this.notifications.success('You have created a new organization!');
|
||||
later(
|
||||
this,
|
||||
() => {
|
||||
@@ -270,9 +208,6 @@ export default class ConsoleController extends Controller {
|
||||
},
|
||||
900
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -289,9 +224,9 @@ export default class ConsoleController extends Controller {
|
||||
}
|
||||
|
||||
this.modalsManager.confirm({
|
||||
title: this.intl.t('console.switch-organization.modal-title', { organizationName: organization.name }),
|
||||
body: this.intl.t('console.switch-organization.modal-body'),
|
||||
acceptButtonText: this.intl.t('console.switch-organization.modal-accept-button-text'),
|
||||
title: `Are you sure you want to switch organization to ${organization.name}?`,
|
||||
body: `By confirming your account will remain logged in, but your primary organization will be switched.`,
|
||||
acceptButtonText: `Yes, I want to switch organization`,
|
||||
acceptButtonScheme: 'primary',
|
||||
confirm: (modal) => {
|
||||
modal.startLoading();
|
||||
@@ -300,14 +235,10 @@ export default class ConsoleController extends Controller {
|
||||
.post('auth/switch-organization', { next: organization.uuid })
|
||||
.then(() => {
|
||||
this.fetch.flushRequestCache('auth/organizations');
|
||||
this.notifications.success(this.intl.t('console.switch-organization.success-notification'));
|
||||
later(
|
||||
this,
|
||||
() => {
|
||||
window.location.reload();
|
||||
},
|
||||
900
|
||||
);
|
||||
this.notifications.success('You have switched organizations');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 900);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
@@ -318,8 +249,8 @@ export default class ConsoleController extends Controller {
|
||||
|
||||
@action viewChangelog() {
|
||||
this.modalsManager.show('modals/changelog', {
|
||||
title: this.intl.t('common.changelog'),
|
||||
acceptButtonText: this.intl.t('common.ok'),
|
||||
title: 'Changelog',
|
||||
acceptButtonText: 'OK',
|
||||
hideDeclineButton: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,16 +104,6 @@ export default class ConsoleAdminBrandingController extends Controller {
|
||||
.save()
|
||||
.then(() => {
|
||||
this.notifications.success('Branding settings saved.');
|
||||
|
||||
// if logo url is null
|
||||
if (this.model.logo_url === null) {
|
||||
this.model.set('logo_url', '/images/fleetbase-logo-svg.svg');
|
||||
}
|
||||
|
||||
// if icon url is null
|
||||
if (this.model.icon_url === null) {
|
||||
this.model.set('icon_url', '/images/icon.png');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* Controller for managing organizations in the admin console.
|
||||
*
|
||||
* @class ConsoleAdminOrganizationsController
|
||||
* @extends Controller
|
||||
*/
|
||||
export default class ConsoleAdminOrganizationsController extends Controller {
|
||||
/**
|
||||
* The Ember Data service for interacting with the store.
|
||||
*
|
||||
* @property {Service} store
|
||||
* @type {Object}
|
||||
*/
|
||||
@service store;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* The Ember Router service for handling transitions between routes.
|
||||
*
|
||||
* @property {Service} router
|
||||
* @type {Object}
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `filters` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service filters;
|
||||
|
||||
/**
|
||||
* The current page of data being viewed
|
||||
*
|
||||
* @var {Integer}
|
||||
*/
|
||||
@tracked page = 1;
|
||||
|
||||
/**
|
||||
* The maximum number of items to show per page
|
||||
*
|
||||
* @var {Integer}
|
||||
*/
|
||||
@tracked limit = 20;
|
||||
|
||||
/**
|
||||
* The filterable param `sort`
|
||||
*
|
||||
* @var {String|Array}
|
||||
*/
|
||||
@tracked sort = '-created_at';
|
||||
|
||||
/**
|
||||
* The filterable param `name`
|
||||
*
|
||||
* @var {String}
|
||||
*/
|
||||
@tracked name;
|
||||
|
||||
/**
|
||||
* The filterable param `country`
|
||||
*
|
||||
* @var {String}
|
||||
*/
|
||||
@tracked country;
|
||||
|
||||
/**
|
||||
* Array to store the fetched companies.
|
||||
*
|
||||
* @var {Array}
|
||||
*/
|
||||
@tracked companies = [];
|
||||
|
||||
/**
|
||||
* Queryable parameters for this controller's model
|
||||
*
|
||||
* @var {Array}
|
||||
*/
|
||||
queryParams = ['name', 'page', 'limit', 'sort'];
|
||||
|
||||
/**
|
||||
* Columns for organization
|
||||
*
|
||||
* @memberof ConsoleAdminOrganizationsController
|
||||
*/
|
||||
columns = [
|
||||
{
|
||||
label: this.intl.t('common.name'),
|
||||
valuePath: 'name',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterComponent: 'filter/string',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('console.admin.organizations.index.phone-column'),
|
||||
valuePath: 'phone',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterComponent: 'filter/string',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('common.created-at'),
|
||||
valuePath: 'createdAt',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* `search` is a task that performs a search query on the 'company' model in the store.
|
||||
*
|
||||
* @method search
|
||||
* @param {string} query - The search query.
|
||||
* @returns {Promise} A promise that resolves with the search results.
|
||||
* @public
|
||||
*/
|
||||
@task({ restartable: true }) *search(event) {
|
||||
this.companies = yield this.store.query('company', { query: event.target.value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the organization-users route for the selected company.
|
||||
*
|
||||
* @method goToCompany
|
||||
* @param {Object} company - The selected company.
|
||||
*/
|
||||
@action goToCompany(company) {
|
||||
this.router.transitionTo('console.admin.organizations.index.users', company.public_id);
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { isBlank } from '@ember/utils';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
import { timeout } from 'ember-concurrency';
|
||||
|
||||
export default class ConsoleAdminOrganizationsIndexUsersController extends Controller {
|
||||
/**
|
||||
* Inject the `filters` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service filters;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the `router` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* The current page of data being viewed
|
||||
*
|
||||
* @var {Integer}
|
||||
*/
|
||||
@tracked nestedPage = 1;
|
||||
|
||||
/**
|
||||
* The maximum number of items to show per page
|
||||
*
|
||||
* @var {Integer}
|
||||
*/
|
||||
@tracked nestedLimit = 20;
|
||||
|
||||
/**
|
||||
* The filterable param `sort`
|
||||
*
|
||||
* @var {Array|String}
|
||||
*/
|
||||
@tracked nestedSort = '-created_at';
|
||||
|
||||
/**
|
||||
* The filterable param `sort`
|
||||
*
|
||||
* @var {String}
|
||||
*/
|
||||
@tracked nestedQuery = '';
|
||||
|
||||
/**
|
||||
* The company loaded.
|
||||
*
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
@tracked company;
|
||||
|
||||
/**
|
||||
* Queryable parameters for this controller's model
|
||||
*
|
||||
* @var {Array}
|
||||
*/
|
||||
queryParams = ['nestedPage', 'nestedLimit', 'nestedSort', 'nestedQuery'];
|
||||
|
||||
/**
|
||||
* Columns to render to the table.
|
||||
*
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
columns = [
|
||||
{
|
||||
label: this.intl.t('common.name'),
|
||||
valuePath: 'name',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('common.phone-number'),
|
||||
valuePath: 'phone',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('common.email'),
|
||||
valuePath: 'email',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('common.status'),
|
||||
valuePath: 'status',
|
||||
cellComponent: 'table/cell/status',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Set the overlay component context object.
|
||||
*
|
||||
* @param {Object} contextApi
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
@action setOverlayContext(contextApi) {
|
||||
this.contextApi = contextApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle closing the overlay.
|
||||
*
|
||||
* @return {Promise<Transition>}
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
@action onPressClose() {
|
||||
if (this.contextApi && typeof this.contextApi.close === 'function') {
|
||||
this.contextApi.close();
|
||||
}
|
||||
|
||||
return this.router.transitionTo('console.admin.organizations.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* `search` is a task that performs a search query on the 'company' model in the store.
|
||||
*
|
||||
* @method search
|
||||
* @param {string} query - The search query.
|
||||
* @returns {Promise} A promise that resolves with the search results.
|
||||
* @public
|
||||
*/
|
||||
@task({ restartable: true }) *search(event) {
|
||||
const searchQuery = event.target.value ?? '';
|
||||
if (isBlank(searchQuery)) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield timeout(600);
|
||||
this.nestedQuery = searchQuery;
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default class ConsoleSettingsAuthController extends Controller {}
|
||||
@@ -1,184 +0,0 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { task } from 'ember-concurrency-decorators';
|
||||
export default class InstallController extends Controller {
|
||||
@service fetch;
|
||||
@service notifications;
|
||||
@service router;
|
||||
@tracked error;
|
||||
@tracked steps = [
|
||||
{ task: 'createdb', name: 'Create Database', status: 'pending' },
|
||||
@@ -47,7 +46,7 @@ export default class InstallController extends Controller {
|
||||
|
||||
if (isCompleted) {
|
||||
this.notifications.success('Install completed successfully!');
|
||||
return this.router.transitionTo('onboard');
|
||||
return this.transitionToRoute('onboard');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ export default class InviteForUserController extends Controller {
|
||||
@service session;
|
||||
@service notifications;
|
||||
@service modalsManager;
|
||||
@service router;
|
||||
@tracked code;
|
||||
@tracked isLoading;
|
||||
|
||||
@@ -25,7 +24,7 @@ export default class InviteForUserController extends Controller {
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
return this.router.transitionTo('console').then(() => {
|
||||
return this.transitionToRoute('console').then(() => {
|
||||
if (response.needs_password && response.needs_password === true) {
|
||||
this.setPassword();
|
||||
}
|
||||
|
||||
@@ -21,13 +21,6 @@ export default class OnboardIndexController extends Controller {
|
||||
*/
|
||||
@service session;
|
||||
|
||||
/**
|
||||
* Inject the `router` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* Inject the `notifications` service
|
||||
*
|
||||
@@ -120,25 +113,21 @@ export default class OnboardIndexController extends Controller {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set user timezone
|
||||
input.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return this.fetch
|
||||
.post('onboard/create-account', input)
|
||||
.then(({ status, skipVerification, token, session }) => {
|
||||
if (status === 'success') {
|
||||
if (skipVerification === true && token) {
|
||||
// only manually authenticate if skip verification
|
||||
this.session.isOnboarding().manuallyAuthenticate(token);
|
||||
.then((response) => {
|
||||
if (response.status === 'success') {
|
||||
this.session.isOnboarding().manuallyAuthenticate(response.token);
|
||||
|
||||
return this.router.transitionTo('console').then(() => {
|
||||
if (response.skipVerification === true) {
|
||||
return this.transitionToRoute('console').then(() => {
|
||||
this.notifications.success('Welcome to Fleetbase!');
|
||||
});
|
||||
}
|
||||
|
||||
return this.router.transitionTo('onboard.verify-email', { queryParams: { hello: session } });
|
||||
return this.transitionToRoute('onboard.verify-email', { queryParams: { hello: response.session } });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -1,7 +1,125 @@
|
||||
import AuthVerificationController from '../auth/verification';
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { later } from '@ember/runloop';
|
||||
import { not } from '@ember/object/computed';
|
||||
|
||||
export default class OnboardVerifyEmailController extends Controller {
|
||||
/**
|
||||
* Inject the `fetch` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Inject the `notifications` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Inject the `modalsManager` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* Inject the `currentUser` service
|
||||
*
|
||||
* @memberof OnboardIndexController
|
||||
*/
|
||||
@service currentUser;
|
||||
|
||||
/**
|
||||
* The session paramerer.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked hello;
|
||||
|
||||
/**
|
||||
* The loading state of the verification request.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked isLoading = false;
|
||||
|
||||
/**
|
||||
* Validation state tracker.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked isReadyToSubmit = false;
|
||||
|
||||
/**
|
||||
* The request timeout to trigger alternative verification options.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked waitTimeout = 800 * 60 * 2;
|
||||
|
||||
/**
|
||||
* Determines if Fleetbase is still awaiting verification after a certain time.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked stillWaiting = false;
|
||||
|
||||
/**
|
||||
* the input code.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked code;
|
||||
|
||||
/**
|
||||
* The query param for the session token.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@tracked queryParams = ['hello'];
|
||||
|
||||
/**
|
||||
* The boolean opposite of `isReadyToSubmit`
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@not('isReadyToSubmit') isNotReadyToSubmit;
|
||||
|
||||
/**
|
||||
* Creates an instance of OnboardVerifyEmailController.
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
later(
|
||||
this,
|
||||
() => {
|
||||
this.stillWaiting = true;
|
||||
},
|
||||
this.waitTimeout
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the input
|
||||
*
|
||||
* @param {InputEvent} { target: { value } }
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action validateInput({ target: { value } }) {
|
||||
if (value.length > 5) {
|
||||
this.isReadyToSubmit = true;
|
||||
} else {
|
||||
this.isReadyToSubmit = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default class OnboardVerifyEmailController extends AuthVerificationController {
|
||||
/**
|
||||
* Submits to verify code.
|
||||
*
|
||||
@@ -9,24 +127,19 @@ export default class OnboardVerifyEmailController extends AuthVerificationContro
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action verifyCode() {
|
||||
const { hello, code } = this;
|
||||
const session = this.hello;
|
||||
const code = this.code;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return this.fetch
|
||||
.post('onboard/verify-email', { session: hello, code })
|
||||
.then(({ status, token }) => {
|
||||
if (status === 'ok') {
|
||||
.post('onboard/verify-email', { session, code })
|
||||
.then((response) => {
|
||||
if (response.status === 'success') {
|
||||
this.notifications.success('Email successfully verified!');
|
||||
this.notifications.info('Welcome to Fleetbase!');
|
||||
|
||||
if (token) {
|
||||
this.notifications.info('Welcome to Fleetbase!');
|
||||
this.session.manuallyAuthenticate(token);
|
||||
|
||||
return this.router.transitionTo('console');
|
||||
}
|
||||
|
||||
return this.router.transitionTo('auth.login');
|
||||
return this.transitionToRoute('console');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -36,4 +149,46 @@ export default class OnboardVerifyEmailController extends AuthVerificationContro
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to resend verification code by SMS.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action resendBySms() {
|
||||
this.modalsManager.show('modals/verify-by-sms', {
|
||||
title: 'Verify Account by Phone',
|
||||
acceptButtonText: 'Send',
|
||||
phone: this.currentUser.phone,
|
||||
confirm: (modal) => {
|
||||
modal.startLoading();
|
||||
const phone = modal.getOption('phone');
|
||||
|
||||
return this.fetch.post('onboard/send-verification-sms', { phone }).then(() => {
|
||||
this.notifications.success('Verification code SMS sent!');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to resend verification code by email.
|
||||
*
|
||||
* @memberof OnboardVerifyEmailController
|
||||
*/
|
||||
@action resendEmail() {
|
||||
this.modalsManager.show('modals/resend-verification-email', {
|
||||
title: 'Resend Verification Code',
|
||||
acceptButtonText: 'Send',
|
||||
email: this.currentUser.email,
|
||||
confirm: (modal) => {
|
||||
modal.startLoading();
|
||||
const email = modal.getOption('email');
|
||||
|
||||
return this.fetch.post('onboard/send-verification-email', { email }).then(() => {
|
||||
this.notifications.success('Verification code email sent!');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { helper } from '@ember/component/helper';
|
||||
|
||||
export default helper(function spreadWidgetOptions([params]) {
|
||||
const { id, options } = params;
|
||||
const gridOptions = { id, ...options };
|
||||
return gridOptions;
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
export function initialize(application) {
|
||||
const universe = application.lookup('service:universe');
|
||||
const defaultWidgets = [
|
||||
{
|
||||
widgetId: 'fleetbase-blog',
|
||||
name: 'Fleetbase Blog',
|
||||
description: 'Lists latest news and events from the Fleetbase official team.',
|
||||
icon: 'newspaper',
|
||||
component: 'fleetbase-blog',
|
||||
grid_options: { w: 8, h: 9, minW: 8, minH: 9 },
|
||||
options: {
|
||||
title: 'Fleetbase Blog',
|
||||
},
|
||||
},
|
||||
{
|
||||
widgetId: 'fleetbase-github-card',
|
||||
name: 'Github Card',
|
||||
description: 'Displays current Github stats from the official Fleetbase repo.',
|
||||
icon: faGithub,
|
||||
component: 'github-card',
|
||||
grid_options: { w: 4, h: 8, minW: 4, minH: 8 },
|
||||
options: {
|
||||
title: 'Github Card',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
universe.registerDefaultDashboardWidgets(defaultWidgets);
|
||||
universe.registerDashboardWidgets(defaultWidgets);
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
import config from 'ember-get-config';
|
||||
|
||||
export function initialize(owner) {
|
||||
const universe = owner.lookup('service:universe');
|
||||
|
||||
if (universe) {
|
||||
universe.registerOrganizationMenuItem(`v${config.version}`, {
|
||||
index: 4,
|
||||
route: null,
|
||||
icon: 'code-branch',
|
||||
iconSize: 'xs',
|
||||
iconClass: 'mr-1.5',
|
||||
wrapperClass: 'app-version-in-nav',
|
||||
overwriteWrapperClass: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
export default class CommentModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') parent_comment_uuid;
|
||||
@attr('string') subject_uuid;
|
||||
@attr('string') subject_type;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('user') author;
|
||||
@belongsTo('comment', { inverse: 'replies' }) parent;
|
||||
@hasMany('comment', { inverse: 'parent' }) replies;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') content;
|
||||
@attr('boolean') editable;
|
||||
@attr('raw') tags;
|
||||
@attr('raw') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
@attr('date') deleted_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('created_at') get createdAgo() {
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
return format(this.created_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
return format(this.updated_at, 'PPP p');
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
export default class DashboardWidgetModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') dashboard_uuid;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('dashboard') dashboard;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('string') component;
|
||||
@attr('object') grid_options;
|
||||
@attr('object') options;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
return format(this.updated_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAtShort() {
|
||||
return format(this.updated_at, 'PP');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
return format(this.created_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAtShort() {
|
||||
return format(this.created_at, 'PP');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the dashboard widget properties on the server
|
||||
*
|
||||
* @param {Object} [properties={}]
|
||||
* @return {Promise<DashboardWidgetModel>}
|
||||
* @memberof DashboardWidgetModel
|
||||
*/
|
||||
updateProperties(properties = {}) {
|
||||
this.setProperties(properties);
|
||||
return this.save();
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import Model, { attr, hasMany } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
import { getOwner } from '@ember/application';
|
||||
|
||||
export default class DashboardModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') user_uuid;
|
||||
|
||||
/** @relationships */
|
||||
@hasMany('dashboard-widget', { async: false }) widgets;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('boolean') is_default;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
return format(this.updated_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAtShort() {
|
||||
return format(this.updated_at, 'PP');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
return format(this.created_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAtShort() {
|
||||
return format(this.created_at, 'PP');
|
||||
}
|
||||
|
||||
/** @methods */
|
||||
addWidget(widget) {
|
||||
const owner = getOwner(this);
|
||||
const store = owner.lookup('service:store');
|
||||
const widgetRecord = store.createRecord('dashboard-widget', { ...widget, dashboard: this });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
widgetRecord
|
||||
.save()
|
||||
.then((widgetRecord) => {
|
||||
this.widgets.pushObject(widgetRecord);
|
||||
resolve(widgetRecord);
|
||||
})
|
||||
.catch((error) => {
|
||||
store.unloadRecord(widgetRecord);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeWidget(widget) {
|
||||
const owner = getOwner(this);
|
||||
const store = owner.lookup('service:store');
|
||||
const widgetRecord = store.peekRecord('dashboard-widget', widget);
|
||||
|
||||
if (widgetRecord) {
|
||||
return new Promise((resolve, reject) => {
|
||||
widgetRecord
|
||||
.destroyRecord()
|
||||
.then(() => {
|
||||
this.widgets.removeObject(widgetRecord);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,11 +79,6 @@ 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(', ');
|
||||
|
||||
@@ -10,9 +10,7 @@ Router.map(function () {
|
||||
this.route('auth', function () {
|
||||
this.route('login', { path: '/' });
|
||||
this.route('forgot-password');
|
||||
this.route('reset-password', { path: '/reset-password/:id' });
|
||||
this.route('two-fa');
|
||||
this.route('verification');
|
||||
this.route('reset-password');
|
||||
});
|
||||
this.route('onboard', function () {
|
||||
this.route('verify-email');
|
||||
@@ -27,11 +25,9 @@ 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 () {
|
||||
@@ -47,12 +43,7 @@ Router.map(function () {
|
||||
});
|
||||
this.route('branding');
|
||||
this.route('notifications');
|
||||
this.route('two-fa-settings');
|
||||
this.route('virtual', { path: '/:slug/:view' });
|
||||
this.route('organizations', function () {
|
||||
this.route('index', { path: '/' });
|
||||
this.route('users', { path: '/:company_id' });
|
||||
});
|
||||
});
|
||||
});
|
||||
this.route('install');
|
||||
|
||||
@@ -11,7 +11,6 @@ export default class ApplicationRoute extends Route {
|
||||
@service urlSearchParams;
|
||||
@service modalsManager;
|
||||
@service intl;
|
||||
@service router;
|
||||
@tracked defaultTheme;
|
||||
|
||||
/**
|
||||
@@ -28,11 +27,11 @@ export default class ApplicationRoute extends Route {
|
||||
this.defaultTheme = defaultTheme;
|
||||
|
||||
if (shouldInstall) {
|
||||
return this.router.transitionTo('install');
|
||||
return this.transitionTo('install');
|
||||
}
|
||||
|
||||
if (shouldOnboard) {
|
||||
return this.router.transitionTo('onboard');
|
||||
return this.transitionTo('onboard');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +49,7 @@ export default class ApplicationRoute extends Route {
|
||||
const shift = this.urlSearchParams.get('shift');
|
||||
|
||||
if (isAuthenticated && shift) {
|
||||
return this.router.transitionTo(pathToRoute(shift));
|
||||
return this.transitionTo(pathToRoute(shift));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,7 @@ import { inject as service } from '@ember/service';
|
||||
export default class AuthResetPasswordRoute extends Route {
|
||||
@service store;
|
||||
|
||||
async model(params) {
|
||||
return params;
|
||||
}
|
||||
|
||||
async setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
|
||||
// set brand to controller
|
||||
controller.brand = await this.store.findRecord('brand', 1);
|
||||
model() {
|
||||
return this.store.findRecord('brand', 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
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');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class AuthVerificationRoute extends Route {
|
||||
@service session;
|
||||
@service fetch;
|
||||
@service router;
|
||||
|
||||
queryParams = {
|
||||
token: {
|
||||
refreshModel: false,
|
||||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
beforeModel(transition) {
|
||||
let { token } = transition.to.queryParams;
|
||||
|
||||
return this.session.store.restore().then(({ email }) => {
|
||||
return this.fetch.post('auth/validate-verification-session', { email, token }).then(({ valid }) => {
|
||||
if (!valid) {
|
||||
return this.router.transitionTo('auth.login');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
let { email } = await this.session.store.restore();
|
||||
controller.email = email;
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,6 @@ export default class ConsoleRoute extends Route {
|
||||
*/
|
||||
@service session;
|
||||
|
||||
/**
|
||||
* Inject the `intl` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the `currentUser` service
|
||||
*
|
||||
@@ -79,12 +72,6 @@ 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;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleAccountAuthRoute extends Route {}
|
||||
@@ -4,12 +4,11 @@ import { inject as service } from '@ember/service';
|
||||
export default class ConsoleAdminRoute extends Route {
|
||||
@service currentUser;
|
||||
@service notifications;
|
||||
@service router;
|
||||
|
||||
beforeModel() {
|
||||
// USER MUST BE ADMIN
|
||||
if (!this.currentUser.user.is_admin) {
|
||||
return this.router.transitionTo('console').then(() => {
|
||||
return this.transitionTo('console').then(() => {
|
||||
this.notifications.error('You do not have authorization to access admin!');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ConsoleAdminOrganizationsRoute extends Route {
|
||||
@service store;
|
||||
|
||||
queryParams = {
|
||||
page: { refreshModel: true },
|
||||
query: { refreshModel: true },
|
||||
sort: { refreshModel: true },
|
||||
limit: { refreshModel: true },
|
||||
name: { refreshModel: true },
|
||||
country: { refreshModel: true },
|
||||
};
|
||||
|
||||
model(params) {
|
||||
return this.store.query('company', params);
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(controller, model);
|
||||
controller.companies = model;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import ArrayProxy from '@ember/array/proxy';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isArray } from '@ember/array';
|
||||
|
||||
export default class ConsoleAdminOrganizationsIndexUsersRoute extends Route {
|
||||
@service fetch;
|
||||
@service store;
|
||||
|
||||
queryParams = {
|
||||
nestedPage: { refreshModel: true },
|
||||
nestedLimit: { refreshModel: true },
|
||||
nestedSort: { refreshModel: true },
|
||||
nestedQuery: { refreshModel: true },
|
||||
};
|
||||
|
||||
model(params) {
|
||||
this.companyId = params.public_id;
|
||||
|
||||
return this.fetch
|
||||
.get(`companies/${this.companyId}/users`, {
|
||||
page: params.nestedPage,
|
||||
limit: params.nestedLimit,
|
||||
sort: params.nestedSort,
|
||||
query: params.nestedQuery,
|
||||
paginate: 1,
|
||||
})
|
||||
.then(this.transformResults.bind(this));
|
||||
}
|
||||
|
||||
transformResults({ users, meta }) {
|
||||
if (isArray(users)) {
|
||||
users = users.map((user) => this.fetch.jsonToModel(user, 'user'));
|
||||
}
|
||||
|
||||
return ArrayProxy.create({ content: users, meta });
|
||||
}
|
||||
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
controller.company = this.getCompany();
|
||||
}
|
||||
|
||||
getCompany() {
|
||||
const companies = this.store.peekAll('company');
|
||||
return companies.find((company) => {
|
||||
return this.companyId === company.public_id || this.companyId === company.id;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleAdminTwoFaSettingsRoute extends Route {}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleSettingsAuthRoute extends Route {}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleSettingsTwoFaRoute extends Route {}
|
||||
@@ -1,10 +1,3 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class InviteForUserRoute extends Route {
|
||||
@service store;
|
||||
|
||||
model() {
|
||||
return this.store.findRecord('brand', 1);
|
||||
}
|
||||
}
|
||||
export default class InviteForUserRoute extends Route {}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class CommentSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
author: { embedded: 'always' },
|
||||
parent: { embedded: 'always' },
|
||||
replies: { embedded: 'always' },
|
||||
};
|
||||
}
|
||||
|
||||
serializeAttribute(snapshot, json, key) {
|
||||
if (key === 'editable') {
|
||||
return;
|
||||
}
|
||||
|
||||
super.serializeAttribute(...arguments);
|
||||
}
|
||||
|
||||
serializeHasMany(snapshot, json, relationship) {
|
||||
let key = relationship.key;
|
||||
if (key === 'replies') {
|
||||
return;
|
||||
} else {
|
||||
super.serializeHasMany(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
serializeBelongsTo() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
|
||||
export default class DashboardWidgetSerializer extends ApplicationSerializer {}
|
||||
@@ -1,18 +0,0 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class DashboardSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
attrs = {
|
||||
widgets: { embedded: 'always' },
|
||||
};
|
||||
|
||||
serializeHasMany(snapshot, json, relationship) {
|
||||
let key = relationship.key;
|
||||
|
||||
if (key === 'widgets') {
|
||||
return;
|
||||
}
|
||||
|
||||
return super.serializeHasMany(...arguments);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class TwoFaSettingsSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {}
|
||||
@@ -1,255 +0,0 @@
|
||||
import Service from '@ember/service';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
import { action } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
|
||||
/**
|
||||
* Service for managing dashboards, including loading, creating, and deleting dashboards, as well as managing the current dashboard and widget states.
|
||||
* Utilizes Ember services such as `store`, `fetch`, `notifications`, and `universe` for data management and user interaction.
|
||||
*
|
||||
* @extends Service
|
||||
*/
|
||||
export default class DashboardService extends Service {
|
||||
/**
|
||||
* Ember Data store service for managing model data.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service store;
|
||||
|
||||
/**
|
||||
* Fetch service for making network requests.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Notifications service for displaying user notifications.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Universe service for accessing global application state or utility methods.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service universe;
|
||||
|
||||
/**
|
||||
* Internationalization service.
|
||||
* @type {Service}
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Tracked array of available dashboards.
|
||||
* @type {Array}
|
||||
*/
|
||||
@tracked dashboards = [];
|
||||
|
||||
/**
|
||||
* Tracked property representing the currently selected dashboard.
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked currentDashboard;
|
||||
|
||||
/**
|
||||
* Tracked boolean indicating if the dashboard is in editing mode.
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isEditingDashboard = false;
|
||||
|
||||
/**
|
||||
* Tracked boolean indicating if a widget is being added.
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isAddingWidget = false;
|
||||
|
||||
/**
|
||||
* Task for loading dashboards from the store. It sets the current dashboard and checks if adding widget is necessary.
|
||||
*/
|
||||
@task *loadDashboards() {
|
||||
const dashboards = yield this.store.findAll('dashboard');
|
||||
|
||||
if (isArray(dashboards)) {
|
||||
this.dashboards = dashboards.toArray();
|
||||
|
||||
// insert default dashboard if it's not loaded
|
||||
const defaultDashboard = this._createDefaultDashboard();
|
||||
if (this._isDefaultDashboardNotLoaded()) {
|
||||
this.dashboards.unshiftObject(defaultDashboard);
|
||||
}
|
||||
|
||||
// Set the current dashboard
|
||||
this.currentDashboard = this._getNextDashboard();
|
||||
if (this.currentDashboard && this.currentDashboard.widgets.length === 0) {
|
||||
this.onAddingWidget(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task for selecting a dashboard. Handles dashboard switching and updates the current dashboard.
|
||||
* @param {Object} dashboard - The dashboard object to select.
|
||||
*/
|
||||
@task *selectDashboard(dashboard) {
|
||||
if (dashboard.user_uuid === 'system') {
|
||||
this.currentDashboard = dashboard;
|
||||
yield this.fetch.post('dashboards/reset-default');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDashboard = yield this.fetch.post('dashboards/switch', { dashboard_uuid: dashboard.id }, { normalizeToEmberData: true }).catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
|
||||
if (currentDashboard) {
|
||||
this.currentDashboard = currentDashboard;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task for creating a new dashboard. It handles dashboard creation, success notification, and dashboard selection.
|
||||
* @param {string} name - Name of the new dashboard.
|
||||
*/
|
||||
@task *createDashboard(name) {
|
||||
const dashboardRecord = this.store.createRecord('dashboard', { name, is_default: true });
|
||||
const dashboard = yield dashboardRecord.save().catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
|
||||
if (dashboard) {
|
||||
this.notifications.success(this.intl.t('services.dashboard-service.create-dashboard-success-notification', { dashboardName: dashboard.name }));
|
||||
this.selectDashboard.perform(dashboard);
|
||||
this.dashboards.pushObject(dashboard);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task for deleting a dashboard. Handles dashboard deletion and success notification.
|
||||
* @param {Object} dashboard - The dashboard object to delete.
|
||||
* @param {Object} [options={}] - Optional configuration options.
|
||||
*/
|
||||
@task *deleteDashboard(dashboard, options = {}) {
|
||||
yield dashboard.destroyRecord().catch((error) => {
|
||||
this.notification.serverError(error);
|
||||
|
||||
if (typeof options.onError === 'function') {
|
||||
options.onError(error, dashboard);
|
||||
}
|
||||
});
|
||||
|
||||
this.notifications.success(this.intl.t('services.dashboard-service.delete-dashboard-success-notification', { dashboardName: dashboard.name }));
|
||||
yield this.loadDashboards.perform();
|
||||
yield this.selectDashboard.perform(this._getNextDashboard());
|
||||
|
||||
if (typeof options.callback === 'function') {
|
||||
options.callback(this.currentDashboard);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task for setting the current dashboard.
|
||||
* @param {Object} dashboard - The dashboard object to set as current.
|
||||
*/
|
||||
@task *setCurrentDashboard(dashboard) {
|
||||
const currentDashboard = yield this.fetch.post('dashboards/switch', { dashboard_uuid: dashboard.id }, { normalizeToEmberData: true }).catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
|
||||
if (currentDashboard) {
|
||||
this.currentDashboard = currentDashboard;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to toggle dashboard editing state.
|
||||
* @param {boolean} [state=true] - State to set for editing.
|
||||
*/
|
||||
@action onChangeEdit(state = true) {
|
||||
this.isEditingDashboard = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to toggle the state of adding a widget.
|
||||
* @param {boolean} [state=true] - State to set for adding a widget.
|
||||
*/
|
||||
@action onAddingWidget(state = true) {
|
||||
this.isAddingWidget = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default dashboard with predefined widgets.
|
||||
* @private
|
||||
* @returns {Object} The default dashboard object.
|
||||
*/
|
||||
_createDefaultDashboard() {
|
||||
let defaultDashboard;
|
||||
|
||||
// check store for default dashboard
|
||||
const loadedDashboars = this.store.peekAll('dashboard');
|
||||
|
||||
// check for default dashboard loaded in store
|
||||
defaultDashboard = loadedDashboars.find((dashboard) => dashboard.id === 'system');
|
||||
if (defaultDashboard) {
|
||||
return defaultDashboard;
|
||||
}
|
||||
|
||||
// create new default dashboard
|
||||
defaultDashboard = this.store.createRecord('dashboard', {
|
||||
id: 'system',
|
||||
uuid: 'system',
|
||||
name: 'Default Dashboard',
|
||||
is_default: false,
|
||||
user_uuid: 'system',
|
||||
widgets: this._createDefaultDashboardWidgets(),
|
||||
});
|
||||
|
||||
return defaultDashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates default widgets for the default dashboard.
|
||||
* @private
|
||||
* @returns {Array} An array of default dashboard widgets.
|
||||
*/
|
||||
_createDefaultDashboardWidgets() {
|
||||
const widgets = this.universe.getDefaultDashboardWidgets().map((defaultWidget) => {
|
||||
return this.store.createRecord('dashboard-widget', defaultWidget);
|
||||
});
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if default dashboard is already loaded.
|
||||
* @private
|
||||
* @return {Boolean}
|
||||
* @memberof DashboardService
|
||||
*/
|
||||
_isDefaultDashboardLoaded() {
|
||||
const defaultDashboard = this._createDefaultDashboard();
|
||||
return this.dashboards.some((dashboard) => dashboard.id === defaultDashboard.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if default dashboard is not already loaded.
|
||||
* @private
|
||||
* @return {Boolean}
|
||||
* @memberof DashboardService
|
||||
*/
|
||||
_isDefaultDashboardNotLoaded() {
|
||||
return !this._isDefaultDashboardLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current dasbhoard or next available dashboard.
|
||||
*
|
||||
* @return {DashboardModel}
|
||||
* @memberof DashboardService
|
||||
*/
|
||||
_getNextDashboard() {
|
||||
return this.dashboards.find((dashboard) => dashboard.is_default) || this.dashboards[0];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import 'inter-ui/inter.css';
|
||||
@import 'console.css';
|
||||
@import 'inter-ui/inter.css';
|
||||
@@ -1,46 +0,0 @@
|
||||
.two-fa-enforcement-alert svg.fa-triangle-exclamation {
|
||||
font-size: 2.25rem;
|
||||
padding-right: 0.5rem;
|
||||
color: rgb(202 138 4);
|
||||
}
|
||||
|
||||
.btn.btn-warning-alert.btn-warning,
|
||||
.two-fa-enforcement-alert button#two-fa-setup-button.btn.btn-warning body[data-theme='dark'] .btn.btn-warning-alert.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;
|
||||
}
|
||||
|
||||
.app-version-in-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
padding-left: 1rem;
|
||||
padding-top: 0.2rem;
|
||||
}
|
||||
|
||||
.fleetbase-pagination-meta-info-wrapper.within-layout-section-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.without-padding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.without-padding-y {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.without-padding-x {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
@@ -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 (t "auth.forgot-password.is-sent.title") (t "auth.forgot-password.not-sent.title")}}
|
||||
{{if this.isSent "Almost done!" "Forgot your password?"}}
|
||||
</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">
|
||||
{{t "auth.forgot-password.is-sent.message" htmlSafe=true}}
|
||||
<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.
|
||||
</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">
|
||||
{{t "auth.forgot-password.not-sent.message" htmlSafe=true appName=(t "app.name")}}
|
||||
<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.
|
||||
</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">
|
||||
{{t "auth.forgot-password.form.email-label"}}
|
||||
Your email address
|
||||
</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={{t "auth.forgot-password.form.email-label"}} />
|
||||
<Input @value={{this.email}} @type="email" id="email" name="email" required class="form-input form-input-lg w-full" placeholder="Your email" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row space-x-2">
|
||||
<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}} />
|
||||
<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}} />
|
||||
</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">
|
||||
{{t "auth.login.title"}}
|
||||
Sign in to your account
|
||||
</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">
|
||||
{{t "auth.login.failed-attempt.message" htmlSafe=true}}
|
||||
<strong>Forgot your password?</strong><br> Click the button below to reset your password.
|
||||
</p>
|
||||
</div>
|
||||
<Button @text={{t "auth.login.failed-attempt.button-text"}} @type="warning" @onClick={{this.forgotPassword}} />
|
||||
<Button @text="Ok, help me reset!" @type="warning" @onClick={{this.forgotPassword}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -25,30 +25,10 @@
|
||||
<input type="hidden" name="remember" value="true" />
|
||||
<div class="rounded-md shadow-sm">
|
||||
<div>
|
||||
<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}}
|
||||
/>
|
||||
<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}} />
|
||||
</div>
|
||||
<div class="-mt-px">
|
||||
<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}}
|
||||
/>
|
||||
<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}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -56,27 +36,22 @@
|
||||
<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">
|
||||
{{t "auth.login.form.remember-me-label"}}
|
||||
Remember me
|
||||
</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"
|
||||
>
|
||||
{{t "auth.login.form.forgot-password-label"}}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<Button @buttonType="submit" @type="primary" @text={{t "auth.login.form.sign-in-button"}} @icon="lock" @wrapperClass="btn-block" @isLoading={{this.isLoading}} @onClick={{this.login}} />
|
||||
<Button @buttonType="submit" @type="primary" @text="Sign In" @icon="lock" @wrapperClass="btn-block" @isLoading={{this.isLoading}} @onClick={{this.login}} />
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<Button @text={{t "auth.login.form.create-account-button"}} @wrapperClass="btn-block" @disabled={{this.isLoading}} @onClick={{fn (transition-to "onboard")}} />
|
||||
<Button @text="Create a new Account" @wrapperClass="btn-block" @disabled={{this.isLoading}} @onClick={{fn (transition-to "onboard")}} />
|
||||
</div>
|
||||
</form>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user