This commit is contained in:
Ronald A. Richardson
2024-07-02 14:36:31 +08:00
parent 6338820372
commit cce1699a75
7 changed files with 227 additions and 47 deletions

View File

@@ -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' });
@task *loadAndMountEngine (id, enginePackage) {
const engineName = enginePackage.name;
const assets = yield this.fetch.get(`load-engine-manifest/${id}`, {}, { namespace: '~registry/v1' });
if (isArray(assets)) {
assets.forEach(asset => {
this.inject(asset);
});
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);
}
});
}
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);
}
}
});
}
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);
} 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;
}
}

View File

@@ -16,4 +16,3 @@
</Layout::Container>
<ChatContainer />
<ConsoleWormhole />
<ExtensionInjector />

View File

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

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

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

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