Compare commits

..

7 Commits

Author SHA1 Message Date
Ron
21ecfb5d93 Merge branch 'feature/extensions-registry' into feature-registry-extension-updates 2024-07-18 00:50:16 +07:00
Ronald A. Richardson
579a369888 preparing arch change for extensions implementation 2024-07-18 01:48:55 +08:00
Ron
b06830990c Merge pull request #270 from fleetbase/update-cd-workflow-v4 2024-07-05 20:26:45 +07:00
Shiv Thakker
a7c4aba512 Update cd.yml to use v4 2024-07-05 20:43:10 +08:00
Shiv Thakker
a625f37e14 Update README.md 2024-07-05 19:41:48 +08:00
Ronald A. Richardson
cce1699a75 wip 2024-07-02 14:36:31 +08:00
Ronald A. Richardson
6338820372 new extension injector component for installed extensions from registry bridge WIP, need to figure out mounted extensions 2024-06-20 17:30:25 +08:00
20 changed files with 342 additions and 62 deletions

View File

@@ -111,7 +111,7 @@ jobs:
with:
node-version: 16
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:

View File

@@ -13,15 +13,15 @@ We use github to host code, to track issues and feature requests, as well as acc
## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests
Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests:
1. Fork the repo and create your branch from `master`.
1. Fork the repo and create your branch from `main`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. Issue that pull request!
## Any contributions you make will be under the MIT Software License
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
## Any contributions you make will be under the AGPL v3 Software License
In short, when you submit code changes, your submissions are understood to be under the same [AGPL v3](https://choosealicense.com/licenses/agpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern.
## Report bugs using Github's [issues](https://github.com/fleetbase/fleetbase/issues)
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/fleetbase/fleetbase/issues), it's that easy!
@@ -41,7 +41,7 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue]
People *love* thorough bug reports. I'm not even kidding.
## License
By contributing, you agree that your contributions will be licensed under its MIT License.
By contributing, you agree that your contributions will be licensed under its AGPL v3 Software License.
## References
This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md)

View File

@@ -10,3 +10,10 @@ http://:8000 {
resolve_root_symlink
}
}
http://:4201 {
root * /fleetbase/console/dist
try_files {path} /
encode zstd gzip
file_server
}

View File

@@ -163,4 +163,4 @@ Get updates on Fleetbase's development and chat with the project maintainers and
# License & Copyright
Code and documentation copyright 20182023 the <a href="https://github.com/fleetbase/fleetbase/graphs/contributors">Fleetbase Authors</a>. Code released under the <a href="https://github.com/fleetbase/storefront-app/blob/main/LICENSE.md">MIT License</a>.
Fleetbase is made available under the terms of the <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">GNU Affero General Public License 3.0 (AGPL 3.0)</a>. For other licenses <a href="mailto:hello@fleetbase.io" target="_blank">contact us</a>.

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,17 +1,8 @@
import loadExtensions from '@fleetbase/ember-core/utils/load-extensions';
export function initialize(owner) {
export function initialize (owner) {
const universe = owner.lookup('service:universe');
loadExtensions().then((extensions) => {
extensions.forEach((extension) => {
universe.loadEngine(extension.name).then((engineInstance) => {
if (engineInstance.base && engineInstance.base.setupExtension) {
engineInstance.base.setupExtension(owner, engineInstance, universe);
}
});
});
});
if (universe) {
universe.bootEngines(owner);
}
}
export default {

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

@@ -40,6 +40,10 @@ module.exports = function (environment) {
port: getenv('SOCKETCLUSTER_PORT', 38000),
},
stripe: {
publishableKey: getenv('STRIPE_KEY')
},
defaultValues: {
categoryImage: getenv('DEFAULT_CATEGORY_IMAGE', 'https://flb-assets.s3.ap-southeast-1.amazonaws.com/images/fallback-placeholder-1.png'),
placeholderImage: getenv('DEFAULT_PLACEHOLDER_IMAGE', 'https://flb-assets.s3.ap-southeast-1.amazonaws.com/images/fallback-placeholder-2.png'),

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

@@ -38,18 +38,9 @@ services:
CACHE_URL: tcp://cache
REDIS_URL: tcp://cache
console:
ports:
- "4200:4200"
volumes:
- ./console:/app/console
build:
context: .
dockerfile: console/Dockerfile
args:
ENVIRONMENT: development
application:
ports:
- "4201:4201"
build:
context: .
dockerfile: docker/Dockerfile

View File

@@ -3,7 +3,7 @@
FROM dunglas/frankenphp:1.1.0-php8.2-bookworm as base
# Install packages
RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm \
RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm nano \
&& mkdir -p /root/.ssh \
&& ssh-keyscan github.com >> /root/.ssh/known_hosts
@@ -31,17 +31,25 @@ 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
# # Create the pnpm directory and set the PNPM_HOME environment variable
# RUN mkdir -p ~/.pnpm
# ENV PNPM_HOME /root/.pnpm
# # Add the pnpm global bin to the PATH
# ENV PATH /root/.pnpm/bin:$PATH
# Set some build ENV variables
ENV LOG_CHANNEL=stdout
ENV CACHE_DRIVER=null
ENV BROADCAST_DRIVER=socketcluster
ENV QUEUE_CONNECTION=redis
ENV CADDYFILE_PATH=/fleetbase/Caddyfile
ENV CONSOLE_PATH=/fleetbase/console
ENV OCTANE_SERVER=frankenphp
# Set environment
@@ -57,17 +65,46 @@ COPY --chown=www-data:www-data ./Caddyfile $CADDYFILE_PATH
# Create /fleetbase directory and set correct permissions
RUN mkdir -p /fleetbase/api && chown -R www-data:www-data /fleetbase
## -- Start Console Setup --
# Set working directory
WORKDIR /fleetbase/console
# TEMPORARILY ADD REGISTRY BRIDGE AND CORE API
COPY ./packages/registry-bridge /fleetbase/packages/registry-bridge
COPY ./packages/core-api /fleetbase/packages/core-api
# Copy pnpm-lock.yaml (or package.json) into the directory /app in the container
COPY ./console/package.json ./console/pnpm-lock.yaml /fleetbase/console/
# Copy over .npmrc if applicable
COPY ./console/.npmr[c] /fleetbase/console/
# Install app dependencies
# RUN pnpm install
# Copy the console directory contents into the container at /app
COPY ./console /fleetbase/console/
# Build the application
# RUN pnpm build --environment $ENVIRONMENT
## -- End Console Setup --
# Set working directory
WORKDIR /fleetbase/api
# If GITHUB_AUTH_KEY is provided, create auth.json with it
RUN if [ -n "$GITHUB_AUTH_KEY" ]; then echo "{\"github-oauth\": {\"github.com\": \"$GITHUB_AUTH_KEY\"}}" > auth.json; fi
# Prepare composer cache directory
RUN mkdir -p /var/www/.cache/composer && chown -R www-data:www-data /var/www/.cache/composer
# Optimize Composer Dependency Installation
COPY --chown=www-data:www-data ./api/composer.json ./api/composer.lock /fleetbase/api/
# Pre-install Composer dependencies
RUN su www-data -s /bin/sh -c "composer install --no-scripts --optimize-autoloader --no-dev"
RUN su www-data -s /bin/sh -c "composer install --no-scripts --optimize-autoloader --no-dev --no-cache"
# Setup application
COPY --chown=www-data:www-data ./api /fleetbase/api