mirror of
https://github.com/fleetbase/fleetbase.git
synced 2025-12-19 14:18:57 +00:00
Merge branch 'feature/extensions-registry' into feature-registry-extension-updates
This commit is contained in:
1
console/app/components/extension-injector.hbs
Normal file
1
console/app/components/extension-injector.hbs
Normal file
@@ -0,0 +1 @@
|
||||
{{yield}}
|
||||
191
console/app/components/extension-injector.js
Normal file
191
console/app/components/extension-injector.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import Component from '@glimmer/component';
|
||||
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 { 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;
|
||||
@service universe;
|
||||
@tracked engines = [];
|
||||
@tracked packages = [];
|
||||
|
||||
constructor () {
|
||||
super(...arguments);
|
||||
this.loadInstalledEngines.perform();
|
||||
}
|
||||
|
||||
@task *loadInstalledEngines () {
|
||||
yield timeout(300);
|
||||
|
||||
try {
|
||||
const engines = yield this.fetch.get('load-installed-engines', {}, { namespace: '~registry/v1' });
|
||||
for (const id in engines) {
|
||||
yield this.loadAndMountEngine.perform(id, engines[id]);
|
||||
}
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@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]);
|
||||
}
|
||||
}
|
||||
|
||||
yield timeout(300);
|
||||
this.registerEngine(enginePackage);
|
||||
}
|
||||
|
||||
registerEngine (enginePackage) {
|
||||
const engineName = enginePackage.name;
|
||||
const owner = getOwner(this);
|
||||
const router = getOwner(this).lookup('router:main');
|
||||
|
||||
if (this.hasAssetManifest(engineName)) {
|
||||
return this.universe.loadEngine(engineName).then(engineInstance => {
|
||||
if (engineInstance.base && engineInstance.base.setupExtension) {
|
||||
engineInstance.base.setupExtension(owner, engineInstance, this.universe);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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' }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConsoleExtensionsRoute extends Route {}
|
||||
3
console/app/templates/console/extension.hbs
Normal file
3
console/app/templates/console/extension.hbs
Normal file
@@ -0,0 +1,3 @@
|
||||
{{#if this.ready}}
|
||||
{{mount "@fleetbase/fleetops-engine"}}
|
||||
{{/if}}
|
||||
@@ -1,12 +0,0 @@
|
||||
{{page-title "Extensions"}}
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full pt-6">
|
||||
<div class="container mx-auto h-screen space-y-4">
|
||||
<div class="flex flex-col items-center justify-center pt-14 px-40">
|
||||
<FaIcon @icon="shapes" @size="4x" class="mb-6 text-blue-500" />
|
||||
<h1 class="dark:text-gray-100 text-black text-4xl font-bold mb-4">{{t "console.extensions.title"}}</h1>
|
||||
<p class="dark:text-gray-300 text-black text-lg">
|
||||
{{t "console.extensions.message"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Layout::Section::Body>
|
||||
46
console/app/utils/asset-injector.js
Normal file
46
console/app/utils/asset-injector.js
Normal file
@@ -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,
|
||||
};
|
||||
@@ -23,7 +23,6 @@ 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' });
|
||||
|
||||
@@ -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`<ExtensionInjector />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<ExtensionInjector>
|
||||
template block text
|
||||
</ExtensionInjector>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
});
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/extensions', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/extensions');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
10
console/tests/unit/utils/asset-injector-test.js
Normal file
10
console/tests/unit/utils/asset-injector-test.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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 ember-cli pnpm
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user