feat: support reboot application manually (#1889)
* feat(reload-btn): reload application manually * feat(app): support reboot * feat: collections load after upgrade * feat(reboot): support reboot manually * chore(reboot): some typo * fix(reboot): fix storage path * fix(reboot): change restart file path * fix: menu divider --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
c0ef071baf
commit
eac034cb23
@ -74,11 +74,22 @@ module.exports = (cli) => {
|
|||||||
if (opts.dbSync) {
|
if (opts.dbSync) {
|
||||||
argv.push('--db-sync');
|
argv.push('--db-sync');
|
||||||
}
|
}
|
||||||
|
const runDevServer = () => {
|
||||||
run('ts-node-dev', argv, {
|
run('ts-node-dev', argv, {
|
||||||
env: {
|
env: {
|
||||||
APP_PORT: serverPort,
|
APP_PORT: serverPort,
|
||||||
},
|
},
|
||||||
|
}).catch((err) => {
|
||||||
|
if (err.exitCode == 100) {
|
||||||
|
console.log('Restarting server...');
|
||||||
|
runDevServer();
|
||||||
|
} else {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
runDevServer();
|
||||||
}
|
}
|
||||||
if (client || !server) {
|
if (client || !server) {
|
||||||
console.log('starting client', 1 * clientPort);
|
console.log('starting client', 1 * clientPort);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { isDev, run, postCheck, runInstall, promptForTs } = require('../util');
|
const { isDev, run, postCheck, runInstall, promptForTs } = require('../util');
|
||||||
const { existsSync } = require('fs');
|
const { existsSync, unlink } = require('fs');
|
||||||
const { resolve } = require('path');
|
const { resolve } = require('path');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
|
||||||
@ -41,6 +41,8 @@ module.exports = (cli) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await postCheck(opts);
|
await postCheck(opts);
|
||||||
|
const restartMark = resolve(process.cwd(), 'storage', 'restart');
|
||||||
|
if (!existsSync(restartMark)) {
|
||||||
if (opts.quickstart) {
|
if (opts.quickstart) {
|
||||||
await run('node', [`./packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, 'install', '--ignore-installed']);
|
await run('node', [`./packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, 'install', '--ignore-installed']);
|
||||||
await run('node', [`./packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, 'upgrade']);
|
await run('node', [`./packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, 'upgrade']);
|
||||||
@ -48,6 +50,7 @@ module.exports = (cli) => {
|
|||||||
if (opts.dbSync) {
|
if (opts.dbSync) {
|
||||||
await run('node', [`./packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, 'db:sync']);
|
await run('node', [`./packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, 'db:sync']);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (opts.daemon) {
|
if (opts.daemon) {
|
||||||
run('pm2', ['start', `packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, '--', ...process.argv.slice(2)]);
|
run('pm2', ['start', `packages/${APP_PACKAGE_ROOT}/server/lib/index.js`, '--', ...process.argv.slice(2)]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -17,18 +17,17 @@ const SnippetCheckboxGroup = connect((props) => {
|
|||||||
}}
|
}}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
onChange={(values) => {
|
onChange={(values) => {
|
||||||
|
const snippets = ['ui.*', 'pm', 'pm.*', 'app'];
|
||||||
|
const disallowSnippets = snippets.map((key) => `!${key}`);
|
||||||
const value = uniq([...(props.value || []), ...values])
|
const value = uniq([...(props.value || []), ...values])
|
||||||
.filter((key) => key && !['!ui.*', '!pm', '!pm.*'].includes(key))
|
.filter((key) => key && !disallowSnippets.includes(key))
|
||||||
.map((key) => {
|
.map((key) => {
|
||||||
if (!['ui.*', 'pm', 'pm.*'].includes(key)) {
|
if (!snippets.includes(key) || values?.includes(key)) {
|
||||||
return key;
|
|
||||||
}
|
|
||||||
if (values?.includes(key)) {
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
return `!${key}`;
|
return `!${key}`;
|
||||||
});
|
});
|
||||||
for (const key of ['ui.*', 'pm', 'pm.*']) {
|
for (const key of snippets) {
|
||||||
if (!value.includes(key) && !value.includes(`!${key}`)) {
|
if (!value.includes(key) && !value.includes(`!${key}`)) {
|
||||||
value.push(`!${key}`);
|
value.push(`!${key}`);
|
||||||
}
|
}
|
||||||
@ -45,6 +44,9 @@ const SnippetCheckboxGroup = connect((props) => {
|
|||||||
<div style={{ marginTop: 8 }}>
|
<div style={{ marginTop: 8 }}>
|
||||||
<Checkbox value="pm.*">{t('Allows to configure plugins')}</Checkbox>
|
<Checkbox value="pm.*">{t('Allows to configure plugins')}</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginTop: 8 }}>
|
||||||
|
<Checkbox value="app">{t('Allows to reboot application')}</Checkbox>
|
||||||
|
</div>
|
||||||
</Checkbox.Group>
|
</Checkbox.Group>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@ const handleErrorMessage = (error) => {
|
|||||||
};
|
};
|
||||||
export class APIClient extends APIClientSDK {
|
export class APIClient extends APIClientSDK {
|
||||||
services: Record<string, Result<any, any>> = {};
|
services: Record<string, Result<any, any>> = {};
|
||||||
|
silence = false;
|
||||||
|
|
||||||
service(uid: string) {
|
service(uid: string) {
|
||||||
return this.services[uid];
|
return this.services[uid];
|
||||||
@ -38,6 +39,9 @@ export class APIClient extends APIClientSDK {
|
|||||||
this.axios.interceptors.response.use(
|
this.axios.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
|
if (this.silence) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
const redirectTo = error?.response?.data?.redirectTo;
|
const redirectTo = error?.response?.data?.redirectTo;
|
||||||
if (redirectTo) {
|
if (redirectTo) {
|
||||||
return (window.location.href = redirectTo);
|
return (window.location.href = redirectTo);
|
||||||
@ -55,4 +59,9 @@ export class APIClient extends APIClientSDK {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
silent() {
|
||||||
|
this.silence = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -777,4 +777,12 @@ export default {
|
|||||||
'Add template': '添加模板',
|
'Add template': '添加模板',
|
||||||
'Display data template selector': '显示数据模板选择框',
|
'Display data template selector': '显示数据模板选择框',
|
||||||
'Form data templates': '表单数据模板',
|
'Form data templates': '表单数据模板',
|
||||||
|
|
||||||
|
'Reload Application': '重载应用',
|
||||||
|
'The application is reloading, please do not close the page.': '应用正在重新加载,请勿关闭页面。',
|
||||||
|
'Application reloading': '应用重新加载中',
|
||||||
|
'Reboot Application': '重启应用',
|
||||||
|
"Allows to reboot application": "允许重启应用",
|
||||||
|
'The will interrupt service, it may take a few seconds to restart. Are you sure to continue?': '重启将会中断当前服务,这个过程可能需要一点时间,确定要继续吗?',
|
||||||
|
'Reboot': '重启',
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Dropdown, Menu } from 'antd';
|
import { Dropdown, Menu, Modal } from 'antd';
|
||||||
import React, { createContext, useState } from 'react';
|
import React, { createContext, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useAPIClient, useCurrentUserContext } from '..';
|
import { useACLRoleContext, useAPIClient, useCurrentUserContext } from '..';
|
||||||
import { useCurrentAppInfo } from '../appInfo/CurrentAppInfoProvider';
|
import { useCurrentAppInfo } from '../appInfo/CurrentAppInfoProvider';
|
||||||
import { ChangePassword } from './ChangePassword';
|
import { ChangePassword } from './ChangePassword';
|
||||||
import { EditProfile } from './EditProfile';
|
import { EditProfile } from './EditProfile';
|
||||||
@ -28,6 +28,30 @@ export const CurrentUser = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { data } = useCurrentUserContext();
|
const { data } = useCurrentUserContext();
|
||||||
|
const { allowAll, snippets } = useACLRoleContext();
|
||||||
|
const allowReboot = allowAll || snippets?.includes('app');
|
||||||
|
const silenceApi = useAPIClient();
|
||||||
|
const check = async () => {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const heartbeat = setInterval(() => {
|
||||||
|
silenceApi
|
||||||
|
.silent()
|
||||||
|
.resource('app')
|
||||||
|
.getInfo()
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
resolve('ok');
|
||||||
|
clearInterval(heartbeat);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// ignore
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'inline-flex', verticalAlign: 'top' }}>
|
<div style={{ display: 'inline-flex', verticalAlign: 'top' }}>
|
||||||
<DropdownVisibleContext.Provider value={{ visible, setVisible }}>
|
<DropdownVisibleContext.Provider value={{ visible, setVisible }}>
|
||||||
@ -42,10 +66,36 @@ export const CurrentUser = () => {
|
|||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<EditProfile />
|
<EditProfile />
|
||||||
<ChangePassword />
|
<ChangePassword />
|
||||||
|
<Menu.Divider />
|
||||||
<SwitchRole />
|
<SwitchRole />
|
||||||
<LanguageSettings />
|
<LanguageSettings />
|
||||||
<ThemeSettings />
|
<ThemeSettings />
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
|
{allowReboot && (
|
||||||
|
<Menu.Item
|
||||||
|
key="reload"
|
||||||
|
onClick={async () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('Reboot Application'),
|
||||||
|
content: t(
|
||||||
|
'The will interrupt service, it may take a few seconds to restart. Are you sure to continue?',
|
||||||
|
),
|
||||||
|
okText: t('Reboot'),
|
||||||
|
okButtonProps: {
|
||||||
|
danger: true,
|
||||||
|
},
|
||||||
|
onOk: async () => {
|
||||||
|
await api.resource('app').reboot();
|
||||||
|
await check();
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Reboot Application')}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
<Menu.Divider />
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key="signout"
|
key="signout"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
@ -506,6 +506,7 @@
|
|||||||
"Allows to configure interface": "允许配置界面",
|
"Allows to configure interface": "允许配置界面",
|
||||||
"Allows to install, activate, disable plugins": "允许安装、激活、禁用插件",
|
"Allows to install, activate, disable plugins": "允许安装、激活、禁用插件",
|
||||||
"Allows to configure plugins": "允许配置插件",
|
"Allows to configure plugins": "允许配置插件",
|
||||||
|
"Allows to reload application": "允许重新加载应用",
|
||||||
"Action display name": "操作名称",
|
"Action display name": "操作名称",
|
||||||
"Allow": "允许",
|
"Allow": "允许",
|
||||||
"Data scope": "数据范围",
|
"Data scope": "数据范围",
|
||||||
@ -993,11 +994,26 @@
|
|||||||
"quarterPlaceholder": "请选择季度",
|
"quarterPlaceholder": "请选择季度",
|
||||||
"monthPlaceholder": "请选择月份",
|
"monthPlaceholder": "请选择月份",
|
||||||
"weekPlaceholder": "请选择周",
|
"weekPlaceholder": "请选择周",
|
||||||
"rangePlaceholder": ["开始日期", "结束日期"],
|
"rangePlaceholder": [
|
||||||
"rangeYearPlaceholder": ["开始年份", "结束年份"],
|
"开始日期",
|
||||||
"rangeMonthPlaceholder": ["开始月份", "结束月份"],
|
"结束日期"
|
||||||
"rangeQuarterPlaceholder": ["开始季度", "结束季度"],
|
],
|
||||||
"rangeWeekPlaceholder": ["开始周", "结束周"],
|
"rangeYearPlaceholder": [
|
||||||
|
"开始年份",
|
||||||
|
"结束年份"
|
||||||
|
],
|
||||||
|
"rangeMonthPlaceholder": [
|
||||||
|
"开始月份",
|
||||||
|
"结束月份"
|
||||||
|
],
|
||||||
|
"rangeQuarterPlaceholder": [
|
||||||
|
"开始季度",
|
||||||
|
"结束季度"
|
||||||
|
],
|
||||||
|
"rangeWeekPlaceholder": [
|
||||||
|
"开始周",
|
||||||
|
"结束周"
|
||||||
|
],
|
||||||
"locale": "zh_CN",
|
"locale": "zh_CN",
|
||||||
"today": "今天",
|
"today": "今天",
|
||||||
"now": "此刻",
|
"now": "此刻",
|
||||||
@ -1025,9 +1041,21 @@
|
|||||||
"previousCentury": "上一世纪",
|
"previousCentury": "上一世纪",
|
||||||
"nextCentury": "下一世纪"
|
"nextCentury": "下一世纪"
|
||||||
},
|
},
|
||||||
"timePickerLocale": { "placeholder": "请选择时间", "rangePlaceholder": ["开始时间", "结束时间"] }
|
"timePickerLocale": {
|
||||||
|
"placeholder": "请选择时间",
|
||||||
|
"rangePlaceholder": [
|
||||||
|
"开始时间",
|
||||||
|
"结束时间"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TimePicker": {
|
||||||
|
"placeholder": "请选择时间",
|
||||||
|
"rangePlaceholder": [
|
||||||
|
"开始时间",
|
||||||
|
"结束时间"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"TimePicker": { "placeholder": "请选择时间", "rangePlaceholder": ["开始时间", "结束时间"] },
|
|
||||||
"Calendar": {
|
"Calendar": {
|
||||||
"lang": {
|
"lang": {
|
||||||
"placeholder": "请选择日期",
|
"placeholder": "请选择日期",
|
||||||
@ -1035,11 +1063,26 @@
|
|||||||
"quarterPlaceholder": "请选择季度",
|
"quarterPlaceholder": "请选择季度",
|
||||||
"monthPlaceholder": "请选择月份",
|
"monthPlaceholder": "请选择月份",
|
||||||
"weekPlaceholder": "请选择周",
|
"weekPlaceholder": "请选择周",
|
||||||
"rangePlaceholder": ["开始日期", "结束日期"],
|
"rangePlaceholder": [
|
||||||
"rangeYearPlaceholder": ["开始年份", "结束年份"],
|
"开始日期",
|
||||||
"rangeMonthPlaceholder": ["开始月份", "结束月份"],
|
"结束日期"
|
||||||
"rangeQuarterPlaceholder": ["开始季度", "结束季度"],
|
],
|
||||||
"rangeWeekPlaceholder": ["开始周", "结束周"],
|
"rangeYearPlaceholder": [
|
||||||
|
"开始年份",
|
||||||
|
"结束年份"
|
||||||
|
],
|
||||||
|
"rangeMonthPlaceholder": [
|
||||||
|
"开始月份",
|
||||||
|
"结束月份"
|
||||||
|
],
|
||||||
|
"rangeQuarterPlaceholder": [
|
||||||
|
"开始季度",
|
||||||
|
"结束季度"
|
||||||
|
],
|
||||||
|
"rangeWeekPlaceholder": [
|
||||||
|
"开始周",
|
||||||
|
"结束周"
|
||||||
|
],
|
||||||
"locale": "zh_CN",
|
"locale": "zh_CN",
|
||||||
"today": "今天",
|
"today": "今天",
|
||||||
"now": "此刻",
|
"now": "此刻",
|
||||||
@ -1067,9 +1110,17 @@
|
|||||||
"previousCentury": "上一世纪",
|
"previousCentury": "上一世纪",
|
||||||
"nextCentury": "下一世纪"
|
"nextCentury": "下一世纪"
|
||||||
},
|
},
|
||||||
"timePickerLocale": { "placeholder": "请选择时间", "rangePlaceholder": ["开始时间", "结束时间"] }
|
"timePickerLocale": {
|
||||||
|
"placeholder": "请选择时间",
|
||||||
|
"rangePlaceholder": [
|
||||||
|
"开始时间",
|
||||||
|
"结束时间"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"global": {
|
||||||
|
"placeholder": "请选择"
|
||||||
},
|
},
|
||||||
"global": { "placeholder": "请选择" },
|
|
||||||
"Table": {
|
"Table": {
|
||||||
"filterTitle": "筛选",
|
"filterTitle": "筛选",
|
||||||
"filterConfirm": "确定",
|
"filterConfirm": "确定",
|
||||||
@ -1088,8 +1139,15 @@
|
|||||||
"triggerAsc": "点击升序",
|
"triggerAsc": "点击升序",
|
||||||
"cancelSort": "取消排序"
|
"cancelSort": "取消排序"
|
||||||
},
|
},
|
||||||
"Modal": { "okText": "确定", "cancelText": "取消", "justOkText": "知道了" },
|
"Modal": {
|
||||||
"Popconfirm": { "cancelText": "取消", "okText": "确定" },
|
"okText": "确定",
|
||||||
|
"cancelText": "取消",
|
||||||
|
"justOkText": "知道了"
|
||||||
|
},
|
||||||
|
"Popconfirm": {
|
||||||
|
"cancelText": "取消",
|
||||||
|
"okText": "确定"
|
||||||
|
},
|
||||||
"Transfer": {
|
"Transfer": {
|
||||||
"searchPlaceholder": "请输入搜索内容",
|
"searchPlaceholder": "请输入搜索内容",
|
||||||
"itemUnit": "项",
|
"itemUnit": "项",
|
||||||
@ -1108,10 +1166,21 @@
|
|||||||
"previewFile": "预览文件",
|
"previewFile": "预览文件",
|
||||||
"downloadFile": "下载文件"
|
"downloadFile": "下载文件"
|
||||||
},
|
},
|
||||||
"Empty": { "description": "暂无数据" },
|
"Empty": {
|
||||||
"Icon": { "icon": "图标" },
|
"description": "暂无数据"
|
||||||
"Text": { "edit": "编辑", "copy": "复制", "copied": "复制成功", "expand": "展开" },
|
},
|
||||||
"PageHeader": { "back": "返回" },
|
"Icon": {
|
||||||
|
"icon": "图标"
|
||||||
|
},
|
||||||
|
"Text": {
|
||||||
|
"edit": "编辑",
|
||||||
|
"copy": "复制",
|
||||||
|
"copied": "复制成功",
|
||||||
|
"expand": "展开"
|
||||||
|
},
|
||||||
|
"PageHeader": {
|
||||||
|
"back": "返回"
|
||||||
|
},
|
||||||
"Form": {
|
"Form": {
|
||||||
"optional": "(可选)",
|
"optional": "(可选)",
|
||||||
"defaultValidateMessages": {
|
"defaultValidateMessages": {
|
||||||
@ -1157,10 +1226,14 @@
|
|||||||
"max": "最多${max}个${label}",
|
"max": "最多${max}个${label}",
|
||||||
"range": "${label}数量须在${min}-${max}之间"
|
"range": "${label}数量须在${min}-${max}之间"
|
||||||
},
|
},
|
||||||
"pattern": { "mismatch": "${label}与模式不匹配${pattern}" }
|
"pattern": {
|
||||||
|
"mismatch": "${label}与模式不匹配${pattern}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Image": { "preview": "预览" }
|
"Image": {
|
||||||
|
"preview": "预览"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"cronstrue": {
|
"cronstrue": {
|
||||||
"atX0SecondsPastTheMinuteGt20": null,
|
"atX0SecondsPastTheMinuteGt20": null,
|
||||||
@ -1213,7 +1286,15 @@
|
|||||||
"commaOnDayX0OfTheMonth": ", 限每月%s",
|
"commaOnDayX0OfTheMonth": ", 限每月%s",
|
||||||
"commaEveryX0Years": ", 每隔 %s 年",
|
"commaEveryX0Years": ", 每隔 %s 年",
|
||||||
"commaStartingX0": ", %s开始",
|
"commaStartingX0": ", %s开始",
|
||||||
"daysOfTheWeek": ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
|
"daysOfTheWeek": [
|
||||||
|
"星期日",
|
||||||
|
"星期一",
|
||||||
|
"星期二",
|
||||||
|
"星期三",
|
||||||
|
"星期四",
|
||||||
|
"星期五",
|
||||||
|
"星期六"
|
||||||
|
],
|
||||||
"monthsOfTheYear": [
|
"monthsOfTheYear": [
|
||||||
"一月",
|
"一月",
|
||||||
"二月",
|
"二月",
|
||||||
@ -1257,9 +1338,51 @@
|
|||||||
"suffixMinutesForHourPeriod": "分钟",
|
"suffixMinutesForHourPeriod": "分钟",
|
||||||
"errorInvalidCron": "不符合 cron 规则的表达式",
|
"errorInvalidCron": "不符合 cron 规则的表达式",
|
||||||
"clearButtonText": "清空",
|
"clearButtonText": "清空",
|
||||||
"weekDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
"weekDays": [
|
||||||
"months": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
"周日",
|
||||||
"altWeekDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
"周一",
|
||||||
"altMonths": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
|
"周二",
|
||||||
|
"周三",
|
||||||
|
"周四",
|
||||||
|
"周五",
|
||||||
|
"周六"
|
||||||
|
],
|
||||||
|
"months": [
|
||||||
|
"一月",
|
||||||
|
"二月",
|
||||||
|
"三月",
|
||||||
|
"四月",
|
||||||
|
"五月",
|
||||||
|
"六月",
|
||||||
|
"七月",
|
||||||
|
"八月",
|
||||||
|
"九月",
|
||||||
|
"十月",
|
||||||
|
"十一月",
|
||||||
|
"十二月"
|
||||||
|
],
|
||||||
|
"altWeekDays": [
|
||||||
|
"周日",
|
||||||
|
"周一",
|
||||||
|
"周二",
|
||||||
|
"周三",
|
||||||
|
"周四",
|
||||||
|
"周五",
|
||||||
|
"周六"
|
||||||
|
],
|
||||||
|
"altMonths": [
|
||||||
|
"一月",
|
||||||
|
"二月",
|
||||||
|
"三月",
|
||||||
|
"四月",
|
||||||
|
"五月",
|
||||||
|
"六月",
|
||||||
|
"七月",
|
||||||
|
"八月",
|
||||||
|
"九月",
|
||||||
|
"十月",
|
||||||
|
"十一月",
|
||||||
|
"十二月"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -94,8 +94,18 @@ export class ClientPlugin extends Plugin {
|
|||||||
this.app.acl.allow('app', 'getInfo');
|
this.app.acl.allow('app', 'getInfo');
|
||||||
this.app.acl.allow('app', 'getPlugins');
|
this.app.acl.allow('app', 'getPlugins');
|
||||||
this.app.acl.allow('plugins', '*', 'public');
|
this.app.acl.allow('plugins', '*', 'public');
|
||||||
|
this.app.acl.registerSnippet({
|
||||||
|
name: 'app',
|
||||||
|
actions: ['app:reload', 'app:reboot'],
|
||||||
|
});
|
||||||
const dialect = this.app.db.sequelize.getDialect();
|
const dialect = this.app.db.sequelize.getDialect();
|
||||||
const locales = require('./locale').default;
|
const locales = require('./locale').default;
|
||||||
|
const restartMark = resolve(process.cwd(), 'storage', 'restart');
|
||||||
|
this.app.on('beforeStart', async () => {
|
||||||
|
if (fs.existsSync(restartMark)) {
|
||||||
|
fs.unlinkSync(restartMark);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.app.resource({
|
this.app.resource({
|
||||||
name: 'app',
|
name: 'app',
|
||||||
actions: {
|
actions: {
|
||||||
@ -161,6 +171,24 @@ export class ClientPlugin extends Plugin {
|
|||||||
.map((item) => item.name);
|
.map((item) => item.name);
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
|
async reload(ctx, next) {
|
||||||
|
await ctx.app.reload();
|
||||||
|
await next();
|
||||||
|
},
|
||||||
|
reboot(ctx) {
|
||||||
|
const RESTART_CODE = 100;
|
||||||
|
process.on('exit', (code) => {
|
||||||
|
if (code === RESTART_CODE && process.env.APP_ENV === 'production') {
|
||||||
|
fs.writeFileSync(restartMark, '1');
|
||||||
|
console.log('Restart mark created.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.app.on('afterStop', () => {
|
||||||
|
// Exit with code 100 will restart the process
|
||||||
|
process.exit(RESTART_CODE);
|
||||||
|
});
|
||||||
|
ctx.app.stop();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.app.resource({
|
this.app.resource({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Database, { Collection, MagicAttributeModel } from '@nocobase/database';
|
import Database, { Collection, MagicAttributeModel } from '@nocobase/database';
|
||||||
|
import lodash from 'lodash';
|
||||||
import { SyncOptions, Transactionable } from 'sequelize';
|
import { SyncOptions, Transactionable } from 'sequelize';
|
||||||
import { FieldModel } from './field';
|
import { FieldModel } from './field';
|
||||||
import lodash from 'lodash';
|
|
||||||
|
|
||||||
interface LoadOptions extends Transactionable {
|
interface LoadOptions extends Transactionable {
|
||||||
// TODO
|
// TODO
|
||||||
@ -50,8 +50,12 @@ export class CollectionModel extends MagicAttributeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadFields(options: Transactionable = {}) {
|
async loadFields(options: Transactionable = {}) {
|
||||||
|
let fields = this.get('fields') || [];
|
||||||
|
if (!fields.length) {
|
||||||
|
fields = await this.getFields(options);
|
||||||
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const instances: FieldModel[] = await this.getFields(options);
|
const instances: FieldModel[] = fields;
|
||||||
|
|
||||||
for (const instance of instances) {
|
for (const instance of instances) {
|
||||||
await instance.load(options);
|
await instance.load(options);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Repository } from '@nocobase/database';
|
import { Repository } from '@nocobase/database';
|
||||||
import { CollectionModel } from '../models/collection';
|
|
||||||
import { CollectionsGraph } from '@nocobase/utils';
|
import { CollectionsGraph } from '@nocobase/utils';
|
||||||
|
import { CollectionModel } from '../models/collection';
|
||||||
|
|
||||||
interface LoadOptions {
|
interface LoadOptions {
|
||||||
filter?: any;
|
filter?: any;
|
||||||
@ -10,7 +10,7 @@ interface LoadOptions {
|
|||||||
export class CollectionRepository extends Repository {
|
export class CollectionRepository extends Repository {
|
||||||
async load(options: LoadOptions = {}) {
|
async load(options: LoadOptions = {}) {
|
||||||
const { filter, skipExist } = options;
|
const { filter, skipExist } = options;
|
||||||
const instances = (await this.find({ filter })) as CollectionModel[];
|
const instances = (await this.find({ filter, appends: ['fields'] })) as CollectionModel[];
|
||||||
|
|
||||||
const graphlib = CollectionsGraph.graphlib();
|
const graphlib = CollectionsGraph.graphlib();
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export class CollectionRepository extends Repository {
|
|||||||
nameMap[collectionName] = instance;
|
nameMap[collectionName] = instance;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const fields = await instance.getFields();
|
const fields = instance.get('fields') || [];
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (field['type'] === 'belongsToMany') {
|
if (field['type'] === 'belongsToMany') {
|
||||||
const throughName = field.options.through;
|
const throughName = field.options.through;
|
||||||
|
@ -216,6 +216,9 @@ export class CollectionManagerPlugin extends Plugin {
|
|||||||
if (options?.method === 'install') {
|
if (options?.method === 'install') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (options?.method === 'upgrade') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const exists = await this.app.db.collectionExistsInDb('collections');
|
const exists = await this.app.db.collectionExistsInDb('collections');
|
||||||
if (exists) {
|
if (exists) {
|
||||||
try {
|
try {
|
||||||
|
@ -102,6 +102,7 @@ export class PresetNocoBase extends Plugin {
|
|||||||
);
|
);
|
||||||
await this.app.reload({ method: 'upgrade' });
|
await this.app.reload({ method: 'upgrade' });
|
||||||
await this.app.db.sync();
|
await this.app.db.sync();
|
||||||
|
await this.app.db.getRepository<any>('collections').load();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.on('beforeInstall', async () => {
|
this.app.on('beforeInstall', async () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user