diff --git a/console/app/components/extension-injector.js b/console/app/components/extension-injector.js
index 2fb4e1b9..26ab0ed1 100644
--- a/console/app/components/extension-injector.js
+++ b/console/app/components/extension-injector.js
@@ -3,9 +3,19 @@ import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { isArray } from '@ember/array';
import { getOwner } from '@ember/application';
+import { later } from '@ember/runloop';
import { task, timeout } from 'ember-concurrency';
-import loadExtensions from '@fleetbase/ember-core/utils/load-extensions';
+import { injectAsset } from '../utils/asset-injector';
+import getMountedEngineRoutePrefix from '@fleetbase/ember-core/utils/get-mounted-engine-route-prefix';
+function removeTrailingDot (str) {
+ if (str.endsWith('.')) {
+ return str.slice(0, -1);
+ }
+ return str;
+}
+
+window.exports = window.exports ?? {};
export default class ExtensionInjectorComponent extends Component {
@service fetch;
@service notifications;
@@ -23,73 +33,159 @@ export default class ExtensionInjectorComponent extends Component {
try {
const engines = yield this.fetch.get('load-installed-engines', {}, { namespace: '~registry/v1' });
- if (isArray(engines)) {
- engines.forEach(extensionId => {
- this.loadExtensionFromManifest.perform(extensionId);
- });
+ for (const id in engines) {
+ yield this.loadAndMountEngine.perform(id, engines[id]);
}
} catch (error) {
this.notifications.serverError(error);
}
}
- @task *loadExtensionFromManifest (extensionId) {
- try {
- const assets = yield this.fetch.get(`load-engine-manifest/${extensionId}`, {}, { namespace: '~registry/v1' });
- if (isArray(assets)) {
- assets.forEach(asset => {
- this.inject(asset);
- });
+ @task *loadAndMountEngine (id, enginePackage) {
+ const engineName = enginePackage.name;
+ const assets = yield this.fetch.get(`load-engine-manifest/${id}`, {}, { namespace: '~registry/v1' });
+
+ if (isArray(assets)) {
+ for (const i in assets) {
+ injectAsset(assets[i]);
}
- } catch (error) {
- this.notifications.serverError(error);
}
- this.initializeExtensions();
+ yield timeout(300);
+ this.registerEngine(enginePackage);
}
- initializeExtensions () {
+ registerEngine (enginePackage) {
+ const engineName = enginePackage.name;
const owner = getOwner(this);
+ const router = getOwner(this).lookup('router:main');
- this.packages.forEach(packageJson => {
- this.universe.loadEngine(packageJson.name).then(engineInstance => {
+ if (this.hasAssetManifest(engineName)) {
+ return this.universe.loadEngine(engineName).then(engineInstance => {
if (engineInstance.base && engineInstance.base.setupExtension) {
engineInstance.base.setupExtension(owner, engineInstance, this.universe);
}
});
- });
- }
+ }
- inject ({ type, content }) {
- switch (type) {
- case 'package':
- this.packages.pushObject(content);
- return;
- case 'css':
- return this.injectStylesheet(content);
- case 'js':
- default:
- return this.injectScript(content);
+ try {
+ if (router._engineIsLoaded(engineName)) {
+ router._registerEngine(engineName);
+
+ const instanceId = Object.values(router._engineInstances).length;
+ const mountPoint = removeTrailingDot(getMountedEngineRoutePrefix(engineName.replace('@fleetbase/', ''), enginePackage.fleetbase));
+ this.universe.constructEngineInstance(engineName, instanceId, mountPoint).then(engineInstance => {
+ if (engineInstance) {
+ this.setupEngine(owner, engineInstance, enginePackage);
+ this.setEnginePromise(engineName, engineInstance);
+ this.addEngineRoutesToRouter(engineName, engineInstance, instanceId, mountPoint, router);
+ this.addEngineRoutesToRecognizer(engineName, router);
+ this.resolveEngineRegistrations(engineInstance);
+
+ console.log(engineInstance, router, owner);
+ if (engineInstance.base && engineInstance.base.setupExtension) {
+ engineInstance.base.setupExtension(owner, engineInstance, this.universe);
+ }
+ }
+ });
+ }
+ } catch (error) {
+ console.trace(error);
}
}
- injectScript (content) {
- const script = document.createElement('script');
- script.type = 'application/javascript';
- script.text = content;
- document.head.appendChild(script);
+ setupEngine (appInstance, engineInstance, enginePackage) {
+ const engineName = enginePackage.name;
+ appInstance.application.engines[engineName] = engineInstance.dependencies ?? { externalRoutes: {}, services: {} };
+ appInstance._dependenciesForChildEngines[engineName] = engineInstance.dependencies ?? { externalRoutes: {}, services: {} };
+ if (isArray(appInstance.application.extensions)) {
+ appInstance.application.extensions.push(enginePackage);
+ }
}
- injectStylesheet (content) {
- const style = document.createElement('style');
- style.type = 'text/css';
- if (style.styleSheet) {
- // This is required for IE8 and below.
- style.styleSheet.cssText = content;
- } else {
- // For most browsers
- style.appendChild(document.createTextNode(content));
+ addEngineRoutesToRecognizer (engineName, router) {
+ const recognizer = router._routerMicrolib.recognizer || router._router._routerMicrolib.recognizer;
+ if (recognizer) {
+ let routeName = `${engineName}.application`;
+
+ recognizer.add(
+ [
+ {
+ path: 'console.fleet-ops',
+ handler: 'console.fleet-ops',
+ },
+ ],
+ { as: 'console.fleet-ops' }
+ );
}
- document.head.appendChild(style);
+ }
+
+ addEngineRoutesToRouter (engineName, engineInstance, instanceId, mountPoint, router) {
+ const getRouteInfo = routeName => {
+ const applicationRoute = routeName.replace(mountPoint, '') === '';
+ return {
+ fullName: mountPoint,
+ instanceId,
+ localFullName: applicationRoute ? 'application' : routeName.replace(`${mountPoint}.`, ''),
+ mountPoint,
+ name: engineName,
+ };
+ };
+
+ const routes = ['console.fleet-ops', 'console.fleet-ops.home'];
+ for (let i = 0; i < routes.length; i++) {
+ const routeName = routes[i];
+ router._engineInfoByRoute[routeName] = getRouteInfo(routeName);
+ }
+
+ // Reinitialize or refresh the router
+ router.setupRouter();
+ }
+
+ setEnginePromise (engineName, engineInstance) {
+ const router = getOwner(this).lookup('router:main');
+ if (router) {
+ router._enginePromises[engineName] = { manual: engineInstance._bootPromise };
+ }
+ }
+
+ resolveEngineRegistrations (engineInstance) {
+ const owner = getOwner(this);
+ const registry = engineInstance.__registry__;
+ const registrations = registry.registrations;
+ const getOwnerSymbol = obj => {
+ const symbols = Object.getOwnPropertySymbols(obj);
+ const ownerSymbol = symbols.find(symbol => symbol.toString() === 'Symbol(OWNER)');
+ return ownerSymbol;
+ };
+ for (let registrationKey in registrations) {
+ const registrationInstance = registrations[registrationKey];
+ if (typeof registrationInstance === 'string') {
+ // Try to resolve from owner
+ let resolvedRegistrationInstance = owner.lookup(registrationKey);
+ // Hack for host-router
+ if (registrationKey === 'service:host-router') {
+ resolvedRegistrationInstance = owner.lookup('service:router');
+ }
+ if (resolvedRegistrationInstance) {
+ // Correct the owner
+ resolvedRegistrationInstance[getOwnerSymbol(resolvedRegistrationInstance)] = engineInstance;
+ // Resolve
+ registrations[registrationKey] = resolvedRegistrationInstance;
+ }
+ }
+ }
+ }
+
+ hasAssetManifest (engineName) {
+ const router = getOwner(this).lookup('router:main');
+ if (router._assetLoader) {
+ const manifest = router._assetLoader.getManifest();
+ if (manifest && manifest.bundles) {
+ return manifest.bundles[engineName] !== undefined;
+ }
+ }
+
+ return false;
}
}
diff --git a/console/app/templates/console.hbs b/console/app/templates/console.hbs
index 50b13ee8..8116d9e9 100644
--- a/console/app/templates/console.hbs
+++ b/console/app/templates/console.hbs
@@ -16,4 +16,3 @@
-
diff --git a/console/app/templates/console/extension.hbs b/console/app/templates/console/extension.hbs
new file mode 100644
index 00000000..e0dfda48
--- /dev/null
+++ b/console/app/templates/console/extension.hbs
@@ -0,0 +1,3 @@
+{{#if this.ready}}
+ {{mount "@fleetbase/fleetops-engine"}}
+{{/if}}
\ No newline at end of file
diff --git a/console/app/utils/asset-injector.js b/console/app/utils/asset-injector.js
new file mode 100644
index 00000000..458d9b19
--- /dev/null
+++ b/console/app/utils/asset-injector.js
@@ -0,0 +1,46 @@
+export function injectAsset ({ type, content, name, syntax }) {
+ switch (type) {
+ case 'css':
+ injectStylesheet(content, syntax);
+ break;
+ case 'meta':
+ injectMeta(name, content);
+ break;
+ case 'js':
+ default:
+ injectScript(content, syntax);
+ break;
+ }
+}
+
+export function injectMeta (name, content) {
+ const meta = document.createElement('meta');
+ meta.name = name;
+ meta.content = content;
+ document.head.appendChild(meta);
+}
+
+export function injectScript (content, type = 'application/javascript') {
+ const script = document.createElement('script');
+ script.type = type;
+ script.text = content;
+ document.head.appendChild(script);
+}
+
+export function injectStylesheet (content, type = 'text/css') {
+ const style = document.createElement('style');
+ style.type = type;
+ if (style.styleSheet) {
+ style.styleSheet.cssText = content;
+ } else {
+ style.appendChild(document.createTextNode(content));
+ }
+ document.head.appendChild(style);
+}
+
+export default {
+ injectAsset,
+ injectMeta,
+ injectScript,
+ injectStylesheet,
+};
diff --git a/console/tests/integration/components/extension-injector-test.js b/console/tests/integration/components/extension-injector-test.js
new file mode 100644
index 00000000..928bdac1
--- /dev/null
+++ b/console/tests/integration/components/extension-injector-test.js
@@ -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 | extension-injector', 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``);
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ await render(hbs`
+
+ template block text
+
+ `);
+
+ assert.dom().hasText('template block text');
+ });
+});
diff --git a/console/tests/unit/utils/asset-injector-test.js b/console/tests/unit/utils/asset-injector-test.js
new file mode 100644
index 00000000..537a9847
--- /dev/null
+++ b/console/tests/unit/utils/asset-injector-test.js
@@ -0,0 +1,10 @@
+import assetInjector from '@fleetbase/console/utils/asset-injector';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | asset-injector', function () {
+ // TODO: Replace this with your real tests.
+ test('it works', function (assert) {
+ let result = assetInjector();
+ assert.ok(result);
+ });
+});
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 4dd127c6..02e3d5f3 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -31,7 +31,7 @@ RUN sed -e 's/^expose_php.*/expose_php = Off/' "$PHP_INI_DIR/php.ini-production"
-e 's/^memory_limit.*/memory_limit = 600M/' "$PHP_INI_DIR/php.ini"
# Install global node modules
-RUN npm install -g chokidar
+RUN npm install -g chokidar pnpm ember-cli
# Install ssm-parent
COPY --from=ghcr.io/springload/ssm-parent:1.8 /usr/bin/ssm-parent /sbin/ssm-parent