feat(multi-app-manager): manual operate sub app (#1488)
Reviewed-on: daoyoucloud/tachybase#1488 Reviewed-by: sealday <zhanglin@daoyoucloud.com> Co-authored-by: TomyJan <tomyjan6@gmail.com> Co-committed-by: TomyJan <tomyjan6@gmail.com>
This commit is contained in:
parent
f376f42691
commit
6b9e9624eb
@ -1,8 +1,9 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { SchemaComponent, useApp, useRecord } from '@tachybase/client';
|
import { SchemaComponent, useAPIClient, useApp, useRecord, useResourceActionContext } from '@tachybase/client';
|
||||||
|
|
||||||
import { Card } from 'antd';
|
import { Card, Divider, Space } from 'antd';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../constants';
|
||||||
import { schema } from './settings/schemas/applications';
|
import { schema } from './settings/schemas/applications';
|
||||||
import { usePluginUtils } from './utils';
|
import { usePluginUtils } from './utils';
|
||||||
|
|
||||||
@ -18,10 +19,40 @@ const useLink = () => {
|
|||||||
const AppVisitor = () => {
|
const AppVisitor = () => {
|
||||||
const { t } = usePluginUtils();
|
const { t } = usePluginUtils();
|
||||||
const link = useLink();
|
const link = useLink();
|
||||||
|
const record = useRecord();
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const { refresh } = useResourceActionContext();
|
||||||
|
const resource = useMemo(() => {
|
||||||
|
return apiClient.resource('applications');
|
||||||
|
}, [apiClient]);
|
||||||
|
const handleStart = () => {
|
||||||
|
resource
|
||||||
|
.start({ filterByTk: record.name })
|
||||||
|
.then(() => {
|
||||||
|
refresh();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleStop = () => {
|
||||||
|
resource
|
||||||
|
.stop({ filterByTk: record.name })
|
||||||
|
.then(() => {
|
||||||
|
refresh();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<a href={link} target={'_blank'} rel="noreferrer">
|
<Space split={<Divider type="horizontal" />}>
|
||||||
{t('View', { ns: 'client' })}
|
<a href={link} target={'_blank'} rel="noreferrer">
|
||||||
</a>
|
{t('View', { ns: NAMESPACE })}
|
||||||
|
</a>
|
||||||
|
<a onClick={() => handleStart()}>{t('Start', { ns: NAMESPACE })}</a>
|
||||||
|
<a onClick={() => handleStop()}>{t('Stop', { ns: NAMESPACE })}</a>
|
||||||
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Plugin } from '@tachybase/client';
|
import { Plugin } from '@tachybase/client';
|
||||||
|
|
||||||
import { NAMESPACE } from '../locale';
|
import { NAMESPACE } from '../constants';
|
||||||
import { AppManager } from './AppManager';
|
import { AppManager } from './AppManager';
|
||||||
import { MultiAppManagerProvider } from './MultiAppManagerProvider';
|
import { MultiAppManagerProvider } from './MultiAppManagerProvider';
|
||||||
|
|
||||||
|
@ -85,6 +85,15 @@ export const useDestroy = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useRefresh = () => {
|
||||||
|
const { refresh } = useResourceActionContext();
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const useDestroyAll = () => {
|
export const useDestroyAll = () => {
|
||||||
const { state, setState, refresh } = useResourceActionContext();
|
const { state, setState, refresh } = useResourceActionContext();
|
||||||
const { resource } = useResourceContext();
|
const { resource } = useResourceContext();
|
||||||
@ -262,6 +271,15 @@ export const schema: ISchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
|
refresh: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Refresh")}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
icon: 'ReloadOutlined',
|
||||||
|
useAction: useRefresh,
|
||||||
|
},
|
||||||
|
},
|
||||||
delete: {
|
delete: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{ t("Delete") }}',
|
title: '{{ t("Delete") }}',
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../constants';
|
||||||
|
|
||||||
export const usePluginUtils = () => {
|
export const usePluginUtils = () => {
|
||||||
const { t } = useTranslation('multi-app-manager');
|
const { t } = useTranslation([NAMESPACE, 'client']);
|
||||||
return { t };
|
return { t };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const i18nText = (text) => {
|
export const i18nText = (text) => {
|
||||||
return `{{t("${text}", { ns: 'multi-app-manager' })}}`;
|
return `{{t("${text}", { ns: ["${NAMESPACE}", "client"] })}}`;
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1 @@
|
|||||||
import { name } from '../package.json';
|
export const NAMESPACE = 'multi-app-manager';
|
||||||
|
|
||||||
export const NAMESPACE = name;
|
|
||||||
|
@ -15,5 +15,10 @@
|
|||||||
"Template": "Template",
|
"Template": "Template",
|
||||||
"App status": "App status",
|
"App status": "App status",
|
||||||
"Template not exists": "模板不存在",
|
"Template not exists": "模板不存在",
|
||||||
"This database does not support to create application using template": "This database does not support to create application using template"
|
"This database does not support to create application using template": "This database does not support to create application using template",
|
||||||
|
"View": "View",
|
||||||
|
"Start": "Start",
|
||||||
|
"Stop": "Stop",
|
||||||
|
"Refresh": "Refresh",
|
||||||
|
"Template is in use": "Template is in use"
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export const NAMESPACE = 'multi-app-manager';
|
|
@ -15,5 +15,10 @@
|
|||||||
"Template": "模板",
|
"Template": "模板",
|
||||||
"App status": "应用状态",
|
"App status": "应用状态",
|
||||||
"Template not exists": "模板不存在",
|
"Template not exists": "模板不存在",
|
||||||
"This database does not support to create application using template": "该数据库不支持使用模板创建应用程序"
|
"This database does not support to create application using template": "该数据库不支持使用模板创建应用程序",
|
||||||
|
"View": "查看",
|
||||||
|
"Start": "启动",
|
||||||
|
"Stop": "停止",
|
||||||
|
"Refresh": "刷新",
|
||||||
|
"Template is in use": "模板正在使用"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import { Context, Next } from '@tachybase/actions';
|
||||||
|
import { AppSupervisor } from '@tachybase/server';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../../constants';
|
||||||
|
|
||||||
|
export async function start(ctx: Context, next: Next) {
|
||||||
|
const targetAppId = String(ctx.request.url.split('filterByTk=')[1]);
|
||||||
|
const appSupervisor = AppSupervisor.getInstance();
|
||||||
|
if (!appSupervisor.hasApp(targetAppId)) {
|
||||||
|
await AppSupervisor.getInstance().getApp(targetAppId);
|
||||||
|
ctx.body = 'ok';
|
||||||
|
await next();
|
||||||
|
} else {
|
||||||
|
ctx.throw(400, ctx.t('App already started', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stop(ctx: Context, next: Next) {
|
||||||
|
const targetAppId = String(ctx.request.url.split('filterByTk=')[1]);
|
||||||
|
const appSupervisor = AppSupervisor.getInstance();
|
||||||
|
if (appSupervisor.hasApp(targetAppId)) {
|
||||||
|
await appSupervisor.removeApp(targetAppId);
|
||||||
|
ctx.body = 'ok';
|
||||||
|
await next();
|
||||||
|
} else {
|
||||||
|
ctx.throw(400, ctx.t('App already stopped', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listPinned(ctx: Context, next: Next) {
|
||||||
|
const items = await ctx.db.getRepository('applications').find({
|
||||||
|
filter: {
|
||||||
|
pinned: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ctx.body = items;
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(ctx: Context, next: Next) {
|
||||||
|
const params = ctx.action.params;
|
||||||
|
const tmpl = params.values?.tmpl;
|
||||||
|
if (tmpl) {
|
||||||
|
const dbType = ctx.db.options.dialect;
|
||||||
|
if (dbType !== 'postgres') {
|
||||||
|
ctx.throw(400, ctx.t('This database does not support to create application using template', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
const matchedApp = await ctx.db.getRepository('applications').find({
|
||||||
|
filter: {
|
||||||
|
name: tmpl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (matchedApp.length === 0) {
|
||||||
|
ctx.throw(400, ctx.t('Template not exists', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
const appStatus = AppSupervisor.getInstance().getAppStatus(tmpl, 'initialized');
|
||||||
|
if (appStatus !== 'stopped' && appStatus !== 'initialized') {
|
||||||
|
ctx.throw(400, ctx.t('Template is in use', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await ctx.db.getRepository('applications').create({
|
||||||
|
values: {
|
||||||
|
...params.values,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const app = await ctx.db.getRepository('applications').find({
|
||||||
|
filter: {
|
||||||
|
name: (ctx.request.body as any).name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ctx.body = app;
|
||||||
|
await next();
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
import Application, { AppSupervisor, Gateway } from '@tachybase/server';
|
||||||
|
|
||||||
|
import { ApplicationModel } from './models/application';
|
||||||
|
|
||||||
|
export type AppOptionsFactory = (appName: string, mainApp: Application, preset: string) => any;
|
||||||
|
|
||||||
|
export function LazyLoadApplication(context: any) {
|
||||||
|
return async ({
|
||||||
|
appSupervisor,
|
||||||
|
appName,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
appSupervisor: AppSupervisor;
|
||||||
|
appName: string;
|
||||||
|
options: any;
|
||||||
|
}) => {
|
||||||
|
const loadButNotStart = options?.upgrading;
|
||||||
|
|
||||||
|
const name = appName;
|
||||||
|
if (appSupervisor.hasApp(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const applicationRecord = (await context.app.db.getRepository('applications').findOne({
|
||||||
|
filter: {
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
})) as ApplicationModel | null;
|
||||||
|
|
||||||
|
if (!applicationRecord) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceOptions = applicationRecord.get('options');
|
||||||
|
|
||||||
|
if (instanceOptions?.standaloneDeployment && appSupervisor.runningMode !== 'single') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!applicationRecord) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subApp = applicationRecord.registerToSupervisor(context.app, {
|
||||||
|
appOptionsFactory: context.appOptionsFactory,
|
||||||
|
});
|
||||||
|
|
||||||
|
// must skip load on upgrade
|
||||||
|
if (!loadButNotStart) {
|
||||||
|
await subApp.runCommand('start', '--quickstart');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onAfterStart(db: any) {
|
||||||
|
return async (app: Application) => {
|
||||||
|
const repository = db.getRepository('applications');
|
||||||
|
const appSupervisor = AppSupervisor.getInstance();
|
||||||
|
|
||||||
|
app.setMaintainingMessage('starting sub applications...');
|
||||||
|
|
||||||
|
if (appSupervisor.runningMode == 'single') {
|
||||||
|
Gateway.getInstance().addAppSelectorMiddleware((ctx) => (ctx.resolvedAppName = appSupervisor.singleAppName));
|
||||||
|
|
||||||
|
// If the sub application is running in single mode, register the application automatically
|
||||||
|
try {
|
||||||
|
await AppSupervisor.getInstance().getApp(appSupervisor.singleAppName);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Auto register sub application in single mode failed: ', appSupervisor.singleAppName, err);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const subApps = await repository.find({
|
||||||
|
filter: {
|
||||||
|
'options.autoStart': true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const subAppInstance of subApps) {
|
||||||
|
promises.push(
|
||||||
|
(async () => {
|
||||||
|
if (!appSupervisor.hasApp(subAppInstance.name)) {
|
||||||
|
await AppSupervisor.getInstance().getApp(subAppInstance.name);
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Auto register sub applications failed: ', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
export function appSelectorMiddleware(app) {
|
||||||
|
return async (ctx, next) => {
|
||||||
|
const { req } = ctx;
|
||||||
|
|
||||||
|
if (!ctx.resolvedAppName && req.headers['x-hostname']) {
|
||||||
|
const repository = app.db.getRepository('applications');
|
||||||
|
if (!repository) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appInstance = await repository.findOne({
|
||||||
|
filter: {
|
||||||
|
cname: req.headers['x-hostname'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (appInstance) {
|
||||||
|
ctx.resolvedAppName = appInstance.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './app-selector';
|
||||||
|
export * from './inject-app-list';
|
@ -0,0 +1,24 @@
|
|||||||
|
import { AppSupervisor } from '@tachybase/server';
|
||||||
|
|
||||||
|
// 注入 APP 运行状态,替换 tmpl 为 displayName(name) 的格式
|
||||||
|
export function injectAppListMiddleware() {
|
||||||
|
return async (ctx, next) => {
|
||||||
|
await next();
|
||||||
|
const { actionName, resourceName, params } = ctx.action;
|
||||||
|
if (actionName === 'list' && resourceName === 'applications') {
|
||||||
|
const applications = ctx.body.rows;
|
||||||
|
for (const application of applications) {
|
||||||
|
const appStatus = AppSupervisor.getInstance().getAppStatus(application.name, 'stopped');
|
||||||
|
application.status = appStatus;
|
||||||
|
if (application.tmpl) {
|
||||||
|
const matchedApp = applications.find((app) => app.name === application.tmpl);
|
||||||
|
if (matchedApp) {
|
||||||
|
application.tmpl = `${matchedApp.displayName}(${application.tmpl})`;
|
||||||
|
} else {
|
||||||
|
application.tmpl = `Not Exists(${application.tmpl})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -6,9 +6,12 @@ import lodash from 'lodash';
|
|||||||
|
|
||||||
import { NAMESPACE } from '../constants';
|
import { NAMESPACE } from '../constants';
|
||||||
import { ApplicationModel } from '../server';
|
import { ApplicationModel } from '../server';
|
||||||
|
import * as actions from './actions/apps';
|
||||||
|
import { AppOptionsFactory, LazyLoadApplication, onAfterStart } from './app-lifecycle';
|
||||||
|
import { appSelectorMiddleware, injectAppListMiddleware } from './middlewares';
|
||||||
|
|
||||||
export type AppDbCreator = (app: Application, options?: Transactionable & { context?: any }) => Promise<void>;
|
export type AppDbCreator = (app: Application, options?: Transactionable & { context?: any }) => Promise<void>;
|
||||||
export type AppOptionsFactory = (appName: string, mainApp: Application, preset: string) => any;
|
export type { AppOptionsFactory };
|
||||||
export type SubAppUpgradeHandler = (mainApp: Application) => Promise<void>;
|
export type SubAppUpgradeHandler = (mainApp: Application) => Promise<void>;
|
||||||
|
|
||||||
const defaultSubAppUpgradeHandle: SubAppUpgradeHandler = async (mainApp: Application) => {
|
const defaultSubAppUpgradeHandle: SubAppUpgradeHandler = async (mainApp: Application) => {
|
||||||
@ -205,10 +208,12 @@ export class PluginMultiAppManager extends Plugin {
|
|||||||
context: options.context,
|
context: options.context,
|
||||||
});
|
});
|
||||||
|
|
||||||
const startPromise = subApp.runCommand('start', '--quickstart');
|
if ((options as any).values.options.autoStart) {
|
||||||
|
const startPromise = subApp.runCommand('start', '--quickstart');
|
||||||
|
|
||||||
if (options?.context?.waitSubAppInstall) {
|
if (options?.context?.waitSubAppInstall) {
|
||||||
await startPromise;
|
await startPromise;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -219,138 +224,16 @@ export class PluginMultiAppManager extends Plugin {
|
|||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async function LazyLoadApplication({
|
AppSupervisor.getInstance().setAppBootstrapper(LazyLoadApplication(self));
|
||||||
appSupervisor,
|
|
||||||
appName,
|
|
||||||
options,
|
|
||||||
}: {
|
|
||||||
appSupervisor: AppSupervisor;
|
|
||||||
appName: string;
|
|
||||||
options: any;
|
|
||||||
}) {
|
|
||||||
const loadButNotStart = options?.upgrading;
|
|
||||||
|
|
||||||
const name = appName;
|
Gateway.getInstance().addAppSelectorMiddleware(appSelectorMiddleware(this.app));
|
||||||
if (appSupervisor.hasApp(name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const applicationRecord = (await self.app.db.getRepository('applications').findOne({
|
this.app.on('afterStart', onAfterStart(this.db));
|
||||||
filter: {
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
})) as ApplicationModel | null;
|
|
||||||
|
|
||||||
if (!applicationRecord) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const instanceOptions = applicationRecord.get('options');
|
|
||||||
|
|
||||||
if (instanceOptions?.standaloneDeployment && appSupervisor.runningMode !== 'single') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!applicationRecord) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subApp = applicationRecord.registerToSupervisor(self.app, {
|
|
||||||
appOptionsFactory: self.appOptionsFactory,
|
|
||||||
});
|
|
||||||
|
|
||||||
// must skip load on upgrade
|
|
||||||
if (!loadButNotStart) {
|
|
||||||
await subApp.runCommand('start', '--quickstart');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSupervisor.getInstance().setAppBootstrapper(LazyLoadApplication);
|
|
||||||
|
|
||||||
Gateway.getInstance().addAppSelectorMiddleware(async (ctx, next) => {
|
|
||||||
const { req } = ctx;
|
|
||||||
|
|
||||||
if (!ctx.resolvedAppName && req.headers['x-hostname']) {
|
|
||||||
const repository = this.db.getRepository('applications');
|
|
||||||
if (!repository) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const appInstance = await repository.findOne({
|
|
||||||
filter: {
|
|
||||||
cname: req.headers['x-hostname'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (appInstance) {
|
|
||||||
ctx.resolvedAppName = appInstance.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.app.on('afterStart', async (app) => {
|
|
||||||
const repository = this.db.getRepository('applications');
|
|
||||||
const appSupervisor = AppSupervisor.getInstance();
|
|
||||||
|
|
||||||
this.app.setMaintainingMessage('starting sub applications...');
|
|
||||||
|
|
||||||
if (appSupervisor.runningMode == 'single') {
|
|
||||||
Gateway.getInstance().addAppSelectorMiddleware((ctx) => (ctx.resolvedAppName = appSupervisor.singleAppName));
|
|
||||||
|
|
||||||
// If the sub application is running in single mode, register the application automatically
|
|
||||||
try {
|
|
||||||
await AppSupervisor.getInstance().getApp(appSupervisor.singleAppName);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Auto register sub application in single mode failed: ', appSupervisor.singleAppName, err);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const subApps = await repository.find({
|
|
||||||
filter: {
|
|
||||||
'options.autoStart': true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for (const subAppInstance of subApps) {
|
|
||||||
promises.push(
|
|
||||||
(async () => {
|
|
||||||
if (!appSupervisor.hasApp(subAppInstance.name)) {
|
|
||||||
await AppSupervisor.getInstance().getApp(subAppInstance.name);
|
|
||||||
} else if (appSupervisor.getAppStatus(subAppInstance.name) === 'initialized') {
|
|
||||||
(await AppSupervisor.getInstance().getApp(subAppInstance.name)).runCommand('start', '--quickstart');
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Auto register sub applications failed: ', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.app.on('afterUpgrade', async (app, options) => {
|
this.app.on('afterUpgrade', async (app, options) => {
|
||||||
await this.subAppUpgradeHandler(app);
|
await this.subAppUpgradeHandler(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.resourcer.registerActionHandlers({
|
|
||||||
'applications:listPinned': async (ctx, next) => {
|
|
||||||
const items = await this.db.getRepository('applications').find({
|
|
||||||
filter: {
|
|
||||||
pinned: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
ctx.body = items;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.app.acl.allow('applications', 'listPinned', 'loggedIn');
|
this.app.acl.allow('applications', 'listPinned', 'loggedIn');
|
||||||
|
|
||||||
this.app.acl.registerSnippet({
|
this.app.acl.registerSnippet({
|
||||||
@ -358,68 +241,18 @@ export class PluginMultiAppManager extends Plugin {
|
|||||||
actions: ['applications:*'],
|
actions: ['applications:*'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 注入 APP 运行状态,替换 tmpl 为 displayName(name) 的格式
|
const injectAppList = injectAppListMiddleware();
|
||||||
this.app.resourcer.use(async (ctx, next) => {
|
|
||||||
await next();
|
this.app.use(async (ctx, next) => {
|
||||||
const { actionName, resourceName, params } = ctx.action;
|
try {
|
||||||
if (actionName === 'list' && resourceName === 'applications') {
|
await injectAppList(ctx, next);
|
||||||
const applications = ctx.body.rows;
|
} catch (error) {
|
||||||
for (const application of applications) {
|
ctx.logger.error(error);
|
||||||
const appStatus = AppSupervisor.getInstance().getAppStatus(application.name, 'stopped');
|
|
||||||
application.status = appStatus;
|
|
||||||
if (application.tmpl) {
|
|
||||||
const matchedApp = applications.find((app) => app.name === application.tmpl);
|
|
||||||
if (matchedApp) {
|
|
||||||
application.tmpl = `${matchedApp.displayName}(${application.tmpl})`;
|
|
||||||
} else {
|
|
||||||
application.tmpl = `Not Exists(${application.tmpl})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 基于模板创建应用时防止使用不适配的数据库,防止存入不存在的模板
|
for (const [key, action] of Object.entries(actions)) {
|
||||||
this.app.resourcer.use(async (ctx, next) => {
|
this.app.resourcer.registerActionHandler(`applications:${key}`, action);
|
||||||
const { actionName, resourceName, params } = ctx.action;
|
}
|
||||||
if (actionName === 'create' && resourceName === 'applications') {
|
|
||||||
const tmpl = params.values?.tmpl;
|
|
||||||
if (tmpl) {
|
|
||||||
const dbType = (this.app.options.database as any).dialect;
|
|
||||||
if (dbType !== 'postgres') {
|
|
||||||
ctx.withoutDataWrapping = true;
|
|
||||||
ctx.status = 400;
|
|
||||||
ctx.body = {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: ctx.t('This database does not support to create application using template', {
|
|
||||||
ns: NAMESPACE,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const matchedApp = await this.db.getRepository('applications').find({
|
|
||||||
filter: {
|
|
||||||
name: tmpl,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (matchedApp.length === 0) {
|
|
||||||
ctx.withoutDataWrapping = true;
|
|
||||||
ctx.status = 400;
|
|
||||||
ctx.body = {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: ctx.t('Template not exists', { ns: NAMESPACE }),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user