mirror of
https://github.com/fleetbase/fleetbase.git
synced 2025-12-30 10:57:22 +00:00
Compare commits
61 Commits
v0.4.4
...
feature/bi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff59941947 | ||
|
|
013e19f39e | ||
|
|
dba2b7afc5 | ||
|
|
a6e6625ae1 | ||
|
|
3777569f22 | ||
|
|
fa5b6700a3 | ||
|
|
6cce6a9db2 | ||
|
|
39d405cb57 | ||
|
|
9f88c7bc79 | ||
|
|
b3816c394a | ||
|
|
34462c61c4 | ||
|
|
080302eb86 | ||
|
|
b063cf6338 | ||
|
|
b6dca79251 | ||
|
|
c35af4d47a | ||
|
|
2a89659cc3 | ||
|
|
f8ae75d767 | ||
|
|
924448a4d0 | ||
|
|
5426ac374d | ||
|
|
73f56b7958 | ||
|
|
095eb87e14 | ||
|
|
fabc16612b | ||
|
|
3389cba935 | ||
|
|
099ea57c39 | ||
|
|
5492bc7299 | ||
|
|
00b037a385 | ||
|
|
151fea2505 | ||
|
|
08f901d865 | ||
|
|
116873a1ce | ||
|
|
20a1793447 | ||
|
|
72ab83dc7a | ||
|
|
171e365ba6 | ||
|
|
009f2d6e53 | ||
|
|
4a59318feb | ||
|
|
8920039b40 | ||
|
|
cd9be05714 | ||
|
|
fbe35545e9 | ||
|
|
56ab967d7a | ||
|
|
79604c7981 | ||
|
|
312eb1aa6f | ||
|
|
6572a59120 | ||
|
|
fa536c6183 | ||
|
|
d4626be332 | ||
|
|
ffc54ecdbb | ||
|
|
9bf097b88b | ||
|
|
52c8df8b66 | ||
|
|
908e0eb9ee | ||
|
|
6438138913 | ||
|
|
aee06a2146 | ||
|
|
ccacc6c597 | ||
|
|
62c396b789 | ||
|
|
295da5f331 | ||
|
|
e775ccc2c8 | ||
|
|
bd0759881f | ||
|
|
830ae69b1d | ||
|
|
3b9a80866f | ||
|
|
76badbf949 | ||
|
|
f968556585 | ||
|
|
6d01eab305 | ||
|
|
9406446db1 | ||
|
|
565db7bbce |
2
.github/workflows/cd.yml
vendored
2
.github/workflows/cd.yml
vendored
@@ -147,7 +147,7 @@ jobs:
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
pnpm build
|
||||
pnpm build --environment production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Deploy Console 🚀
|
||||
|
||||
170
.github/workflows/eks-cd.yml
vendored
Normal file
170
.github/workflows/eks-cd.yml
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
name: Fleetbase EKS CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["eksdeploy/*"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PROJECT: ${{ secrets.PROJECT }}
|
||||
GITHUB_AUTH_KEY: ${{ secrets._GITHUB_AUTH_TOKEN }}
|
||||
|
||||
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
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.EKS_DEPLOYER_ROLE }}
|
||||
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: 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: Update kube config
|
||||
run: aws eks update-kubeconfig --name ${{ secrets.EKS_CLUSTER_NAME }} --region ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Deploy the images 🚀
|
||||
env:
|
||||
REGISTRY: ${{ steps.login-ecr.outputs.registry }}/${{ env.PROJECT }}-${{ env.STACK }}
|
||||
run: |-
|
||||
set -eu
|
||||
# run deploy.sh script before deployments
|
||||
helm upgrade -i ${{ env.PROJECT }} infra/helm -n ${{ env.PROJECT}}-${{ env.STACK }} --set image.repository=${{ env.REGISTRY }} \
|
||||
--set image.tag=${{ env.VERSION }} --set 'api_host=${{ secrets.API_HOST }}' --set 'socketcluster_host=${{ secrets.SOCKETCLUSTER_HOST }}' \
|
||||
--set gcp=false --set 'ingress.annotations.kubernetes\.io/ingress\.class=null' --set 'ingress.annotations.alb\.ingress\.kubernetes\.io/scheme=internet-facing' \
|
||||
--set serviceAccount.name=default --set serviceAccount.create=false --set ingress.className=alb \
|
||||
--set 'ingress.annotations.alb\.ingress\.kubernetes\.io/listen-ports=[{"HTTPS":443}]' \
|
||||
--set service.type=NodePort
|
||||
|
||||
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
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
role-to-assume: ${{ secrets.EKS_DEPLOYER_ROLE }}
|
||||
role-session-name: github
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- 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
|
||||
|
||||
./ssm-parent -n /actions/${{ env.PROJECT }}/${{ env.STACK }}/configuration dotenv /tmp/dotenv.file
|
||||
# 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
|
||||
|
||||
- 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:
|
||||
API_HOST: ${{ secrets.API_HOST }}
|
||||
SOCKETCLUSTER_HOST: ${{ secrets.SOCKETCLUSTER_HOST }}
|
||||
SOCKETCLUSTER_PORT: "443" # it uses common ingress so port 443
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
pnpm build --environment production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Deploy Console 🚀
|
||||
run: |
|
||||
set -u
|
||||
|
||||
DEPLOY_BUCKET=${STATIC_DEPLOY_BUCKET:-${{ env.PROJECT }}-${{ env.STACK }}}
|
||||
# this value will come from the dotenv above
|
||||
echo "Deploying to $DEPLOY_BUCKET"
|
||||
wget -O- https://github.com/bep/s3deploy/releases/download/v2.11.0/s3deploy_2.11.0_linux-amd64.tar.gz | tar xzv -f - s3deploy
|
||||
./s3deploy -region ${AWS_REGION} -source console/dist -bucket ${DEPLOY_BUCKET}
|
||||
2
.github/workflows/gcp-cd.yml
vendored
2
.github/workflows/gcp-cd.yml
vendored
@@ -173,7 +173,7 @@ jobs:
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
pnpm build
|
||||
pnpm build --environment production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Deploy Console 🚀
|
||||
|
||||
11
COMMERCIAL_LICENSE.md
Normal file
11
COMMERCIAL_LICENSE.md
Normal file
@@ -0,0 +1,11 @@
|
||||
FLEETBASE DUAL LICENSE
|
||||
|
||||
COPYRIGHT (C) 2024 FLEETBASE PTE LTD.
|
||||
|
||||
PERMISSION IS HEREBY GRANTED, FREE OF CHARGE, TO ANY PERSON OBTAINING A COPY OF THIS SOFTWARE AND ASSOCIATED DOCUMENTATION FILES (THE "SOFTWARE"), TO USE THE SOFTWARE FOR NON-COMMERCIAL PURPOSES ONLY. NON-COMMERCIAL PURPOSES INCLUDE INTERNAL OPERATIONS, ACADEMIC RESEARCH, PERSONAL PROJECTS, OR ANY OTHER USE THAT IS NOT INTENDED FOR COMMERCIAL GAIN.
|
||||
|
||||
FOR VERSIONS 0.4.10 ONWARDS, YOU ARE PERMITTED TO USE THE SOFTWARE FOR NON-COMMERCIAL PURPOSES FREE OF CHARGE. HOWEVER, COMMERCIAL USE OF THIS SOFTWARE, INCLUDING BUT NOT LIMITED TO BUILDING SAAS PLATFORMS, OFFERING SERVICES TO THIRD PARTIES, OR INTEGRATING WITH COMMERCIAL PRODUCTS, REQUIRES THE PURCHASE OF A COMMERCIAL LICENSE FROM FLEETBASE PTE LTD.
|
||||
|
||||
FOR INQUIRIES REGARDING COMMERCIAL LICENSING OR ANY OTHER QUESTIONS RELATED TO FLEETBASE, PLEASE CONTACT HELLO@FLEETBASE.IO.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Fleetbase Pte Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT LICENSE
|
||||
|
||||
COPYRIGHT (C) 2023 FLEETBASE PTE LTD
|
||||
|
||||
PERMISSION IS HEREBY GRANTED, FREE OF CHARGE, TO ANY PERSON OBTAINING A COPY
|
||||
OF THIS SOFTWARE AND ASSOCIATED DOCUMENTATION FILES (THE "SOFTWARE"), TO DEAL
|
||||
IN THE SOFTWARE WITHOUT RESTRICTION, INCLUDING WITHOUT LIMITATION THE RIGHTS
|
||||
TO USE, COPY, MODIFY, MERGE, PUBLISH, DISTRIBUTE, SUBLICENSE, AND/OR SELL
|
||||
COPIES OF THE SOFTWARE, AND TO PERMIT PERSONS TO WHOM THE SOFTWARE IS
|
||||
FURNISHED TO DO SO, SUBJECT TO THE FOLLOWING CONDITIONS:
|
||||
|
||||
THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN ALL
|
||||
COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -9,9 +9,9 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"fleetbase/core-api": "^1.4.4",
|
||||
"fleetbase/fleetops-api": "^0.4.10",
|
||||
"fleetbase/storefront-api": "^0.3.0",
|
||||
"fleetbase/core-api": "^1.4.16",
|
||||
"fleetbase/fleetops-api": "^0.4.24",
|
||||
"fleetbase/storefront-api": "^0.3.7",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/octane": "^2.3",
|
||||
@@ -84,6 +84,7 @@
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"secure-http": false,
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
|
||||
3073
api/composer.lock
generated
3073
api/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ return [
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => [],
|
||||
'exposed_headers' => ['x-compressed-json', 'access-console-sandbox', 'access-console-sandbox-key'],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ return [
|
||||
'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')),
|
||||
'visibility_handler' => \League\Flysystem\GoogleCloudStorage\UniformBucketLevelAccessVisibility::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -17,3 +17,6 @@ php artisan fleetbase:seed
|
||||
|
||||
# Restart queue
|
||||
php artisan queue:restart
|
||||
|
||||
# Sync scheduler
|
||||
php artisan schedule-monitor:sync
|
||||
|
||||
@@ -7,6 +7,31 @@
|
||||
<InputGroup @name="S3 URL" @value={{this.s3Url}} disabled={{this.isLoading}} />
|
||||
<InputGroup @name="S3 Endpoint" @value={{this.s3Endpoint}} disabled={{this.isLoading}} />
|
||||
{{/if}}
|
||||
{{#if (eq this.driver "gcs")}}
|
||||
{{#if this.isGoogleCloudStorageEnvConfigued}}
|
||||
<div class="border border-yellow-900 shadow-sm rounded-lg bg-yellow-200 mb-4">
|
||||
<div class="px-3 py-2 text-sm text-yellow-900 flex items-center">
|
||||
<FaIcon @icon="triangle-exclamation" @size="md" class="mr-1.5" />
|
||||
Warning! GCS is already configured in the server environment. Changing values below may break this.
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<InputGroup @name="GCS Bucket" @value={{this.gcsBucket}} disabled={{this.isLoading}} />
|
||||
<InputGroup @name="GCS Service Account Key File" @wrapperClass="">
|
||||
<div class="flex flex-row items-center mb-0i">
|
||||
<UploadButton @name="firebase-service-account" @accept="text/plain,text/javascript,application/json" @onFileAdded={{this.uploadGcsCredentialsFile}} @buttonText="Upload Service Account JSON" @icon="upload" class="w-auto m-0i mt-0i" />
|
||||
{{#if this.gcsCredentialsFile}}
|
||||
<div class="ml-2.5 text-sm dark:text-white text-black flex flex-row items-center border border-blue-500 rounded-lg px-2 py-0.5 -mt-1">
|
||||
<FaIcon @icon="file-text" @size="sm" class="mr-2 dark:text-white text-black" />
|
||||
<span>{{this.gcsCredentialsFile.original_filename}}</span>
|
||||
<a href="javascript:;" class="text-red-500 ml-2" {{on "click" this.removeGcsCredentialsFile}}>
|
||||
<FaIcon @icon="times" class="text-red-500" />
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</InputGroup>
|
||||
{{/if}}
|
||||
{{#if this.testResponse}}
|
||||
<div class="animate-pulse flex flex-row items-center rounded-lg border {{if (eq this.testResponse.status 'error') 'border-red-900 bg-red-800 text-red-100' 'border-green-900 bg-green-800 text-green-100'}} shadow-sm my-2 px-4 py-2">
|
||||
<FaIcon @icon={{if (eq this.testResponse.status 'error') 'triangle-exclamation' 'circle-check'}} class="mr-1.5 {{if (eq this.testResponse.status 'error') 'text-red-200' 'text-green-200'}}" />
|
||||
|
||||
@@ -6,6 +6,7 @@ import { action } from '@ember/object';
|
||||
export default class ConfigureFilesystemComponent extends Component {
|
||||
@service fetch;
|
||||
@service notifications;
|
||||
@service currentUser;
|
||||
@tracked isLoading = false;
|
||||
@tracked testResponse;
|
||||
@tracked disks = [];
|
||||
@@ -13,6 +14,10 @@ export default class ConfigureFilesystemComponent extends Component {
|
||||
@tracked s3Bucket = null;
|
||||
@tracked s3Url = null;
|
||||
@tracked s3Endpoint = null;
|
||||
@tracked gcsBucket = null;
|
||||
@tracked gcsCredentialsFileId = null;
|
||||
@tracked gcsCredentialsFile = null;
|
||||
@tracked isGoogleCloudStorageEnvConfigued = false;
|
||||
|
||||
/**
|
||||
* Creates an instance of ConfigureFilesystemComponent.
|
||||
@@ -59,6 +64,8 @@ export default class ConfigureFilesystemComponent extends Component {
|
||||
url: this.s3Url,
|
||||
endpoint: this.s3Endpoint,
|
||||
},
|
||||
gcsCredentialsFileId: this.gcsCredentialsFileId,
|
||||
gcsBucket: this.gcsBucket,
|
||||
})
|
||||
.then(() => {
|
||||
this.notifications.success('Filesystem configuration saved.');
|
||||
@@ -82,4 +89,31 @@ export default class ConfigureFilesystemComponent extends Component {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action removeGcsCredentialsFile() {
|
||||
this.gcsCredentialsFileId = undefined;
|
||||
this.gcsCredentialsFile = undefined;
|
||||
}
|
||||
|
||||
@action uploadGcsCredentialsFile(file) {
|
||||
try {
|
||||
this.fetch.uploadFile.perform(
|
||||
file,
|
||||
{
|
||||
path: 'gcs',
|
||||
subject_uuid: this.currentUser.companyId,
|
||||
subject_type: 'company',
|
||||
type: 'gcs_credentials',
|
||||
},
|
||||
(uploadedFile) => {
|
||||
console.log('uploadedFile', uploadedFile);
|
||||
this.gcsCredentialsFileId = uploadedFile.id;
|
||||
this.gcsCredentialsFile = uploadedFile;
|
||||
console.log('this.gcsCredentialsFile', this.gcsCredentialsFile);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<Textarea class="form-input w-full" @value={{this.apn.private_key_content}} placeholder="APN Private Key" rows="10" disabled={{this.isLoading}} />
|
||||
</InputGroup> --}}
|
||||
<InputGroup @wrapperClass="flex flex-row items-center">
|
||||
<UploadButton @name="apn-key" @accept="text/plain,application/x-pem-file,application/x-pkcs12,application/x-x509-ca-cert,.p12,.pem,.p8" @onFileAdded={{this.uploadApnKey}} @buttonText="Upload P8 Key File" @uploadIcon="upload" class="w-auto m-0i mt-0i" />
|
||||
<UploadButton @name="apn-key" @accept="text/plain,application/x-pem-file,application/x-pkcs12,application/x-x509-ca-cert,.p12,.pem,.p8" @onFileAdded={{this.uploadApnKey}} @buttonText="Upload P8 Key File" @icon="upload" class="w-auto m-0i mt-0i" />
|
||||
{{#if this.apn.private_key_file}}
|
||||
<div class="ml-2.5 text-sm dark:text-white text-black flex flex-row items-center border border-blue-500 rounded-lg px-2 py-0.5 -mt-1">
|
||||
<FaIcon @icon="file-text" @size="sm" class="mr-2 dark:text-white text-black" />
|
||||
@@ -15,16 +15,32 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroup @wrapperClass="mb-0i">
|
||||
<Checkbox @label="APN Production" @value={{this.apn.production}} @onToggle={{fn (mut this.apn.production)}} @disabled={{this.isLoading}} />
|
||||
</InputGroup>
|
||||
</ContentPanel>
|
||||
|
||||
<ContentPanel @title="Firebase Configutation" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<InputGroup @wrapperClass="flex flex-row items-center mb-0i">
|
||||
<UploadButton @name="firebase-service-account" @accept="text/plain,text/javascript,application/json" @onFileAdded={{this.uploadFirebaseCredentials}} @buttonText="Upload Service Account JSON" @icon="upload" class="w-auto m-0i mt-0i" />
|
||||
{{#if this.firebase.credentials_file}}
|
||||
<div class="ml-2.5 text-sm dark:text-white text-black flex flex-row items-center border border-blue-500 rounded-lg px-2 py-0.5 -mt-1">
|
||||
<FaIcon @icon="file-text" @size="sm" class="mr-2 dark:text-white text-black" />
|
||||
<span>{{this.firebase.credentials_file.original_filename}}</span>
|
||||
<a href="javascript:;" class="text-red-500 ml-2" {{on "click" this.removeFirebaseCredentialsFile}}><FaIcon @icon="times" class="text-red-500" /></a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</InputGroup>
|
||||
</ContentPanel>
|
||||
|
||||
<ContentPanel @title="Test Push Notification" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-900">
|
||||
{{#if this.testResponse}}
|
||||
<div class="animate-pulse flex flex-row items-center rounded-lg border {{if (eq this.testResponse.status 'error') 'border-red-900 bg-red-800 text-red-100' 'border-green-900 bg-green-800 text-green-100'}} shadow-sm my-2 px-4 py-2">
|
||||
<FaIcon @icon={{if (eq this.testResponse.status 'error') 'triangle-exclamation' 'circle-check'}} class="mr-1.5 {{if (eq this.testResponse.status 'error') 'text-red-200' 'text-green-200'}}" />
|
||||
<span class="text-xs">{{this.this.testResponse.message}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="mt-3 rounded-lg bg-gray-900 shadow-inner p-3">
|
||||
<div class="">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="flex flex-row items-center">
|
||||
<div class="text-sm w-40">Title:</div>
|
||||
@@ -47,6 +63,8 @@
|
||||
</div>
|
||||
</ContentPanel>
|
||||
|
||||
<Spacer @height="300px" />
|
||||
|
||||
<EmberWormhole @to="next-view-section-subheader-actions">
|
||||
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
|
||||
</EmberWormhole>
|
||||
@@ -18,14 +18,13 @@ export default class ConfigureNotificationChannelsComponent extends Component {
|
||||
team_id: '',
|
||||
app_bundle_id: '',
|
||||
private_key_path: '',
|
||||
_private_key_path: '',
|
||||
private_key_file_id: '',
|
||||
private_key_file: null,
|
||||
production: true,
|
||||
};
|
||||
@tracked fcm = {
|
||||
firebase_credentials_json: '',
|
||||
firebase_database_url: '',
|
||||
firebase_project_name: '',
|
||||
@tracked firebase = {
|
||||
credentials: '',
|
||||
};
|
||||
|
||||
constructor() {
|
||||
@@ -38,26 +37,36 @@ export default class ConfigureNotificationChannelsComponent extends Component {
|
||||
apnConfig.private_key_file = null;
|
||||
apnConfig.private_key_file_id = '';
|
||||
apnConfig.private_key_path = '';
|
||||
apnConfig._private_key_path = '';
|
||||
|
||||
this.apn = apnConfig;
|
||||
}
|
||||
|
||||
@action removeFirebaseCredentialsFile() {
|
||||
const firebaseConfig = this.firebase;
|
||||
firebaseConfig.credentials_file = null;
|
||||
firebaseConfig.credentials_file_id = '';
|
||||
firebaseConfig.credentials = '';
|
||||
|
||||
this.firebase = firebaseConfig;
|
||||
}
|
||||
|
||||
@action uploadApnKey(file) {
|
||||
try {
|
||||
this.fetch.uploadFile.perform(
|
||||
file,
|
||||
{
|
||||
disk: 'local',
|
||||
path: `apn`,
|
||||
path: 'apn',
|
||||
subject_uuid: this.currentUser.companyId,
|
||||
subject_type: `company`,
|
||||
type: `apn_key`,
|
||||
subject_type: 'company',
|
||||
type: 'apn_key',
|
||||
},
|
||||
(uploadedFile) => {
|
||||
const apnConfig = this.apn;
|
||||
apnConfig.private_key_file = uploadedFile;
|
||||
apnConfig.private_key_file_id = uploadedFile.id;
|
||||
apnConfig.private_key_path = uploadedFile.path;
|
||||
apnConfig._private_key_path = uploadedFile.path;
|
||||
|
||||
this.apn = apnConfig;
|
||||
}
|
||||
@@ -67,6 +76,30 @@ export default class ConfigureNotificationChannelsComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@action uploadFirebaseCredentials(file) {
|
||||
try {
|
||||
this.fetch.uploadFile.perform(
|
||||
file,
|
||||
{
|
||||
path: 'firebase',
|
||||
subject_uuid: this.currentUser.companyId,
|
||||
subject_type: 'company',
|
||||
type: 'firebase_credentials',
|
||||
},
|
||||
(uploadedFile) => {
|
||||
const firebaseConfig = this.firebase;
|
||||
firebaseConfig.credentials_file = uploadedFile;
|
||||
firebaseConfig.credentials_file_id = uploadedFile.id;
|
||||
firebaseConfig.credentials_file_path = uploadedFile.path;
|
||||
|
||||
this.firebase = firebaseConfig;
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@action setConfigValues(config) {
|
||||
for (const key in config) {
|
||||
if (this[key] !== undefined) {
|
||||
@@ -94,9 +127,13 @@ export default class ConfigureNotificationChannelsComponent extends Component {
|
||||
const apnConfig = this.apn;
|
||||
delete apnConfig.private_key_file;
|
||||
|
||||
const firebaseConfig = this.firebase;
|
||||
delete firebaseConfig.credentials_file;
|
||||
|
||||
this.fetch
|
||||
.post('settings/notification-channels-config', {
|
||||
apn: apnConfig,
|
||||
firebase: firebaseConfig,
|
||||
})
|
||||
.then(() => {
|
||||
this.notifications.success("Notification channel's configuration saved.");
|
||||
@@ -112,6 +149,7 @@ export default class ConfigureNotificationChannelsComponent extends Component {
|
||||
this.fetch
|
||||
.post('settings/test-notification-channels-config', {
|
||||
apn: this.apn,
|
||||
firebase: this.firebase,
|
||||
title: this.testTitle,
|
||||
message: this.testMessage,
|
||||
apnToken: this.apnToken,
|
||||
|
||||
1
console/app/components/console-wormhole.hbs
Normal file
1
console/app/components/console-wormhole.hbs
Normal file
@@ -0,0 +1 @@
|
||||
<div id="console-wormhole" />
|
||||
@@ -1,11 +1,15 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action, computed } from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
import { isBlank } from '@ember/utils';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { storageFor } from 'ember-local-storage';
|
||||
import { add, isPast } from 'date-fns';
|
||||
import fetch from 'fetch';
|
||||
|
||||
export default class GithubCardComponent extends Component {
|
||||
@storageFor('local-cache') localCache;
|
||||
@tracked data;
|
||||
@tracked tags;
|
||||
@tracked isLoading = false;
|
||||
@@ -30,29 +34,47 @@ export default class GithubCardComponent extends Component {
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.loadRepositoryData();
|
||||
this.loadRepositoryTags();
|
||||
this.getRepositoryData.perform();
|
||||
this.getRepositoryTags.perform();
|
||||
}
|
||||
|
||||
@action loadRepositoryData() {
|
||||
this.isLoading = true;
|
||||
@task *getRepositoryData() {
|
||||
// Check if cached data and expiration are available
|
||||
const cachedData = this.localCache.get('fleetbase-github-data');
|
||||
const expiration = this.localCache.get('fleetbase-github-data-expiration');
|
||||
|
||||
return fetch('https://api.github.com/repos/fleetbase/fleetbase')
|
||||
.then((response) => {
|
||||
return response.json().then((data) => {
|
||||
this.data = data;
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
// Check if the cached data is still valid
|
||||
if (cachedData && expiration && !isPast(new Date(expiration))) {
|
||||
// Use cached data
|
||||
this.data = cachedData;
|
||||
} else {
|
||||
// Fetch new data
|
||||
const response = yield fetch('https://api.github.com/repos/fleetbase/fleetbase');
|
||||
if (response.ok) {
|
||||
this.data = yield response.json();
|
||||
this.localCache.set('fleetbase-github-data', this.data);
|
||||
this.localCache.set('fleetbase-github-data-expiration', add(new Date(), { hours: 6 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action loadRepositoryTags() {
|
||||
return fetch('https://api.github.com/repos/fleetbase/fleetbase/tags').then((response) => {
|
||||
return response.json().then((data) => {
|
||||
this.tags = data;
|
||||
});
|
||||
});
|
||||
@task *getRepositoryTags() {
|
||||
// Check if cached tags and expiration are available
|
||||
const cachedTags = this.localCache.get('fleetbase-github-tags');
|
||||
const expiration = this.localCache.get('fleetbase-github-tags-expiration');
|
||||
|
||||
// Check if the cached tags are still valid
|
||||
if (cachedTags && expiration && !isPast(new Date(expiration))) {
|
||||
// Use cached tags
|
||||
this.tags = cachedTags;
|
||||
} else {
|
||||
// Fetch new tags
|
||||
const response = yield fetch('https://api.github.com/repos/fleetbase/fleetbase/tags');
|
||||
if (response.ok) {
|
||||
this.tags = yield response.json();
|
||||
this.localCache.set('fleetbase-github-tags', this.tags);
|
||||
this.localCache.set('fleetbase-github-tags-expiration', add(new Date(), { hours: 6 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
console/app/components/modals/validate-password.hbs
Normal file
13
console/app/components/modals/validate-password.hbs
Normal file
@@ -0,0 +1,13 @@
|
||||
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
||||
<div class="px-5">
|
||||
{{#if @options.body}}
|
||||
<p class="dark:text-gray-400 text-gray-700 mb-4">{{@options.body}}</p>
|
||||
{{/if}}
|
||||
<InputGroup>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Password" @type="password" @value={{this.password}} @wrapperClass="mb-0i" />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.confirmPassword}} @wrapperClass="mb-0i" />
|
||||
</div>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</Modal::Default>
|
||||
49
console/app/components/modals/validate-password.js
Normal file
49
console/app/components/modals/validate-password.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ModalsValidatePasswordComponent extends Component {
|
||||
@service fetch;
|
||||
@service notifications;
|
||||
@tracked options = {};
|
||||
@tracked password;
|
||||
@tracked confirmPassword;
|
||||
|
||||
constructor(owner, { options }) {
|
||||
super(...arguments);
|
||||
this.options = options;
|
||||
this.setupOptions();
|
||||
}
|
||||
|
||||
setupOptions() {
|
||||
this.options.title = 'Validate Current Password';
|
||||
this.options.acceptButtonText = 'Validate Password';
|
||||
this.options.declineButtonHidden = true;
|
||||
this.options.confirm = (modal) => {
|
||||
modal.startLoading();
|
||||
return this.validatePassword.perform();
|
||||
};
|
||||
}
|
||||
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
try {
|
||||
yield this.fetch.post('users/validate-password', {
|
||||
password: this.password,
|
||||
password_confirmation: this.confirmPassword,
|
||||
});
|
||||
|
||||
isPasswordValid = true;
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Invalid current password.');
|
||||
}
|
||||
|
||||
if (typeof this.options.onValidated === 'function') {
|
||||
this.options.onValidated(isPasswordValid);
|
||||
}
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class AuthResetPasswordController extends Controller {
|
||||
/**
|
||||
@@ -54,38 +54,23 @@ export default class AuthResetPasswordController extends Controller {
|
||||
@tracked password_confirmation;
|
||||
|
||||
/**
|
||||
* Loading stae of password reset.
|
||||
* The reset password task.
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@tracked isLoading;
|
||||
|
||||
/**
|
||||
* The reset password action.
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@action resetPassword(event) {
|
||||
// firefox patch
|
||||
@task *resetPassword(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const { code, password, password_confirmation } = this;
|
||||
const { id } = this.model;
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
yield this.fetch.post('auth/reset-password', { link: id, code, password, password_confirmation });
|
||||
} catch (error) {
|
||||
return this.notifications.serverError(error);
|
||||
}
|
||||
|
||||
this.fetch
|
||||
.post('auth/reset-password', { link: id, code, password, password_confirmation })
|
||||
.then(() => {
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
|
||||
return this.router.transitionTo('auth.login');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
yield this.router.transitionTo('auth.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,17 +34,11 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* The user's current password.
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked password;
|
||||
|
||||
/**
|
||||
* The user's confirmation of the new password.
|
||||
* Service for managing modals.
|
||||
*
|
||||
* @type {string}
|
||||
* @type {router}
|
||||
*/
|
||||
@tracked confirmPassword;
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* The new password the user intends to set.
|
||||
@@ -60,13 +54,6 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
*/
|
||||
@tracked newConfirmPassword;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the current password has been validated.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isPasswordValidated = false;
|
||||
|
||||
/**
|
||||
* System-wide two-factor authentication configuration.
|
||||
*
|
||||
@@ -106,28 +93,6 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
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.
|
||||
*
|
||||
@@ -163,6 +128,58 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
this.saveUserTwoFaSettings.perform(this.twoFaSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changePassword
|
||||
*/
|
||||
@task *changePassword(event) {
|
||||
// If from event fired
|
||||
if (event instanceof Event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Validate current password
|
||||
const isPasswordValid = yield this.validatePassword.perform();
|
||||
if (!isPasswordValid) {
|
||||
this.newPassword = undefined;
|
||||
this.newConfirmPassword = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
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.serverError(error, 'Failed to change password.');
|
||||
}
|
||||
|
||||
this.newPassword = undefined;
|
||||
this.newConfirmPassword = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to validate current password
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
yield this.modalsManager.show('modals/validate-password', {
|
||||
body: 'You must validate your current password before it can be changed.',
|
||||
onValidated: (isValid) => {
|
||||
isPasswordValid = isValid;
|
||||
},
|
||||
});
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
@@ -209,40 +226,4 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ConsoleAccountIndexController extends Controller {
|
||||
/**
|
||||
@@ -18,6 +18,7 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Inject the `notifications` service.
|
||||
*
|
||||
@@ -26,11 +27,11 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* The loading state of request.
|
||||
* Inject the `modalsManager` service.
|
||||
*
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@tracked isLoading = false;
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* Alias to the currentUser service user record.
|
||||
@@ -50,9 +51,9 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
file,
|
||||
{
|
||||
path: `uploads/${this.user.company_uuid}/users/${this.user.slug}`,
|
||||
key_uuid: this.user.id,
|
||||
key_type: `user`,
|
||||
type: `user_avatar`,
|
||||
subject_uuid: this.user.id,
|
||||
subject_type: 'user',
|
||||
type: 'user_avatar',
|
||||
},
|
||||
(uploadedFile) => {
|
||||
this.user.setProperties({
|
||||
@@ -66,27 +67,64 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the Profile settings.
|
||||
* Starts the task to change password
|
||||
*
|
||||
* @return {Promise}
|
||||
* @param {Event} event
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@action saveProfile() {
|
||||
const user = this.user;
|
||||
@task *saveProfile(event) {
|
||||
// If from event fired
|
||||
if (event instanceof Event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
let canUpdateProfile = true;
|
||||
// If email has been changed prompt for password validation
|
||||
if (this.changedUserAttribute('email')) {
|
||||
canUpdateProfile = yield this.validatePassword.perform();
|
||||
}
|
||||
|
||||
return user
|
||||
.save()
|
||||
.then((user) => {
|
||||
if (canUpdateProfile === true) {
|
||||
try {
|
||||
const user = yield this.user.save();
|
||||
this.notifications.success('Profile changes saved.');
|
||||
this.currentUser.set('user', user);
|
||||
})
|
||||
.catch((error) => {
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.user.rollbackAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to validate current password
|
||||
*
|
||||
* @return {boolean}
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
yield this.modalsManager.show('modals/validate-password', {
|
||||
body: 'You must validate your password to update the account email address.',
|
||||
onValidated: (isValid) => {
|
||||
isPasswordValid = isValid;
|
||||
},
|
||||
});
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any user attribute has been changed
|
||||
*
|
||||
* @param {string} attributeKey
|
||||
* @return {boolean}
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
changedUserAttribute(attributeKey) {
|
||||
const changedAttributes = this.user.changedAttributes();
|
||||
return changedAttributes[attributeKey] !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
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.
|
||||
@@ -41,6 +40,13 @@ export default class ConsoleAdminOrganizationsController extends Controller {
|
||||
*/
|
||||
@service filters;
|
||||
|
||||
/**
|
||||
* The search query param value.
|
||||
*
|
||||
* @var {String|null}
|
||||
*/
|
||||
@tracked query;
|
||||
|
||||
/**
|
||||
* The current page of data being viewed
|
||||
*
|
||||
@@ -104,14 +110,36 @@ export default class ConsoleAdminOrganizationsController extends Controller {
|
||||
filterable: true,
|
||||
filterComponent: 'filter/string',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('console.admin.organizations.index.owner-name-column'),
|
||||
valuePath: 'owner.name',
|
||||
width: '200px',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.intl.t('console.admin.organizations.index.owner-email-column'),
|
||||
valuePath: 'owner.email',
|
||||
width: '200px',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
{
|
||||
label: this.intl.t('console.admin.organizations.index.phone-column'),
|
||||
valuePath: 'phone',
|
||||
valuePath: 'owner.phone',
|
||||
width: '200px',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterComponent: 'filter/string',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('console.admin.organizations.index.users-count-column'),
|
||||
valuePath: 'users_count',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.intl.t('common.created-at'),
|
||||
valuePath: 'createdAt',
|
||||
@@ -119,15 +147,14 @@ export default class ConsoleAdminOrganizationsController extends Controller {
|
||||
];
|
||||
|
||||
/**
|
||||
* `search` is a task that performs a search query on the 'company' model in the store.
|
||||
* Update search query param and reset page to 1
|
||||
*
|
||||
* @method search
|
||||
* @param {string} query - The search query.
|
||||
* @returns {Promise} A promise that resolves with the search results.
|
||||
* @public
|
||||
* @param {Event} event
|
||||
* @memberof ConsoleAdminOrganizationsController
|
||||
*/
|
||||
@task({ restartable: true }) *search(event) {
|
||||
this.companies = yield this.store.query('company', { query: event.target.value });
|
||||
@action search(event) {
|
||||
this.query = event.target.value ?? '';
|
||||
this.page = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,6 @@ 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 {
|
||||
/**
|
||||
@@ -63,6 +60,13 @@ export default class ConsoleAdminOrganizationsIndexUsersController extends Contr
|
||||
*/
|
||||
@tracked company;
|
||||
|
||||
/**
|
||||
* The overlay context API.
|
||||
*
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
@tracked contextApi;
|
||||
|
||||
/**
|
||||
* Queryable parameters for this controller's model
|
||||
*
|
||||
@@ -95,6 +99,17 @@ export default class ConsoleAdminOrganizationsIndexUsersController extends Contr
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Update search query param and reset page to 1
|
||||
*
|
||||
* @param {Event} event
|
||||
* @memberof ConsoleAdminOrganizationsController
|
||||
*/
|
||||
@action search(event) {
|
||||
this.nestedQuery = event.target.value ?? '';
|
||||
this.nestedPage = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the overlay component context object.
|
||||
*
|
||||
@@ -118,22 +133,4 @@ export default class ConsoleAdminOrganizationsIndexUsersController extends Contr
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ConsoleAdminScheduleMonitorLogsController extends Controller {
|
||||
/**
|
||||
* The router service.
|
||||
*
|
||||
* @memberof ConsoleAdminScheduleMonitorLogsController
|
||||
*/
|
||||
@service router;
|
||||
/**
|
||||
* The fetch service.
|
||||
*
|
||||
* @memberof ConsoleAdminScheduleMonitorLogsController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Tracked property for logs.
|
||||
* @type {Array}
|
||||
*/
|
||||
@tracked logs = [];
|
||||
|
||||
/**
|
||||
* Tracked property for the context API.
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked contextApi;
|
||||
|
||||
/**
|
||||
* Periodically reloads logs every 3 seconds.
|
||||
*
|
||||
* @memberof ConsoleAdminScheduleMonitorLogsController
|
||||
*/
|
||||
@task *reload(task) {
|
||||
this.logs = yield this.fetch.get(`schedule-monitor/${task.id}/logs`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.schedule-monitor');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
export function initialize() {
|
||||
const socketClusterClientScript = document.createElement('script');
|
||||
socketClusterClientScript.src = '/assets/socketcluster-client.min.js';
|
||||
document.body.appendChild(socketClusterClientScript);
|
||||
// Check if the script already exists
|
||||
// Only insert the script tag if it doesn't already exist
|
||||
if (!document.querySelector('script[data-socketcluster-client]')) {
|
||||
const socketClusterClientScript = document.createElement('script');
|
||||
socketClusterClientScript.setAttribute('data-socketcluster-client', '1');
|
||||
socketClusterClientScript.src = '/assets/socketcluster-client.min.js';
|
||||
document.body.appendChild(socketClusterClientScript);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -29,6 +29,7 @@ export default class CategoryModel extends Model {
|
||||
@attr('string') slug;
|
||||
@attr('string') order;
|
||||
@attr('raw') translations;
|
||||
@attr('raw') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') deleted_at;
|
||||
|
||||
@@ -13,6 +13,7 @@ export default class Company extends Model {
|
||||
@attr('string') place_uuid;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('user') owner;
|
||||
@belongsTo('file') logo;
|
||||
@belongsTo('file') backdrop;
|
||||
|
||||
@@ -23,6 +24,7 @@ export default class Company extends Model {
|
||||
@attr('string') backdrop_url;
|
||||
@attr('string') description;
|
||||
@attr('raw') options;
|
||||
@attr('number') users_count;
|
||||
@attr('string') type;
|
||||
@attr('string') currency;
|
||||
@attr('string') country;
|
||||
|
||||
54
console/app/models/custom-field-value.js
Normal file
54
console/app/models/custom-field-value.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
function isValidFileObjectJson(str) {
|
||||
return typeof str === 'string' && str.startsWith('{') && str.endsWith('}');
|
||||
}
|
||||
|
||||
export default class CustomFieldValueModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') custom_field_uuid;
|
||||
@attr('string') subject_uuid;
|
||||
@attr('string') subject_type;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') value;
|
||||
@attr('string') value_type;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
@attr('date') deleted_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('value') get asFile() {
|
||||
const owner = getOwner(this);
|
||||
const fetch = owner.lookup(`service:fetch`);
|
||||
const value = this.value;
|
||||
if (!isValidFileObjectJson(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fileModel = fetch.jsonToModel(value, 'file');
|
||||
return fileModel;
|
||||
}
|
||||
|
||||
@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');
|
||||
}
|
||||
}
|
||||
48
console/app/models/custom-field.js
Normal file
48
console/app/models/custom-field.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
export default class CustomFieldModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') category_uuid;
|
||||
@attr('string') subject_uuid;
|
||||
@attr('string') subject_type;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('string') description;
|
||||
@attr('string') help_text;
|
||||
@attr('string') label;
|
||||
@attr('string') type;
|
||||
@attr('string') component;
|
||||
@attr('string') default_value;
|
||||
@attr('number') order;
|
||||
@attr('boolean') required;
|
||||
@attr('boolean', { defaultValue: true }) editable;
|
||||
@attr('raw') options;
|
||||
@attr('raw') validation_rules;
|
||||
@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,6 +1,5 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
export default class NotificationModel extends Model {
|
||||
|
||||
@@ -3,13 +3,21 @@ import { inject as service } from '@ember/service';
|
||||
|
||||
export default class AuthResetPasswordRoute extends Route {
|
||||
@service store;
|
||||
@service fetch;
|
||||
@service router;
|
||||
@service notifications;
|
||||
@service intl;
|
||||
|
||||
async model(params) {
|
||||
return params;
|
||||
async model({ id }) {
|
||||
return this.fetch.get('auth/validate-verification', { id });
|
||||
}
|
||||
|
||||
async setupController(controller) {
|
||||
async setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
if (model.is_valid === false) {
|
||||
this.notifications.warning(this.intl.t('auth.reset-password.invalid-verification-code'));
|
||||
return this.router.transitionTo('auth');
|
||||
}
|
||||
|
||||
// set brand to controller
|
||||
controller.brand = await this.store.findRecord('brand', 1);
|
||||
|
||||
@@ -14,11 +14,6 @@ export default class ConsoleAdminOrganizationsRoute extends Route {
|
||||
};
|
||||
|
||||
model(params) {
|
||||
return this.store.query('company', params);
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(controller, model);
|
||||
controller.companies = model;
|
||||
return this.store.query('company', { view: 'admin', ...params });
|
||||
}
|
||||
}
|
||||
|
||||
10
console/app/routes/console/admin/schedule-monitor.js
Normal file
10
console/app/routes/console/admin/schedule-monitor.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ConsoleAdminScheduleMonitorRoute extends Route {
|
||||
@service fetch;
|
||||
|
||||
model() {
|
||||
return this.fetch.get('schedule-monitor/tasks');
|
||||
}
|
||||
}
|
||||
14
console/app/routes/console/admin/schedule-monitor/logs.js
Normal file
14
console/app/routes/console/admin/schedule-monitor/logs.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ConsoleAdminScheduleMonitorLogsRoute extends Route {
|
||||
@service fetch;
|
||||
|
||||
model({ id }) {
|
||||
return this.fetch.get(`schedule-monitor/${id}`);
|
||||
}
|
||||
|
||||
async setupController(controller, model) {
|
||||
controller.logs = await this.fetch.get(`schedule-monitor/${model.id}/logs`);
|
||||
}
|
||||
}
|
||||
15
console/app/serializers/company.js
Normal file
15
console/app/serializers/company.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class CompanySerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
owner: { embedded: 'always' },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -44,3 +44,7 @@ body[data-theme='dark'] .two-fa-enforcement-alert button#two-fa-setup-button.btn
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
body.console-admin-organizations-index-index .next-table-wrapper > table {
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form class="space-y-6" {{on "submit" this.resetPassword}}>
|
||||
<form class="space-y-6" {{on "submit" (perform this.resetPassword)}}>
|
||||
<InputGroup @name={{t "auth.reset-password.form.code.label"}} @value={{this.code}} @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.code.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.password.label"}} @value={{this.password}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.password.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.confirm-password.label"}} @value={{this.password_confirmation}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.confirm-password.help-text"}} />
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "auth.reset-password.form.submit-button"}} @onClick={{this.resetPassword}} @isLoading={{this.isLoading}} />
|
||||
<Button @size="lg" @buttonType="button" @text={{t "auth.reset-password.form.back-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "auth.reset-password.form.submit-button"}} @onClick={{perform this.resetPassword}} @isLoading={{not this.resetPassword.isIdle}} />
|
||||
<Button @size="lg" @buttonType="button" @text={{t "auth.reset-password.form.back-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{not this.resetPassword.isIdle}} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -19,4 +19,4 @@
|
||||
<EmberWormhole @to="view-header-actions">
|
||||
<LocaleSelector class="mr-0.5" />
|
||||
</EmberWormhole>
|
||||
<div id="console-wormhole" />
|
||||
<ConsoleWormhole />
|
||||
@@ -5,51 +5,28 @@
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title="Change Password" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#if this.isPasswordValidated}}
|
||||
<form id="change-password-form" aria-label="change-password" {{on "submit" this.changeUserPassword}}>
|
||||
<legend class="mb-3">Change Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Enter new Password" @type="password" @value={{this.newPassword}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.newConfirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Confirm & Change Password" @icon="save" {{on "click" this.changeUserPassword}} />
|
||||
</form>
|
||||
{{else}}
|
||||
<form id="validate-password-form" aria-label="validate-password" {{on "submit" this.validatePassword}}>
|
||||
<legend class="mb-3">Validate Current Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Password" @type="password" @value={{this.password}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.confirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Continue" @icon="check" {{on "click" this.validatePassword}} />
|
||||
</form>
|
||||
{{/if}}
|
||||
<form id="change-password-form" aria-label="change-password" {{on "submit" (perform this.changePassword)}}>
|
||||
<legend class="mb-3">Change Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Enter new Password" @type="password" @value={{this.newPassword}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.newConfirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Confirm & Change Password" @icon="save" {{on "click" (perform this.changePassword)}} />
|
||||
</form>
|
||||
</ContentPanel>
|
||||
|
||||
{{#if this.isSystemTwoFaEnabled}}
|
||||
<ContentPanel @title="2FA Settings" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<div class="mb-3">
|
||||
{{#if this.loadUserTwoFaSettings.isIdle}}
|
||||
<TwoFaSettings
|
||||
@twoFaMethods={{this.methods}}
|
||||
@twoFaSettings={{this.twoFaSettings}}
|
||||
@onTwoFaToggled={{this.onTwoFaToggled}}
|
||||
@onTwoFaMethodSelected={{this.onTwoFaMethodSelected}}
|
||||
/>
|
||||
<TwoFaSettings @twoFaMethods={{this.methods}} @twoFaSettings={{this.twoFaSettings}} @onTwoFaToggled={{this.onTwoFaToggled}} @onTwoFaMethodSelected={{this.onTwoFaMethodSelected}} />
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<Spinner @loadingMessage="Loading User 2FA Settings..." @wrapperClass="flex flex-row" @iconClass="mr-2" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<Button
|
||||
@type="primary"
|
||||
@buttonType="submit"
|
||||
@text="Save 2FA Settings"
|
||||
@icon="save"
|
||||
@onClick={{this.saveTwoFactorAuthSettings}}
|
||||
@isLoading={{this.saveUserTwoFaSettings.isRunning}}
|
||||
/>
|
||||
<Button @type="primary" @buttonType="submit" @text="Save 2FA Settings" @icon="save" @onClick={{this.saveTwoFactorAuthSettings}} @isLoading={{this.saveUserTwoFaSettings.isRunning}} />
|
||||
</ContentPanel>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<Layout::Section::Header @title={{t "common.profile"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto">
|
||||
<ContentPanel @title={{t "common.your-profile"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" this.saveProfile}}>
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
||||
<div class="w-32 flex flex-col justify-center mb-6 mr-6">
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md" />
|
||||
<FileUpload @name={{t "console.account.index.photos"}} @accept="image/*" @onFileAdded={{this.uploadNewPhoto}} @labelClass="flex flex-row items-center justify-center" as |queue|>
|
||||
@@ -35,11 +35,12 @@
|
||||
<InputGroup @name={{t "common.date-of-birth"}} @type="date" @value={{this.user.date_of_birth}} />
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-end">
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{this.saveProfile}} @isLoading={{this.isLoading}} />
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="500px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -6,6 +6,7 @@
|
||||
<Layout::Sidebar::Item @route="console.admin.branding" @icon="palette">{{t "common.branding"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.notifications" @icon="bell">{{t "common.notifications"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.two-fa-settings" @icon="shield-halved">{{t "common.2fa-config"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.schedule-monitor" @icon="calendar-check">{{t "console.admin.schedule-monitor.schedule-monitor"}}</Layout::Sidebar::Item>
|
||||
{{#each this.universe.adminMenuItems as |menuItem|}}
|
||||
<Layout::Sidebar::Item
|
||||
@onClick={{fn this.universe.transitionMenuItem "console.admin.virtual" menuItem}}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 300}}>
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title={{t "console.admin.branding.title"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form class="flex flex-col" {{on "submit" this.save}}>
|
||||
@@ -63,4 +63,5 @@
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -4,7 +4,7 @@
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 1200}}>
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-4">
|
||||
{{#each-in this.groupedNotifications as |groupName notifications|}}
|
||||
<ContentPanel @title={{concat (smart-humanize groupName) (t "console.admin.notifications.notification-settings") }} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
@@ -29,4 +29,5 @@
|
||||
{{/each-in}}
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -1,15 +1,16 @@
|
||||
{{page-title (t "console.admin.organizations.index.title")}}
|
||||
<Layout::Section::Header @title={{t "console.admin.organizations.index.title"}} @searchQuery={{this.query}} @onSearch={{perform this.search}} />
|
||||
{{!-- template-lint-disable no-unbound --}}
|
||||
<Layout::Section::Header @title={{t "console.admin.organizations.index.title"}} @searchQuery={{unbound this.query}} @onSearch={{this.search}} />
|
||||
|
||||
<Layout::Section::Body>
|
||||
<Table
|
||||
@rows={{this.companies}}
|
||||
@rows={{@model}}
|
||||
@columns={{this.columns}}
|
||||
@selectable={{true}}
|
||||
@canSelectAll={{true}}
|
||||
@selectable={{false}}
|
||||
@canSelectAll={{false}}
|
||||
@onSetup={{fn (mut this.table)}}
|
||||
@pagination={{true}}
|
||||
@paginationMeta={{this.companies}}
|
||||
@paginationMeta={{@model.meta}}
|
||||
@page={{this.page}}
|
||||
@onPageChange={{fn (mut this.page)}}
|
||||
@tfootVerticalOffset="53"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{page-title (t "common.users")}}
|
||||
<Overlay @isOpen={{@isOpen}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width={{or this.width @width "600px"}}>
|
||||
<Overlay @isOpen={{@isOpen}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width="600px" @isResizable={{true}}>
|
||||
<Overlay::Header @title={{concat this.company.name " - " (t "common.users")}} @hideStatusDot={{true}} @titleWrapperClass="leading-5">
|
||||
<div class="flex flex-1 justify-end">
|
||||
<Button @type="default" @icon="times" @helpText={{t "common.close-and-save"}} @onClick={{this.onPressClose}} />
|
||||
@@ -7,12 +7,13 @@
|
||||
</Overlay::Header>
|
||||
|
||||
<Overlay::Body class="without-padding">
|
||||
<Layout::Section::Header @title={{t "console.admin.organizations.users.title"}} @searchQuery={{this.nestedQuery}} @onSearch={{perform this.search}}>
|
||||
{{!-- template-lint-disable no-unbound --}}
|
||||
<Layout::Section::Header @title={{t "console.admin.organizations.users.title"}} @searchQuery={{unbound this.nestedQuery}} @onSearch={{this.search}}>
|
||||
<Pagination @meta={{@model.meta}} @page={{this.nestedPage}} @onPageChange={{fn (mut this.nestedPage)}} @metaInfoClass="hidden" @metaInfoWrapperClass="within-layout-section-header" />
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body>
|
||||
<Table @rows={{@model}} @columns={{this.columns}} @selectable={{true}} @canSelectAll={{true}} @onSetup={{fn (mut this.table)}} />
|
||||
<Table @rows={{@model}} @columns={{this.columns}} @selectable={{false}} @canSelectAll={{false}} @onSetup={{fn (mut this.table)}} />
|
||||
</Layout::Section::Body>
|
||||
</Overlay::Body>
|
||||
</Overlay>
|
||||
34
console/app/templates/console/admin/schedule-monitor.hbs
Normal file
34
console/app/templates/console/admin/schedule-monitor.hbs
Normal file
@@ -0,0 +1,34 @@
|
||||
{{page-title (t "console.admin.schedule-monitor.schedule-monitor")}}
|
||||
<Layout::Section::Header @title={{t "console.admin.schedule-monitor.schedule-monitor"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="next-table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th {{set-width "300px"}}>{{t "console.admin.schedule-monitor.name"}}</th>
|
||||
<th {{set-width "100px"}}>{{t "console.admin.schedule-monitor.type"}}</th>
|
||||
<th {{set-width "90px"}}>{{t "console.admin.schedule-monitor.timezone"}}</th>
|
||||
<th>{{t "console.admin.schedule-monitor.last-started"}}</th>
|
||||
<th>{{t "console.admin.schedule-monitor.last-finished"}}</th>
|
||||
<th>{{t "console.admin.schedule-monitor.last-failure"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each @model as |task|}}
|
||||
<tr>
|
||||
<td>
|
||||
<LinkTo @route="console.admin.schedule-monitor.logs" @model={{task}}>{{task.name}}</LinkTo>
|
||||
</td>
|
||||
<td>{{task.type}}</td>
|
||||
<td>{{task.timezone}}</td>
|
||||
<td>{{n-a task.last_started_at_fmt}}</td>
|
||||
<td>{{n-a task.last_finished_at_fmt}}</td>
|
||||
<td>{{n-a task.last_failed_at_fmt}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Layout::Section::Body>
|
||||
{{outlet}}
|
||||
@@ -0,0 +1,34 @@
|
||||
{{page-title (concat (t "console.admin.schedule-monitor.schedule-monitor") " - " @model.name)}}
|
||||
<Overlay @isOpen={{true}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width="600px" @isResizable={{true}}>
|
||||
<Overlay::Header @title={{concat (t "console.admin.schedule-monitor.task-logs-for") @model.name}} @titleClass="max-w-400px truncate" @hideStatusDot={{true}} @titleWrapperClass="leading-5">
|
||||
<div class="flex flex-1 justify-end">
|
||||
<Button @type="default" @icon="times" @helpText={{t "common.close-and-save"}} @onClick={{this.onPressClose}} />
|
||||
</div>
|
||||
</Overlay::Header>
|
||||
|
||||
<Overlay::Body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-sm">{{t "console.admin.schedule-monitor.showing-last-count" count=20}}</div>
|
||||
<Button @size="xs" @icon="arrows-rotate" @onClick={{perform this.reload @model}} @isLoading={{not this.reload.isIdle}} />
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
{{#each this.logs as |log|}}
|
||||
<div class="bg-gray-100 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 rounded-lg p-2 font-mono text-xs">
|
||||
<div class="font-mono"><span class="font-semibold font-mono">{{t "console.admin.schedule-monitor.date"}}:</span> {{log.created_at_fmt}}</div>
|
||||
<div class="font-mono"><span class="font-semibold font-mono">{{t "console.admin.schedule-monitor.memory"}}:</span> {{format-bytes log.meta.memory}}</div>
|
||||
<div class="font-mono"><span class="font-semibold font-mono">{{t "console.admin.schedule-monitor.runtime"}}:</span> {{format-milliseconds log.meta.runtime}}</div>
|
||||
<div class="font-semibold font-mono mb-2">{{t "console.admin.schedule-monitor.output"}}:</div>
|
||||
<div class="whitespace-pre-line overflow-hidden bg-black text-green-400 rounded-lg p-2 font-mono text-xs border border-gray-900">
|
||||
{{#if log.meta.output}}
|
||||
{{log.meta.output}}
|
||||
{{else}}
|
||||
{{t "console.admin.schedule-monitor.no-output"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</Overlay::Body>
|
||||
</Overlay>
|
||||
@@ -4,7 +4,7 @@
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 1200}}>
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-4">
|
||||
<ContentPanel @title="2FA Config" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#if this.loadSystemTwoFaConfig.isIdle}}
|
||||
@@ -25,4 +25,5 @@
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -46,9 +46,13 @@ module.exports = function (environment) {
|
||||
driverImage: getenv('DEFAULT_DRIVER_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
|
||||
userImage: getenv('DEFAULT_USER_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
|
||||
contactImage: getenv('DEFAULT_CONTACT_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
|
||||
entityImage: getenv('DEFAULT_ENTITY_IMAGE', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/parcels/medium.png'),
|
||||
vendorImage: getenv('DEFAULT_VENDOR_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
|
||||
vehicleImage: getenv('DEFAULT_VEHICLE_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/vehicle-placeholder.png'),
|
||||
vehicleAvatar: getenv('DEFAUL_VEHICLE_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg'),
|
||||
vehicleAvatar: getenv('DEFAULT_VEHICLE_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg'),
|
||||
driverAvatar: getenv('DEFAULT_DRIVER_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/driver-icons/moto-driver.png'),
|
||||
placeAvatar: getenv('DEFAULT_PLACE_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/place-icons/basic-building.png'),
|
||||
extensionIcon: getenv('DEFAULT_EXTENSION_ICON', 'https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/default-extension-icon.svg'),
|
||||
},
|
||||
|
||||
'ember-simple-auth': {
|
||||
@@ -60,6 +64,11 @@ module.exports = function (environment) {
|
||||
keyDelimiter: '/',
|
||||
includeEmberDataSupport: true,
|
||||
},
|
||||
|
||||
'ember-cli-notifications': {
|
||||
autoClear: true,
|
||||
clearDuration: 1000 * 3.5,
|
||||
},
|
||||
};
|
||||
|
||||
if (environment === 'development') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fleetbase/console",
|
||||
"version": "0.4.4",
|
||||
"version": "0.4.16",
|
||||
"private": true,
|
||||
"description": "Fleetbase Console",
|
||||
"repository": "https://github.com/fleetbase/fleetbase",
|
||||
@@ -24,17 +24,18 @@
|
||||
"postinstall": "patch-package",
|
||||
"lint:intl": "fleetbase-intl-lint",
|
||||
"start": "pnpm run prebuild && ember serve",
|
||||
"start:dev": "pnpm run prebuild && ember serve --environment development",
|
||||
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
|
||||
"test:ember": "ember test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fleetbase/ember-core": "^0.2.4",
|
||||
"@fleetbase/ember-ui": "^0.2.10",
|
||||
"@fleetbase/storefront-engine": "^0.3.0",
|
||||
"@fleetbase/fleetops-engine": "^0.4.10",
|
||||
"@fleetbase/fleetops-data": "^0.1.8",
|
||||
"@fleetbase/dev-engine": "^0.2.1",
|
||||
"@fleetbase/iam-engine": "^0.0.9",
|
||||
"@fleetbase/ember-core": "^0.2.8",
|
||||
"@fleetbase/ember-ui": "^0.2.12",
|
||||
"@fleetbase/fleetops-engine": "^0.4.24",
|
||||
"@fleetbase/fleetops-data": "^0.1.14",
|
||||
"@fleetbase/storefront-engine": "^0.3.7",
|
||||
"@fleetbase/dev-engine": "^0.2.2",
|
||||
"@fleetbase/iam-engine": "^0.0.10",
|
||||
"@fleetbase/leaflet-routing-machine": "^3.2.16",
|
||||
"@ember/legacy-built-in-components": "^0.4.1",
|
||||
"@fortawesome/ember-fontawesome": "^0.4.1",
|
||||
@@ -141,9 +142,9 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@fleetbase/ember-core": "^0.2.4",
|
||||
"@fleetbase/ember-ui": "^0.2.10",
|
||||
"@fleetbase/fleetops-data": "^0.1.8"
|
||||
"@fleetbase/ember-core": "^0.2.8",
|
||||
"@fleetbase/ember-ui": "^0.2.12",
|
||||
"@fleetbase/fleetops-data": "^0.1.14"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
4388
console/pnpm-lock.yaml
generated
4388
console/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,9 @@ Router.map(function () {
|
||||
this.route('users', { path: '/:public_id/users' });
|
||||
});
|
||||
});
|
||||
this.route('schedule-monitor', function () {
|
||||
this.route('logs', { path: '/:id/logs' });
|
||||
});
|
||||
});
|
||||
});
|
||||
this.route('install');
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | console-wormhole', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<ConsoleWormhole />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<ConsoleWormhole>
|
||||
template block text
|
||||
</ConsoleWormhole>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | modals/validate-password', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<Modals::ValidatePassword />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<Modals::ValidatePassword>
|
||||
template block text
|
||||
</Modals::ValidatePassword>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Controller | console/admin/schedule-monitor/logs', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup('controller:console/admin/schedule-monitor/logs');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
||||
14
console/tests/unit/models/custom-field-test.js
Normal file
14
console/tests/unit/models/custom-field-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Model | custom field', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('custom-field', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
14
console/tests/unit/models/custom-field-value-test.js
Normal file
14
console/tests/unit/models/custom-field-value-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Model | custom field value', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('custom-field-value', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/admin/schedule-monitor', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/admin/schedule-monitor');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/admin/schedule-monitor/logs', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/admin/schedule-monitor/logs');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
24
console/tests/unit/serializers/company-test.js
Normal file
24
console/tests/unit/serializers/company-test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | company', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('company');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('company', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ common:
|
||||
confirm: Confirm
|
||||
edit: Edit
|
||||
save: Save
|
||||
save-changes: Save Changes
|
||||
cancel: Cancel
|
||||
2fa-config: 2FA Config
|
||||
account: Account
|
||||
@@ -59,6 +60,11 @@ common:
|
||||
users: Users
|
||||
changelog: Changelog
|
||||
ok: OK
|
||||
select-file: Select File
|
||||
back: Back
|
||||
next: Next
|
||||
continue: Continue
|
||||
done: Done
|
||||
component:
|
||||
file:
|
||||
dropdown-label: File actions
|
||||
@@ -71,7 +77,9 @@ component:
|
||||
upload-images-videos: Upload Images & Videos
|
||||
upload-documents: Upload Documents
|
||||
upload-documents-files: Upload Documents & Files
|
||||
upload-avatar-files: Upload Custom Avatars
|
||||
dropzone-supported-images-videos: Drag and drop image and video files onto this dropzone
|
||||
dropzone-supported-avatars: Drag and drop SVG or PNG files
|
||||
dropzone-supported-files: Drag and drop files onto this dropzone
|
||||
or-select-button-text: or select files to upload.
|
||||
upload-queue: Upload Queue
|
||||
@@ -171,6 +179,7 @@ auth:
|
||||
slow-connection-message: Experiencing connectivity issues.
|
||||
reset-password:
|
||||
success-message: Your password has been reset! Login to continue.
|
||||
invalid-verification-code: This reset password link is invalid or expired.
|
||||
title: Reset your password
|
||||
form:
|
||||
code:
|
||||
@@ -200,6 +209,22 @@ console:
|
||||
phone: Your phone number.
|
||||
photos: photos
|
||||
admin:
|
||||
schedule-monitor:
|
||||
schedule-monitor: Schedule Monitor
|
||||
task-logs-for: >-
|
||||
Task Logs For:
|
||||
showing-last-count: Showing last {count} logs
|
||||
name: Name
|
||||
type: Type
|
||||
timezone: Timezone
|
||||
last-started: Last Started
|
||||
last-finished: Last Finished
|
||||
last-failure: Last Failure
|
||||
date: Date
|
||||
memory: Memory
|
||||
runtime: Runtime
|
||||
output: Output
|
||||
no-output: No output
|
||||
config:
|
||||
database:
|
||||
title: Database Configuration
|
||||
@@ -232,6 +257,10 @@ console:
|
||||
organizations:
|
||||
index:
|
||||
title: Organizations
|
||||
owner-name-column: Owner
|
||||
owner-phone-column: Owner Phone
|
||||
owner-email-column: Owner Phone
|
||||
users-count-column: Users
|
||||
phone-column: Phone
|
||||
email-column: Email
|
||||
users:
|
||||
|
||||
@@ -12,7 +12,32 @@ server {
|
||||
send_timeout 600;
|
||||
index index.php;
|
||||
|
||||
# tweaks
|
||||
client_max_body_size 600M;
|
||||
client_body_buffer_size 1m;
|
||||
client_header_buffer_size 1k;
|
||||
large_client_header_buffers 4 16k;
|
||||
keepalive_timeout 2 2;
|
||||
|
||||
#gzip tweaks
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1100;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain application/javascript application/json application/x-javascript text/xml text/css;
|
||||
|
||||
#open file cache tweaks
|
||||
open_file_cache max=2000 inactive=20s;
|
||||
open_file_cache_valid 60s;
|
||||
open_file_cache_min_uses 5;
|
||||
open_file_cache_errors off;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://${NGINX_APPLICATION_HOSTNAME}:8000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,13 +52,13 @@ app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
|
||||
{{- define "helm.commonVariables" -}}
|
||||
- name: CACHE_URL
|
||||
value: $(REDIS_SERVICE_PORT)/1
|
||||
value: tcp://redis-service.{{ .Release.Namespace }}.svc.cluster.local/1
|
||||
- name: CACHE_DRIVER
|
||||
value: redis
|
||||
- name: SOCKETCLUSTER_PORT
|
||||
value: "80"
|
||||
- name: SOCKETCLUSTER_HOST
|
||||
value: $(SOCKETCLUSTER_SERVICE_HOST)
|
||||
value: socketcluster.{{ .Release.Namespace }}.svc.cluster.local
|
||||
{{- end }}
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
|
||||
@@ -34,7 +34,11 @@ spec:
|
||||
- name: {{ .Chart.Name }}-httpd
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/app-httpd:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:app-httpd-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: NGINX_APPLICATION_HOSTNAME
|
||||
@@ -56,7 +60,11 @@ spec:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/app:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:app-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
@@ -35,7 +35,11 @@ spec:
|
||||
command: ["php", "artisan", "queue:work"]
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/events:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:events-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
@@ -93,7 +97,11 @@ spec:
|
||||
- name: scheduler
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/scheduler:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:scheduler-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
@@ -10,7 +10,7 @@ metadata:
|
||||
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install,pre-upgrade
|
||||
"helm.sh/hook-weight": "0"
|
||||
"helm.sh/hook-weight": "20"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation
|
||||
spec:
|
||||
template:
|
||||
@@ -28,8 +28,12 @@ spec:
|
||||
- name: deployment-job
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 10 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/app:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
command: ["./deploy.sh"]
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:app-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
args: ["./deploy.sh"]
|
||||
env:
|
||||
{{- include "helm.commonVariables" . | nindent 12 }}
|
||||
envFrom:
|
||||
|
||||
@@ -44,7 +44,7 @@ spec:
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: fleetbase-app
|
||||
name: {{ include "helm.fullname" . }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
- host: {{ .Values.socketcluster_host }}
|
||||
|
||||
@@ -30,4 +30,3 @@ spec:
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
type: ClusterIP
|
||||
|
||||
|
||||
@@ -32,5 +32,4 @@ spec:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 8000
|
||||
type: ClusterIP
|
||||
|
||||
type: {{ .Values.service.type }}
|
||||
|
||||
Submodule packages/core-api updated: 04ae1ac455...20b33106eb
Submodule packages/dev-engine updated: 64a379ce12...0c5c4daf99
Submodule packages/ember-core updated: e9f4dc4086...f01867c219
Submodule packages/ember-ui updated: e532986a5b...700a229449
Submodule packages/fleetops updated: dcb1c79c8c...ece7edf086
Submodule packages/fleetops-data updated: 9d92927c9b...56b8ae5436
Submodule packages/iam-engine updated: 99698152e2...44dc31d91d
Submodule packages/storefront updated: b5776acef6...ddc4c6f804
47
static-build.Dockerfile
Normal file
47
static-build.Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# # Install packages
|
||||
# RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm \
|
||||
# && mkdir -p /root/.ssh \
|
||||
# && ssh-keyscan github.com >> /root/.ssh/known_hosts
|
||||
|
||||
# Set some build ENV variables
|
||||
ENV LOG_CHANNEL=stdout
|
||||
ENV CACHE_DRIVER=null
|
||||
ENV BROADCAST_DRIVER=socketcluster
|
||||
ENV QUEUE_CONNECTION=redis
|
||||
ENV CADDYFILE_PATH=/fleetbase/Caddyfile
|
||||
ENV OCTANE_SERVER=frankenphp
|
||||
|
||||
# Set environment
|
||||
ARG ENVIRONMENT=production
|
||||
ENV APP_ENV=$ENVIRONMENT
|
||||
|
||||
# Copy Caddyfile
|
||||
COPY ./Caddyfile $CADDYFILE_PATH
|
||||
|
||||
# Create /fleetbase directory and set correct permissions
|
||||
RUN mkdir -p /fleetbase && mkdir -p /fleetbase/api && mkdir -p /fleetbase/console
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /fleetbase/api
|
||||
|
||||
# Setup api
|
||||
COPY ./api /fleetbase/api
|
||||
|
||||
# Setup console
|
||||
COPY ./console /fleetbase/console
|
||||
|
||||
# Set permissions for deploy script
|
||||
RUN chmod +x /fleetbase/api/deploy.sh
|
||||
|
||||
# Pre-install Composer dependencies
|
||||
# RUN /bin/sh -c "composer install --no-scripts --optimize-autoloader --no-dev"
|
||||
|
||||
# Dump autoload
|
||||
# RUN /bin/sh -c "composer dumpautoload"
|
||||
|
||||
# Build binary
|
||||
RUN EMBED=/ \
|
||||
PHP_EXTENSIONS=pdo_mysql,gd,bcmath,redis,intl,zip,gmp,apcu,opcache,memcached,imagick,geos,sockets,pcntl \
|
||||
./build-static.sh
|
||||
@@ -62,7 +62,7 @@ deploy:
|
||||
|
||||
# build the builder
|
||||
- docker buildx build -t frontend-${STACK}-${VERSION} --load --target builder -f console/Dockerfile .
|
||||
- docker run -i --rm --env-file /tmp/dotenv.file -v "$(pwd)/console_dist:/app/dist" frontend-${STACK}-${VERSION} pnpm build
|
||||
- docker run -i --rm --env-file /tmp/dotenv.file -v "$(pwd)/console_dist:/app/dist" frontend-${STACK}-${VERSION} pnpm build --environment production
|
||||
- |-
|
||||
DEPLOY_BUCKET=${PROJECT}-${STACK}
|
||||
wget -O- https://github.com/bep/s3deploy/releases/download/v2.11.0/s3deploy_2.11.0_linux-amd64.tar.gz | tar xzv -f - s3deploy
|
||||
|
||||
Reference in New Issue
Block a user