Compare commits

...

14 Commits

Author SHA1 Message Date
Ronald A. Richardson
dba2b7afc5 hotfix: bump console version 2024-04-02 17:56:06 +08:00
Ron
a6e6625ae1 Merge pull request #239 from fleetbase/dev-v0.4.16
v0.4.16
2024-04-02 16:55:13 +07:00
Ronald A. Richardson
3777569f22 added upgraded storefront 2024-04-02 17:50:07 +08:00
Ronald A. Richardson
fa5b6700a3 ready for release, full core dependency upgrades, security improvements and new feature for driver onboard 2024-04-02 17:45:01 +08:00
Ronald A. Richardson
6cce6a9db2 added cache to github card component, add password verification for changing email or password, ensured reset password link is is invalidated when expired or deleted 2024-04-02 17:09:10 +08:00
Ronald A. Richardson
39d405cb57 refactored github card to use cache for 6 hours 2024-03-28 15:12:17 +08:00
Ron
9f88c7bc79 Merge pull request #238 from fleetbase/dev-v0.4.15
v0.4.15
2024-03-27 20:04:55 +07:00
Ron
b3816c394a Merge pull request #236 from fleetbase/feature/helm_eks
feature/helm eks
2024-03-27 20:00:08 +07:00
Ronald A. Richardson
34462c61c4 Prepeared next release with latest upgrades/ patches 2024-03-27 20:58:40 +08:00
Eugene Dementyev
080302eb86 Fix socketcluster port for eks 2024-03-20 18:19:06 +13:00
Eugene Dementyev
b063cf6338 Set api host and socketcluster host from github 2024-03-20 18:03:15 +13:00
Eugene Dementyev
b6dca79251 Fix registry 2024-03-19 23:26:38 +13:00
Eugene Dementyev
c35af4d47a Add EKS deployments 2024-03-19 21:23:21 +13:00
Eugene Dementyev
2a89659cc3 Adapt the Helm chart to be used with AWS EKS 2024-03-19 21:05:47 +13:00
32 changed files with 3268 additions and 1609 deletions

170
.github/workflows/eks-cd.yml vendored Normal file
View 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}

View File

@@ -9,9 +9,9 @@
"license": "MIT",
"require": {
"php": "^8.0",
"fleetbase/core-api": "^1.4.14",
"fleetbase/fleetops-api": "^0.4.21",
"fleetbase/storefront-api": "^0.3.5",
"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,

406
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
<div id="console-wormhole" />

View File

@@ -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 }));
}
}
}
}

View 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>

View 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;
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -19,4 +19,4 @@
<EmberWormhole @to="view-header-actions">
<LocaleSelector class="mr-0.5" />
</EmberWormhole>
<div id="console-wormhole" />
<ConsoleWormhole />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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': {

View File

@@ -1,6 +1,6 @@
{
"name": "@fleetbase/console",
"version": "0.4.14",
"version": "0.4.16",
"private": true,
"description": "Fleetbase Console",
"repository": "https://github.com/fleetbase/fleetbase",
@@ -30,12 +30,12 @@
},
"dependencies": {
"@fleetbase/ember-core": "^0.2.8",
"@fleetbase/ember-ui": "^0.2.11",
"@fleetbase/fleetops-engine": "^0.4.21",
"@fleetbase/ember-ui": "^0.2.12",
"@fleetbase/fleetops-engine": "^0.4.24",
"@fleetbase/fleetops-data": "^0.1.14",
"@fleetbase/storefront-engine": "^0.3.5",
"@fleetbase/dev-engine": "^0.2.1",
"@fleetbase/iam-engine": "^0.0.9",
"@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",
@@ -143,7 +143,7 @@
"pnpm": {
"overrides": {
"@fleetbase/ember-core": "^0.2.8",
"@fleetbase/ember-ui": "^0.2.11",
"@fleetbase/ember-ui": "^0.2.12",
"@fleetbase/fleetops-data": "^0.1.14"
}
},

3739
console/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | 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');
});
});

View File

@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | 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');
});
});

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -44,7 +44,7 @@ spec:
pathType: ImplementationSpecific
backend:
service:
name: fleetbase-app
name: {{ include "helm.fullname" . }}
port:
number: {{ $svcPort }}
- host: {{ .Values.socketcluster_host }}

View File

@@ -32,4 +32,4 @@ spec:
- protocol: TCP
port: 80
targetPort: 8000
type: ClusterIP
type: {{ .Values.service.type }}