diff --git a/addon/services/session.js b/addon/services/session.js
index 4abd23a..b967615 100644
--- a/addon/services/session.js
+++ b/addon/services/session.js
@@ -2,6 +2,7 @@ import SimpleAuthSessionService from 'ember-simple-auth/services/session';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { later } from '@ember/runloop';
+import { debug } from '@ember/debug';
import getWithDefault from '../utils/get-with-default';
export default class SessionService extends SimpleAuthSessionService {
@@ -28,7 +29,7 @@ export default class SessionService extends SimpleAuthSessionService {
*
* @return {SessionService}
*/
- isOnboarding() {
+ isOnboarding () {
this._isOnboarding = true;
return this;
@@ -37,7 +38,7 @@ export default class SessionService extends SimpleAuthSessionService {
/**
* Manually authenticate user
*/
- manuallyAuthenticate(authToken) {
+ manuallyAuthenticate (authToken) {
return this.session._setup('authenticator:fleetbase', { token: authToken }, true);
}
@@ -46,16 +47,13 @@ export default class SessionService extends SimpleAuthSessionService {
*
* @void
*/
- async handleAuthentication() {
+ async handleAuthentication () {
if (this._isOnboarding) {
return;
}
const loaderNode = this.showLoader('Starting session...');
- this.isLoaderNodeOpen = true;
-
- try {
- await this.router.transitionTo(this.redirectTo);
+ const removeLoaderNode = () => {
later(
this,
() => {
@@ -63,11 +61,18 @@ export default class SessionService extends SimpleAuthSessionService {
document.body.removeChild(loaderNode);
this.isLoaderNodeOpen = false;
},
- 600 * 6
+ 600 * 3
);
+ };
+ this.isLoaderNodeOpen = true;
+
+ try {
+ await this.router.transitionTo(this.redirectTo);
} catch (error) {
- this.notifications.serverError(error);
+ debug(`Session's handleAuthentication() failed to transition: ${error.message}`);
}
+
+ removeLoaderNode();
}
/**
@@ -75,7 +80,7 @@ export default class SessionService extends SimpleAuthSessionService {
*
* @void
*/
- async loadCurrentUser() {
+ async loadCurrentUser () {
try {
const user = await this.currentUser.load();
@@ -95,7 +100,7 @@ export default class SessionService extends SimpleAuthSessionService {
* @param {Transition} transition
* @void
*/
- async promiseCurrentUser(transition = null) {
+ async promiseCurrentUser (transition = null) {
const invalidateWithLoader = this.invalidateWithLoader.bind(this);
try {
@@ -114,6 +119,7 @@ export default class SessionService extends SimpleAuthSessionService {
if (transition) {
transition.abort();
}
+
await invalidateWithLoader(error.message ?? 'Session authentication failed...');
throw error;
}
@@ -125,7 +131,7 @@ export default class SessionService extends SimpleAuthSessionService {
* @param {String} loadingMessage
* @return {HTMLElement} loader
*/
- showLoader(loadingMessage) {
+ showLoader (loadingMessage) {
const loader = document.createElement('div');
loader.classList.add('overloader');
loader.innerHTML = `
@@ -147,15 +153,14 @@ export default class SessionService extends SimpleAuthSessionService {
* @param {String} loadingMessage
* @return {Promise}
*/
- invalidateWithLoader(loadingMessage = 'Ending session...') {
+ invalidateWithLoader (loadingMessage = 'Ending session...') {
// if loader node is open already just invalidate
if (this.isLoaderNodeOpen === true) {
return this.session.invalidate();
}
const loaderNode = this.showLoader(loadingMessage);
-
- this.isLoaderNodeOpen = false;
+ this.isLoaderNodeOpen = true;
return this.session.invalidate().then(() => {
later(
@@ -174,7 +179,7 @@ export default class SessionService extends SimpleAuthSessionService {
*
* @void
*/
- setRedirect(whereTo = 'console') {
+ setRedirect (whereTo = 'console') {
this.redirectTo = whereTo;
}
@@ -183,7 +188,7 @@ export default class SessionService extends SimpleAuthSessionService {
*
* @return {Date}
*/
- getExpiresAtDate() {
+ getExpiresAtDate () {
return new Date(this.data.authenticated.expires_at);
}
@@ -192,7 +197,7 @@ export default class SessionService extends SimpleAuthSessionService {
*
* @return {Integer}
*/
- getSessionSecondsRemaining() {
+ getSessionSecondsRemaining () {
const date = this.getExpiresAtDate();
const now = new Date();
@@ -206,8 +211,8 @@ export default class SessionService extends SimpleAuthSessionService {
* @return {Promise}
* @throws {Error}
*/
- checkForTwoFactor(identity) {
- return this.fetch.get('two-fa/check', { identity }).catch((error) => {
+ checkForTwoFactor (identity) {
+ return this.fetch.get('two-fa/check', { identity }).catch(error => {
throw new Error(error.message);
});
}
diff --git a/addon/services/theme.js b/addon/services/theme.js
index b14d76b..7ac246d 100644
--- a/addon/services/theme.js
+++ b/addon/services/theme.js
@@ -15,7 +15,7 @@ export default class ThemeService extends Service {
*/
get router() {
const owner = getOwner(this);
- const router = owner.lookup('service:router');
+ const router = owner.lookup('router:main');
return router;
}
diff --git a/addon/services/universe.js b/addon/services/universe.js
index 0b49281..ab2a4f8 100644
--- a/addon/services/universe.js
+++ b/addon/services/universe.js
@@ -7,8 +7,9 @@ import { isBlank } from '@ember/utils';
import { A, isArray } from '@ember/array';
import { later } from '@ember/runloop';
import { dasherize, camelize } from '@ember/string';
+import { pluralize } from 'ember-inflector';
import { getOwner } from '@ember/application';
-import { assert, debug } from '@ember/debug';
+import { assert, debug, warn } from '@ember/debug';
import RSVP from 'rsvp';
import loadInstalledExtensions from '../utils/load-installed-extensions';
import loadExtensions from '../utils/load-extensions';
@@ -18,18 +19,21 @@ import config from 'ember-get-config';
export default class UniverseService extends Service.extend(Evented) {
@service router;
@service intl;
+ @tracked applicationInstance;
+ @tracked enginesBooted = false;
+ @tracked bootedExtensions = A([]);
@tracked headerMenuItems = A([]);
@tracked organizationMenuItems = A([]);
@tracked userMenuItems = A([]);
- @tracked adminRegistry = {
+ @tracked consoleAdminRegistry = {
menuItems: A([]),
menuPanels: A([]),
};
- @tracked accountRegistry = {
+ @tracked consoleAccountRegistry = {
menuItems: A([]),
menuPanels: A([]),
};
- @tracked settingsRegistry = {
+ @tracked consoleSettingsRegistry = {
menuItems: A([]),
menuPanels: A([]),
};
@@ -37,6 +41,7 @@ export default class UniverseService extends Service.extend(Evented) {
defaultWidgets: A([]),
widgets: A([]),
};
+ @tracked hooks = {};
/**
* Computed property that returns all administrative menu items.
@@ -47,8 +52,8 @@ export default class UniverseService extends Service.extend(Evented) {
* @memberof UniverseService
* @returns {Array} Array of administrative menu items
*/
- @computed('adminRegistry.menuItems.[]') get adminMenuItems() {
- return this.adminRegistry.menuItems;
+ @computed('consoleAdminRegistry.menuItems.[]') get adminMenuItems () {
+ return this.consoleAdminRegistry.menuItems;
}
/**
@@ -60,8 +65,8 @@ export default class UniverseService extends Service.extend(Evented) {
* @memberof UniverseService
* @returns {Array} Array of administrative menu panels
*/
- @computed('adminRegistry.menuPanels.[]') get adminMenuPanels() {
- return this.adminRegistry.menuPanels;
+ @computed('consoleAdminRegistry.menuPanels.[]') get adminMenuPanels () {
+ return this.consoleAdminRegistry.menuPanels;
}
/**
@@ -73,8 +78,8 @@ export default class UniverseService extends Service.extend(Evented) {
* @memberof UniverseService
* @returns {Array} Array of administrative menu items
*/
- @computed('settingsRegistry.menuItems.[]') get settingsMenuItems() {
- return this.settingsRegistry.menuItems;
+ @computed('consoleSettingsRegistry.menuItems.[]') get settingsMenuItems () {
+ return this.consoleSettingsRegistry.menuItems;
}
/**
@@ -86,8 +91,8 @@ export default class UniverseService extends Service.extend(Evented) {
* @memberof UniverseService
* @returns {Array} Array of administrative menu panels
*/
- @computed('settingsRegistry.menuPanels.[]') get settingsMenuPanels() {
- return this.settingsRegistry.menuPanels;
+ @computed('consoleSettingsRegistry.menuPanels.[]') get settingsMenuPanels () {
+ return this.consoleSettingsRegistry.menuPanels;
}
/**
@@ -106,7 +111,7 @@ export default class UniverseService extends Service.extend(Evented) {
* // Transitions to the 'management.fleets.index.new' route within the '@fleetbase/fleet-ops' engine.
* this.transitionToEngineRoute('@fleetbase/fleet-ops', 'management.fleets.index.new');
*/
- @action transitionToEngineRoute(engineName, route, ...args) {
+ @action transitionToEngineRoute (engineName, route, ...args) {
const engineInstance = this.getEngineInstance(engineName);
if (engineInstance) {
@@ -130,13 +135,22 @@ export default class UniverseService extends Service.extend(Evented) {
return this.router.transitionTo(route, ...args);
}
+ /**
+ * Retrieves the application instance.
+ *
+ * @returns {ApplicationInstance} - The application instance object.
+ */
+ getApplicationInstance () {
+ return this.applicationInstance;
+ }
+
/**
* Retrieves the mount point of a specified engine by its name.
* @param {string} engineName - The name of the engine for which to get the mount point.
* @returns {string|null} The mount point of the engine or null if not found.
*/
- getEngineMountPoint(engineName) {
+ getEngineMountPoint (engineName) {
const engineInstance = this.getEngineInstance(engineName);
return this._getMountPointFromEngineInstance(engineInstance);
}
@@ -148,7 +162,7 @@ export default class UniverseService extends Service.extend(Evented) {
* @returns {string|null} The resolved mount point or null if the instance is undefined or the configuration is not set.
* @private
*/
- _getMountPointFromEngineInstance(engineInstance) {
+ _getMountPointFromEngineInstance (engineInstance) {
if (engineInstance) {
const config = engineInstance.resolveRegistration('config:environment');
@@ -184,7 +198,7 @@ export default class UniverseService extends Service.extend(Evented) {
* // returns 'console.some'
* _mountPathFromEngineName('@fleetbase/some-engine');
*/
- _mountPathFromEngineName(engineName) {
+ _mountPathFromEngineName (engineName) {
let engineNameSegments = engineName.split('/');
let mountName = engineNameSegments[1];
@@ -210,7 +224,7 @@ export default class UniverseService extends Service.extend(Evented) {
* // To refresh the current route
* this.refreshRoute();
*/
- @action refreshRoute() {
+ @action refreshRoute () {
return this.router.refresh();
}
@@ -229,14 +243,22 @@ export default class UniverseService extends Service.extend(Evented) {
*
* @returns {Transition} Returns a Transition object representing the transition to the route.
*/
- @action transitionMenuItem(route, menuItem) {
- const { slug, view } = menuItem;
+ @action transitionMenuItem (route, menuItem) {
+ const { slug, view, section } = menuItem;
+
+ if (section && slug && view) {
+ return this.router.transitionTo(route, section, slug, { queryParams: { view } });
+ }
- if (view) {
- return this.router.transitionTo(route, slug, view);
+ if (section && slug) {
+ return this.router.transitionTo(route, section, slug);
}
- return this.router.transitionTo(route, slug, 'index');
+ if (slug && view) {
+ return this.router.transitionTo(route, slug, { queryParams: { view } });
+ }
+
+ return this.router.transitionTo(route, slug);
}
/**
@@ -256,16 +278,23 @@ export default class UniverseService extends Service.extend(Evented) {
* @example
* createRegistry('myRegistry', { menuItems: ['item1', 'item2'], menuPanel: ['panel1', 'panel2'] });
*/
- @action createRegistry(registryName, options = {}) {
+ @action createRegistry (registryName, options = {}) {
const internalRegistryName = this.createInternalRegistryName(registryName);
- this[internalRegistryName] = {
- name: registryName,
- menuItems: [],
- menuPanels: [],
- renderableComponents: [],
- ...options,
- };
+ if (this[internalRegistryName] == undefined) {
+ this[internalRegistryName] = {
+ name: registryName,
+ menuItems: [],
+ menuPanels: [],
+ renderableComponents: [],
+ ...options,
+ };
+ } else {
+ this[internalRegistryName] = {
+ ...this[internalRegistryName],
+ ...options,
+ };
+ }
// trigger registry created event
this.trigger('registry.created', this[internalRegistryName]);
@@ -287,7 +316,7 @@ export default class UniverseService extends Service.extend(Evented) {
* @action
* @memberof YourComponentOrClassName
*/
- @action createRegistries(registries = []) {
+ @action createRegistries (registries = []) {
if (!isArray(registries)) {
throw new Error('`createRegistries()` method must take an array.');
}
@@ -318,7 +347,7 @@ export default class UniverseService extends Service.extend(Evented) {
* @param {string} event - The name of the event to trigger.
* @param {...*} params - Additional parameters to pass to the event handler.
*/
- @action createRegistryEvent(registryName, event, ...params) {
+ @action createRegistryEvent (registryName, event, ...params) {
this.trigger(`${registryName}.${event}`, ...params);
}
@@ -334,7 +363,7 @@ export default class UniverseService extends Service.extend(Evented) {
* @example
* const myRegistry = getRegistry('myRegistry');
*/
- @action getRegistry(registryName) {
+ @action getRegistry (registryName) {
const internalRegistryName = this.createInternalRegistryName(registryName);
const registry = this[internalRegistryName];
@@ -362,7 +391,7 @@ export default class UniverseService extends Service.extend(Evented) {
* // Handle the error or absence of the registry
* });
*/
- lookupRegistry(registryName) {
+ lookupRegistry (registryName) {
const internalRegistryName = this.createInternalRegistryName(registryName);
const registry = this[internalRegistryName];
@@ -397,7 +426,7 @@ export default class UniverseService extends Service.extend(Evented) {
* @example
* const items = getMenuItemsFromRegistry('myRegistry');
*/
- @action getMenuItemsFromRegistry(registryName) {
+ @action getMenuItemsFromRegistry (registryName) {
const internalRegistryName = this.createInternalRegistryName(registryName);
const registry = this[internalRegistryName];
@@ -420,7 +449,7 @@ export default class UniverseService extends Service.extend(Evented) {
* @example
* const panels = getMenuPanelsFromRegistry('myRegistry');
*/
- @action getMenuPanelsFromRegistry(registryName) {
+ @action getMenuPanelsFromRegistry (registryName) {
const internalRegistryName = this.createInternalRegistryName(registryName);
const registry = this[internalRegistryName];
@@ -440,7 +469,7 @@ export default class UniverseService extends Service.extend(Evented) {
* @param {string} registryName - The name of the registry to retrieve components from.
* @returns {Array} An array of renderable components from the specified registry, or an empty array if none found.
*/
- @action getRenderableComponentsFromRegistry(registryName) {
+ @action getRenderableComponentsFromRegistry (registryName) {
const internalRegistryName = this.createInternalRegistryName(registryName);
const registry = this[internalRegistryName];
@@ -460,11 +489,11 @@ export default class UniverseService extends Service.extend(Evented) {
*
* @returns {Promise} Returns a Promise that resolves with the component if it is found, or null.
*/
- loadComponentFromRegistry(registryName, slug, view = null) {
+ loadComponentFromRegistry (registryName, slug, view = null) {
const internalRegistryName = this.createInternalRegistryName(registryName);
const registry = this[internalRegistryName];
- return new Promise((resolve) => {
+ return new Promise(resolve => {
let component = null;
if (isBlank(registry)) {
@@ -519,14 +548,15 @@ export default class UniverseService extends Service.extend(Evented) {
* @param {string} registryName - The name of the registry where the menu item is located.
* @param {string} slug - The slug of the menu item.
* @param {string} [view=null] - The view of the menu item, if applicable.
+ * @param {string} [section=null] - The section of the menu item, if applicable.
*
* @returns {Promise} Returns a Promise that resolves with the menu item if it is found, or null.
*/
- lookupMenuItemFromRegistry(registryName, slug, view = null) {
+ lookupMenuItemFromRegistry (registryName, slug, view = null, section = null) {
const internalRegistryName = this.createInternalRegistryName(registryName);
const registry = this[internalRegistryName];
- return new Promise((resolve) => {
+ return new Promise(resolve => {
let foundMenuItem = null;
if (isBlank(registry)) {
@@ -537,8 +567,7 @@ export default class UniverseService extends Service.extend(Evented) {
for (let i = 0; i < registry.menuItems.length; i++) {
const menuItem = registry.menuItems[i];
- // no view hack
- if (menuItem && menuItem.slug === slug && menuItem.view === null && view === 'index') {
+ if (menuItem && menuItem.slug === slug && menuItem.section === section && menuItem.view === view) {
foundMenuItem = menuItem;
break;
}
@@ -557,8 +586,7 @@ export default class UniverseService extends Service.extend(Evented) {
for (let j = 0; j < menuPanel.items.length; j++) {
const menuItem = menuPanel.items[j];
- // no view hack
- if (menuItem && menuItem.slug === slug && menuItem.view === null && view === 'index') {
+ if (menuItem && menuItem.slug === slug && menuItem.section === section && menuItem.view === view) {
foundMenuItem = menuItem;
break;
}
@@ -575,6 +603,134 @@ export default class UniverseService extends Service.extend(Evented) {
});
}
+ /**
+ * Gets the view param from the transition object.
+ *
+ * @param {Transition} transition
+ * @return {String|Null}
+ * @memberof UniverseService
+ */
+ getViewFromTransition (transition) {
+ const queryParams = transition.to.queryParams ?? { view: null };
+ return queryParams.view;
+ }
+
+ /**
+ * Creates an internal registry name for hooks based on a given registry name.
+ * The registry name is transformed to camel case and appended with 'Hooks'.
+ * Non-alphanumeric characters are replaced with hyphens.
+ *
+ * @param {string} registryName - The name of the registry for which to create an internal hook registry name.
+ * @returns {string} - The internal hook registry name, formatted as camel case with 'Hooks' appended.
+ */
+ createInternalHookRegistryName (registryName) {
+ return `${camelize(registryName.replace(/[^a-zA-Z0-9]/g, '-'))}Hooks`;
+ }
+
+ /**
+ * Registers a hook function under a specified registry name.
+ * The hook is stored in an internal registry, and its hash is computed for identification.
+ * If the hook is already registered, it is appended to the existing list of hooks.
+ *
+ * @param {string} registryName - The name of the registry where the hook should be registered.
+ * @param {Function} hook - The hook function to be registered.
+ */
+ registerHook (registryName, hook) {
+ if (typeof hook !== 'function') {
+ throw new Error('The hook must be a function.');
+ }
+
+ // no duplicate hooks
+ if (this.didRegisterHook(registryName, hook)) {
+ return;
+ }
+
+ const internalHookRegistryName = this.createInternalHookRegistryName(registryName);
+ const hookRegistry = this.hooks[internalHookRegistryName] || [];
+ hookRegistry.pushObject({ id: this._createHashFromFunctionDefinition(hook), hook });
+
+ this.hooks[internalHookRegistryName] = hookRegistry;
+ }
+
+ /**
+ * Checks if a hook was registered already.
+ *
+ * @param {String} registryName
+ * @param {Function} hook
+ * @return {Boolean}
+ * @memberof UniverseService
+ */
+ didRegisterHook (registryName, hook) {
+ const hooks = this.getHooks(registryName);
+ const hookId = this._createHashFromFunctionDefinition(hook);
+ return isArray(hooks) && hooks.some(h => h.id === hookId);
+ }
+
+ /**
+ * Retrieves the list of hooks registered under a specified registry name.
+ * If no hooks are registered, returns an empty array.
+ *
+ * @param {string} registryName - The name of the registry for which to retrieve hooks.
+ * @returns {Array