Merge branch 'feature/extensions-registry' into feature-registry-extension-updates

This commit is contained in:
Ron
2024-07-18 00:50:16 +07:00
committed by GitHub
11 changed files with 278 additions and 28 deletions

View File

@@ -0,0 +1 @@
{{yield}}

View 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;
}
}

View File

@@ -1,3 +0,0 @@
import Route from '@ember/routing/route';
export default class ConsoleExtensionsRoute extends Route {}

View File

@@ -0,0 +1,3 @@
{{#if this.ready}}
{{mount "@fleetbase/fleetops-engine"}}
{{/if}}

View File

@@ -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>

View 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,
};

View File

@@ -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' });

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 | 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');
});
});

View File

@@ -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);
});
});

View 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);
});
});

View File

@@ -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