mirror of
https://github.com/fleetbase/fleetbase.git
synced 2025-12-30 19:07:07 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a35c0354b1 | ||
|
|
e9bf831ce8 | ||
|
|
2c20db4155 | ||
|
|
801d73215f | ||
|
|
ed1609ccf4 | ||
|
|
82ae99eb84 | ||
|
|
7874edf0f3 | ||
|
|
7281174d12 | ||
|
|
cc4e28cd00 | ||
|
|
bbd1e1bdfe | ||
|
|
4191d6a0bb | ||
|
|
dba2b7afc5 | ||
|
|
a6e6625ae1 | ||
|
|
3777569f22 | ||
|
|
fa5b6700a3 | ||
|
|
6cce6a9db2 | ||
|
|
29bd3ce217 | ||
|
|
39d405cb57 | ||
|
|
9f88c7bc79 | ||
|
|
b3816c394a | ||
|
|
34462c61c4 | ||
|
|
080302eb86 | ||
|
|
b063cf6338 | ||
|
|
b6dca79251 | ||
|
|
c35af4d47a | ||
|
|
2a89659cc3 | ||
|
|
f8ae75d767 | ||
|
|
924448a4d0 | ||
|
|
5426ac374d | ||
|
|
73f56b7958 | ||
|
|
095eb87e14 | ||
|
|
fabc16612b | ||
|
|
3389cba935 | ||
|
|
099ea57c39 | ||
|
|
5492bc7299 | ||
|
|
00b037a385 | ||
|
|
151fea2505 | ||
|
|
08f901d865 |
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}
|
||||
@@ -9,9 +9,9 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"fleetbase/core-api": "^1.4.11",
|
||||
"fleetbase/fleetops-api": "^0.4.17",
|
||||
"fleetbase/storefront-api": "^0.3.4",
|
||||
"fleetbase/core-api": "^1.4.17",
|
||||
"fleetbase/fleetops-api": "^0.4.25",
|
||||
"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,
|
||||
|
||||
1398
api/composer.lock
generated
1398
api/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -52,7 +52,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
'url' => env('APP_URL', 'http://localhost:8000'),
|
||||
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<div class="next-user-button" ...attributes>
|
||||
<BasicDropdown @defaultClass={{@wrapperClass}} @onOpen={{@onOpen}} @onClose={{@onClose}} @verticalPosition={{@verticalPosition}} @horizontalPosition={{@horizontalPosition}} @renderInPlace={{or @renderInPlace true}} @initiallyOpened={{@initiallyOpened}} as |dd|>
|
||||
<dd.Trigger class={{@triggerClass}}>
|
||||
<div class="next-org-button-trigger flex-shrink-0 {{if dd.isOpen 'is-open'}}">
|
||||
<FaIcon @icon="globe" @size="sm" />
|
||||
</div>
|
||||
</dd.Trigger>
|
||||
<dd.Content class={{@contentClass}}>
|
||||
<div class="next-dd-menu {{@dropdownMenuClass}} {{if dd.isOpen 'is-open'}}">
|
||||
{{#each-in this.availableLocales as |key country|}}
|
||||
<div class="px-1">
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (fn this.changeLocale key)}}>
|
||||
<div class="flex flex-row items-center justify-between w-full">
|
||||
<div class="flex-1">
|
||||
<span class="mr-1">{{country.emoji}}</span>
|
||||
<span>{{country.language}}</span>
|
||||
</div>
|
||||
{{#if (eq this.currentLocale key)}}
|
||||
<div>
|
||||
<FaIcon @icon="check" class="text-green-400" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{/each-in}}
|
||||
</div>
|
||||
</dd.Content>
|
||||
</BasicDropdown>
|
||||
</div>
|
||||
@@ -1,144 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
|
||||
export default class LocaleSelectorComponent extends Component {
|
||||
/**
|
||||
* Inject the intl service.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the intl service.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Tracks all the available locales.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked locales = [];
|
||||
|
||||
/**
|
||||
* All available countries data.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked countries = [];
|
||||
|
||||
/**
|
||||
* The current locale in use.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked currentLocale;
|
||||
|
||||
/**
|
||||
* Creates an instance of LocaleSelectorComponent.
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.locales = this.intl.locales;
|
||||
this.currentLocale = this.intl.primaryLocale;
|
||||
this.loadAvailableCountries.perform();
|
||||
|
||||
// Check for locale change
|
||||
this.intl.onLocaleChanged(() => {
|
||||
this.currentLocale = this.intl.primaryLocale;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of locale.
|
||||
* @param {string} selectedLocale - The selected locale.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method changeLocale
|
||||
* @instance
|
||||
* @action
|
||||
*/
|
||||
@action changeLocale(selectedLocale) {
|
||||
this.currentLocale = selectedLocale;
|
||||
this.intl.setLocale(selectedLocale);
|
||||
// Persist to server
|
||||
this.saveUserLocale.perform(selectedLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads available countries asynchronously.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method loadAvailableCountries
|
||||
* @instance
|
||||
* @task
|
||||
* @generator
|
||||
*/
|
||||
@task *loadAvailableCountries() {
|
||||
this.countries = yield this.fetch.get('lookup/countries', { columns: ['name', 'cca2', 'flag', 'emoji', 'languages'] });
|
||||
this.availableLocales = this._createAvailableLocaleMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the user's selected locale to the server.
|
||||
* @param {string} locale - The user's selected locale.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method saveUserLocale
|
||||
* @instance
|
||||
* @task
|
||||
* @generator
|
||||
*/
|
||||
@task *saveUserLocale(locale) {
|
||||
yield this.fetch.post('users/locale', { locale });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a map of available locales.
|
||||
* @private
|
||||
* @returns {Object} - The map of available locales.
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method _createAvailableLocaleMap
|
||||
* @instance
|
||||
*/
|
||||
_createAvailableLocaleMap() {
|
||||
const localeMap = {};
|
||||
|
||||
for (let i = 0; i < this.locales.length; i++) {
|
||||
const locale = this.locales.objectAt(i);
|
||||
|
||||
localeMap[locale] = this._findCountryDataForLocale(locale);
|
||||
}
|
||||
|
||||
return localeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds country data for a given locale.
|
||||
* @private
|
||||
* @param {string} locale - The locale to find country data for.
|
||||
* @returns {Object|null} - The country data or null if not found.
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method _findCountryDataForLocale
|
||||
* @instance
|
||||
*/
|
||||
_findCountryDataForLocale(locale) {
|
||||
const localeCountry = locale.split('-')[1];
|
||||
const country = this.countries.find((country) => country.cca2.toLowerCase() === localeCountry);
|
||||
|
||||
if (country) {
|
||||
// get the language
|
||||
country.language = Object.values(country.languages)[0];
|
||||
}
|
||||
|
||||
return country;
|
||||
}
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ export default class ConsoleController extends Controller {
|
||||
@action setSidebarContext(sidebarContext) {
|
||||
this.sidebarContext = sidebarContext;
|
||||
this.universe.sidebarContext = sidebarContext;
|
||||
this.universe.trigger('sidebarContext.available', sidebarContext);
|
||||
|
||||
if (this.hiddenSidebarRoutes.includes(this.router.currentRouteName)) {
|
||||
this.sidebarContext.hideNow();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,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
|
||||
*
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
84
console/app/models/chat-attachment.js
Normal file
84
console/app/models/chat-attachment.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
import isVideoFile from '@fleetbase/ember-core/utils/is-video-file';
|
||||
import isImageFile from '@fleetbase/ember-core/utils/is-image-file';
|
||||
|
||||
export default class ChatAttachment extends Model {
|
||||
/** @ids */
|
||||
@attr('string') chat_channel_uuid;
|
||||
@attr('string') sender_uuid;
|
||||
@attr('string') file_uuid;
|
||||
@attr('string') chat_message_uuid;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('user', { async: true }) sender;
|
||||
@belongsTo('chat-channel', { async: true }) chatChannel;
|
||||
@belongsTo('file', { async: true }) file;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') chat_channel_uuid;
|
||||
@attr('string') sender_uuid;
|
||||
@attr('string') file_uuid;
|
||||
@attr('string') url;
|
||||
@attr('string') filename;
|
||||
@attr('string') content_type;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('content_type') get isVideo() {
|
||||
return isVideoFile(this.content_type);
|
||||
}
|
||||
|
||||
@computed('content_type') get isImage() {
|
||||
return isImageFile(this.content_type);
|
||||
}
|
||||
|
||||
/** @methods */
|
||||
downloadFromApi() {
|
||||
window.open(ENV.api.host + '/' + ENV.api.namespace + '/files/download?file=' + this.file_uuid, '_self');
|
||||
}
|
||||
|
||||
download() {
|
||||
const owner = getOwner(this);
|
||||
const fetch = owner.lookup('service:fetch');
|
||||
|
||||
return fetch.download('files/download', { file: this.file_uuid }, { fileName: this.filename, mimeType: this.content_type });
|
||||
}
|
||||
}
|
||||
87
console/app/models/chat-channel.js
Normal file
87
console/app/models/chat-channel.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import Model, { attr, hasMany, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { format, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatChannelModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') public_id;
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') created_by_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('string') title;
|
||||
@attr('number') unread_count;
|
||||
@attr('string') slug;
|
||||
@attr('array') feed;
|
||||
@attr('array') meta;
|
||||
|
||||
/** @relationships */
|
||||
@hasMany('chat-participant', { async: false }) participants;
|
||||
@belongsTo('chat-message', { async: false }) last_message;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
/** @methods */
|
||||
toJSON() {
|
||||
return {
|
||||
company_uuid: this.company_uuid,
|
||||
name: this.name,
|
||||
meta: this.meta,
|
||||
};
|
||||
}
|
||||
|
||||
reloadParticipants() {
|
||||
const owner = getOwner(this);
|
||||
const store = owner.lookup('service:store');
|
||||
|
||||
return store.query('chat-participant', { chat_channel_uuid: this.id }).then((participants) => {
|
||||
this.set('participants', participants);
|
||||
return participants;
|
||||
});
|
||||
}
|
||||
|
||||
existsInFeed(type, record) {
|
||||
return this.feed.find((_) => _.type === type && _.record.id === record.id) !== undefined;
|
||||
}
|
||||
|
||||
doesntExistsInFeed(type, record) {
|
||||
return this.feed.find((_) => _.type === type && _.record.id === record.id) === undefined;
|
||||
}
|
||||
}
|
||||
54
console/app/models/chat-log.js
Normal file
54
console/app/models/chat-log.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatLogModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') chat_channel_uuid;
|
||||
@attr('string') initiator_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') content;
|
||||
@attr('string') resolved_content;
|
||||
@attr('string') event_type;
|
||||
@attr('string') status;
|
||||
@attr('array') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
}
|
||||
64
console/app/models/chat-message.js
Normal file
64
console/app/models/chat-message.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatMessage extends Model {
|
||||
/** @ids */
|
||||
@attr('string') chat_channel_uuid;
|
||||
@attr('string') sender_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') content;
|
||||
@attr('array') attachment_files;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('chat-participant', { async: false }) sender;
|
||||
@hasMany('chat-attachment', { async: false }) attachments;
|
||||
@hasMany('chat-receipt', { async: false }) receipts;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
/** @methods */
|
||||
hasReadReceipt(chatParticipant) {
|
||||
return chatParticipant && this.receipts.find((receipt) => chatParticipant.id === receipt.participant_uuid) !== undefined;
|
||||
}
|
||||
|
||||
doesntHaveReadReceipt(chatParticipant) {
|
||||
return !this.hasReadReceipt(chatParticipant);
|
||||
}
|
||||
}
|
||||
75
console/app/models/chat-participant.js
Normal file
75
console/app/models/chat-participant.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatParticipant extends Model {
|
||||
/** @ids */
|
||||
@attr('string') user_uuid;
|
||||
@attr('string') chat_channel_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('string') username;
|
||||
@attr('string') phone;
|
||||
@attr('string') email;
|
||||
@attr('string') avatar_url;
|
||||
@attr('boolean') is_online;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('user', { async: true }) user;
|
||||
@belongsTo('chat-channel', { async: true }) chatChannel;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') last_seen_at;
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('last_seen_at') get lastSeenAgo() {
|
||||
if (!isValidDate(this.last_seen_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.last_seen_at);
|
||||
}
|
||||
|
||||
@computed('last_seen_at') get lastSeenAt() {
|
||||
if (!isValidDate(this.last_seen_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.last_seen_at, 'PP HH:mm');
|
||||
}
|
||||
}
|
||||
70
console/app/models/chat-receipt.js
Normal file
70
console/app/models/chat-receipt.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format as formatDate, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatReceipt extends Model {
|
||||
/** @ids */
|
||||
@attr('string') participant_uuid;
|
||||
@attr('string') chat_message_uuid;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('chat-participant', { async: true }) participant;
|
||||
@belongsTo('chat-message', { async: true }) chatMessage;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') participant_name;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
@attr('date') read_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('read_at') get readAgo() {
|
||||
if (!isValidDate(this.read_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.read_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('read_at') get readAt() {
|
||||
if (!isValidDate(this.read_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.read_at, 'PP HH:mm');
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,8 @@ export default class FileModel extends Model {
|
||||
|
||||
download() {
|
||||
const owner = getOwner(this);
|
||||
const fetch = owner.lookup(`service:store`);
|
||||
const fetch = owner.lookup('service:fetch');
|
||||
|
||||
return fetch.download('files/download', { file: this.id });
|
||||
return fetch.download('files/download', { file: this.id }, { fileName: this.original_filename, mimeType: this.content_type });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,13 @@ export default class UserModel extends Model {
|
||||
@attr('string') type;
|
||||
@attr('string') session_status;
|
||||
@attr('string') status;
|
||||
@attr('boolean') is_online;
|
||||
@attr('boolean') is_admin;
|
||||
@attr('raw') types;
|
||||
@attr('raw') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') last_seen_at;
|
||||
@attr('date') phone_verified_at;
|
||||
@attr('date') email_verified_at;
|
||||
@attr('date') last_login;
|
||||
|
||||
@@ -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);
|
||||
|
||||
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`);
|
||||
}
|
||||
}
|
||||
60
console/app/serializers/chat-channel.js
Normal file
60
console/app/serializers/chat-channel.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { underscore } from '@ember/string';
|
||||
import { isArray } from '@ember/array';
|
||||
|
||||
export default class ChatChannelSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
participants: { embedded: 'always' },
|
||||
last_message: { embedded: 'always' },
|
||||
};
|
||||
}
|
||||
|
||||
serialize(snapshot) {
|
||||
let json = {
|
||||
name: snapshot.attr('name'),
|
||||
meta: snapshot.attr('meta'),
|
||||
};
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
normalize(typeClass, hash) {
|
||||
if (isArray(hash.feed)) {
|
||||
hash.feed = this.serializeFeed(hash.feed);
|
||||
}
|
||||
|
||||
return super.normalize(...arguments);
|
||||
}
|
||||
|
||||
serializeFeed(feed = []) {
|
||||
return feed.map((item) => this.serializeItem(item)).sortBy('created_at');
|
||||
}
|
||||
|
||||
serializeItem(item) {
|
||||
switch (item.type) {
|
||||
case 'message':
|
||||
return { ...item, record: this.serializeItemType('chat-message', item.data) };
|
||||
case 'log':
|
||||
return { ...item, record: this.serializeItemType('chat-log', item.data) };
|
||||
case 'attachment':
|
||||
return { ...item, record: this.serializeItemType('chat-attachment', item.data) };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
serializeItemType(modelType, data) {
|
||||
const owner = getOwner(this);
|
||||
const store = owner.lookup('service:store');
|
||||
const normalized = store.normalize(modelType, data);
|
||||
return store.push(normalized);
|
||||
}
|
||||
}
|
||||
17
console/app/serializers/chat-message.js
Normal file
17
console/app/serializers/chat-message.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class ChatMessageSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
sender: { embedded: 'always' },
|
||||
attachments: { embedded: 'always' },
|
||||
receipts: { embedded: 'always' },
|
||||
};
|
||||
}
|
||||
}
|
||||
3
console/app/serializers/chat-participant.js
Normal file
3
console/app/serializers/chat-participant.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
|
||||
export default class ChatParticipantSerializer extends ApplicationSerializer {}
|
||||
@@ -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>
|
||||
@@ -14,9 +14,5 @@
|
||||
</Layout::Main>
|
||||
<Layout::MobileNavbar @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} />
|
||||
</Layout::Container>
|
||||
|
||||
{{!-- Add Locale Selector to Header --}}
|
||||
<EmberWormhole @to="view-header-actions">
|
||||
<LocaleSelector class="mr-0.5" />
|
||||
</EmberWormhole>
|
||||
<div id="console-wormhole" />
|
||||
<ChatContainer />
|
||||
<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>
|
||||
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>
|
||||
@@ -49,9 +49,10 @@ module.exports = function (environment) {
|
||||
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'),
|
||||
driverAvatar: getenv('DEFAUL_DRIVER_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/driver-icons/moto-driver.png'),
|
||||
placeAvatar: getenv('DEFAUL_PLACE_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/place-icons/basic-building.png'),
|
||||
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': {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fleetbase/console",
|
||||
"version": "0.4.10",
|
||||
"version": "0.4.17",
|
||||
"private": true,
|
||||
"description": "Fleetbase Console",
|
||||
"repository": "https://github.com/fleetbase/fleetbase",
|
||||
@@ -29,13 +29,13 @@
|
||||
"test:ember": "ember test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fleetbase/ember-core": "^0.2.5",
|
||||
"@fleetbase/ember-ui": "^0.2.11",
|
||||
"@fleetbase/fleetops-engine": "^0.4.17",
|
||||
"@fleetbase/fleetops-data": "^0.1.12",
|
||||
"@fleetbase/storefront-engine": "^0.3.4",
|
||||
"@fleetbase/dev-engine": "^0.2.1",
|
||||
"@fleetbase/iam-engine": "^0.0.9",
|
||||
"@fleetbase/ember-core": "^0.2.9",
|
||||
"@fleetbase/ember-ui": "^0.2.13",
|
||||
"@fleetbase/fleetops-engine": "^0.4.25",
|
||||
"@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",
|
||||
@@ -142,9 +142,9 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@fleetbase/ember-core": "^0.2.5",
|
||||
"@fleetbase/ember-ui": "^0.2.11",
|
||||
"@fleetbase/fleetops-data": "^0.1.12"
|
||||
"@fleetbase/ember-core": "^0.2.9",
|
||||
"@fleetbase/ember-ui": "^0.2.13",
|
||||
"@fleetbase/fleetops-data": "^0.1.14"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
4021
console/pnpm-lock.yaml
generated
4021
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');
|
||||
|
||||
@@ -3,22 +3,22 @@ import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | locale-selector', function (hooks) {
|
||||
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`<LocaleSelector />`);
|
||||
await render(hbs`<ConsoleWormhole />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<LocaleSelector>
|
||||
<ConsoleWormhole>
|
||||
template block text
|
||||
</LocaleSelector>
|
||||
</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/chat-log-test.js
Normal file
14
console/tests/unit/models/chat-log-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Model | chat log', 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('chat-log', {});
|
||||
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/chat-channel-test.js
Normal file
24
console/tests/unit/serializers/chat-channel-test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | chat channel', 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('chat-channel');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('chat-channel', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
24
console/tests/unit/serializers/chat-message-test.js
Normal file
24
console/tests/unit/serializers/chat-message-test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | chat message', 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('chat-message');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('chat-message', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
24
console/tests/unit/serializers/chat-participant-test.js
Normal file
24
console/tests/unit/serializers/chat-participant-test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | chat participant', 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('chat-participant');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('chat-participant', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
@@ -61,6 +61,10 @@ common:
|
||||
changelog: Changelog
|
||||
ok: OK
|
||||
select-file: Select File
|
||||
back: Back
|
||||
next: Next
|
||||
continue: Continue
|
||||
done: Done
|
||||
component:
|
||||
file:
|
||||
dropdown-label: File actions
|
||||
@@ -175,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:
|
||||
@@ -204,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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -32,4 +32,4 @@ spec:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 8000
|
||||
type: ClusterIP
|
||||
type: {{ .Values.service.type }}
|
||||
|
||||
Submodule packages/core-api updated: c614dfd01c...00a3502da7
Submodule packages/dev-engine updated: 64a379ce12...0c5c4daf99
Submodule packages/ember-core updated: d0c575778c...a068790fa5
Submodule packages/ember-ui updated: 813241f0c3...9899510caa
Submodule packages/fleetops updated: bd8fc66dbe...ece7edf086
Submodule packages/fleetops-data updated: b5ba2a311c...56b8ae5436
Submodule packages/iam-engine updated: 99698152e2...44dc31d91d
Submodule packages/storefront updated: da534cea36...ddc4c6f804
Reference in New Issue
Block a user