Compare commits

..

36 Commits

Author SHA1 Message Date
Ronald A. Richardson
435552f332 updated docker-compose.yml to set baseUrl for solid server 2023-12-06 13:51:45 +08:00
Ronald A. Richardson
e0615c5a9b api setup to link to solid extension 2023-12-05 18:09:37 +08:00
Ronald A. Richardson
a830e190fa getting started with solid protocol integration, setting up oidc client 2023-12-05 18:06:23 +08:00
Shiv Thakker
5c7e0a1c56 Update README.md 2023-11-28 15:08:12 +08:00
Ron
e3acd28c18 Merge pull request #162 from fleetbase/dev-v0.3.1
v0.3.1
2023-11-24 17:39:37 +08:00
Ronald A. Richardson
313b6e63a8 added other extensions 2023-11-24 17:30:53 +08:00
Ronald A. Richardson
a7ed7ee935 resolved conflicts for merge 2023-11-24 17:25:37 +08:00
Ronald A. Richardson
1e28d9d8d8 Upgraded dependencies with patches and improvements for improved UX 2023-11-24 17:20:03 +08:00
Ron
0c31b54fde Merge pull request #159 from fleetbase/fix-deploy-workflow
Fix CD workflow
2023-11-22 16:40:41 +08:00
Ronald A. Richardson
f6b83e5638 upgraded to aws-actions/configure-aws-credentials@v4 2023-11-22 16:39:04 +08:00
Ronald A. Richardson
b30ee818fc added api/auth.json to gitignore 2023-11-22 16:34:30 +08:00
Ronald A. Richardson
6ec9ad59d3 few patches for flespi extension integration 2023-11-17 13:38:55 +08:00
Ron
10ff2e066b Merge pull request #156 from fleetbase/dev-v0.3.0
v0.3.0
2023-11-16 19:01:14 +08:00
Ronald A. Richardson
f56db88ad6 fleetops: fix filters, improve drawer component w/ state, several patches and upgrades 2023-11-16 18:49:17 +08:00
Ron
033cf5cfe0 Merge pull request #155 from fleetbase/dev-v0.2.9
v0.2.9
2023-11-09 17:21:56 +08:00
Ronald A. Richardson
4a4dc76e60 Added scheduler feature, scope/live map drawer feature, and bug fixes and dependency upgrades 2023-11-09 17:04:10 +08:00
Ron
a52af94b00 Merge pull request #154 from fleetbase/dev-v0.2.8
v0.2.8
2023-11-06 20:12:39 +08:00
Ronald A. Richardson
9c4daf7a68 fix dependencies 2023-11-06 19:55:42 +08:00
Ronald A. Richardson
a8904ba112 Full refactor of management section, upgrade of dependencies 2023-11-06 19:39:11 +08:00
Ronald A. Richardson
6880664d9e update packages to latest commits 2023-10-30 19:57:33 +08:00
Ron
99b30d7f58 Merge pull request #148 from fleetbase/feature-notifications
Feature notifications
2023-10-30 19:56:10 +08:00
Ronald A. Richardson
c8539fd2a0 merged with main 2023-10-30 19:41:23 +08:00
Ronald A. Richardson
cb1aec40fd ready for new release with notification tray and settings 2023-10-30 19:37:37 +08:00
Ronald A. Richardson
b728b366a0 patch notification settings in admin 2023-10-30 19:17:01 +08:00
Ronald A. Richardson
5cfc3f1cc7 add back package.json 2023-10-30 14:58:54 +08:00
Ronald A. Richardson
e7b5282aa3 updated notificaiton setting structure to be more robust 2023-10-30 14:53:19 +08:00
Ronald A. Richardson
7ffb7ac24a Latest updates, moved save buttons to subheader for easy access to save 2023-10-30 12:40:39 +08:00
Ron
6e4a9edd7d Merge pull request #146 from fleetbase/add-notification-settings
Add notification settings to Admin
2023-10-30 11:55:57 +08:00
Ron
d9d01c8bbc Merge branch 'feature-notifications' into add-notification-settings 2023-10-30 11:55:12 +08:00
Ronald A. Richardson
c54d75fa0f hotfix notification read event trigger 2023-10-27 17:50:35 +08:00
Ronald A. Richardson
91904c3836 Patches and improved styling for notification page 2023-10-27 17:35:31 +08:00
Ronald A. Richardson
0e075e3b24 dont check in dependency mgmt files 2023-10-27 15:24:58 +08:00
TemuulenBM
74a782f4ea WIP: Notification Settings interface on Admin 2023-10-26 17:54:29 +08:00
TemuulenBM
a8b2042d85 Latest updates for notification tray and page 2023-10-26 11:32:54 +08:00
Temuulen Bayanmunkh
cc52a40660 Almost completed the notifications route, <NotificationTray /> component responds with notifications route changes 2023-10-24 17:58:52 +08:00
Temuulen Bayanmunkh
4fb2dec8c3 Added notification tray component 2023-10-20 18:06:43 +08:00
54 changed files with 3082 additions and 1793 deletions

View File

@@ -35,7 +35,7 @@ jobs:
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_NUMBER }}:role/${{ env.PROJECT }}-${{ env.STACK }}-deployer
role-session-name: github

1
.gitignore vendored
View File

@@ -14,6 +14,7 @@ api/storage/public
api/vendor
api/composer.dev.json
api/composer-install-dev.sh
api/auth.json
act.sh
composer-auth.json
packages/billing-api

3
.gitmodules vendored
View File

@@ -39,3 +39,6 @@
[submodule "docs/api-reference"]
path = docs/api-reference
url = git@github.com:fleetbase/api-reference.git
[submodule "packages/solid"]
path = packages/solid
url = git@github.com:fleetbase/solid.git

View File

@@ -10,6 +10,9 @@
<a href="https://fleetbase.github.io/guides" rel="nofollow">Fleetbase Documentation →</a>
<br>
<br>
<a href="https://meetings.hubspot.com/shiv-thakker" rel="nofollow">Book a Demo</a>
<br>
<br>
<a href="https://github.com/fleetbase/fleetbase/issues">Report an Issue</a>
·
<a href="https://fleetbase.github.io/api-reference">API Reference</a>

View File

@@ -9,9 +9,10 @@
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"fleetbase/core-api": "^1.3.1",
"fleetbase/fleetops-api": "^0.3.0",
"fleetbase/storefront-api": "^0.2.2",
"fleetbase/core-api": "^1.3.2",
"fleetbase/fleetops-api": "^0.3.5",
"fleetbase/storefront-api": "^0.2.4",
"fleetbase/solid-api": "^0.0.1",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.75",
@@ -32,6 +33,12 @@
"nunomaduro/collision": "^5.10",
"phpunit/phpunit": "^9.5.10"
},
"repositories": [
{
"type": "path",
"url": "../packages/solid"
}
],
"autoload": {
"psr-4": {
"App\\": "app/",

1776
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,6 @@
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Config" @onClick={{this.test}} @isLoading={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -22,6 +22,6 @@
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Config" @onClick={{this.test}} @isLoading={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -47,6 +47,6 @@
</div>
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -20,6 +20,6 @@
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Config" @onClick={{this.test}} @isLoading={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -40,6 +40,6 @@
<InputGroup @name="IP Info API Key" @value={{this.ipinfoApiKey}} disabled={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -65,13 +65,6 @@ export default class ConsoleController extends Controller {
*/
@tracked organizations = [];
/**
* Whether or not to hide the sidebar.
*
* @var {Boolean}
*/
@tracked hideSidebar = true;
/**
* Sidebar Context Controls
*
@@ -79,6 +72,13 @@ export default class ConsoleController extends Controller {
*/
@tracked sidebarContext;
/**
* Routes which should hide the sidebar menu.
*
* @var {Array}
*/
@tracked hiddenSidebarRoutes = ['console.home', 'console.extensions', 'console.notifications'];
/**
* Installed extensions.
*
@@ -104,7 +104,7 @@ export default class ConsoleController extends Controller {
this.router.on('routeDidChange', (transition) => {
if (this.sidebarContext) {
if (transition.to.name === 'console.home' || transition.to.name === 'console.extensions') {
if (this.hiddenSidebarRoutes.includes(transition.to.name)) {
this.sidebarContext.hideNow();
} else {
this.sidebarContext.show();
@@ -123,7 +123,7 @@ export default class ConsoleController extends Controller {
this.sidebarContext = sidebarContext;
this.universe.sidebarContext = sidebarContext;
if (this.router.currentRouteName === 'console.home' || this.router.currentRouteName === 'console.extensions') {
if (this.hiddenSidebarRoutes.includes(this.router.currentRouteName)) {
this.sidebarContext.hideNow();
}
}

View File

@@ -2,5 +2,10 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ConsoleAccountController extends Controller {
/**
* Inject the `universe` service.
*
* @memberof ConsoleAdminController
*/
@service universe;
}

View File

@@ -2,5 +2,10 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ConsoleAdminController extends Controller {
/**
* Inject the `universe` service.
*
* @memberof ConsoleAdminController
*/
@service universe;
}

View File

@@ -0,0 +1,136 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import createNotificationKey from '../../../utils/create-notification-key';
export default class ConsoleAdminNotificationsController extends Controller {
/**
* Inject the notifications service.
*
* @memberof ConsoleAdminNotificationsController
*/
@service notifications;
/**
* Inject the fetch service.
*
* @memberof ConsoleAdminNotificationsController
*/
@service fetch;
/**
* The notification settings value JSON.
*
* @memberof ConsoleAdminNotificationsController
* @var {Object}
*/
@tracked notificationSettings = {};
/**
* Notification transport methods enabled.
*
* @memberof ConsoleAdminNotificationsController
* @var {Array}
*/
@tracked notificationTransportMethods = ['email', 'sms'];
/**
* Tracked property for the loading state
*
* @memberof ConsoleAdminNotificationsController
* @var {Boolean}
*/
@tracked isLoading = false;
/**
* Creates an instance of ConsoleAdminNotificationsController.
* @memberof ConsoleAdminNotificationsController
*/
constructor() {
super(...arguments);
this.getSettings();
}
/**
* Selectes notifiables for settings.
*
* @param {Object} notification
* @param {Array} notifiables
* @memberof ConsoleAdminNotificationsController
*/
@action onSelectNotifiable(notification, notifiables) {
const notificationKey = createNotificationKey(notification.definition, notification.name);
const _notificationSettings = { ...this.notificationSettings };
if (!_notificationSettings[notificationKey]) {
_notificationSettings[notificationKey] = {};
}
_notificationSettings[notificationKey].notifiables = notifiables;
_notificationSettings[notificationKey].definition = notification.definition;
_notificationSettings[notificationKey].via = notifiables.map((notifiable) => {
return {
identifier: notifiable.value,
methods: this.notificationTransportMethods,
};
});
this.mutateNotificationSettings(_notificationSettings);
}
/**
* Mutates the notification settings property.
*
* @param {Object} [_notificationSettings={}]
* @memberof ConsoleAdminNotificationsController
*/
mutateNotificationSettings(_notificationSettings = {}) {
this.notificationSettings = {
...this.notificationSettings,
..._notificationSettings,
};
}
/**
* Save notification settings to the server.
*
* @action
* @method saveSettings
* @returns {Promise}
* @memberof ConsoleAdminNotificationsController
*/
@action saveSettings() {
const { notificationSettings } = this;
this.isLoading = true;
return this.fetch
.post('notifications/save-settings', { notificationSettings })
.then(() => {
this.notifications.success('Notification settings successfully saved.');
})
.catch((error) => {
this.notifications.serverError(error);
})
.finally(() => {
this.isLoading = false;
});
}
/**
* Fetches and updates notification settings asynchronously.
*
* @returns {Promise<void>} A promise for successful retrieval and update, or an error on failure.
*/
getSettings() {
return this.fetch
.get('notifications/get-settings')
.then(({ notificationSettings }) => {
this.notificationSettings = notificationSettings;
})
.catch((error) => {
this.notifications.serverError(error);
});
}
}

View File

@@ -0,0 +1,188 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
/**
* Controller for managing notifications.
*/
export default class NotificationsController extends Controller {
/**
* Inject the `socket` service
*
* @memberof NotificationsController
*/
@service socket;
/**
* Inject the `store` service
*
* @memberof NotificationsController
*/
@service store;
/**
* Inject the `fetch` service
*
* @memberof NotificationsController
*/
@service fetch;
/**
* Inject the `notifications` service
*
* @memberof NotificationsController
*/
@service notifications;
/**
* Inject the `universe` service
*
* @memberof NotificationsController
*/
@service universe;
/**
* Inject the `router` service
*
* @memberof NotificationsController
*/
@service router;
/**
* Queryable parameters for this controller's model
*
* @var {Array}
*/
queryParams = ['page', 'limit', 'sort', 'query', 'created_at'];
/**
* The current page of data being viewed
*
* @var {Integer}
*/
@tracked page = 1;
/**
* The maximum number of items to show per page
*
* @var {Integer}
*/
@tracked limit = 20;
/**
* The param to sort the data on, the param with prepended `-` is descending
*
* @var {String}
*/
@tracked sort = '-created_at';
/**
* The selected notifications.
*
* @tracked
* @var {Array}
* @memberof NotificationsController
*/
@tracked selected = [];
/**
* Creates an instance of NotificationsController.
* @memberof NotificationsController
*/
constructor() {
super(...arguments);
// listen for received notifications
this.universe.on('notification.received', () => {
this.router.refresh();
});
}
/**
* Action to select or deselect a notification.
*
* @param {NotificationModel} notification - The notification to select or deselect.
* @memberof NotificationsController
*/
@action selectNotification(notification) {
if (this.selected.includes(notification)) {
this.selected.removeObject(notification);
} else {
this.selected.pushObject(notification);
}
}
/**
* Action to delete selected notifications.
*
* @memberof NotificationsController
*/
@action delete() {
return this.fetch
.delete('notifications/bulk-delete', {
notifications: this.selected.map(({ id }) => id),
})
.then(() => {
this.notifications.success(`${this.selected.length} notifications deleted`);
this.universe.trigger('notifications.deleted', [...this.selected]);
this.selected.clear();
return this.router.refresh();
})
.catch((error) => {
this.notifications.serverError(error);
});
}
/**
* Action to mark selected notifications as read.
*
* @memberof NotificationsController
*/
@action read() {
const unreadSelectedNotifications = this.selected.filter((notification) => notification.unread);
return this.fetch
.put('notifications/mark-as-read', {
notifications: unreadSelectedNotifications.map(({ id }) => id),
})
.then(() => {
this.notifications.success(`${unreadSelectedNotifications.length} notifications marked as read`);
this.universe.trigger('notifications.read', [...unreadSelectedNotifications]);
this.selected.clear();
return this.router.refresh();
})
.catch((error) => {
this.notifications.serverError(error);
});
}
/**
* Action to select all notifications.
*
* @memberof NotificationsController
*/
@action selectAll() {
if (this.selected.length === this.model.length) {
this.selected.clear();
} else {
this.selected = this.model.toArray();
}
}
/**
* Action to mark a notification as read.
*
* @param {NotificationModel} notification
* @return {Promise}
* @memberof NotificationsController
*/
@action markNotificationAsRead(notification) {
return notification.markAsRead().then(() => {
this.notifications.info('Notification marked as read.');
this.universe.trigger('notifications.read', [notification]);
});
}
}

View File

@@ -2,5 +2,10 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ConsoleSettingsController extends Controller {
/**
* INject the `universe` service
*
* @memberof ConsoleSettingsController
*/
@service universe;
}

View File

@@ -0,0 +1,6 @@
import { helper } from '@ember/component/helper';
import createNotificationKey from '../utils/create-notification-key';
export default helper(function getNotificationKey([definition, name]) {
return createNotificationKey(definition, name);
});

View File

@@ -10,7 +10,7 @@ export default class Company extends Model {
@attr('string') owner_uuid;
@attr('string') logo_uuid;
@attr('string') backdrop_uuid;
@attr('string') address_uuid;
@attr('string') place_uuid;
/** @relationships */
@belongsTo('file') logo;

View File

@@ -0,0 +1,53 @@
import Model, { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { format, formatDistanceToNow } from 'date-fns';
export default class NotificationModel extends Model {
@attr('string') notifiable_id;
@attr('string') notifiable_type;
/** @attributes */
@attr('string') type;
@attr('raw') data;
@attr('raw') meta;
/** @dates */
@attr('date') read_at;
@attr('date') created_at;
/** @computed */
@computed('created_at') get createdAgo() {
return formatDistanceToNow(this.created_at);
}
@computed('created_at') get createdAt() {
return format(this.created_at, 'PPP p');
}
@computed('read_at') get readAt() {
return format(this.read_at, 'PPP p');
}
@computed('read_at') get isRead() {
return this.read_at instanceof Date;
}
@computed('read_at') get read() {
return this.read_at instanceof Date;
}
@computed('isRead') get unread() {
return !this.get('isRead');
}
/** @actions */
markAsRead() {
if (this.isRead) {
return;
}
this.set('read_at', new Date());
return this.save();
}
}

View File

@@ -22,6 +22,7 @@ Router.map(function () {
this.route('console', { path: '/' }, function () {
this.route('home', { path: '/' });
this.route('extensions');
this.route('notifications');
this.route('account', function () {
this.route('virtual', { path: '/:slug/:view' });
});
@@ -41,28 +42,9 @@ Router.map(function () {
this.route('socket');
});
this.route('branding');
this.route('notifications');
this.route('virtual', { path: '/:slug/:view' });
});
this.mount('@fleetbase/dev-engine', {
as: 'developers',
path: 'developers'
});
this.mount('@fleetbase/fleetops-engine', {
as: 'fleet-ops',
path: 'fleet-ops'
});
this.mount('@fleetbase/iam-engine', {
as: 'iam',
path: 'iam'
});
this.mount('@fleetbase/storefront-engine', {
as: 'storefront',
path: 'storefront'
});
});
this.route('install');
});

View File

@@ -0,0 +1,20 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { hash } from 'rsvp';
import groupBy from '@fleetbase/ember-core/utils/group-by';
export default class ConsoleAdminNotificationsRoute extends Route {
@service fetch;
model() {
return hash({
registry: this.fetch.get('notifications/registry'),
notifiables: this.fetch.get('notifications/notifiables'),
});
}
setupController(controller, { registry, notifiables }) {
controller.groupedNotifications = groupBy(registry, 'package');
controller.notifiables = notifiables;
}
}

View File

@@ -0,0 +1,27 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
/**
* Route for managing console notifications.
*/
export default class ConsoleNotificationsRoute extends Route {
@service store;
queryParams = {
page: { refreshModel: true },
limit: { refreshModel: true },
sort: { refreshModel: true },
query: { refreshModel: true },
created_at: { refreshModel: true },
};
/**
* Fetch the model data based on the specified parameters.
*
* @param {Object} params - Query parameters for fetching notifications.
* @returns {Promise} - A promise that resolves with the notification data.
*/
model(params = {}) {
return this.store.query('notification', params);
}
}

View File

@@ -0,0 +1,5 @@
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
export default class NotificationSerializer extends ApplicationSerializer {
primaryKey = 'id';
}

View File

@@ -3,6 +3,7 @@
<EmberWormhole @to="sidebar-menu-items">
<Layout::Sidebar::Item @route="console.admin.index" @icon="rectangle-list">Overview</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.branding" @icon="palette">Branding</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.notifications" @icon="bell">Notifications</Layout::Sidebar::Item>
{{#each this.universe.adminMenuItems as |menuItem|}}
<Layout::Sidebar::Item @onClick={{fn this.universe.transitionMenuItem "console.admin.virtual" menuItem}} @item={{menuItem}} @icon={{menuItem.icon}}>{{menuItem.title}}</Layout::Sidebar::Item>
{{/each}}

View File

@@ -1,5 +1,7 @@
{{page-title "Branding"}}
<Layout::Section::Header @title="Branding" />
<Layout::Section::Header @title="Branding">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</Layout::Section::Header>
<Layout::Section::Body class="overflow-y-scroll h-full">
<div class="container mx-auto h-screen" {{increase-height-by 300}}>
@@ -59,10 +61,6 @@
</InputGroup>
</form>
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
</div>
</div>
</Layout::Section::Body>

View File

@@ -0,0 +1,32 @@
{{page-title "Notifications"}}
<Layout::Section::Header @title="Notifications">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.saveSettings}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</Layout::Section::Header>
<Layout::Section::Body class="overflow-y-scroll h-full">
<div class="container mx-auto h-screen" {{increase-height-by 1200}}>
<div class="max-w-3xl my-10 mx-auto space-y-4">
{{#each-in this.groupedNotifications as |groupName notifications|}}
<ContentPanel @title={{concat (smart-humanize groupName) " Notification Settings"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
{{#each notifications as |notification|}}
<InputGroup @name={{notification.name}} @helpText={{notification.description}}>
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelectMultiple
@searchEnabled={{true}}
@options={{this.notifiables}}
@selected={{get this.notificationSettings (concat (get-notification-key notification.definition notification.name) ".notifiables")}}
@onChange={{fn this.onSelectNotifiable notification}}
@placeholder="Select notifiables..."
@triggerClass="form-select form-input form-input-sm flex-1"
as |notifiable|
>
{{notifiable.label}}
</PowerSelectMultiple>
</div>
</InputGroup>
{{/each}}
</ContentPanel>
{{/each-in}}
</div>
</div>
</Layout::Section::Body>

View File

@@ -0,0 +1,51 @@
<Layout::Section::Header @title="Notifications">
<Button @icon="check-square" @type="default" @text="Select All" {{on "click" this.selectAll}} class="mr-2" />
<Button @icon="envelope" @type="primary" @text="Mark as Read" {{on "click" this.read}} class="mr-2" />
<Button @icon="trash" @type="danger" @text="Delete" {{on "click" this.delete}} />
</Layout::Section::Header>
<Layout::Section::Body class="h-full w-full">
<div class="max-h-[calc(100vh-10rem)] h-full w-full overflow-y-scroll">
<div class="h-full w-full">
{{#each @model as |notification|}}
<div class="flex flex-row justify-between px-4 py-3 text-black dark:text-white border-b dark:border-gray-800 border-gray-200 text-sm hover:opacity-60 {{if notification.read_at 'bg-gray-100 dark:bg-gray-900' 'bg-white dark:bg-gray-800'}}">
<div class="flex flex-row flex-1">
<div class="flex items-center justify-center mr-6">
<Checkbox @value={{includes notification this.selected}} @onToggle={{fn this.selectNotification notification}} />
</div>
<a href="javascript:;" class="flex-1 flex flex-row" {{on "click" (fn this.markNotificationAsRead notification)}}>
<div class="mr-4 flex items-center justify-center">
{{#if notification.read_at}}
<FaIcon @icon="envelope-open" class="text-gray-200" />
{{else}}
<FaIcon @icon="envelope" class="text-gray-200" />
{{/if}}
</div>
<div class="flex flex-col">
<div class="flex flex-row space-x-2">
<h1 class="text-sm font-semibold antialiased leading-4">{{notification.data.subject}}</h1>
<div class="text-xs antialiased text-gray-900 dark:text-gray-200">- {{notification.data.message}}</div>
</div>
<div class="text-gray-300 text-xs antialiased mt-1">Received: {{notification.createdAgo}}</div>
</div>
</a>
<div>
<FaIcon @icon="clock" class="text-gray-400 dark:text-gray-700" @size="sm" />
<span class="text-gray-400 dark:text-gray-600 text-xs">{{notification.createdAt}}</span>
</div>
</div>
</div>
{{else}}
<div class="flex items-center justify-center h-full w-full">
<p class="text-base text-gray-800 dark:text-gray-300 italic">No notifications to display.</p>
</div>
{{/each}}
</div>
</div>
</Layout::Section::Body>
<div class="fixed bottom-0 w-full">
<Layout::Section::Footer>
<Pagination @meta={{@model.meta}} @currentPage={{this.page}} @onPageChange={{fn (mut this.page)}} @tfootVerticalOffset="53" @tfootVerticalOffsetElements=".next-view-section-subheader" />
</Layout::Section::Footer>
</div>

View File

@@ -0,0 +1,8 @@
import { camelize } from '@ember/string';
export default function createNotificationKey(definition, name) {
const withoutSlashes = definition.replace(/[\W_]+/g, '');
const key = `${camelize(withoutSlashes)}__${camelize(name)}`;
return key;
}

View File

@@ -43,7 +43,8 @@ module.exports = function (environment) {
driverImage: getenv('DEFAULT_DRIVER_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
userImage: getenv('DEFAULT_USER_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
contactImage: getenv('DEFAULT_CONTACT_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
vehicleImage: getenv('DEFAULT_VEHICLE_IMAGE', 'https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/vehicle-icons/light_commercial_van.svg'),
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'),
},

View File

@@ -43,7 +43,7 @@ module.exports = function (defaults) {
postcssMixins,
postcssPresetEnv({ stage: 1 }),
postcssEach,
tailwind('./tailwind.js'),
tailwind('./tailwind.config.js'),
autoprefixer,
],
},

View File

@@ -1,6 +1,6 @@
{
"name": "@fleetbase/console",
"version": "0.2.6",
"version": "0.3.1",
"private": true,
"description": "Fleetbase Console",
"repository": "",
@@ -25,25 +25,25 @@
},
"dependencies": {
"@ember/legacy-built-in-components": "^0.4.1",
"@fleetbase/ember-core": "^0.1.6",
"@fleetbase/ember-ui": "^0.2.1",
"@fleetbase/ember-core": "^0.1.8",
"@fleetbase/ember-ui": "^0.2.6",
"@fleetbase/fleetops-data": "^0.1.5",
"@fleetbase/fleetops-engine": "^0.3.5",
"@fleetbase/storefront-engine": "^0.2.4",
"@fleetbase/dev-engine": "^0.1.9",
"@fleetbase/iam-engine": "^0.0.7",
"@fleetbase/fleetops-engine": "^0.3.0",
"@fleetbase/fleetops-data": "^0.1.1",
"@fleetbase/storefront-engine": "^0.2.2",
"@fleetbase/leaflet-routing-machine": "^3.2.16",
"@fortawesome/ember-fontawesome": "^0.4.1",
"ember-intl": "^6.0.0-beta.6",
"ember-changeset": "^4.1.2",
"ember-changeset-validations": "^4.1.1",
"ember-composable-helpers": "^5.0.0",
"ember-concurrency": "^3.0.0",
"ember-concurrency-decorators": "^2.0.3",
"ember-intl": "6.0.0-beta.6",
"ember-math-helpers": "^2.18.2",
"ember-power-select": "^6.0.1",
"ember-prism": "^0.13.0",
"ember-radio-button": "^3.0.0-beta.1",
"ember-radio-button": "3.0.0-beta.1",
"ember-tag-input": "^3.1.0",
"fleetbase-extensions-indexer": "^0.0.4",
"postcss-at-rules-variables": "^0.3.0",
@@ -67,7 +67,7 @@
"dragula": "^3.7.3",
"ember-auto-import": "^2.4.2",
"ember-cli": "~4.6.0",
"ember-cli-app-version": "^5.0.0",
"ember-cli-app-version": "^6.0.1",
"ember-cli-babel": "^7.26.11",
"ember-cli-dependency-checker": "^3.3.1",
"ember-cli-dotenv": "^3.1.0",
@@ -125,9 +125,9 @@
},
"pnpm": {
"overrides": {
"@fleetbase/fleetops-data": "^0.1.1",
"@fleetbase/ember-core": "^0.1.6",
"@fleetbase/ember-ui": "^0.2.1"
"@fleetbase/fleetops-data": "^0.1.5",
"@fleetbase/ember-core": "^0.1.8",
"@fleetbase/ember-ui": "^0.2.6"
}
},
"prettier": {

2238
console/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@ Router.map(function () {
this.route('console', { path: '/' }, function () {
this.route('home', { path: '/' });
this.route('extensions');
this.route('notifications');
this.route('account', function () {
this.route('virtual', { path: '/:slug/:view' });
});
@@ -41,6 +42,7 @@ Router.map(function () {
this.route('socket');
});
this.route('branding');
this.route('notifications');
this.route('virtual', { path: '/:slug/:view' });
});
});

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 | notification-list', 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`<NotificationList />`);
assert.dom(this.element).hasText('');
// Template block usage:
await render(hbs`
<NotificationList>
template block text
</NotificationList>
`);
assert.dom(this.element).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 | notifications-list', 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`<NotificationsList />`);
assert.dom(this.element).hasText('');
// Template block usage:
await render(hbs`
<NotificationsList>
template block text
</NotificationsList>
`);
assert.dom(this.element).hasText('template block text');
});
});

View File

@@ -0,0 +1,17 @@
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 | Helper | get-notification-key', function (hooks) {
setupRenderingTest(hooks);
// TODO: Replace this with your real tests.
test('it renders', async function (assert) {
this.set('inputValue', '1234');
await render(hbs`{{get-notification-key this.inputValue}}`);
assert.dom(this.element).hasText('1234');
});
});

View File

@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Controller | console/admin/notifications', function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup('controller:console/admin/notifications');
assert.ok(controller);
});
});

View File

@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Controller | console/notifications', function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup('controller:console/notifications');
assert.ok(controller);
});
});

View File

@@ -0,0 +1,14 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Model | notification', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('notification', {});
assert.ok(model);
});
});

View File

@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Route | console/admin/notifications', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:console/admin/notifications');
assert.ok(route);
});
});

View File

@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Route | console/notifications', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:console/notifications');
assert.ok(route);
});
});

View File

@@ -0,0 +1,24 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Serializer | notification', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('notification');
assert.ok(serializer);
});
test('it serializes records', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('notification', {});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
});

View File

@@ -0,0 +1,10 @@
import createNotificationKey from '@fleetbase/console/utils/create-notification-key';
import { module, test } from 'qunit';
module('Unit | Utility | create-notification-key', function () {
// TODO: Replace this with your real tests.
test('it works', function (assert) {
let result = createNotificationKey();
assert.ok(result);
});
});

View File

@@ -15,6 +15,14 @@ services:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
MYSQL_DATABASE: "fleetbase"
solid:
image: solidproject/community-server:7
command: ["--baseUrl", "http://solid:3000"]
ports:
- "3000:3000"
volumes:
- ./solid/data:/data
socket:
image: socketcluster/socketcluster:v17.4.0
ports:

1
packages/solid Submodule

Submodule packages/solid added at dc2ee3a0ec