Compare commits

...

24 Commits

Author SHA1 Message Date
Ron
6bc76a1b33 Merge pull request #292 from fleetbase/dev-v0.5.6
v0.5.6 - hotfix only load organization with valid owners - no stale org
2024-08-30 17:18:07 +07:00
Ronald A. Richardson
30695b3ebe hotfix only load organization with valid owners - no stale org 2024-08-30 18:06:42 +08:00
Ron
7ff9c24ad5 Merge pull request #288 from fleetbase/dev-v0.5.5
v0.5.5 - Documentation & README Updates
2024-08-30 14:20:57 +07:00
Ronald A. Richardson
1a9b9c06e5 fully implemented iam permission based restrictions and controls, few bugfixes and improvements 2024-08-30 15:14:56 +08:00
Ronald A. Richardson
5f949c3b7f implementing IAM permission controls, policies and roles 2024-08-13 20:26:32 +08:00
Ronald A. Richardson
0e5e4e07dd updating docs WIP, and updated README 2024-08-08 17:01:18 +08:00
Ronald A. Richardson
2eabfc4698 version 0.5.4 2024-08-06 13:51:29 +08:00
Ron
77c2c01e58 Merge pull request #286 from fleetbase/dev-v0.5.5
v0.5.5
2024-08-06 12:48:41 +07:00
Ronald A. Richardson
7c5b5b5858 again, fix depn order 2024-08-06 13:33:29 +08:00
Ronald A. Richardson
aad072cf4c reverted dependency order in package.json 2024-08-06 13:32:39 +08:00
Ronald A. Richardson
c1c6dcafd8 ordered packages 2024-08-06 13:30:34 +08:00
Ronald A. Richardson
61992ee924 fix lockfile 2024-08-06 13:28:39 +08:00
Ronald A. Richardson
b18d6197bc upgraded registry bridge and updated erd 2024-08-06 13:25:32 +08:00
Ron
04bdb52c08 Merge pull request #284 from fleetbase/dev-v0.5.3
Upgraded Registry Bridge to v0.0.11
2024-07-31 16:42:17 +07:00
Ronald A. Richardson
8e5a45dd09 Upgraded Registry Bridge to v0.0.11 2024-07-31 17:24:01 +08:00
Ron
196af155ae Merge pull request #283 from fleetbase/dev-v0.5.2
v0.5.2
2024-07-30 21:46:10 +07:00
Ronald A. Richardson
056a717d08 changed default osrm server to project-osrm router, added admin bypass for email verification, patched somethings in registry-bridge, added new UI wysiwyg component <TipTapEditor /> 2024-07-30 22:39:57 +08:00
Ronald A. Richardson
fd008d7f73 hotfix cd for putting extensions in env config 2024-07-25 19:05:34 +08:00
Ron
451c95d0f0 Merge pull request #277 from fleetbase/dev-v0.5.1
v0.5.1
2024-07-25 17:33:52 +07:00
Ronald A. Richardson
b267b303cf fix console depnds 2024-07-25 18:17:36 +08:00
Ronald A. Richardson
441b4f3f0c Extension boot patches, dependency upgrades, performance upgrades 2024-07-25 18:14:11 +08:00
Ronald A. Richardson
0e4d4a7c8c ability to provide installed extensions via environment config 2024-07-23 17:05:35 +08:00
Ronald A. Richardson
24392527e0 fix cd workflow, upgrade node 18x and pnpm 9x 2024-07-23 12:42:16 +08:00
Ronald A. Richardson
c19d838757 hotfix Dockerfile for deployments 2024-07-23 10:45:39 +08:00
57 changed files with 25702 additions and 22056 deletions

View File

@@ -109,13 +109,13 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:
version: 8
version: 9.5.0
run_install: false
- name: Get pnpm Store Directory
@@ -142,13 +142,15 @@ jobs:
- name: Set Env Variables for QA
if: startsWith(github.ref, 'refs/heads/deploy/qa')
run: |
echo "STRIPE_KEY=${{ secrets.STRIPE_TEST_KEY }}" >> ./console/environments/.env.production
echo "STRIPE_KEY=${{ secrets.STRIPE_TEST_KEY }}" >> ./environments/.env.production
echo "EXTENSIONS=@fleetbase/billing-engine,@fleetbase/internals-engine" >> ./environments/.env.production
working-directory: ./console
- name: Set Env Variables for Production
if: startsWith(github.ref, 'refs/heads/deploy/production')
run: |
echo "STRIPE_KEY=${{ secrets.STRIPE_KEY }}" >> ./console/environments/.env.production
echo "STRIPE_KEY=${{ secrets.STRIPE_KEY }}" >> ./environments/.env.production
echo "EXTENSIONS=@fleetbase/billing-engine,@fleetbase/internals-engine" >> ./environments/.env.production
working-directory: ./console
- name: Install dependencies

3
.gitmodules vendored
View File

@@ -39,3 +39,6 @@
[submodule "packages/registry-bridge"]
path = packages/registry-bridge
url = git@github.com:fleetbase/registry-bridge.git
[submodule "packages/ledger"]
path = packages/ledger
url = git@github.com:fleetbase/ledger.git

View File

@@ -1,11 +1,13 @@
{
frankenphp
frankenphp {
num_threads 24
}
order php_server before file_server
}
http://:8000 {
root * /fleetbase/api/public
encode zstd gzip
encode zstd br gzip
php_server {
resolve_root_symlink
}

View File

@@ -42,6 +42,7 @@ sh deploy.sh
- [Features](#-features)
- [Install](#-install)
- [Extensions](#-extensions)
- [Apps](#-apps)
- [Roadmap](#-roadmap)
- [Bugs and Feature Requests](#-bugs-and--feature-requests)
@@ -90,7 +91,7 @@ Fleetbase API: http://localhost:8000
**CORS:** If youre installing directly on a server you may need to add your IP address or domain to the `api/config/cors.php` file in the `allowed_hosts` array.
**Routing:** Fleetbase ships with its own OSRM server hosted at `[bundle.routing.fleetbase.io](https://bundle.routing.fleetbase.io)` but youre able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the env file of the environment youre deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
**Routing:** Fleetbase ships with a default OSRM server hosted by `[router.project-osrm.org](https://router.project-osrm.org)` but youre able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the .env file of the environment youre deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
**Services:** There are a few environment variables which need to be set for Fleetbase to function with full features. If youre deploying with docker then its easiest to just create a `docker-compose.override.yml` and supply the environment variables in this file.
@@ -100,7 +101,7 @@ services:
application:
environment:
MAIL_MAILER: (ses, smtp, mailgun, postmark, sendgrid)
OSRM_HOST: https://bundle.routing.fleetbase.io
OSRM_HOST: https://router.project-osrm.org
IPINFO_API_KEY:
GOOGLE_MAPS_API_KEY:
GOOGLE_MAPS_LOCALE: us
@@ -112,6 +113,26 @@ services:
You can learn more about full installation, and configuration in the [official documentation](https://docs.fleetbase.io/getting-started/install).
# 🧩 Extensions
Extensions are modular components that enhance the functionality of your Fleetbase instance. They allow you to add new features, customize existing behavior, or integrate with external systems.
You can find extensions available from the official [Fleetbase Console](https://console.fleetbase.io), here you will also be able get your registry token to install extensions to a self-hosted Fleetbase instance.
Additionally you're able to develop and publish your own extensions as well which you can read more about developing extensions via the [extension building guide](https://docs.fleetbase.io/developers/building-an-extension).
## ⌨️ Fleetbase CLI
The Fleetbase CLI is a powerful tool designed to simplify the management of extensions for your Fleetbase instance. With the CLI, you can effortlessly handle authentication, install and uninstall extensions, and scaffold new extensions if you are developing your own.
Get started with the CLI with npm:
```bash
npm i -g @fleetbase/cli
```
Once installed, you can access a variety of commands to manage your Fleetbase extensions.
# 📱 Apps
Fleetbase offers a few open sourced apps which are built on Fleetbase which can be cloned and customized. Every app is built so that the Fleetbase instance can be switched out whether on-premise install or cloud hosted.
@@ -122,11 +143,10 @@ Fleetbase offers a few open sourced apps which are built on Fleetbase which can
</ul>
## 🛣️ Roadmap
1. **Extensions Registry and Marketplace** ~ Allows users to publish and sell installable extensions on Fleetbase instances.
1. **Customer Facing Views** ~ Extensions will be able to create public/customer facing views tracking and management from outside of the console UI.
2. **Inventory and Warehouse Management** ~ Pallet will be Fleetbases first official extension for WMS & Inventory.
3. **Customer Facing Views** ~ Extensions will be able to create public/customer facing views tracking and management from outside of the console UI.
3. **Accounting and Invoicing** ~ Pallet will be Fleetbases first official extension for WMS & Inventory.
4. **Binary Builds** ~ Run Fleetbase from a single binary.
5. **Fleetbase CLI** ~ Official CLI for publishing and managing extensions, as well as scaffolding extensions.
6. **Fleetbase for Desktop** ~ Desktop builds for OSX and Windows.
7. **Custom Maps and Routing Engines** ~ Feature to enable easy integrations with custom maps and routing engines like Google Maps or Mapbox etc…

View File

@@ -9,10 +9,10 @@
"license": "AGPL-3.0-or-later",
"require": {
"php": "^8.0",
"fleetbase/core-api": "^1.4.30",
"fleetbase/fleetops-api": "^0.5.4",
"fleetbase/registry-bridge": "^0.0.8",
"fleetbase/storefront-api": "^0.3.13",
"fleetbase/core-api": "^1.5.4",
"fleetbase/fleetops-api": "^0.5.6",
"fleetbase/registry-bridge": "^0.0.13",
"fleetbase/storefront-api": "^0.3.14",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^10.0",
"laravel/octane": "^2.3",

962
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,9 @@ php artisan sandbox:migrate --force
# Seed database
php artisan fleetbase:seed
# Create permissions, policies, and roles
php artisan fleetbase:create-permissions
# Restart queue
php artisan queue:restart

View File

@@ -106,10 +106,8 @@ export default class ConfigureFilesystemComponent extends Component {
type: 'gcs_credentials',
},
(uploadedFile) => {
console.log('uploadedFile', uploadedFile);
this.gcsCredentialsFileId = uploadedFile.id;
this.gcsCredentialsFile = uploadedFile;
console.log('this.gcsCredentialsFile', this.gcsCredentialsFile);
}
);
} catch (error) {

View File

@@ -21,7 +21,7 @@
{{/if}}
<div class="flex flex-row items-center mt-3">
<Input @value={{this.twilioTestPhone}} @type="tel" placeholder="Send Test SMS Here" class="form-input form-input-sm" />
<Button @wrapperClass="ml-2" @icon="plug" @text="Test Twilio Config" @onClick={{this.testTwilio}} @isLoading={{this.isLoading}} @disabled={{not this.twilioTestPhone}} />
<Button @wrapperClass="ml-2" @icon="plug" @text="Test Twilio Config" @onClick={{perform this.testTwilio}} @isLoading={{this.testTwilio.isRunning}} @disabled={{not this.twilioTestPhone}} />
</div>
</ContentPanel>
@@ -33,13 +33,15 @@
<span class="text-xs">{{this.this.sentryTestResponse.message}}</span>
</div>
{{/if}}
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Sentry Config" @onClick={{this.testSentry}} @isLoading={{this.isLoading}} />
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Sentry Config" @onClick={{perform this.testSentry}} @isLoading={{this.testSentry.isRunning}} @disabled={{not this.sentryDsn}} />
</ContentPanel>
<ContentPanel @title="IP Info" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
<InputGroup @name="IP Info API Key" @value={{this.ipinfoApiKey}} disabled={{this.isLoading}} />
</ContentPanel>
<Spacer @height="200px" />
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{perform this.save}} @disabled={{or this.save.isRunning this.loadConfigValues.isRunning}} @isLoading={{or this.save.isRunning this.loadConfigValues.isRunning}} />
</EmberWormhole>

View File

@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
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 ConfigureServicesComponent extends Component {
@service fetch;
@@ -37,7 +38,7 @@ export default class ConfigureServicesComponent extends Component {
*/
constructor() {
super(...arguments);
this.loadConfigValues();
this.loadConfigValues.perform();
}
@action setConfigValues(config) {
@@ -48,24 +49,19 @@ export default class ConfigureServicesComponent extends Component {
}
}
@action loadConfigValues() {
this.isLoading = true;
this.fetch
.get('settings/services-config')
.then((response) => {
this.setConfigValues(response);
})
.finally(() => {
this.isLoading = false;
});
@task *loadConfigValues() {
try {
const config = yield this.fetch.get('settings/services-config');
this.setConfigValues(config);
return config;
} catch (error) {
this.notifications.serverError(error);
}
}
@action save() {
this.isLoading = true;
this.fetch
.post('settings/services-config', {
@task *save() {
try {
yield this.fetch.post('settings/services-config', {
aws: {
key: this.awsKey,
secret: this.awsSecret,
@@ -86,45 +82,36 @@ export default class ConfigureServicesComponent extends Component {
sentry: {
dsn: this.sentryDsn,
},
})
.then(() => {
this.notifications.success('Services configuration saved.');
})
.finally(() => {
this.isLoading = false;
});
} catch (error) {
this.notifications.serverError(error);
}
}
@action testTwilio() {
this.isLoading = true;
this.fetch
.post('settings/test-twilio-config', {
@task *testTwilio() {
try {
const twilioTestResponse = yield this.fetch.post('settings/test-twilio-config', {
sid: this.twilioSid,
token: this.twilioToken,
from: this.twilioFrom,
phone: this.twilioTestPhone,
})
.then((response) => {
this.twilioTestResponse = response;
})
.finally(() => {
this.isLoading = false;
});
this.twilioTestResponse = twilioTestResponse;
return twilioTestResponse;
} catch (error) {
this.notifications.serverError(error);
}
}
@action testSentry() {
this.isLoading = true;
this.fetch
.post('settings/test-sentry-config', {
@task *testSentry() {
try {
const sentryTestResponse = yield this.fetch.post('settings/test-sentry-config', {
dsn: this.sentryDsn,
})
.then((response) => {
this.sentryTestResponse = response;
})
.finally(() => {
this.isLoading = false;
});
this.sentryTestResponse = sentryTestResponse;
return sentryTestResponse;
} catch (error) {
this.notifications.serverError(error);
}
}
}

View File

@@ -27,9 +27,10 @@ export default class DashboardCountComponent extends Component {
* @param {Object} { options }
* @memberof WidgetKeyMetricsCountComponent
*/
constructor(owner, { options, title }) {
constructor(owner, { options, title, value = null }) {
super(...arguments);
this.title = title;
this.value = value;
this.createRenderValueFromOptions(options);
}
@@ -40,6 +41,10 @@ export default class DashboardCountComponent extends Component {
* @memberof WidgetKeyMetricsCountComponent
*/
createRenderValueFromOptions(options = {}) {
if (value !== null) {
return;
}
let { format, currency, dateFormat, value } = options;
switch (format) {

View File

@@ -1,14 +1,16 @@
<div class="fleetbase-dashboard-grid" ...attributes>
<GridStack @options={{this.gridOptions}} @onChange={{this.onChangeGrid}}>
{{#each @dashboard.widgets as |widget|}}
<GridStackItem id={{widget.id}} @options={{spread-widget-options (hash id=widget.id options=widget.grid_options)}} class="relative">
{{component widget.component options=widget.options}}
{{#if @isEdit}}
<div class="absolute top-2 right-2">
<Button @type="default" @icon="trash" @helpText={{"Remove widget from the dashboard"}} @onClick={{fn this.removeWidget widget}} />
</div>
{{/if}}
</GridStackItem>
{{#if (component-resolvable widget.component)}}
<GridStackItem id={{widget.id}} @options={{spread-widget-options (hash id=widget.id options=widget.grid_options)}} class="relative">
{{component widget.component options=widget.options}}
{{#if @isEdit}}
<div class="absolute top-2 right-2">
<Button @type="default" @icon="trash" @helpText={{"Remove widget from the dashboard"}} @onClick={{fn this.removeWidget widget}} />
</div>
{{/if}}
</GridStackItem>
{{/if}}
{{/each}}
</GridStack>
</div>

View File

@@ -1,6 +1,6 @@
<div class="fleetbase-github-card relative flex-1 w-full" ...attributes>
<div class="border dark:border-gray-700 border-gray-200 dark:bg-gray-800 bg-gray-50 rounded-lg shadow-sm flex flex-col">
{{#if this.isLoading}}
{{#if this.getRepositoryData.isRunning}}
<div class="p-4">
<Spinner />
</div>

View File

@@ -10,9 +10,12 @@ import fetch from 'fetch';
export default class GithubCardComponent extends Component {
@storageFor('local-cache') localCache;
@tracked data;
@tracked tags;
@tracked isLoading = false;
@tracked data = {
owner: {
avatar_url: 'https://avatars.githubusercontent.com/u/38091894?v=4',
},
};
@tracked tags = [];
@computed('tags.length') get latestRelease() {
if (isArray(this.tags) && this.tags.length) {

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 AuthForgotPasswordController extends Controller {
/**
@@ -32,13 +32,6 @@ export default class AuthForgotPasswordController extends Controller {
*/
@tracked email;
/**
* The loading state
*
* @memberof AuthForgotPasswordController
*/
@tracked isLoading;
/**
* Indicator if request has been sent.
*
@@ -46,30 +39,27 @@ export default class AuthForgotPasswordController extends Controller {
*/
@tracked isSent = false;
/**
* Query parameters.
*
* @memberof AuthForgotPasswordController
*/
queryParams = ['email'];
/**
* Sends a secure magic reset link to the user provided email.
*
* @memberof AuthForgotPasswordController
*/
@action sendSecureLink(event) {
// firefox patch
@task *sendSecureLink(event) {
event.preventDefault();
const { email } = this;
this.isLoading = true;
this.fetch
.post('auth/get-magic-reset-link', { email })
.then(() => {
this.notifications.success(this.intl.t('auth.forgot-password.success-message'));
this.isSent = true;
})
.catch((error) => {
this.notifications.serverError(error);
})
.finally(() => {
this.isLoading = false;
});
try {
yield this.fetch.post('auth/get-magic-reset-link', { email: this.email });
this.notifications.success(this.intl.t('auth.forgot-password.success-message'));
this.isSent = true;
} catch (error) {
this.notifications.serverError(error);
}
}
}

View File

@@ -166,6 +166,11 @@ export default class AuthLoginController extends Controller {
return this.sendUserForEmailVerification(identity);
}
// Handle password reset required
if (error.toString().includes('reset required')) {
return this.sendUserForPasswordReset(identity);
}
return this.failure(error);
}
@@ -210,6 +215,20 @@ export default class AuthLoginController extends Controller {
});
}
/**
* Sends user to forgot password flow.
*
* @param {String} email
* @return {Promise<Transition>}
* @memberof AuthLoginController
*/
@action sendUserForPasswordReset(email) {
this.notifications.warning(this.intl.t('auth.login.password-reset-required'));
return this.router.transitionTo('auth.forgot-password', { queryParams: { email } }).then(() => {
this.reset('error');
});
}
/**
* Sets correct route to send user to after login.
*

View File

@@ -53,6 +53,13 @@ export default class AuthResetPasswordController extends Controller {
*/
@tracked password_confirmation;
/**
* Query parameters.
*
* @memberof AuthResetPasswordController
*/
queryParams = ['code'];
/**
* The reset password task.
*

View File

@@ -1,69 +1,21 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { getOwner } from '@ember/application';
import { later } from '@ember/runloop';
import { action, computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import { action } from '@ember/object';
import { isArray } from '@ember/array';
import first from '@fleetbase/ember-core/utils/first';
export default class ConsoleController extends Controller {
/**
* Inject the `currentUser` service.
*
* @var {Service}
*/
@service currentUser;
/**
* Inject the `modalsManager` service.
*
* @var {Service}
*/
@service modalsManager;
/**
* Inject the `session` service.
*
* @var {Service}
*/
@service session;
/**
* Inject the `fetch` service.
*
* @var {Service}
*/
@service fetch;
/**
* Inject the `notifications` service.
*
* @var {Service}
*/
@service notifications;
/**
* Inject the `router` service.
*
* @var {Service}
*/
@service router;
/**
* Inject the `intl` service.
*
* @var {Service}
*/
@service intl;
/**
* Inject the `universe` service.
*
* @var {Service}
*/
@service universe;
@service abilities;
/**
* Authenticated user organizations.
@@ -98,23 +50,28 @@ export default class ConsoleController extends Controller {
*
* @var {Array}
*/
@tracked hiddenSidebarRoutes = ['console.home', 'console.extensions', 'console.notifications'];
@tracked hiddenSidebarRoutes = ['console.home', 'console.notifications'];
/**
* Installed extensions.
* Menu items to be added to the main header navigation bar.
*
* @var {Array}
* @memberof ConsoleController
*/
@computed() get extensions() {
return getOwner(this).application.extensions;
}
@tracked menuItems = [];
/**
* Get the currently authenticated user
* Menu items to be added to the user dropdown menu located in the header.
*
* @var {Model}
* @memberof ConsoleController
*/
@alias('currentUser.user') user;
@tracked userMenuItems = [];
/**
* Menu items to be added to the organization dropdown menu located in the header.
*
* @memberof ConsoleController
*/
@tracked organizationMenuItems = [];
/**
* Creates an instance of ConsoleController.
@@ -228,53 +185,51 @@ export default class ConsoleController extends Controller {
changeAction: (action) => {
this.modalsManager.setOption('action', action);
},
confirm: (modal) => {
confirm: async (modal) => {
modal.startLoading();
const { action, next, name, description, phone, currency, country, timezone } = modal.getOptions();
if (action === 'join') {
return this.fetch
.post('auth/join-organization', { next })
.then(() => {
this.fetch.flushRequestCache('auth/organizations');
this.notifications.success(this.intl.t('console.create-or-join-organization.join-success-notification'));
later(
this,
() => {
window.location.reload();
},
900
);
})
.catch((error) => {
this.notifications.serverError(error);
});
}
return this.fetch
.post('auth/create-organization', {
name,
description,
phone,
currency,
country,
timezone,
})
.then(() => {
try {
await this.fetch.post('auth/join-organization', { next });
this.fetch.flushRequestCache('auth/organizations');
this.notifications.success(this.intl.t('console.create-or-join-organization.create-success-notification'));
later(
this.notifications.success(this.intl.t('console.create-or-join-organization.join-success-notification'));
return later(
this,
() => {
window.location.reload();
},
900
);
})
.catch((error) => {
this.notifications.serverError(error);
} catch (error) {
modal.stopLoading();
return this.notifications.serverError(error);
}
}
try {
await this.fetch.post('auth/create-organization', {
name,
description,
phone,
currency,
country,
timezone,
});
this.fetch.flushRequestCache('auth/organizations');
this.notifications.success(this.intl.t('console.create-or-join-organization.create-success-notification'));
return later(
this,
() => {
window.location.reload();
},
900
);
} catch (error) {
modal.stopLoading();
return this.notifications.serverError(error);
}
},
});
}
@@ -294,25 +249,24 @@ export default class ConsoleController extends Controller {
body: this.intl.t('console.switch-organization.modal-body'),
acceptButtonText: this.intl.t('console.switch-organization.modal-accept-button-text'),
acceptButtonScheme: 'primary',
confirm: (modal) => {
confirm: async (modal) => {
modal.startLoading();
return this.fetch
.post('auth/switch-organization', { next: organization.uuid })
.then(() => {
this.fetch.flushRequestCache('auth/organizations');
this.notifications.success(this.intl.t('console.switch-organization.success-notification'));
later(
this,
() => {
window.location.reload();
},
900
);
})
.catch((error) => {
this.notifications.serverError(error);
});
try {
await this.fetch.post('auth/switch-organization', { next: organization.uuid });
this.fetch.flushRequestCache('auth/organizations');
this.notifications.success(this.intl.t('console.switch-organization.success-notification'));
return later(
this,
() => {
window.location.reload();
},
900
);
} catch (error) {
modal.stopLoading();
return this.notifications.serverError(error);
}
},
});
}

View File

@@ -1,6 +1,7 @@
export function initialize (owner) {
export function initialize(owner) {
const universe = owner.lookup('service:universe');
if (universe) {
universe.createRegistry('@fleetbase/console');
universe.bootEngines(owner);
}
}

View File

@@ -1,21 +0,0 @@
import config from 'ember-get-config';
export function initialize(owner) {
const universe = owner.lookup('service:universe');
if (universe) {
universe.registerOrganizationMenuItem(`v${config.version}`, {
index: 4,
route: null,
icon: 'code-branch',
iconSize: 'xs',
iconClass: 'mr-1.5',
wrapperClass: 'app-version-in-nav',
overwriteWrapperClass: true,
});
}
}
export default {
initialize,
};

View File

@@ -1,5 +1,6 @@
import Model, { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { capitalize } from '@ember/string';
import { pluralize } from 'ember-inflector';
import { format, formatDistanceToNow } from 'date-fns';
import humanize from '@fleetbase/ember-core/utils/humanize';
@@ -26,14 +27,27 @@ export const getPermissionResource = function (permissionName) {
return parserPermissionName(permissionName, 2);
};
const lowercase = function (string) {
let words = string.split(' ');
words[0] = words[0].toLowerCase();
return words.join(' ');
const titleize = function (string = '') {
if (typeof string !== 'string') {
return '';
}
return humanize(string)
.split(' ')
.map((w) => capitalize(w))
.join(' ');
};
const titleize = function (string) {
return lowercase(humanize(string));
const smartTitleize = function (string = '') {
if (typeof string !== 'string') {
return '';
}
let titleized = titleize(string);
if (titleized === 'Iam') {
titleized = titleized.toUpperCase();
}
return titleized;
};
/**
@@ -49,6 +63,7 @@ export default class PermissionModel extends Model {
/** @attributes */
@attr('string') name;
@attr('string') guard_name;
@attr('string') service;
/** @dates */
@attr('date') created_at;
@@ -59,6 +74,7 @@ export default class PermissionModel extends Model {
return {
name: this.name,
guard_name: this.guard_name,
service: this.service,
created_at: this.created_at,
updated_at: this.updated_at,
};
@@ -80,6 +96,10 @@ export default class PermissionModel extends Model {
return 'do anything';
}
if (action === 'see') {
return 'Visibly See';
}
return titleize(action);
}
@@ -90,9 +110,9 @@ export default class PermissionModel extends Model {
@computed('actionName', 'name', 'resourceName', 'extensionName') get description() {
let actionName = this.actionName;
let actionPreposition = 'to';
let resourceName = pluralize(humanize(this.resourceName));
let resourceName = pluralize(smartTitleize(this.resourceName));
let resourcePreposition = getPermissionAction(this.name) === '*' && resourceName ? 'with' : '';
let extensionName = humanize(this.extensionName);
let extensionName = smartTitleize(this.extensionName);
let extensionPreposition = 'on';
let descriptionParts = ['Permission', actionPreposition, actionName, resourcePreposition, resourceName, extensionPreposition, extensionName];

View File

@@ -12,6 +12,7 @@ export default class PolicyModel extends Model {
/** @attributes */
@attr('string') name;
@attr('string') type;
@attr('string') service;
@attr('string') guard_name;
@attr('string') description;
@attr('boolean') is_mutable;

View File

@@ -14,6 +14,10 @@ export default class RoleModel extends Model {
@attr('string') name;
@attr('string') guard_name;
@attr('string') description;
@attr('string') service;
@attr('string') type;
@attr('boolean') is_mutable;
@attr('boolean') is_deletable;
/** @dates */
@attr('date') created_at;

View File

@@ -1,4 +1,4 @@
import Model, { attr } from '@ember-data/model';
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
import { computed, get } from '@ember/object';
import { not } from '@ember/object/computed';
import { getOwner } from '@ember/application';
@@ -28,9 +28,13 @@ export default class UserModel extends Model {
@attr('string') status;
@attr('boolean') is_online;
@attr('boolean') is_admin;
@attr('raw') types;
@attr('raw') meta;
/** @relationships */
@belongsTo('role') role;
@hasMany('policy') policies;
@hasMany('permission') permissions;
/** @dates */
@attr('date') last_seen_at;
@attr('date') phone_verified_at;
@@ -43,7 +47,7 @@ export default class UserModel extends Model {
/** @methods */
deactivate() {
const owner = getOwner(this);
const fetch = owner.lookup(`service:fetch`);
const fetch = owner.lookup('service:fetch');
return fetch.patch(`users/deactivate/${this.id}`).then((response) => {
this.session_status = 'inactive';
@@ -54,7 +58,7 @@ export default class UserModel extends Model {
activate() {
const owner = getOwner(this);
const fetch = owner.lookup(`service:fetch`);
const fetch = owner.lookup('service:fetch');
return fetch.patch(`users/activate/${this.id}`).then((response) => {
this.session_status = 'active';
@@ -65,27 +69,72 @@ export default class UserModel extends Model {
removeFromCurrentCompany() {
const owner = getOwner(this);
const fetch = owner.lookup(`service:fetch`);
const fetch = owner.lookup('service:fetch');
return fetch.delete(`users/remove-from-company/${this.id}`);
}
resendInvite() {
const owner = getOwner(this);
const fetch = owner.lookup(`service:fetch`);
const fetch = owner.lookup('service:fetch');
return fetch.post(`users/resend-invite`, { user: this.id });
}
getPermissions() {
const permissions = [];
// get direct applied permissions
if (this.get('permissions')) {
permissions.pushObjects(this.get('permissions').toArray());
}
// get role permissions and role policies permissions
if (this.get('role')) {
if (this.get('role.permissions')) {
permissions.pushObjects(this.get('role.permissions').toArray());
}
if (this.get('role.policies')) {
for (let i = 0; i < this.get('role.policies').length; i++) {
const policy = this.get('role.policies').objectAt(i);
if (policy.get('permissions')) {
permissions.pushObjects(policy.get('permissions').toArray());
}
}
}
}
// get direct applied policy permissions
if (this.get('policies')) {
for (let i = 0; i < this.get('policies').length; i++) {
const policy = this.get('policies').objectAt(i);
if (policy.get('permissions')) {
permissions.pushObjects(policy.get('permissions').toArray());
}
}
}
return permissions;
}
/** @computed */
@not('isEmailVerified') emailIsNotVerified;
@not('isPhoneVerified') phoneIsNotVerified;
/** @computed */
get allPermissions() {
return this.getPermissions();
}
@computed('meta.two_factor_enabled') get isTwoFactorEnabled() {
return this.meta && this.meta.two_factor_enabled;
}
@computed('is_admin') get isAdmin() {
return this.is_admin === true;
}
@computed('types') get typesList() {
const types = Array.from(this.types);
return types.join(', ');

View File

@@ -4,6 +4,12 @@ import { inject as service } from '@ember/service';
export default class AuthForgotPasswordRoute extends Route {
@service store;
queryParams = {
email: {
refreshModel: false,
},
};
model() {
return this.store.findRecord('brand', 1);
}

View File

@@ -4,41 +4,9 @@ import { action } from '@ember/object';
import '@fleetbase/leaflet-routing-machine';
export default class ConsoleRoute extends Route {
/**
* Inject the `store` service
*
* @var {Service}
*/
@service store;
/**
* Inject the `fetch` service
*
* @var {Service}
*/
@service fetch;
/**
* Inject the `session` service
*
* @var {Service}
*/
@service session;
/**
* Inject the `intl` service
*
* @var {Service}
*/
@service intl;
/**
* Inject the `currentUser` service
*
* @var {Service}
*/
@service currentUser;
/**
* Require authentication to access all `console` routes.
*
@@ -61,54 +29,4 @@ export default class ConsoleRoute extends Route {
model() {
return this.store.findRecord('brand', 1);
}
/**
* We will use this hook to preload engines
*
* @void
*/
@action afterModel() {
this.fetchSessionInfo();
}
/**
* We will use this hook to setup controller and more
*
* @void
*/
@action setupController(controller, model) {
super.setupController(controller, model);
// Get and set user locale
this.fetch.get('users/locale').then(({ locale }) => {
this.intl.setLocale(locale);
});
// Get user organizations
this.fetch.get('auth/organizations').then((organizations) => {
this.currentUser.setOption('organizations', organizations);
controller.organizations = organizations;
});
}
/**
* Use this hook to fetch user related queries
*
* @void
*/
@action fetchSessionInfo() {
this.fetch.shouldResetCache();
this.fetch
.cachedGet(
'lookup/whois',
{},
{
expirationInterval: 60,
expirationIntervalUnit: 'minutes',
}
)
.then((whois) => {
this.currentUser.setOption('whois', whois);
});
}
}

View File

@@ -1,6 +1,20 @@
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default class UserSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
/**
* Embedded relationship attributes
*
* @var {Object}
*/
get attrs() {
return {
role: { serialize: 'ids', deserialize: 'records' },
policies: { serialize: 'ids', deserialize: 'records' },
permissions: { serialize: 'ids', deserialize: 'records' },
};
}
export default class UserSerializer extends ApplicationSerializer {
/**
* Customize serializer so that the password is never sent to the server via Ember Data
*

View File

@@ -15,6 +15,9 @@
{{t "auth.forgot-password.is-sent.message" htmlSafe=true}}
</p>
</div>
<div class="flex flex-row mt-4">
<Button @icon="check" @type="primary" @text={{t "common.continue"}} @onClick={{transition-to "auth.login"}} />
</div>
{{else}}
<div class="flex px-3 py-2 mb-6 rounded-md shadow-sm bg-blue-200">
<div>
@@ -31,12 +34,27 @@
{{t "auth.forgot-password.form.email-label"}}
</label>
<div class="mt-2">
<Input @value={{this.email}} @type="email" id="email" name="email" required class="form-input form-input-lg w-full" placeholder={{t "auth.forgot-password.form.email-label"}} />
<Input
@value={{this.email}}
@type="email"
id="email"
name="email"
required
class="form-input form-input-lg w-full"
placeholder={{t "auth.forgot-password.form.email-label"}}
/>
</div>
</div>
<div class="flex flex-row space-x-2">
<Button @icon="magic" @type="primary" @buttonType="submit" @text={{t "auth.forgot-password.form.submit-button"}} @onClick={{this.sendSecureLink}} @isLoading={{this.isLoading}} />
<Button
@icon="magic"
@type="primary"
@buttonType="submit"
@text={{t "auth.forgot-password.form.submit-button"}}
@onClick={{perform this.sendSecureLink}}
@isLoading={{this.sendSecureLink.isRunning}}
/>
<Button @buttonType="button" @text={{t "auth.forgot-password.form.nevermind-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
</div>
</form>

View File

@@ -1,6 +1,6 @@
{{page-title (t "app.name")}}
<Layout::Container>
<Layout::Header @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} @showSidebarToggle={{true}} @sidebarToggleEnabled={{this.sidebarToggleEnabled}} @onSidebarToggle={{this.onSidebarToggle}} />
<Layout::Header @brand={{@model}} @menuItems={{this.menuItems}} @organizationMenuItems={{this.organizationMenuItems}} @userMenuItems={{this.userMenuItems}} @onAction={{this.onAction}} @showSidebarToggle={{true}} @sidebarToggleEnabled={{this.sidebarToggleEnabled}} @onSidebarToggle={{this.onSidebarToggle}} />
<Layout::Main>
<Layout::Sidebar @onSetup={{this.setSidebarContext}}>
<div class="next-sidebar-content-inner">
@@ -12,7 +12,11 @@
{{outlet}}
</Layout::Section>
</Layout::Main>
<Layout::MobileNavbar @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} />
<Layout::MobileNavbar @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.menuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} />
</Layout::Container>
<ChatContainer />
<ConsoleWormhole />
{{!-- template-lint-disable no-potential-path-strings --}}
<RegistryYield @registry="@fleetbase/console" as |RegistryComponent|>
<RegistryComponent @controller={{this}} />
</RegistryYield>

View File

@@ -31,7 +31,7 @@
<Layout::Sidebar::Item @route="console.admin.config.filesystem" @icon="hard-drive">{{t "common.filesystem"}}</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.config.queue" @icon="layer-group">{{t "common.queue"}}</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.config.socket" @icon="plug">{{t "common.socket"}}</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.config.notification-channels" @icon="tower-broadcast">{{t "common.notification-channels"}}</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.config.notification-channels" @icon="tower-broadcast">{{t "common.push-notifications"}}</Layout::Sidebar::Item>
</Layout::Sidebar::Panel>
</EmberWormhole>

View File

@@ -1,4 +1,4 @@
{{page-title "Notifications"}}
{{page-title "Push Notifications"}}
<Layout::Section::Header @title={{t "console.admin.notifications.title"}}>
<Button @type="primary" @size="sm" @icon="save" @text={{t "common.save-button-text"}} @onClick={{this.saveSettings}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</Layout::Section::Header>
@@ -7,9 +7,9 @@
<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">
<ContentPanel @title={{concat (smart-humanize groupName) " " (t "console.admin.notifications.notification-settings") }} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
{{#each notifications as |notification|}}
<InputGroup @name={{notification.name}} @helpText={{notification.description}}>
<InputGroup @name={{titleize notification.name}} @helpText={{notification.description}}>
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelectMultiple
@searchEnabled={{true}}

View File

@@ -13,7 +13,7 @@
<FaIcon @icon="info-circle" class="text-blue-900 mr-4" />
</div>
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
{{t "invite.invitation-sent-message" htnmlSafe=true companyName=@model.name appName=(t "app.name")}}
{{t "invite.for-users.invitation-sent-message" htnmlSafe=true companyName=@model.name appName=(t "app.name")}}
</p>
</div>

View File

@@ -2,6 +2,7 @@
const toBoolean = require('./utils/to-boolean');
const getenv = require('./utils/getenv');
const fixApiHost = require('./utils/fix-api-host');
const asArray = require('./utils/as-array');
const { version } = require('../package');
module.exports = function (environment) {
@@ -20,7 +21,7 @@ module.exports = function (environment) {
},
APP: {
showExtensionsLink: toBoolean(getenv('SHOW_EXTENSIONS_LINK', true)),
extensions: asArray(getenv('EXTENSIONS')),
},
API: {
@@ -29,8 +30,8 @@ module.exports = function (environment) {
},
osrm: {
host: getenv('OSRM_HOST', 'https://bundle.routing.fleetbase.io'),
servers: getenv('OSRM_SERVERS', '').split(',').filter(Boolean),
host: getenv('OSRM_HOST', 'https://router.project-osrm.org'),
servers: {},
},
socket: {
@@ -41,7 +42,7 @@ module.exports = function (environment) {
},
stripe: {
publishableKey: getenv('STRIPE_KEY')
publishableKey: getenv('STRIPE_KEY'),
},
defaultValues: {

View File

@@ -1,5 +1,6 @@
module.exports = function () {
return {
'free-solid-svg-icons': 'all',
'free-brands-svg-icons': 'all',
};
};

View File

@@ -0,0 +1,11 @@
module.exports = function asArray(value) {
if (Array.isArray(value)) {
return value;
}
if (typeof value === 'string' && value.includes(',')) {
return value.split(',');
}
return [];
};

View File

@@ -4,5 +4,4 @@ SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=localhost
SOCKETCLUSTER_SECURE=false
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://bundle.routing.fleetbase.io
OSRM_SERVERS=https://canada.routing.fleetbase.io,https://us.routing.fleetbase.io
OSRM_HOST=https://router.project-osrm.org

View File

@@ -5,5 +5,4 @@ SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=
SOCKETCLUSTER_SECURE=true
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://bundle.routing.fleetbase.io
OSRM_SERVERS=https://canada.routing.fleetbase.io,https://us.routing.fleetbase.io
OSRM_HOST=https://router.project-osrm.org

View File

@@ -1,6 +1,6 @@
{
"name": "@fleetbase/console",
"version": "0.5.0",
"version": "0.5.6",
"private": true,
"description": "Modular logistics and supply chain operating system (LSOS)",
"repository": "https://github.com/fleetbase/fleetbase",
@@ -29,21 +29,21 @@
"test:ember": "ember test"
},
"dependencies": {
"@fleetbase/ember-core": "^0.2.13",
"@fleetbase/ember-ui": "^0.2.19",
"@fleetbase/fleetops-engine": "^0.5.4",
"@fleetbase/storefront-engine": "^0.3.13",
"@fleetbase/dev-engine": "^0.2.5",
"@fleetbase/iam-engine": "^0.0.14",
"@fleetbase/registry-bridge-engine": "^0.0.8",
"@fleetbase/ember-core": "^0.2.17",
"@fleetbase/ember-ui": "^0.2.24",
"@fleetbase/fleetops-engine": "^0.5.6",
"@fleetbase/storefront-engine": "^0.3.14",
"@fleetbase/dev-engine": "^0.2.6",
"@fleetbase/iam-engine": "^0.1.0",
"@fleetbase/registry-bridge-engine": "^0.0.13",
"@fleetbase/fleetops-data": "^0.1.17",
"@fleetbase/leaflet-routing-machine": "^3.2.16",
"@ember/legacy-built-in-components": "^0.4.1",
"@ember/legacy-built-in-components": "^0.4.2",
"@fortawesome/ember-fontawesome": "^2.0.0",
"ember-changeset": "^4.1.2",
"ember-changeset-validations": "^4.1.1",
"ember-composable-helpers": "^5.0.0",
"ember-concurrency": "^3.0.0",
"ember-concurrency": "^3.1.1",
"ember-concurrency-decorators": "^2.0.3",
"ember-gridstack": "^4.0.0",
"ember-intl": "6.3.2",
@@ -53,34 +53,34 @@
"ember-radio-button": "3.0.0-beta.1",
"ember-tag-input": "^3.1.0",
"fleetbase-extensions-indexer": "^0.0.5",
"gridstack": "^7.2.2",
"gridstack": "^7.3.0",
"patch-package": "^8.0.0",
"postcss-at-rules-variables": "^0.3.0",
"postcss-custom-properties": "^12.1.9",
"postcss-custom-properties": "^12.1.11",
"postcss-nth-list": "^1.0.2"
},
"devDependencies": {
"@fleetbase/intl-lint": "^0.0.1",
"@babel/core": "^7.23.2",
"@babel/eslint-parser": "^7.22.15",
"@babel/plugin-proposal-decorators": "^7.23.2",
"@ember/optional-features": "^2.0.0",
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.25.1",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@ember/optional-features": "^2.1.0",
"@ember/string": "^3.1.1",
"@ember/test-helpers": "^3.2.0",
"@ember/test-helpers": "^3.3.1",
"@fleetbase/intl-lint": "^0.0.1",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-brands-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@glimmer/component": "^1.1.2",
"@glimmer/tracking": "^1.1.2",
"@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.8",
"@tailwindcss/forms": "^0.5.7",
"autoprefixer": "^10.4.20",
"broccoli-asset-rev": "^3.0.0",
"broccoli-funnel": "^3.0.8",
"concurrently": "^8.2.2",
"date-fns": "^2.30.0",
"dragula": "^3.7.3",
"ember-auto-import": "^2.6.3",
"ember-cli": "~5.4.1",
"ember-auto-import": "^2.7.4",
"ember-cli": "~5.4.2",
"ember-cli-app-version": "^6.0.1",
"ember-cli-babel": "^8.2.0",
"ember-cli-clean-css": "^3.0.0",
@@ -92,48 +92,48 @@
"ember-cli-sri": "^2.1.1",
"ember-cli-string-helpers": "^6.1.0",
"ember-cli-terser": "^4.0.2",
"ember-data": "^4.12.5",
"ember-engines": "^0.8.23",
"ember-data": "^4.12.8",
"ember-engines": "^0.9.0",
"ember-fetch": "^8.1.2",
"ember-leaflet": "^5.1.1",
"ember-leaflet": "^5.1.3",
"ember-load-initializers": "^2.1.2",
"ember-modifier": "^4.1.0",
"ember-page-title": "^8.0.0",
"ember-qunit": "^8.0.1",
"ember-modifier": "^4.2.0",
"ember-page-title": "^8.2.3",
"ember-qunit": "^8.1.0",
"ember-resolver": "^11.0.1",
"ember-responsive": "^5.0.0",
"ember-source": "~5.4.0",
"ember-template-lint": "^5.11.2",
"ember-source": "~5.4.1",
"ember-template-lint": "^5.13.0",
"ember-wormhole": "^0.6.0",
"eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-ember": "^11.11.1",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-qunit": "^8.0.1",
"fast-glob": "^3.3.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-ember": "^11.12.0",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-qunit": "^8.1.2",
"fast-glob": "^3.3.2",
"fs": "0.0.1-security",
"inter-ui": "^3.19.3",
"leaflet": "^1.9.4",
"loader.js": "^4.7.0",
"normalize.css": "^8.0.1",
"postcss": "^8.4.21",
"postcss": "^8.4.41",
"postcss-conditionals-renewed": "^1.0.0",
"postcss-each": "^1.1.0",
"postcss-import": "14.1.0",
"postcss-mixins": "^9.0.4",
"postcss-preset-env": "^7.8.2",
"postcss-simple-vars": "^7.0.0",
"prettier": "^3.0.3",
"qunit": "^2.20.0",
"postcss-preset-env": "^7.8.3",
"postcss-simple-vars": "^7.0.1",
"prettier": "^3.3.3",
"qunit": "^2.22.0",
"qunit-dom": "^2.0.0",
"recast": "^0.23.3",
"recast": "^0.23.9",
"stylelint": "^15.11.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-prettier": "^4.0.2",
"tailwindcss": "^3.1.8",
"stylelint-prettier": "^4.1.0",
"tailwindcss": "^3.4.10",
"tracked-built-ins": "^3.3.0",
"webpack": "^5.89.0"
"webpack": "^5.94.0"
},
"engines": {
"node": ">= 18"
@@ -143,8 +143,8 @@
},
"pnpm": {
"overrides": {
"@fleetbase/ember-core": "^0.2.13",
"@fleetbase/ember-ui": "^0.2.19",
"@fleetbase/ember-core": "^0.2.17",
"@fleetbase/ember-ui": "^0.2.24",
"@fleetbase/fleetops-data": "^0.1.17"
}
},

24960
console/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@ Router.map(function () {
this.route('cache');
this.route('filesystem');
this.route('mail');
this.route('notification-channels');
this.route('notification-channels', { path: '/push-notifications' });
this.route('queue');
this.route('services');
this.route('socket');

View File

@@ -1,39 +0,0 @@
import Application from '@ember/application';
import config from '@fleetbase/console/config/environment';
import { initialize } from '@fleetbase/console/instance-initializers/register-app-version';
import { module, test } from 'qunit';
import Resolver from 'ember-resolver';
import { run } from '@ember/runloop';
module('Unit | Instance Initializer | register-app-version', function (hooks) {
hooks.beforeEach(function () {
this.TestApplication = class TestApplication extends Application {
modulePrefix = config.modulePrefix;
podModulePrefix = config.podModulePrefix;
Resolver = Resolver;
};
this.TestApplication.instanceInitializer({
name: 'initializer under test',
initialize,
});
this.application = this.TestApplication.create({
autoboot: false,
});
this.instance = this.application.buildInstance();
});
hooks.afterEach(function () {
run(this.instance, 'destroy');
run(this.application, 'destroy');
});
// TODO: Replace this with your real tests.
test('it works', async function (assert) {
await this.instance.boot();
assert.ok(true);
});
});

View File

@@ -68,6 +68,12 @@ common:
export: Export
reload: Reload
reload-data: Reload data
unauthorized: Unauthorized
unauthorized-to: Unauthorized to
unauthorized-access: Unauthorized Access
unauthorized-access-message: Unauthorized Access, you must request permissions to access.
permissions-required-for-changes: You do not have the required permissions to make changes.
push-notifications: Push Notifications
component:
file:
dropdown-label: File actions
@@ -179,6 +185,7 @@ auth:
no-identity-notification: Did you forget to enter your email?
no-password-notification: Did you forget to enter your password?
unverified-notification: Your account needs to be verified to proceed.
password-reset-required: A password reset is required to continue.
failed-attempt:
message: <strong>Forgot your password?</strong><br> Click the button below to reset your password.
button-text: Ok, help me reset!
@@ -246,7 +253,7 @@ console:
mail:
title: Mail Configuration
notification-channels:
title: Notification Channels Configuration
title: Push Notifications Configuration
queue:
title: Queue Configuration
services:

View File

@@ -825,88 +825,6 @@ erDiagram
TIMESTAMP deleted_at
}
fleetbase_billing_customers {
BIGINTUNSIGNED id PK
CHAR uuid
CHAR company_uuid FK
VARCHAR payment_gateway_id
VARCHAR pm_type
VARCHAR pm_last_four
TIMESTAMP trial_ends_at
JSON options
TIMESTAMP deleted_at
TIMESTAMP created_at
TIMESTAMP updated_at
}
fleetbase_billing_payment_gateways {
BIGINTUNSIGNED id PK
CHAR uuid
VARCHAR name
VARCHAR code
VARCHAR description
VARCHAR api_key
VARCHAR api_secret
VARCHAR callback_url
VARCHAR return_url
VARCHAR webhook_secret
CHAR logo_uuid FK
CHAR backdrop_uuid FK
JSON options
TIMESTAMP deleted_at
TIMESTAMP created_at
TIMESTAMP updated_at
}
fleetbase_billing_plans {
BIGINTUNSIGNED id PK
CHAR uuid
CHAR payment_gateway_uuid FK
VARCHAR name
TEXT description
VARCHAR payment_gateway_id
INT price
BIT recurring
VARCHAR billing_period
VARCHAR interval
INT trial_period_days
JSON options
TIMESTAMP deleted_at
TIMESTAMP created_at
TIMESTAMP updated_at
}
fleetbase_billing_subscription_items {
BIGINTUNSIGNED id PK
CHAR uuid
CHAR subscription_uuid FK
VARCHAR payment_gateway_id
VARCHAR payment_gateway_product
VARCHAR payment_gateway_price
INT quantity
TIMESTAMP deleted_at
TIMESTAMP created_at
TIMESTAMP updated_at
}
fleetbase_billing_subscriptions {
BIGINTUNSIGNED id PK
CHAR uuid
CHAR company_uuid FK
CHAR payment_gateway_uuid FK
CHAR plan_uuid FK
VARCHAR type
VARCHAR payment_gateway_id
VARCHAR payment_gateway_status
VARCHAR payment_gateway_price
INT quantity
TIMESTAMP trial_ends_at
TIMESTAMP ends_at
TIMESTAMP deleted_at
TIMESTAMP created_at
TIMESTAMP updated_at
}
fleetbase_categories {
INTUNSIGNED id PK
VARCHAR _key
@@ -1599,6 +1517,7 @@ erDiagram
CHAR purchase_rate_uuid FK
CHAR tracking_number_uuid FK
CHAR driver_assigned_uuid FK
CHAR vehicle_assigned_uuid FK
JSON meta
JSON options
BIT dispatched
@@ -1764,6 +1683,121 @@ erDiagram
TIMESTAMP updated_at
}
fleetbase_registry_extension_bundles {
INTUNSIGNED id PK
CHAR uuid
CHAR public_id
CHAR bundle_id
CHAR company_uuid FK
CHAR created_by_uuid FK
CHAR extension_uuid FK
CHAR bundle_uuid FK
VARCHAR bundle_number
VARCHAR version
VARCHAR status
JSON meta
TIMESTAMP created_at
TIMESTAMP updated_at
TIMESTAMP deleted_at
}
fleetbase_registry_extension_installs {
INTUNSIGNED id PK
CHAR uuid
CHAR company_uuid FK
CHAR extension_uuid FK
JSON meta
TIMESTAMP created_at
TIMESTAMP updated_at
TIMESTAMP deleted_at
}
fleetbase_registry_extension_purchases {
INTUNSIGNED id PK
CHAR uuid
CHAR company_uuid FK
CHAR extension_uuid FK
VARCHAR stripe_checkout_session_id
VARCHAR stripe_payment_intent_id
BIT is_subcription
INT locked_price
VARCHAR subscription_billing_period
VARCHAR subscription_model
JSON meta
TIMESTAMP created_at
TIMESTAMP updated_at
TIMESTAMP deleted_at
}
fleetbase_registry_extensions {
INTUNSIGNED id PK
CHAR uuid
CHAR company_uuid FK
CHAR created_by_uuid FK
CHAR registry_user_uuid FK
CHAR current_bundle_uuid FK
CHAR next_bundle_uuid FK
CHAR icon_uuid FK
CHAR category_uuid FK
VARCHAR public_id
VARCHAR stripe_product_id
VARCHAR name
VARCHAR subtitle
BIT self_managed
BIT payment_required
INT price
INT sale_price
BIT on_sale
BIT subscription_required
VARCHAR subscription_billing_period
VARCHAR subscription_model
INT subscription_amount
JSON subscription_tiers
VARCHAR currency
VARCHAR slug
VARCHAR version
VARCHAR fa_icon
MEDIUMTEXT description
MEDIUMTEXT promotional_text
VARCHAR website_url
VARCHAR repo_url
VARCHAR support_url
VARCHAR privacy_policy_url
VARCHAR tos_url
VARCHAR copyright
VARCHAR primary_language
JSON tags
JSON languages
JSON meta
BIT core_extension
VARCHAR status
TIMESTAMP published_at
TIMESTAMP accepted_at
TIMESTAMP rejected_at
TIMESTAMP created_at
TIMESTAMP updated_at
TIMESTAMP deleted_at
}
fleetbase_registry_users {
INTUNSIGNED id PK
CHAR uuid
CHAR company_uuid FK
CHAR user_uuid FK
VARCHAR public_id
VARCHAR token
VARCHAR registry_token
VARCHAR scope
TIMESTAMP expires_at
TIMESTAMP last_used_at
VARCHAR name
JSON meta
BIT revoked
TIMESTAMP created_at
TIMESTAMP updated_at
TIMESTAMP deleted_at
}
fleetbase_role_has_permissions {
CHAR permission_id PK
CHAR role_id PK
@@ -2195,6 +2229,8 @@ erDiagram
CHAR place_uuid FK
CHAR payload_uuid FK
CHAR tracking_number_uuid FK
CHAR customer_uuid
VARCHAR customer_type
VARCHAR _import_id
VARCHAR type
INT order
@@ -2701,13 +2737,10 @@ erDiagram
fleetbase_api_credentials ||--o{ fleetbase_webhook_endpoints : "foreign key"
fleetbase_api_credentials ||--o{ fleetbase_webhook_request_logs : "foreign key"
fleetbase_api_events ||--o{ fleetbase_webhook_request_logs : "foreign key"
fleetbase_billing_payment_gateways ||--o{ fleetbase_billing_plans : "foreign key"
fleetbase_billing_payment_gateways ||--o{ fleetbase_billing_subscriptions : "foreign key"
fleetbase_billing_plans ||--o{ fleetbase_billing_subscriptions : "foreign key"
fleetbase_billing_subscriptions ||--o{ fleetbase_billing_subscription_items : "foreign key"
fleetbase_categories ||--o{ fleetbase_entities : "foreign key"
fleetbase_categories ||--o{ fleetbase_extensions : "foreign key"
fleetbase_categories ||--o{ fleetbase_order_configs : "foreign key"
fleetbase_categories ||--o{ fleetbase_registry_extensions : "foreign key"
fleetbase_categories ||--o{ fleetbase_storefront_network_stores : "foreign key"
fleetbase_categories ||--o{ fleetbase_storefront_product_addon_categories : "foreign key"
fleetbase_categories ||--o{ fleetbase_storefront_product_addons : "foreign key"
@@ -2725,8 +2758,6 @@ erDiagram
fleetbase_comments ||--o{ fleetbase_comments : "foreign key"
fleetbase_companies ||--o{ fleetbase_api_credentials : "foreign key"
fleetbase_companies ||--o{ fleetbase_api_request_logs : "foreign key"
fleetbase_companies ||--o{ fleetbase_billing_customers : "foreign key"
fleetbase_companies ||--o{ fleetbase_billing_subscriptions : "foreign key"
fleetbase_companies ||--o{ fleetbase_chat_attachments : "foreign key"
fleetbase_companies ||--o{ fleetbase_chat_channels : "foreign key"
fleetbase_companies ||--o{ fleetbase_chat_logs : "foreign key"
@@ -2756,6 +2787,11 @@ erDiagram
fleetbase_companies ||--o{ fleetbase_positions : "foreign key"
fleetbase_companies ||--o{ fleetbase_proofs : "foreign key"
fleetbase_companies ||--o{ fleetbase_purchase_rates : "foreign key"
fleetbase_companies ||--o{ fleetbase_registry_extension_bundles : "foreign key"
fleetbase_companies ||--o{ fleetbase_registry_extension_installs : "foreign key"
fleetbase_companies ||--o{ fleetbase_registry_extension_purchases : "foreign key"
fleetbase_companies ||--o{ fleetbase_registry_extensions : "foreign key"
fleetbase_companies ||--o{ fleetbase_registry_users : "foreign key"
fleetbase_companies ||--o{ fleetbase_roles : "foreign key"
fleetbase_companies ||--o{ fleetbase_routes : "foreign key"
fleetbase_companies ||--o{ fleetbase_service_areas : "foreign key"
@@ -2790,7 +2826,6 @@ erDiagram
fleetbase_drivers ||--o{ fleetbase_issues : "foreign key"
fleetbase_drivers ||--o{ fleetbase_orders : "foreign key"
fleetbase_extensions ||--o{ fleetbase_extension_installs : "foreign key"
fleetbase_files ||--o{ fleetbase_billing_payment_gateways : "foreign key"
fleetbase_files ||--o{ fleetbase_categories : "foreign key"
fleetbase_files ||--o{ fleetbase_chat_attachments : "foreign key"
fleetbase_files ||--o{ fleetbase_companies : "foreign key"
@@ -2798,6 +2833,8 @@ erDiagram
fleetbase_files ||--o{ fleetbase_extensions : "foreign key"
fleetbase_files ||--o{ fleetbase_order_configs : "foreign key"
fleetbase_files ||--o{ fleetbase_proofs : "foreign key"
fleetbase_files ||--o{ fleetbase_registry_extension_bundles : "foreign key"
fleetbase_files ||--o{ fleetbase_registry_extensions : "foreign key"
fleetbase_files ||--o{ fleetbase_users : "foreign key"
fleetbase_files ||--o{ fleetbase_vehicles : "foreign key"
fleetbase_files ||--o{ fleetbase_vendors : "foreign key"
@@ -2834,6 +2871,11 @@ erDiagram
fleetbase_places ||--o{ fleetbase_storefront_store_locations : "foreign key"
fleetbase_policies ||--o{ fleetbase_model_has_policies : "foreign key"
fleetbase_purchase_rates ||--o{ fleetbase_orders : "foreign key"
fleetbase_registry_extension_bundles ||--o{ fleetbase_registry_extensions : "foreign key"
fleetbase_registry_extensions ||--o{ fleetbase_registry_extension_bundles : "foreign key"
fleetbase_registry_extensions ||--o{ fleetbase_registry_extension_installs : "foreign key"
fleetbase_registry_extensions ||--o{ fleetbase_registry_extension_purchases : "foreign key"
fleetbase_registry_users ||--o{ fleetbase_registry_extensions : "foreign key"
fleetbase_roles ||--o{ fleetbase_model_has_roles : "foreign key"
fleetbase_roles ||--o{ fleetbase_role_has_permissions : "foreign key"
fleetbase_routes ||--o{ fleetbase_orders : "foreign key"
@@ -2871,6 +2913,9 @@ erDiagram
fleetbase_users ||--o{ fleetbase_issues : "foreign key"
fleetbase_users ||--o{ fleetbase_order_configs : "foreign key"
fleetbase_users ||--o{ fleetbase_orders : "foreign key"
fleetbase_users ||--o{ fleetbase_registry_extension_bundles : "foreign key"
fleetbase_users ||--o{ fleetbase_registry_extensions : "foreign key"
fleetbase_users ||--o{ fleetbase_registry_users : "foreign key"
fleetbase_users ||--o{ fleetbase_user_devices : "foreign key"
fleetbase_users ||--o{ fleetbase_webhook_endpoints : "foreign key"
fleetbase_users ||--o{ fleetbase_storefront_carts : "foreign key"
@@ -2888,6 +2933,7 @@ erDiagram
fleetbase_vehicles ||--o{ fleetbase_fleet_vehicles : "foreign key"
fleetbase_vehicles ||--o{ fleetbase_fuel_reports : "foreign key"
fleetbase_vehicles ||--o{ fleetbase_issues : "foreign key"
fleetbase_vehicles ||--o{ fleetbase_orders : "foreign key"
fleetbase_vehicles ||--o{ fleetbase_vehicle_devices : "foreign key"
fleetbase_vendors ||--o{ fleetbase_drivers : "foreign key"
fleetbase_vendors ||--o{ fleetbase_entities : "foreign key"

View File

@@ -75,6 +75,7 @@ services:
RESPONSE_CACHE_DRIVER: redis
REGISTRY_HOST: https://registry.fleetbase.io
REGISTRY_PREINSTALLED_EXTENSIONS: 'true'
OSRM_HOST: https://router.project-osrm.org
depends_on:
- database
- cache

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
# Base stage
FROM dunglas/frankenphp:1.2.2-php8.2-bookworm as base
FROM dunglas/frankenphp:1.2.3-php8.2-bookworm as base
# Install packages
RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm nano \
@@ -63,7 +63,7 @@ ARG GITHUB_AUTH_KEY
COPY --chown=www-data:www-data ./Caddyfile $CADDYFILE_PATH
# Create /fleetbase directory and set correct permissions
RUN mkdir -p /fleetbase/api && chown -R www-data:www-data /fleetbase
RUN mkdir -p /fleetbase/api && mkdir -p /fleetbase/console && chown -R www-data:www-data /fleetbase
# Set working directory
WORKDIR /fleetbase/api
@@ -130,9 +130,9 @@ CMD ["php", "artisan", "queue:work"]
FROM base as app-dev
ENTRYPOINT ["docker-php-entrypoint"]
# Add --watch flag later
CMD ["sh", "-c", "php artisan octane:frankenphp --port=8000 --host=0.0.0.0 --caddyfile $CADDYFILE_PATH"]
CMD ["sh", "-c", "php artisan octane:frankenphp --workers=6 --max-requests=250 --port=8000 --host=0.0.0.0 --caddyfile $CADDYFILE_PATH"]
# Application stage
FROM base as app
ENTRYPOINT ["/sbin/ssm-parent", "-c", ".ssm-parent.yaml", "run", "--", "docker-php-entrypoint"]
CMD ["sh", "-c", "php artisan octane:frankenphp --port=8000 --host=0.0.0.0 --https --http-redirect --caddyfile $CADDYFILE_PATH"]
CMD ["sh", "-c", "php artisan octane:frankenphp --workers=6 --max-requests=250 --port=8000 --host=0.0.0.0 --https --http-redirect --caddyfile $CADDYFILE_PATH"]

2
docs

Submodule docs updated: 6e1281f528...61704f4855

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

20714
erd.svg

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

1
packages/ledger Submodule

Submodule packages/ledger added at 1f6f27f501