Compare commits

...

22 Commits

Author SHA1 Message Date
whyour 56eb0c5408 更新版本 v2.15.18 2023-07-19 21:17:22 +08:00
whyour fe55929959 更新 readme 2023-07-19 21:02:52 +08:00
whyour 01e2bd007d 修复重启提示持续时间 2023-07-18 21:39:03 +08:00
whyour 4e091b0c3e 修改 nginx body 限制,服务重启延迟时间 2023-07-18 15:30:43 +08:00
whyour db94cd3799 修复备份数据超时时间 2023-07-18 13:43:02 +08:00
whyour a15192b9b2 升级 @umijs/max 版本 2023-07-17 23:18:48 +08:00
whyour efd4f1d5ab umi-request 替换为 axios 2023-07-17 23:13:06 +08:00
whyour bd166ee794 修复 pm2 重启逻辑 2023-07-17 13:25:56 +08:00
whyour 93e94ea94c 系统设置增加数据恢复功能 2023-07-16 22:02:30 +08:00
whyour 88b87de391 增加数据备份功能 2023-07-16 00:23:29 +08:00
whyour 8affff96f3 修改系统内更新系统逻辑 2023-07-13 22:09:45 +08:00
whyour 936b565fb1 修改系统更新时间和版本获取 2023-07-12 00:05:46 +08:00
whyour b69ff2895e 增加依赖是否已经安装判断 2023-07-10 23:48:05 +08:00
whyour f3791cbb62 修复编译错误 2023-07-09 17:46:02 +08:00
whyour b0f3b51736 修复服务启动时定时删除日志失效 2023-07-08 20:58:36 +08:00
whyour 3aa112e373 修改 node 进程错误监控 2023-07-08 00:27:05 +08:00
whyour 683482e067 修改 ql update 逻辑 2023-07-08 00:18:09 +08:00
whyour 5a2caeb66b 更新版本 v2.15.17 2023-07-02 16:09:09 +08:00
whyour ee6e5bd8b4 修改任务状态值 2023-07-02 16:09:01 +08:00
whyour f970322f0a 修复定时任务状态筛选 2023-07-02 12:19:34 +08:00
whyour 1718120623 修改更新仓库逻辑 2023-07-01 20:48:28 +08:00
whyour 702c3160ec 修改并发逻辑,系统设置增加定时任务并发设置 2023-07-01 15:26:20 +08:00
68 changed files with 1110 additions and 690 deletions
+2 -1
View File
@@ -28,4 +28,5 @@
/db
/manual_log
/scripts
/bak
/bak
/.tmp
+18
View File
@@ -35,6 +35,24 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript
- Support dark mode
- Support cell phone operation
## Version
### docker
The `latest` image is built on `alpine` and the `debian` image is built on `debian-slim`. If you need to use a dependency that is not supported by `alpine`, it is recommended that you use the `debian` image.
```bash
docker pull whyour/qinglong:latest
docker pull whyour/qinglong:debian
```
### npm
The npm version supports `debian/ubuntu/centos/alpine` systems and requires `node/python3` to be installed.
```bash
npm i @whyour/qinglong
```
## Deployment
### Docker (Recommended)
+18
View File
@@ -37,6 +37,24 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript
- 支持暗黑模式
- 支持手机端操作
## 版本
### docker
`latest` 镜像是基于 `alpine` 构建,`debian` 镜像是基于 `debian-slim` 构建。如果需要使用 `alpine` 不支持的依赖,建议使用 `debian` 镜像
```bash
docker pull whyour/qinglong:latest
docker pull whyour/qinglong:debian
```
### npm
npm 版本支持 `debian/ubuntu/centos/alpine` 系统,需要自行安装 `node/python3`
```bash
npm i @whyour/qinglong
```
## 部署
### docker (推荐)
+64 -25
View File
@@ -14,8 +14,18 @@ import {
promiseExec,
} from '../config/util';
import dayjs from 'dayjs';
import multer from 'multer';
const route = Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, config.tmpPath);
},
filename: function (req, file, cb) {
cb(null, 'data.tgz');
},
});
const upload = multer({ storage: storage });
export default (app: Router) => {
app.use('/system', route);
@@ -25,23 +35,9 @@ export default (app: Router) => {
try {
const userService = Container.get(UserService);
const authInfo = await userService.getUserInfo();
const envCount = await EnvModel.count();
const { version, changeLog, changeLogLink } = await parseVersion(
const { version, changeLog, changeLogLink, publishTime } = await parseVersion(
config.versionFile,
);
const lastCommitTime = (
await promiseExec(
`cd ${config.rootPath} && git show -s --format=%ai | head -1`,
)
).replace('\n', '');
const lastCommitId = (
await promiseExec(`cd ${config.rootPath} && git rev-parse --short HEAD`)
).replace('\n', '');
const branch = (
await promiseExec(
`cd ${config.rootPath} && git symbolic-ref --short HEAD`,
)
).replace('\n', '');
let isInitialized = true;
if (
@@ -56,9 +52,8 @@ export default (app: Router) => {
data: {
isInitialized,
version,
lastCommitTime: dayjs(lastCommitTime).unix(),
lastCommitId,
branch,
publishTime: dayjs(publishTime).unix(),
branch: process.env.QL_BRANCH || 'master',
changeLog,
changeLogLink,
},
@@ -70,12 +65,12 @@ export default (app: Router) => {
});
route.get(
'/log/remove',
'/config',
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const systemService = Container.get(SystemService);
const data = await systemService.getLogRemoveFrequency();
const data = await systemService.getSystemConfig();
res.send({ code: 200, data });
} catch (e) {
return next(e);
@@ -84,19 +79,18 @@ export default (app: Router) => {
);
route.put(
'/log/remove',
'/config',
celebrate({
body: Joi.object({
frequency: Joi.number().required(),
logRemoveFrequency: Joi.number().optional().allow(null),
cronConcurrency: Joi.number().optional().allow(null),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const systemService = Container.get(SystemService);
const result = await systemService.updateLogRemoveFrequency(
req.body.frequency,
);
const result = await systemService.updateSystemConfig(req.body);
res.send(result);
} catch (e) {
return next(e);
@@ -132,6 +126,25 @@ export default (app: Router) => {
},
);
route.put(
'/reload',
celebrate({
body: Joi.object({
type: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const systemService = Container.get(SystemService);
const result = await systemService.reloadSystem(req.body.type);
res.send(result);
} catch (e) {
return next(e);
}
},
);
route.put(
'/notify',
celebrate({
@@ -211,4 +224,30 @@ export default (app: Router) => {
}
},
);
route.put(
'/data/export',
async (req: Request, res: Response, next: NextFunction) => {
try {
const systemService = Container.get(SystemService);
await systemService.exportData(res);
} catch (e) {
return next(e);
}
},
);
route.put(
'/data/import',
upload.single('data'),
async (req: Request, res: Response, next: NextFunction) => {
try {
const systemService = Container.get(SystemService);
const result = await systemService.importData();
res.send(result);
} catch (e) {
return next(e);
}
},
);
};
+5
View File
@@ -20,6 +20,7 @@ const rootPath = process.env.QL_DIR as string;
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
const dataPath = path.join(rootPath, 'data/');
const tmpPath = path.join(rootPath, '.tmp/');
const samplePath = path.join(rootPath, 'sample/');
const configPath = path.join(dataPath, 'config/');
const scriptPath = path.join(dataPath, 'scripts/');
@@ -42,6 +43,7 @@ const authError = '错误的用户名密码,请重试';
const loginFaild = '请先登录!';
const configString = 'config sample crontab shareCode diy';
const versionFile = path.join(rootPath, 'version.yaml');
const dataTgzFile = path.join(tmpPath, 'data.tgz');
if (envFound.error) {
throw new Error("⚠️ Couldn't find .env file ⚠️");
@@ -59,6 +61,9 @@ export default {
prefix: '/api',
},
rootPath,
tmpPath,
dataPath,
dataTgzFile,
configString,
loginFaild,
authError,
+13
View File
@@ -399,6 +399,18 @@ export function promiseExec(command: string): Promise<string> {
});
}
export function promiseExecSuccess(command: string): Promise<string> {
return new Promise((resolve) => {
exec(
command,
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
(err, stdout, stderr) => {
resolve(stdout || '');
},
);
});
}
export function parseHeaders(headers: string) {
if (!headers) return {};
@@ -505,6 +517,7 @@ interface IVersion {
version: string;
changeLogLink: string;
changeLog: string;
publishTime: string;
}
export async function parseVersion(path: string): Promise<IVersion> {
+19 -2
View File
@@ -1,10 +1,11 @@
import { sequelize } from '.';
import { DataTypes, Model, ModelDefined } from 'sequelize';
import { NotificationInfo } from './notify';
export class AuthInfo {
ip?: string;
type: AuthDataType;
info?: any;
info?: AuthModelInfo;
id?: number;
constructor(options: AuthInfo) {
@@ -25,9 +26,25 @@ export enum AuthDataType {
'authToken' = 'authToken',
'notification' = 'notification',
'removeLogFrequency' = 'removeLogFrequency',
'systemConfig' = 'systemConfig',
}
interface AuthInstance extends Model<AuthInfo, AuthInfo>, AuthInfo {}
export interface SystemConfigInfo {
logRemoveFrequency?: number;
cronConcurrency?: number;
}
export interface LoginLogInfo {
timestamp?: number;
address?: string;
ip?: string;
platform?: string;
status?: LoginStatus,
}
export type AuthModelInfo = SystemConfigInfo & Partial<NotificationInfo> & LoginLogInfo;
export interface AuthInstance extends Model<AuthInfo, AuthInfo>, AuthInfo { }
export const AuthModel = sequelize.define<AuthInstance>('Auth', {
ip: DataTypes.STRING,
type: DataTypes.STRING,
+4 -4
View File
@@ -43,13 +43,13 @@ export class Crontab {
}
export enum CrontabStatus {
'running',
'idle',
'running' = 0,
'queued' = 0.5,
'idle' = 1,
'disabled',
'queued',
}
interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
export interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
name: {
unique: 'compositeIndex',
+1 -1
View File
@@ -39,7 +39,7 @@ export class CrontabView {
}
}
interface CronViewInstance
export interface CronViewInstance
extends Model<CrontabView, CrontabView>,
CrontabView {}
export const CrontabViewModel = sequelize.define<CronViewInstance>(
+20 -8
View File
@@ -4,9 +4,9 @@ import { DataTypes, Model, ModelDefined } from 'sequelize';
export class Dependence {
timestamp?: string;
id?: number;
status?: DependenceStatus;
type?: DependenceTypes;
name?: number;
status: DependenceStatus;
type: DependenceTypes;
name: string;
log?: string[];
remark?: string;
@@ -18,7 +18,7 @@ export class Dependence {
: DependenceStatus.queued;
this.type = options.type || DependenceTypes.nodejs;
this.timestamp = new Date().toString();
this.name = options.name;
this.name = options.name.trim();
this.log = options.log || [];
this.remark = options.remark || '';
}
@@ -42,19 +42,31 @@ export enum DependenceTypes {
export enum InstallDependenceCommandTypes {
'pnpm add -g',
'pip3 install',
'pip3 install --disable-pip-version-check --root-user-action=ignore',
'apk add',
}
export enum GetDependenceCommandTypes {
'pnpm ls -g ',
'pip3 list --disable-pip-version-check',
'apk info',
}
export enum versionDependenceCommandTypes {
'@',
'==',
'=',
}
export enum unInstallDependenceCommandTypes {
'pnpm remove -g',
'pip3 uninstall -y',
'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y',
'apk del',
}
interface DependenceInstance
export interface DependenceInstance
extends Model<Dependence, Dependence>,
Dependence {}
Dependence { }
export const DependenceModel = sequelize.define<DependenceInstance>(
'Dependence',
{
+1 -1
View File
@@ -34,7 +34,7 @@ export const initPosition = 4500000000000000;
export const stepPosition = 10000000000;
export const minPosition = 100;
interface EnvInstance extends Model<Env, Env>, Env {}
export interface EnvInstance extends Model<Env, Env>, Env {}
export const EnvModel = sequelize.define<EnvInstance>('Env', {
value: { type: DataTypes.STRING, unique: 'compositeIndex' },
timestamp: DataTypes.STRING,
+1 -1
View File
@@ -26,7 +26,7 @@ export interface AppToken {
export type AppScope = 'envs' | 'crons' | 'configs' | 'scripts' | 'logs';
interface AppInstance extends Model<App, App>, App {}
export interface AppInstance extends Model<App, App>, App {}
export const AppModel = sequelize.define<AppInstance>('App', {
name: { type: DataTypes.STRING, unique: 'name' },
scopes: DataTypes.JSON,
+2 -1
View File
@@ -16,4 +16,5 @@ export type SockMessageType =
| 'uninstallDependence'
| 'updateSystemVersion'
| 'manuallyRunScript'
| 'runSubscriptionEnd';
| 'runSubscriptionEnd'
| 'reloadSystem';
+1 -1
View File
@@ -70,7 +70,7 @@ export enum SubscriptionStatus {
'queued',
}
interface SubscriptionInstance
export interface SubscriptionInstance
extends Model<Subscription, Subscription>,
Subscription {}
export const SubscriptionModel = sequelize.define<SubscriptionInstance>(
+1 -1
View File
@@ -32,7 +32,7 @@ export default async () => {
// 初始化更新所有任务状态为空闲
await CrontabModel.update(
{ status: CrontabStatus.idle },
{ where: { status: [CrontabStatus.running, CrontabStatus.queued] } },
{ where: {} },
);
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
+13
View File
@@ -18,10 +18,13 @@ const confFile = path.join(configPath, 'config.sh');
const authConfigFile = path.join(configPath, 'auth.json');
const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
const sampleAuthFile = path.join(samplePath, 'auth.sample.json');
const sampleTaskShellFile = path.join(samplePath, 'task.sample.sh');
const sampleNotifyJsFile = path.join(samplePath, 'notify.js');
const sampleNotifyPyFile = path.join(samplePath, 'notify.py');
const scriptNotifyJsFile = path.join(scriptPath, 'sendNotify.js');
const scriptNotifyPyFile = path.join(scriptPath, 'notify.py');
const TaskBeforeFile = path.join(configPath, 'task_before.sh');
const TaskAfterFile = path.join(configPath, 'task_after.sh');
const homedir = os.homedir();
const sshPath = path.resolve(homedir, '.ssh');
const sshdPath = path.join(dataPath, 'ssh.d');
@@ -39,6 +42,8 @@ export default async () => {
const tmpDirExist = await fileExist(tmpPath);
const scriptNotifyJsFileExist = await fileExist(scriptNotifyJsFile);
const scriptNotifyPyFileExist = await fileExist(scriptNotifyPyFile);
const TaskBeforeFileExist = await fileExist(TaskBeforeFile);
const TaskAfterFileExist = await fileExist(TaskAfterFile);
if (!configDirExist) {
fs.mkdirSync(configPath);
@@ -89,6 +94,14 @@ export default async () => {
fs.writeFileSync(scriptNotifyPyFile, fs.readFileSync(sampleNotifyPyFile));
}
if (!TaskBeforeFileExist) {
fs.writeFileSync(TaskBeforeFile, fs.readFileSync(sampleTaskShellFile));
}
if (!TaskAfterFileExist) {
fs.writeFileSync(TaskAfterFile, fs.readFileSync(sampleTaskShellFile));
}
dotenv.config({ path: confFile });
Logger.info('✌️ Init file down');
+5 -5
View File
@@ -29,16 +29,16 @@ export default async () => {
});
// 运行删除日志任务
const data = await systemService.getLogRemoveFrequency();
if (data && data.info && data.info.frequency) {
const data = await systemService.getSystemConfig();
if (data && data.info && data.info.logRemoveFrequency) {
const rmlogCron = {
id: data.id,
id: data.id as number,
name: '删除日志',
command: `ql rmlog ${data.info.frequency}`,
command: `ql rmlog ${data.info.logRemoveFrequency}`,
};
await scheduleService.cancelIntervalTask(rmlogCron);
scheduleService.createIntervalTask(rmlogCron, {
days: data.info.frequency,
days: data.info.logRemoveFrequency,
});
}
+27 -11
View File
@@ -5,18 +5,34 @@ import Sock from './sock';
export default async ({ server }: { server: Server }) => {
await Sock({ server });
Logger.info('✌️ Sock loaded');
let exitTime = 0;
let timer: NodeJS.Timeout;
process.on('SIGINT', () => {
Logger.info('✌️ Server need close');
server.close(() => {
setTimeout(() => {
process.exit();
}, 10000);
});
setTimeout(() => {
console.log('Forcing server close !!!');
process.on('SIGINT', (singal) => {
Logger.warn(`Server need close, singal ${singal}`);
exitTime++;
if (exitTime >= 3) {
Logger.warn('Forcing server close');
clearTimeout(timer);
process.exit(1);
}, 15000);
}
server.close(() => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
process.exit();
}, 15000);
});
});
process.on('uncaughtException', (error) => {
Logger.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
Logger.error('Unhandled rejection:', reason);
process.exit(1);
});
};
+5 -6
View File
@@ -7,21 +7,20 @@ import fs from 'fs';
import cron_parser from 'cron-parser';
import {
getFileContentByName,
concurrentRun,
fileExist,
killTask,
} from '../config/util';
import { promises, existsSync } from 'fs';
import { Op, where, col as colFn, FindOptions } from 'sequelize';
import { Op, where, col as colFn, FindOptions, fn } from 'sequelize';
import path from 'path';
import { TASK_PREFIX, QL_PREFIX } from '../config/const';
import cronClient from '../schedule/client';
import { runWithCpuLimit } from '../shared/pLimit';
import taskLimit from '../shared/pLimit';
import { spawn } from 'cross-spawn';
@Service()
export default class CronService {
constructor(@Inject('logger') private logger: winston.Logger) {}
constructor(@Inject('logger') private logger: winston.Logger) { }
private isSixCron(cron: Crontab) {
const { schedule } = cron;
@@ -268,7 +267,7 @@ export default class CronService {
let q: any = {};
if (!filterQuery[key]) continue;
if (key === 'status') {
if (filterQuery[key].includes(2)) {
if (filterQuery[key].includes(CrontabStatus.disabled)) {
q = { [Op.or]: [{ [key]: filterQuery[key] }, { isDisabled: 1 }] };
} else {
q = { [Op.and]: [{ [key]: filterQuery[key] }, { isDisabled: 0 }] };
@@ -387,7 +386,7 @@ export default class CronService {
}
private async runSingle(cronId: number): Promise<number> {
return runWithCpuLimit(() => {
return taskLimit.runWithCpuLimit(() => {
return new Promise(async (resolve: any) => {
const cron = await this.getDb({ id: cronId });
if (cron.status !== CrontabStatus.queued) {
+57 -15
View File
@@ -8,20 +8,22 @@ import {
DependenceTypes,
unInstallDependenceCommandTypes,
DependenceModel,
GetDependenceCommandTypes,
versionDependenceCommandTypes,
} from '../data/dependence';
import { spawn } from 'cross-spawn';
import SockService from './sock';
import { FindOptions, Op } from 'sequelize';
import { concurrentRun } from '../config/util';
import { promiseExecSuccess } from '../config/util';
import dayjs from 'dayjs';
import { runOneByOne, runWithCpuLimit } from '../shared/pLimit';
import taskLimit from '../shared/pLimit';
@Service()
export default class DependenceService {
constructor(
@Inject('logger') private logger: winston.Logger,
private sockService: SockService,
) { }
) {}
public async create(payloads: Dependence[]): Promise<Dependence[]> {
const tabs = payloads.map((x) => {
@@ -137,9 +139,17 @@ export default class DependenceService {
}
private async updateLog(ids: number[], log: string): Promise<void> {
const doc = await DependenceModel.findOne({ where: { id: ids } });
const newLog = doc?.log ? [...doc.log, log] : [log];
await DependenceModel.update({ log: newLog }, { where: { id: ids } });
taskLimit.updateDepLog(async () => {
const docs = await DependenceModel.findAll({ where: { id: ids } });
for (const doc of docs) {
const newLog = doc?.log ? [...doc.log, log] : [log];
await DependenceModel.update(
{ log: newLog },
{ where: { id: doc.id } },
);
}
return null;
});
}
public installOrUninstallDependency(
@@ -147,7 +157,7 @@ export default class DependenceService {
isInstall: boolean = true,
force: boolean = false,
) {
return runOneByOne(() => {
return taskLimit.runOneByOne(() => {
return new Promise(async (resolve) => {
const depIds = [dependency.id!];
const status = isInstall
@@ -155,15 +165,15 @@ export default class DependenceService {
: DependenceStatus.removing;
await DependenceModel.update({ status }, { where: { id: depIds } });
const socketMessageType = !force
const socketMessageType = isInstall
? 'installDependence'
: 'uninstallDependence';
const depName = dependency.name;
const depName = dependency.name.trim();
const depRunCommand = (
isInstall
? InstallDependenceCommandTypes
: unInstallDependenceCommandTypes
)[dependency.type as any];
)[dependency.type];
const actionText = isInstall ? '安装' : '删除';
const startTime = dayjs();
@@ -175,7 +185,39 @@ export default class DependenceService {
message,
references: depIds,
});
await this.updateLog(depIds, message);
this.updateLog(depIds, message);
// 判断是否已经安装过依赖
if (isInstall) {
const getCommandPrefix = GetDependenceCommandTypes[dependency.type];
const depVersionStr = versionDependenceCommandTypes[dependency.type];
const [_depName] = dependency.name.trim().split(depVersionStr);
const depInfo = (
await promiseExecSuccess(
dependency.type === DependenceTypes.linux
? `${getCommandPrefix} ${_depName}`
: `${getCommandPrefix} | grep "${_depName}"`,
)
).replace(/\s{2,}/, ' ');
if (depInfo) {
const endTime = dayjs();
const _message = `检测到已经安装 ${_depName}\n\n${depInfo}\n跳过安装\n\n依赖${actionText}成功,结束时间 ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)},耗时 ${endTime.diff(startTime, 'second')}`;
this.sockService.sendMessage({
type: socketMessageType,
message: _message,
references: depIds,
});
this.updateLog(depIds, _message);
await DependenceModel.update(
{ status: DependenceStatus.installed },
{ where: { id: depIds } },
);
return resolve(null);
}
}
const cp = spawn(`${depRunCommand} ${depName}`, {
shell: '/bin/bash',
@@ -187,7 +229,7 @@ export default class DependenceService {
message: data.toString(),
references: depIds,
});
await this.updateLog(depIds, data.toString());
this.updateLog(depIds, data.toString());
});
cp.stderr.on('data', async (data) => {
@@ -196,7 +238,7 @@ export default class DependenceService {
message: data.toString(),
references: depIds,
});
await this.updateLog(depIds, data.toString());
this.updateLog(depIds, data.toString());
});
cp.on('error', async (err) => {
@@ -205,7 +247,7 @@ export default class DependenceService {
message: JSON.stringify(err),
references: depIds,
});
await this.updateLog(depIds, JSON.stringify(err));
this.updateLog(depIds, JSON.stringify(err));
});
cp.on('close', async (code) => {
@@ -221,7 +263,7 @@ export default class DependenceService {
message,
references: depIds,
});
await this.updateLog(depIds, message);
this.updateLog(depIds, message);
let status = null;
if (isSucceed) {
+2 -2
View File
@@ -9,7 +9,7 @@ import {
Task,
} from 'toad-scheduler';
import dayjs from 'dayjs';
import { runWithCpuLimit } from '../shared/pLimit';
import taskLimit from '../shared/pLimit';
import { spawn } from 'cross-spawn';
interface ScheduleTaskType {
@@ -49,7 +49,7 @@ export default class ScheduleService {
callbacks: TaskCallbacks = {},
completionTime: 'start' | 'end' = 'end',
) {
return runWithCpuLimit(() => {
return taskLimit.runWithCpuLimit(() => {
return new Promise(async (resolve, reject) => {
try {
const startTime = dayjs();
+87 -26
View File
@@ -1,8 +1,14 @@
import { Response } from 'express';
import { Service, Inject } from 'typedi';
import winston from 'winston';
import config from '../config';
import * as fs from 'fs';
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
import {
AuthDataType,
AuthInfo,
AuthInstance,
AuthModel,
AuthModelInfo,
} from '../data/auth';
import { NotificationInfo } from '../data/notify';
import NotificationService from './notify';
import ScheduleService, { TaskCallbacks } from './schedule';
@@ -14,8 +20,12 @@ import {
killTask,
parseContentVersion,
parseVersion,
promiseExec,
} from '../config/util';
import { TASK_COMMAND } from '../config/const';
import taskLimit from '../shared/pLimit';
import tar from 'tar';
import path from 'path';
@Service()
export default class SystemService {
@@ -26,22 +36,22 @@ export default class SystemService {
@Inject('logger') private logger: winston.Logger,
private scheduleService: ScheduleService,
private sockService: SockService,
) {}
) { }
public async getLogRemoveFrequency() {
const doc = await this.getDb({ type: AuthDataType.removeLogFrequency });
return doc || {};
public async getSystemConfig() {
const doc = await this.getDb({ type: AuthDataType.systemConfig });
return doc || ({} as AuthInstance);
}
private async updateAuthDb(payload: AuthInfo): Promise<any> {
private async updateAuthDb(payload: AuthInfo): Promise<AuthInstance> {
await AuthModel.upsert({ ...payload });
const doc = await this.getDb({ type: payload.type });
return doc;
}
public async getDb(query: any): Promise<any> {
public async getDb(query: any): Promise<AuthInstance> {
const doc: any = await AuthModel.findOne({ where: { ...query } });
return doc && (doc.get({ plain: true }) as any);
return doc && doc.get({ plain: true });
}
public async updateNotificationMode(notificationInfo: NotificationInfo) {
@@ -62,25 +72,30 @@ export default class SystemService {
}
}
public async updateLogRemoveFrequency(frequency: number) {
const oDoc = await this.getLogRemoveFrequency();
public async updateSystemConfig(info: AuthModelInfo) {
const oDoc = await this.getSystemConfig();
const result = await this.updateAuthDb({
...oDoc,
type: AuthDataType.removeLogFrequency,
info: { frequency },
type: AuthDataType.systemConfig,
info,
});
const cron = {
id: result.id,
name: '删除日志',
command: `ql rmlog ${frequency}`,
};
await this.scheduleService.cancelIntervalTask(cron);
if (frequency > 0) {
this.scheduleService.createIntervalTask(cron, {
days: frequency,
});
if (info.logRemoveFrequency) {
const cron = {
id: result.id,
name: '删除日志',
command: `ql rmlog ${info.logRemoveFrequency}`,
};
await this.scheduleService.cancelIntervalTask(cron);
if (info.logRemoveFrequency > 0) {
this.scheduleService.createIntervalTask(cron, {
days: info.logRemoveFrequency,
});
}
}
return { code: 200, data: { ...cron } };
if (info.cronConcurrency) {
await taskLimit.setCustomLimit(info.cronConcurrency);
}
return { code: 200, data: info };
}
public async checkUpdate() {
@@ -96,7 +111,7 @@ export default class SystemService {
},
);
lastVersionContent = await parseContentVersion(result.body);
} catch (error) {}
} catch (error) { }
if (!lastVersionContent) {
lastVersionContent = currentVersionContent;
@@ -142,7 +157,7 @@ export default class SystemService {
}
public async updateSystem() {
const cp = spawn('ql -l update', { shell: '/bin/bash' });
const cp = spawn('ql -l update false', { shell: '/bin/bash' });
cp.stdout.on('data', (data) => {
this.sockService.sendMessage({
@@ -168,6 +183,33 @@ export default class SystemService {
return { code: 200 };
}
public async reloadSystem(target: 'system' | 'data') {
const cp = spawn(`ql -l reload ${target || ''}`, { shell: '/bin/bash' });
cp.stdout.on('data', (data) => {
this.sockService.sendMessage({
type: 'reloadSystem',
message: data.toString(),
});
});
cp.stderr.on('data', (data) => {
this.sockService.sendMessage({
type: 'reloadSystem',
message: data.toString(),
});
});
cp.on('error', (err) => {
this.sockService.sendMessage({
type: 'reloadSystem',
message: JSON.stringify(err),
});
});
return { code: 200 };
}
public async notify({ title, content }: { title: string; content: string }) {
const isSuccess = await this.notificationService.notify(title, content);
if (isSuccess) {
@@ -211,4 +253,23 @@ export default class SystemService {
return { code: 400, message: '任务未找到' };
}
}
public async exportData(res: Response) {
try {
await tar.create({ gzip: true, file: config.dataTgzFile, cwd: config.rootPath }, ['data'])
res.download(config.dataTgzFile);
} catch (error: any) {
return res.send({ code: 400, message: error.message });
}
}
public async importData() {
try {
await promiseExec(`rm -rf ${path.join(config.tmpPath, 'data')}`);
await tar.x({ file: config.dataTgzFile, cwd: config.tmpPath });
return { code: 200 };
} catch (error: any) {
return { code: 400, message: error.message };
}
}
}
+6 -8
View File
@@ -10,7 +10,7 @@ import config from '../config';
import * as fs from 'fs';
import jwt from 'jsonwebtoken';
import { authenticator } from '@otplib/preset-default';
import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth';
import { AuthDataType, AuthInfo, AuthModel, AuthModelInfo, LoginStatus } from '../data/auth';
import { NotificationInfo } from '../data/notify';
import NotificationService from './notify';
import { Request } from 'express';
@@ -27,7 +27,7 @@ export default class UserService {
@Inject('logger') private logger: winston.Logger,
private scheduleService: ScheduleService,
private sockService: SockService,
) {}
) { }
public async login(
payloads: {
@@ -119,8 +119,7 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${req.platform
}端 登录成功,ip地址 ${ip}`,
);
await this.getLoginLog();
@@ -148,8 +147,7 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${req.platform
}端 登录失败,ip地址 ${ip}`,
);
await this.getLoginLog();
@@ -187,12 +185,12 @@ export default class UserService {
});
}
public async getLoginLog(): Promise<AuthInfo[]> {
public async getLoginLog(): Promise<Array<AuthModelInfo | undefined>> {
const docs = await AuthModel.findAll({
where: { type: AuthDataType.loginLog },
});
if (docs && docs.length > 0) {
const result = docs.sort((a, b) => b.info.timestamp - a.info.timestamp);
const result = docs.sort((a, b) => b.info!.timestamp! - a.info!.timestamp!);
if (result.length > 100) {
await AuthModel.destroy({
where: { id: result[result.length - 1].id },
+32 -11
View File
@@ -1,17 +1,38 @@
import pLimit from "p-limit";
import os from 'os';
import { AuthDataType, AuthModel } from "../data/auth";
const cpuLimit = pLimit(os.cpus().length);
const oneLimit = pLimit(1);
class TaskLimit {
private oneLimit = pLimit(1);
private updateLogLimit = pLimit(1);
private cpuLimit = pLimit(Math.max(os.cpus().length, 4));
export function runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
return cpuLimit(() => {
return fn();
});
constructor() {
this.setCustomLimit();
}
public async setCustomLimit(limit?: number) {
if (limit) {
this.cpuLimit = pLimit(limit);
return;
}
const doc = await AuthModel.findOne({ where: { type: AuthDataType.systemConfig } });
if (doc?.info?.cronConcurrency) {
this.cpuLimit = pLimit(doc?.info?.cronConcurrency);
}
}
public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
return this.cpuLimit(fn);
}
public runOneByOne<T>(fn: () => Promise<T>): Promise<T> {
return this.oneLimit(fn);
}
public updateDepLog<T>(fn: () => Promise<T>): Promise<T> {
return this.updateLogLimit(fn);
}
}
export function runOneByOne<T>(fn: () => Promise<T>): Promise<T> {
return oneLimit(() => {
return fn();
});
}
export default new TaskLimit();
+2 -2
View File
@@ -1,9 +1,9 @@
import { spawn } from 'cross-spawn';
import { runWithCpuLimit } from "./pLimit";
import taskLimit from "./pLimit";
import Logger from '../loaders/logger';
export function runCron(cmd: string): Promise<number> {
return runWithCpuLimit(() => {
return taskLimit.runWithCpuLimit(() => {
return new Promise(async (resolve: any) => {
Logger.silly('运行命令: ' + cmd);
+4 -3
View File
@@ -4,8 +4,6 @@ dir_shell=/ql/shell
. $dir_shell/share.sh
link_shell
export isFirstStartServer=true
echo -e "======================1. 检测配置文件========================\n"
make_dir /etc/nginx/conf.d
make_dir /run/nginx
@@ -25,7 +23,10 @@ if [[ "$is_equal_registry" == "" ]]; then
cd && pnpm config set registry $NpmMirror
pnpm install -g
fi
update_depend
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
cp -f $dir_sample/package.json $dir_scripts/package.json
npm_install_2 $dir_scripts
fi
echo
echo -e "======================3. 启动nginx========================\n"
+1 -1
View File
@@ -14,7 +14,7 @@ http {
server_tokens off;
client_max_body_size 20m;
client_max_body_size 4096m;
client_body_buffer_size 20m;
keepalive_timeout 65;
+6 -2
View File
@@ -91,6 +91,7 @@
"serve-handler": "^6.1.3",
"sockjs": "^0.3.24",
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3",
"tar": "^6.1.15",
"toad-scheduler": "^1.6.0",
"typedi": "^0.10.0",
"uuid": "^8.3.2",
@@ -108,6 +109,7 @@
"@types/cross-spawn": "^6.0.2",
"@types/express": "^4.17.13",
"@types/express-jwt": "^6.0.4",
"@types/file-saver": "^2.0.5",
"@types/js-yaml": "^4.0.5",
"@types/jsonwebtoken": "^8.5.8",
"@types/lodash": "^4.14.185",
@@ -123,15 +125,18 @@
"@types/serve-handler": "^6.1.1",
"@types/sockjs": "^0.3.33",
"@types/sockjs-client": "^1.5.1",
"@types/tar": "^6.1.5",
"@types/uuid": "^8.3.4",
"@umijs/max": "^4.0.55",
"@umijs/max": "^4.0.72",
"@umijs/ssr-darkreader": "^4.9.45",
"ansi-to-react": "^6.1.6",
"antd": "^4.24.8",
"antd-img-crop": "^4.2.3",
"axios": "^1.4.0",
"codemirror": "^5.65.2",
"compression-webpack-plugin": "9.2.0",
"concurrently": "^7.0.0",
"file-saver": "^2.0.5",
"lint-staged": "^13.0.3",
"monaco-editor": "0.33.0",
"nodemon": "^2.0.15",
@@ -154,7 +159,6 @@
"tslib": "^2.4.0",
"tsx": "^3.12.3",
"typescript": "4.8.4",
"umi-request": "^1.4.0",
"vh-check": "^2.0.5",
"virtualizedtableforantd4": "1.3.0",
"webpack": "^5.70.0",
+152 -124
View File
@@ -109,6 +109,9 @@ dependencies:
sqlite3:
specifier: git+https://github.com/whyour/node-sqlite3.git#v1.0.3
version: github.com/whyour/node-sqlite3/3a00af0b5d7603b7f1b290032507320b18a6b741
tar:
specifier: ^6.1.15
version: 6.1.15
toad-scheduler:
specifier: ^1.6.0
version: 1.6.1
@@ -156,6 +159,9 @@ devDependencies:
'@types/express-jwt':
specifier: ^6.0.4
version: 6.0.4
'@types/file-saver':
specifier: ^2.0.5
version: 2.0.5
'@types/js-yaml':
specifier: ^4.0.5
version: 4.0.5
@@ -201,12 +207,15 @@ devDependencies:
'@types/sockjs-client':
specifier: ^1.5.1
version: 1.5.1
'@types/tar':
specifier: ^6.1.5
version: 6.1.5
'@types/uuid':
specifier: ^8.3.4
version: 8.3.4
'@umijs/max':
specifier: ^4.0.55
version: 4.0.70(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
specifier: ^4.0.72
version: 4.0.72(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/ssr-darkreader':
specifier: ^4.9.45
version: 4.9.45
@@ -219,6 +228,9 @@ devDependencies:
antd-img-crop:
specifier: ^4.2.3
version: 4.12.2(antd@4.24.10)(react-dom@18.2.0)(react@18.2.0)
axios:
specifier: ^1.4.0
version: 1.4.0
codemirror:
specifier: ^5.65.2
version: 5.65.13
@@ -228,6 +240,9 @@ devDependencies:
concurrently:
specifier: ^7.0.0
version: 7.6.0
file-saver:
specifier: ^2.0.5
version: 2.0.5
lint-staged:
specifier: ^13.0.3
version: 13.2.2
@@ -294,9 +309,6 @@ devDependencies:
typescript:
specifier: 4.8.4
version: 4.8.4
umi-request:
specifier: ^1.4.0
version: 1.4.0
vh-check:
specifier: ^2.0.5
version: 2.0.5
@@ -3450,7 +3462,7 @@ packages:
react:
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@babel/runtime': 7.22.3
hoist-non-react-statics: 3.3.2
react: 18.1.0
react-is: 16.13.1
@@ -3465,7 +3477,7 @@ packages:
react:
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@babel/runtime': 7.22.3
hoist-non-react-statics: 3.3.2
react: 18.2.0
react-is: 16.13.1
@@ -3879,7 +3891,7 @@ packages:
postcss:
optional: true
dependencies:
'@babel/core': 7.21.0
'@babel/core': 7.22.1
postcss: 8.4.24
postcss-syntax: 0.36.2(postcss@8.4.24)
transitivePeerDependencies:
@@ -4248,6 +4260,10 @@ packages:
'@types/qs': 6.9.7
'@types/serve-static': 1.15.1
/@types/file-saver@2.0.5:
resolution: {integrity: sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==}
dev: true
/@types/graceful-fs@4.1.6:
resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
dependencies:
@@ -4457,6 +4473,13 @@ packages:
'@types/node': 17.0.45
dev: true
/@types/tar@6.1.5:
resolution: {integrity: sha512-qm2I/RlZij5RofuY7vohTpYNaYcrSQlN2MyjucQc7ZweDwaEWkdN/EeNh6e9zjK6uEm6PwjdMXkcj05BxZdX1Q==}
dependencies:
'@types/node': 17.0.45
minipass: 4.2.8
dev: true
/@types/triple-beam@1.3.2:
resolution: {integrity: sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==}
dev: false
@@ -4692,21 +4715,21 @@ packages:
eslint-visitor-keys: 3.4.1
dev: true
/@umijs/ast@4.0.70:
resolution: {integrity: sha512-scrAlEGzgD3Ks/cRSJZza5QCPsdnZdtgPNcgpPU8xV4mXyWGyg98u9o2EE08awQDjqlPbHIvo7ZVZkvcC9nxnQ==}
/@umijs/ast@4.0.72:
resolution: {integrity: sha512-WatRvU09vsx4Hlu5hemPA7a+QK4pJvzmQz/9LxN/KVgn+wZXi717qHFLu5eoV6XO7HlFZaEBGq2aHpDj0ngA8w==}
dependencies:
'@umijs/bundler-utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
transitivePeerDependencies:
- supports-color
dev: true
/@umijs/babel-preset-umi@4.0.70:
resolution: {integrity: sha512-xF2KHPw3VH65nziiC8Bu68dQsm2+/DNqYC4upgEyiuNwsLx4P+yzffFGkKfDsEpWAtYqAfClDT9m6o46inRxrA==}
/@umijs/babel-preset-umi@4.0.72:
resolution: {integrity: sha512-9L2zwcux8iMOD9ji6YK1kiFbA9ZI1o0O/9NJo69QdCv3N41ENXWeNfXVu72GD6+mytmKZEWNCtD0qAzBTXj5jQ==}
dependencies:
'@babel/runtime': 7.21.0
'@bloomberg/record-tuple-polyfill': 0.0.4
'@umijs/bundler-utils': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
'@umijs/utils': 4.0.72
babel-plugin-styled-components: 2.1.1
core-js: 3.28.0
transitivePeerDependencies:
@@ -4714,12 +4737,12 @@ packages:
- supports-color
dev: true
/@umijs/bundler-esbuild@4.0.70:
resolution: {integrity: sha512-elDtAGD/sVgY626E6OfhSmZbgXYmYBIe1uiTunQrbWzlHUP2lQ9iB4wJ6GGcoqNU/7itKXRf3rcihCIq2DCDtQ==}
/@umijs/bundler-esbuild@4.0.72:
resolution: {integrity: sha512-T7nonD78F6RG94xATF5n/KkdJCOVYukokGFDAd4nPTNhbdYVakgNqwpRVwLEFofYMAN9uJ7rIUKFEt3qMpFR7w==}
hasBin: true
dependencies:
'@umijs/bundler-utils': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
'@umijs/utils': 4.0.72
enhanced-resolve: 5.9.3
postcss: 8.4.24
postcss-flexbugs-fixes: 5.0.2(postcss@8.4.24)
@@ -4728,10 +4751,10 @@ packages:
- supports-color
dev: true
/@umijs/bundler-utils@4.0.70:
resolution: {integrity: sha512-ZvM2Ga+BoHo8OonrmptCR1Bo/mjbtbXJVJmMQCSrb/mtn2ZFvOGddZ/0YTL+ysXnBIA7vALnlNhGWnvArCls6w==}
/@umijs/bundler-utils@4.0.72:
resolution: {integrity: sha512-ROGNx6dy3tiMwhC29F6xvWC9O3F4CXnND2raupljTk+QDuvc1hmwUiB/gmCWrts/98cKN2959js03ivIPn9NNw==}
dependencies:
'@umijs/utils': 4.0.70
'@umijs/utils': 4.0.72
esbuild: 0.17.19
regenerate: 1.4.2
regenerate-unicode-properties: 10.1.0
@@ -4740,13 +4763,13 @@ packages:
- supports-color
dev: true
/@umijs/bundler-vite@4.0.70(@types/node@17.0.45):
resolution: {integrity: sha512-19aDfNxPbOVfFttNSHEp9DnZFB/fFMEpsH6nBPkOIkXhr0UnmaWeOk7HJPbbT9T7vBA0mPxHA/vEIZPSWy84PQ==}
/@umijs/bundler-vite@4.0.72(@types/node@17.0.45):
resolution: {integrity: sha512-dsinf6yMW66ZAijHYTrNgzwPfSBXtEd+UxsB2gZFgsdVDDvE19Htuee4HqBCGBr+W66SLn99addckT8mzivfiA==}
hasBin: true
dependencies:
'@svgr/core': 6.5.1
'@umijs/bundler-utils': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
'@umijs/utils': 4.0.72
'@vitejs/plugin-react': 4.0.0(vite@4.3.1)
less: 4.1.3
postcss-preset-env: 7.5.0(postcss@8.4.24)
@@ -4763,8 +4786,8 @@ packages:
- terser
dev: true
/@umijs/bundler-webpack@4.0.70(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-QHaIEFesPzFSHnIMhHui9Ru54VYwNdnO683CzSoIoBjYAIcpDsGM7Tl3hIesujbfhrZl2eCJQVc//ZJ0SEFiTw==}
/@umijs/bundler-webpack@4.0.72(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-0oTvna4AdMoSvRWeF0E8u8/aKASwll226DkLKvjYDs5FE9nhbxIHevsoSRX7TnlEDPNfV/a3kVRnRG1dR2oJnQ==}
hasBin: true
dependencies:
'@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.14.0)(sockjs-client@1.6.1)(webpack@5.85.1)
@@ -4772,11 +4795,11 @@ packages:
'@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1)
'@svgr/plugin-svgo': 6.5.1(@svgr/core@6.5.1)
'@types/hapi__joi': 17.1.9
'@umijs/babel-preset-umi': 4.0.70
'@umijs/bundler-utils': 4.0.70
'@umijs/babel-preset-umi': 4.0.72
'@umijs/bundler-utils': 4.0.72
'@umijs/case-sensitive-paths-webpack-plugin': 1.0.1
'@umijs/mfsu': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/mfsu': 4.0.72
'@umijs/utils': 4.0.72
cors: 2.8.5
css-loader: 6.7.1(webpack@5.85.1)
es5-imcompatible-versions: 0.1.83
@@ -4805,11 +4828,11 @@ packages:
resolution: {integrity: sha512-kDKJ8yTarxwxGJDInG33hOpaQRZ//XpNuuznQ/1Mscypw6kappzFmrBr2dOYave++K7JHouoANF354UpbEQw0Q==}
dev: true
/@umijs/core@4.0.70:
resolution: {integrity: sha512-l2Hv8dRAJ6F9FD7VCUBAD2ars+yRBf7woQu8O88cWOgLbI/YOYEnt1n74g0uTfQBFdvnKNAsZhTY8yX8i0olGQ==}
/@umijs/core@4.0.72:
resolution: {integrity: sha512-E4+V/SuM8hcnmX/B+phU24LtNvV5Y7DY2ggtkQbJXtEGVFIImeC2ZEEWvGCFzq3soRCCTYG3Iwun4/TpInWIdg==}
dependencies:
'@umijs/bundler-utils': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
'@umijs/utils': 4.0.72
transitivePeerDependencies:
- supports-color
dev: true
@@ -4850,6 +4873,7 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
requiresBuild: true
dev: true
optional: true
@@ -4859,6 +4883,7 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
requiresBuild: true
dev: true
optional: true
@@ -4868,6 +4893,7 @@ packages:
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
requiresBuild: true
dev: true
optional: true
@@ -4877,6 +4903,7 @@ packages:
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
requiresBuild: true
dev: true
optional: true
@@ -4917,19 +4944,19 @@ packages:
/@umijs/history@5.3.1:
resolution: {integrity: sha512-/e0cEGrR2bIWQD7pRl3dl9dcyRGeC9hoW0OCvUTT/hjY0EfUrkd6G8ZanVghPMpDuY5usxq9GVcvrT8KNXLWvA==}
dependencies:
'@babel/runtime': 7.21.0
'@babel/runtime': 7.22.3
query-string: 6.14.1
dev: true
/@umijs/lint@4.0.70(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4):
resolution: {integrity: sha512-89+1BC/taDfEcubrWGXI6Yzk6hVb3br21jx+7eYYOwJjOXDMULy3+8GCFqZN+TxIz9WXOG3NFHehcFehx9YPwg==}
/@umijs/lint@4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4):
resolution: {integrity: sha512-kH3L81Rex+jj5WeyJjR2G6yI1/0KFpr91ZtXeMy9Iyd4G7mEUJl3Fl/9iUEwZ2sgUa7kEJ+28H43eER5W4A6bg==}
dependencies:
'@babel/core': 7.21.0
'@babel/eslint-parser': 7.19.1(@babel/core@7.21.0)(eslint@8.35.0)
'@stylelint/postcss-css-in-js': 0.38.0(postcss-syntax@0.36.2)(postcss@8.4.24)
'@typescript-eslint/eslint-plugin': 5.48.1(@typescript-eslint/parser@5.48.1)(eslint@8.35.0)(typescript@4.8.4)
'@typescript-eslint/parser': 5.48.1(eslint@8.35.0)(typescript@4.8.4)
'@umijs/babel-preset-umi': 4.0.70
'@umijs/babel-preset-umi': 4.0.72
eslint-plugin-jest: 27.2.1(@typescript-eslint/eslint-plugin@5.48.1)(eslint@8.35.0)(typescript@4.8.4)
eslint-plugin-react: 7.32.2(eslint@8.35.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.35.0)
@@ -4950,16 +4977,16 @@ packages:
- typescript
dev: true
/@umijs/max@4.0.70(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-5Fsob8SV1OLLunQOs4jmRQfCbp/pefkmaRkDEIisklBkSdEz+Oj6fa+TzijBoSR1h71aHg1iJIu5UWLRDOhKSg==}
/@umijs/max@4.0.72(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-5e5BwaSBCdGWlj0PZ2I73+WLLe/8AibgDt2OtXc86YujRkhSKGY740pYgqy2PAszV8MjDMg8Sqs+gZG13C5z8w==}
hasBin: true
dependencies:
'@umijs/lint': 4.0.70(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4)
'@umijs/plugins': 4.0.70(@types/react-dom@18.2.4)(@types/react@18.2.8)(antd@4.24.10)(react-dom@18.2.0)(react@18.2.0)
'@umijs/lint': 4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4)
'@umijs/plugins': 4.0.72(@types/react-dom@18.2.4)(@types/react@18.2.8)(antd@4.24.10)(react-dom@18.2.0)(react@18.2.0)
antd: 4.24.10(react-dom@18.2.0)(react@18.2.0)
eslint: 8.35.0
stylelint: 14.8.2
umi: 4.0.70(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@4.8.4)(webpack@5.85.1)
umi: 4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@4.8.4)(webpack@5.85.1)
transitivePeerDependencies:
- '@babel/core'
- '@reduxjs/toolkit'
@@ -5001,26 +5028,26 @@ packages:
- webpack-plugin-serve
dev: true
/@umijs/mfsu@4.0.70:
resolution: {integrity: sha512-Kg4SfEvU90DW9Nxfr/WozWduQmGvKAWEyUUrn6ND8i3AapUA8MOYDWRVZ/61HKHBcPt9Y6ZPg2cLWSFeNk529g==}
/@umijs/mfsu@4.0.72:
resolution: {integrity: sha512-oyWNIRVK6/FCewMZ8jjn5ICTWWZ7VWP7javN1/zLczvPy2s3cMp5lZuN2Ca39v2Px/DfkCBKSiB/EI6Kts0G8A==}
dependencies:
'@umijs/bundler-esbuild': 4.0.70
'@umijs/bundler-utils': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/bundler-esbuild': 4.0.72
'@umijs/bundler-utils': 4.0.72
'@umijs/utils': 4.0.72
enhanced-resolve: 5.9.3
is-equal: 1.6.4
transitivePeerDependencies:
- supports-color
dev: true
/@umijs/plugin-run@4.0.70:
resolution: {integrity: sha512-9hTRdY3+UVqptiK7SFFqojGDFMrHngyPPHQNu1amxIIi2oBEim4mLq3XOGPzy4c3hA7K6BHLEZlWSsmTGpbudw==}
/@umijs/plugin-run@4.0.72:
resolution: {integrity: sha512-z5p5z8BNcDb5LxbbeLoB7EfDesaSxSO9zFa3IbulgGFM/qfnaPPwfUEJDYLXkggxiDhbiUP95TDvC1R6AGDWBg==}
dependencies:
tsx: 3.12.7
dev: true
/@umijs/plugins@4.0.70(@types/react-dom@18.2.4)(@types/react@18.2.8)(antd@4.24.10)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3Hu79VZJpwcn9HFZWH/katqrk6YryMpfMYJOdgzbdyAW4mipPt9Oa9x+ZYVWgr754kqY2X4IFqLhb3Y6+rO0Jg==}
/@umijs/plugins@4.0.72(@types/react-dom@18.2.4)(@types/react@18.2.8)(antd@4.24.10)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-AaCEjVdvSF+ilnATisBApMYMWVsLvBD4BUjkNV3SIaKHCLpSDeVNVyIFBm4A8tWLz+tz0p25Cu7SWGuggqQdQA==}
dependencies:
'@ahooksjs/use-request': 2.8.15(react@18.2.0)
'@ant-design/antd-theme-variable': 1.0.0
@@ -5030,7 +5057,7 @@ packages:
'@ant-design/pro-components': 2.5.5(antd@4.24.10)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query': 4.29.12(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query-devtools': 4.29.12(@tanstack/react-query@4.29.12)(react-dom@18.2.0)(react@18.2.0)
'@umijs/bundler-utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
'@umijs/valtio': 1.0.3(react@18.2.0)
antd-dayjs-webpack-plugin: 1.0.6(dayjs@1.11.8)
axios: 0.27.2
@@ -5067,28 +5094,28 @@ packages:
- supports-color
dev: true
/@umijs/preset-umi@4.0.70(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-N9TQbuaZNz+3HTtXm1QG+LpALJx/XLEJk1CYfff8Ey3hgQVRtvR/hTo6EhsUtovoqdcfBClxidHAfy9dvJ9Ebw==}
/@umijs/preset-umi@4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-DDRzPCyP2K667YrOHrexAPcEacTdA9/TBn73erwreZpi+c4qhFZ2eYQo27SAGAhfZlYDYthL8JAfbgfhYmLo1A==}
dependencies:
'@iconify/utils': 2.1.1
'@svgr/core': 6.5.1
'@umijs/ast': 4.0.70
'@umijs/babel-preset-umi': 4.0.70
'@umijs/bundler-esbuild': 4.0.70
'@umijs/bundler-utils': 4.0.70
'@umijs/bundler-vite': 4.0.70(@types/node@17.0.45)
'@umijs/bundler-webpack': 4.0.70(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/core': 4.0.70
'@umijs/ast': 4.0.72
'@umijs/babel-preset-umi': 4.0.72
'@umijs/bundler-esbuild': 4.0.72
'@umijs/bundler-utils': 4.0.72
'@umijs/bundler-vite': 4.0.72(@types/node@17.0.45)
'@umijs/bundler-webpack': 4.0.72(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/core': 4.0.72
'@umijs/did-you-know': 1.0.3
'@umijs/es-module-parser': 0.0.7
'@umijs/history': 5.3.1
'@umijs/mfsu': 4.0.70
'@umijs/plugin-run': 4.0.70
'@umijs/renderer-react': 4.0.70(react-dom@18.1.0)(react@18.1.0)
'@umijs/server': 4.0.70
'@umijs/mfsu': 4.0.72
'@umijs/plugin-run': 4.0.72
'@umijs/renderer-react': 4.0.72(react-dom@18.1.0)(react@18.1.0)
'@umijs/server': 4.0.72
'@umijs/ui': 3.0.1
'@umijs/utils': 4.0.70
'@umijs/zod2ts': 4.0.70
'@umijs/utils': 4.0.72
'@umijs/zod2ts': 4.0.72
babel-plugin-dynamic-import-node: 2.3.3
click-to-react-component: 1.0.8(@types/react@18.2.8)(react-dom@18.1.0)(react@18.1.0)
core-js: 3.28.0
@@ -5124,8 +5151,8 @@ packages:
- webpack-plugin-serve
dev: true
/@umijs/renderer-react@4.0.70(react-dom@18.1.0)(react@18.1.0):
resolution: {integrity: sha512-TcqCd6uwkVyy7vvZ+yi49q/dcfxHOXihz6GJbOBkH4grkeaX2hwisJkU6RG1kwXeyv0ShIZCi4ewiCTnCSJb2g==}
/@umijs/renderer-react@4.0.72(react-dom@18.1.0)(react@18.1.0):
resolution: {integrity: sha512-eOJgxbwFR23wWMvR2FFcSy0Ba8d7MtOit68SE+PfX43Gy/51Ywsa1BF1G9QNgs3UAjWKK5DpK1UCHrgQ4cgegA==}
peerDependencies:
react: '>=16.8 || 18'
react-dom: '>=16.8 || 18'
@@ -5144,8 +5171,8 @@ packages:
react-router-dom: 6.3.0(react-dom@18.1.0)(react@18.1.0)
dev: true
/@umijs/renderer-react@4.0.70(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-TcqCd6uwkVyy7vvZ+yi49q/dcfxHOXihz6GJbOBkH4grkeaX2hwisJkU6RG1kwXeyv0ShIZCi4ewiCTnCSJb2g==}
/@umijs/renderer-react@4.0.72(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-eOJgxbwFR23wWMvR2FFcSy0Ba8d7MtOit68SE+PfX43Gy/51Ywsa1BF1G9QNgs3UAjWKK5DpK1UCHrgQ4cgegA==}
peerDependencies:
react: '>=16.8 || 18'
react-dom: '>=16.8 || 18'
@@ -5177,10 +5204,10 @@ packages:
resolution: {integrity: sha512-+1ixf1BTOLuH+ORb4x8vYMPeIt38n9q0fJDwhv9nSxrV46mxbLF0nmELIo9CKQB2gHfuC4+hww6xejJ6VYnBHQ==}
dev: true
/@umijs/server@4.0.70:
resolution: {integrity: sha512-aoTjXCe1hDjWTNxJ8c5XZRbur+H7feifG07SBe+2kc6EIpykvtRwgp7dZmMHlgmv7ptaXmSAZjWAUDdS0NhLyg==}
/@umijs/server@4.0.72:
resolution: {integrity: sha512-J6seC7HPZIRoDirCEelEdXzeTJsvg5FpxRkV3BP+ZS3xsg140ckGQRdCzqykJ2ZZHjhxpdJsLtdleAmE8Iq6Ew==}
dependencies:
'@umijs/bundler-utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
history: 5.3.0
react: 18.1.0
react-dom: 18.1.0(react@18.1.0)
@@ -5193,13 +5220,13 @@ packages:
resolution: {integrity: sha512-XlcwzSYQ/SRZpHdwIyMDS4FOGX5kP4U/2g2mykyn/iPQTK4xTiQAyBu6UnnDnn7d5P8s7Atzh1C7H0ETNOypJg==}
dev: true
/@umijs/test@4.0.70:
resolution: {integrity: sha512-DFu65yo8QIPKvw/p0/7Tm87ViLk+fCtfbWMQ8e4o2u17mCKgmaQP6X+GCHWs94lgtRYHjM3zHjUUvM+UKynLHg==}
/@umijs/test@4.0.72:
resolution: {integrity: sha512-sSknbprNSQhVcyIIfOlRMay9yyG189XdmIgoJWHG0sqKOaJLt0iEJ4RkOWGv9YVnW7M06cE6+8+FGUEF8/lFXw==}
dependencies:
'@babel/plugin-transform-modules-commonjs': 7.21.2
'@jest/types': 27.5.1
'@umijs/bundler-utils': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
'@umijs/utils': 4.0.72
babel-jest: 29.5.0
esbuild: 0.17.19
identity-obj-proxy: 3.0.0
@@ -5224,8 +5251,8 @@ packages:
react: 18.2.0
dev: true
/@umijs/utils@4.0.70:
resolution: {integrity: sha512-ZfDrtE7GtfYsdd5QwJiZHLMql8ZbyzUw37S7eCgIl5RTOxec1Ojqbzpfis7j8nyWyMGd/PpsuUl5909gA0U9bg==}
/@umijs/utils@4.0.72:
resolution: {integrity: sha512-+BOOGCipnr3iEzAliYrfFQeyQd3DrT1vMMXlsBqyD3Qh1owrSb/FsTvFTUYU0jrVgBc3MR5UneEBcPbqxq36Pw==}
dependencies:
chokidar: 3.5.3
pino: 7.11.0
@@ -5239,8 +5266,8 @@ packages:
- react
dev: true
/@umijs/zod2ts@4.0.70:
resolution: {integrity: sha512-W7Uvyb9Rx3OjUuxrgTaUdkffHNH8yEVG2TW0AgEirNL0os/mC6Wp60r5lRCftgVe+sh9d6L0x2eqp05065fTzg==}
/@umijs/zod2ts@4.0.72:
resolution: {integrity: sha512-qjfoAT7yODzKaj9AxOM0qh8e3rpPe7xvUq6ux+0yaxNrt+JbjC6gy66S3f836NpforMBCWEOuSLbfdyVhzKxNQ==}
dev: true
/@vitejs/plugin-react@4.0.0(vite@4.3.1):
@@ -5829,6 +5856,16 @@ packages:
- debug
dev: true
/axios@1.4.0:
resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==}
dependencies:
follow-redirects: 1.15.2
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: true
/babel-jest@29.5.0:
resolution: {integrity: sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -7404,8 +7441,11 @@ packages:
/encoding@0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
requiresBuild: true
dependencies:
iconv-lite: 0.6.3
dev: false
optional: true
/end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -8085,6 +8125,10 @@ packages:
flat-cache: 3.0.4
dev: true
/file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: true
/file-uri-to-path@2.0.0:
resolution: {integrity: sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==}
engines: {node: '>= 6'}
@@ -8628,7 +8672,7 @@ packages:
/history@5.3.0:
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
dependencies:
'@babel/runtime': 7.21.0
'@babel/runtime': 7.22.3
dev: true
/hmac-drbg@1.0.1:
@@ -9323,13 +9367,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/isomorphic-fetch@2.2.1:
resolution: {integrity: sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==}
dependencies:
node-fetch: 1.7.3
whatwg-fetch: 3.6.2
dev: true
/isomorphic-unfetch@4.0.2:
resolution: {integrity: sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==}
dependencies:
@@ -9645,6 +9682,7 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
requiresBuild: true
dev: true
optional: true
@@ -9654,6 +9692,7 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
requiresBuild: true
dev: true
optional: true
@@ -9663,6 +9702,7 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
requiresBuild: true
dev: true
optional: true
@@ -9672,6 +9712,7 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
requiresBuild: true
dev: true
optional: true
@@ -10212,6 +10253,11 @@ packages:
yallist: 4.0.0
dev: false
/minipass@4.2.8:
resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==}
engines: {node: '>=8'}
dev: true
/minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
@@ -10362,13 +10408,6 @@ packages:
engines: {node: '>=10.5.0'}
dev: true
/node-fetch@1.7.3:
resolution: {integrity: sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==}
dependencies:
encoding: 0.1.13
is-stream: 1.1.0
dev: true
/node-fetch@2.6.11:
resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==}
engines: {node: 4.x || >=6.0.0}
@@ -12855,7 +12894,7 @@ packages:
react-dom:
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@babel/runtime': 7.22.3
invariant: 2.2.4
prop-types: 15.8.1
react: 18.1.0
@@ -12875,7 +12914,7 @@ packages:
react-dom:
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@babel/runtime': 7.22.3
invariant: 2.2.4
prop-types: 15.8.1
react: 18.2.0
@@ -14702,28 +14741,21 @@ packages:
hasBin: true
dev: true
/umi-request@1.4.0:
resolution: {integrity: sha512-OknwtQZddZHi0Ggi+Vr/olJ7HNMx4AzlywyK0W3NZBT7B0stjeZ9lcztA85dBgdAj3KVk8uPJPZSnGaDjELhrA==}
dependencies:
isomorphic-fetch: 2.2.1
qs: 6.11.2
dev: true
/umi@4.0.70(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-e6GwzZXC1U+XPJhLaOMIr6IBWpi8mGap6ExRkApidfbYZ8HxilvrVHnaLUYSykp206RhZBnJWI7r99mYu3e5eQ==}
/umi@4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(eslint@8.35.0)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(stylelint@14.8.2)(typescript@4.8.4)(webpack@5.85.1):
resolution: {integrity: sha512-VXXwhHtZAApRR02c2F+uDv84m/Bf5g56pMKrArtIUFsrWM8hqS3f7whzgpdjzh0H8EFjpXwan0kJrkXFr6dAPg==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@babel/runtime': 7.21.0
'@umijs/bundler-utils': 4.0.70
'@umijs/bundler-webpack': 4.0.70(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/core': 4.0.70
'@umijs/lint': 4.0.70(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4)
'@umijs/preset-umi': 4.0.70(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/renderer-react': 4.0.70(react-dom@18.2.0)(react@18.2.0)
'@umijs/server': 4.0.70
'@umijs/test': 4.0.70
'@umijs/utils': 4.0.70
'@umijs/bundler-utils': 4.0.72
'@umijs/bundler-webpack': 4.0.72(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/core': 4.0.72
'@umijs/lint': 4.0.72(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.8.4)
'@umijs/preset-umi': 4.0.72(@types/node@17.0.45)(@types/react@18.2.8)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
'@umijs/renderer-react': 4.0.72(react-dom@18.2.0)(react@18.2.0)
'@umijs/server': 4.0.72
'@umijs/test': 4.0.72
'@umijs/utils': 4.0.72
prettier-plugin-organize-imports: 3.2.2(prettier@2.8.8)(typescript@4.8.4)
prettier-plugin-packagejson: 2.4.3(prettier@2.8.8)
transitivePeerDependencies:
@@ -15197,10 +15229,6 @@ packages:
resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
engines: {node: '>=0.8.0'}
/whatwg-fetch@3.6.2:
resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==}
dev: true
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
+4
View File
@@ -91,6 +91,10 @@ export TG_API_HOST=""
export DD_BOT_TOKEN=""
export DD_BOT_SECRET=""
## 企业微信反向代理地址
## (环境变量名 QYWX_ORIGIN)
export QYWX_ORIGIN=""
## 5. 企业微信机器人
## 官方说明文档:https://work.weixin.qq.com/api/doc/90000/90136/91770
## 下方填写密钥,企业微信推送 webhook 后面的 key
+6 -70
View File
@@ -2,6 +2,7 @@
## 目录
dir_root=$QL_DIR
dir_tmp=$dir_root/.tmp
dir_data=$dir_root/data
dir_shell=$dir_root/shell
dir_sample=$dir_root/sample
@@ -175,6 +176,7 @@ define_cmd() {
}
fix_config() {
make_dir $dir_tmp
make_dir $dir_static
make_dir $dir_data
make_dir $dir_config
@@ -263,6 +265,7 @@ npm_install_sub() {
else
pnpm install --loglevel error --production
fi
exit_status=$?
}
npm_install_2() {
@@ -283,90 +286,22 @@ diff_and_copy() {
fi
}
update_depend() {
local dir_current=$(pwd)
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
cp -f $dir_sample/package.json $dir_scripts/package.json
npm_install_2 $dir_scripts
fi
cd $dir_current
}
git_clone_scripts() {
local url="$1"
local dir="$2"
local branch="$3"
local proxy="$4"
[[ $branch ]] && local part_cmd="-b $branch "
echo -e "开始克隆仓库 $url$dir\n"
echo -e "开始拉取仓库 ${uniq_path}$dir\n"
set_proxy "$proxy"
git clone --depth=1 $part_cmd $url $dir
exit_status=$?
reset_branch "$branch" "$dir"
unset_proxy
}
git_pull_scripts() {
local dir_current=$(pwd)
local dir_work="$1"
local branch="$2"
local proxy="$3"
cd $dir_work
echo -e "开始更新仓库:$dir_work"
set_proxy "$proxy"
if [[ ! $branch ]]; then
branch=$(cd $dir_work && git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
fi
local pre_commit_id=$(git rev-parse --short HEAD)
reset_branch "$branch" "$dir_work"
git fetch --depth 1 origin $branch
exit_status=$?
reset_branch "$branch" "$dir_work"
local cur_commit_id=$(git rev-parse --short HEAD)
if [[ $cur_commit_id != $pre_commit_id ]]; then
exit_status=0
fi
unset_proxy
cd $dir_current
}
reset_romote_url() {
local dir_current=$(pwd)
local dir_work=$1
local url=$2
local branch="$3"
cd $dir_work
if [[ -d "$dir_work/.git" ]]; then
[[ -f ".git/index.lock" ]] && rm -f .git/index.lock >/dev/null
git remote set-url origin $url &>/dev/null
else
git init
git remote add origin $url &>/dev/null
fi
cd $dir_current
}
reset_branch() {
local branch="$1"
local part_cmd="origin/${branch}"
git remote set-branches origin $branch
git reset --hard $part_cmd &>/dev/null
git checkout -b $branch $part_cmd &>/dev/null
}
random_range() {
local beg=$1
local end=$2
@@ -378,7 +313,8 @@ reload_pm2() {
# 代理会影响 grpc 服务
unset_proxy
pm2 flush &>/dev/null
pm2 startOrGracefulReload $file_ecosystem_js
pm2 startOrGracefulReload $file_ecosystem_js --update-env
pm2 sendSignal SIGKILL panel &>/dev/null
}
diff_time() {
+70 -34
View File
@@ -130,13 +130,9 @@ update_repo() {
make_dir "${dir_scripts}/${uniq_path}"
local formatUrl="$url"
if [[ -d ${repo_path}/.git ]]; then
reset_romote_url ${repo_path} "${formatUrl}" "${branch}"
git_pull_scripts ${repo_path} "${branch}" "${proxy}"
else
rm -rf ${repo_path} &>/dev/null
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}"
fi
rm -rf ${repo_path} &>/dev/null
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}"
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新${repo_path}成功...\n"
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
@@ -233,62 +229,98 @@ usage() {
echo -e "9. $cmd_update resettfa # 禁用两步登录"
}
reload_qinglong() {
local reload_target="${1}"
local primary_branch="master"
if [[ "${QL_BRANCH}" == "develop" ]]; then
primary_branch="develop"
fi
if [[ "$reload_target" == 'system' ]]; then
cp -rf ${dir_tmp}/qinglong-${primary_branch}/* ${dir_root}/
rm -rf $dir_static/*
cp -rf ${dir_tmp}/qinglong-static-${primary_branch}/* ${dir_static}/
cp -f $file_config_sample $dir_config/config.sample.sh
fi
if [[ "$reload_target" == 'data' ]]; then
cp -rf ${dir_tmp}/data ${dir_root}/
fi
reload_pm2
}
## 更新qinglong
update_qinglong() {
rm -rf ${dir_tmp}/*
local mirror="gitee"
local downloadQLUrl="https://gitee.com/whyour/qinglong/repository/archive"
local downloadStaticUrl="https://gitee.com/whyour/qinglong-static/repository/archive"
local githubStatus=$(curl -s -m 2 -IL "https://google.com" | grep 200)
if [[ ! -z $githubStatus ]]; then
mirror="github"
downloadQLUrl="https://github.com/whyour/qinglong/archive/refs/heads"
downloadStaticUrl="https://github.com/whyour/qinglong-static/archive/refs/heads"
fi
echo -e "使用 ${mirror} 源更新...\n"
export isFirstStartServer=false
local primary_branch="master"
if [[ "${QL_BRANCH}" == "develop" ]]; then
primary_branch="develop"
fi
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
reset_romote_url ${dir_root} "https://${mirror}.com/whyour/qinglong.git" ${primary_branch}
git_pull_scripts $dir_root ${primary_branch}
wget -cqO "${dir_tmp}/ql.zip" "${downloadQLUrl}/${primary_branch}.zip"
exit_status=$?
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新青龙源文件成功...\n"
cp -f $file_config_sample $dir_config/config.sample.sh
update_depend
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
[[ "$ql_depend_old" != "$ql_depend_new" ]] && npm_install_2 $dir_root
unzip -oq ${dir_tmp}/ql.zip -d ${dir_tmp}
update_qinglong_static "$1" "$primary_branch"
update_qinglong_static
else
echo -e "\n更新青龙源文件失败,请检查网络...\n"
fi
}
update_qinglong_static() {
local no_restart="$1"
local primary_branch="$2"
local url="https://${mirror}.com/whyour/qinglong-static.git"
if [[ -d ${ql_static_repo}/.git ]]; then
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
git_pull_scripts ${ql_static_repo} ${primary_branch}
else
rm -rf ${ql_static_repo} &>/dev/null
git_clone_scripts ${url} ${ql_static_repo} ${primary_branch}
fi
wget -cqO "${dir_tmp}/static.zip" "${downloadStaticUrl}/${primary_branch}.zip"
exit_status=$?
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新青龙静态资源成功...\n"
unzip -oq ${dir_tmp}/static.zip -d ${dir_tmp}
check_update_dep
else
echo -e "\n更新青龙静态资源失败,请检查网络...\n"
fi
}
check_update_dep() {
echo -e "\n开始检测依赖...\n"
if [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
cp -f $dir_sample/package.json $dir_scripts/package.json
npm_install_2 $dir_scripts
fi
if [[ $(diff $dir_root/package.json ${dir_tmp}/qinglong-${primary_branch}/package.json) ]]; then
npm_install_2 "${dir_tmp}/qinglong-${primary_branch}"
fi
if [[ $exit_status -eq 0 ]]; then
echo -e "\n依赖检测安装成功...\n"
echo -e "\n更新包下载成功...\n"
if [[ "$needRestart" == 'true' ]]; then
cp -rf ${dir_tmp}/qinglong-${primary_branch}/* ${dir_root}/
rm -rf $dir_static/*
cp -rf ${dir_tmp}/qinglong-static-${primary_branch}/* ${dir_static}/
cp -f $file_config_sample $dir_config/config.sample.sh
rm -rf $dir_static/*
cp -rf $ql_static_repo/* $dir_static
if [[ $no_restart != "no-restart" ]]; then
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
echo -e "重启面板中..."
sleep 3
reload_pm2
fi
else
echo -e "\n更新青龙静态资源失败,请检查网络...\n"
echo -e "\n依赖检测安装失败,请检查网络...\n"
fi
}
@@ -464,7 +496,11 @@ main() {
case $p1 in
update)
fix_config
eval update_qinglong "$2" $cmd
local needRestart=${p2:-"true"}
eval update_qinglong $cmd
;;
reload)
eval reload_qinglong "$p2" $cmd
;;
extra)
eval run_extra_shell $cmd
+1 -2
View File
@@ -49,8 +49,7 @@ export interface SharedContext {
interface TSystemInfo {
branch: 'develop' | 'master';
isInitialized: boolean;
lastCommitId: string;
lastCommitTime: number;
publishTime: number;
version: string;
changeLog: string;
changeLogLink: string;
+1 -3
View File
@@ -51,9 +51,7 @@ const Config = () => {
: value;
request
.post(`${config.apiPrefix}configs/save`, {
data: { content, name: select },
})
.post(`${config.apiPrefix}configs/save`, { content, name: select })
.then(({ code, data }) => {
if (code === 200) {
message.success('保存成功');
+8 -14
View File
@@ -22,7 +22,7 @@ import {
PauseCircleOutlined,
FullscreenOutlined,
} from '@ant-design/icons';
import { CrontabStatus } from './index';
import { CrontabStatus } from './type';
import { diffTime } from '@/utils/date';
import { request } from '@/utils/http';
import config from '@/utils/config';
@@ -184,11 +184,9 @@ const CronDetailModal = ({
return new Promise((resolve, reject) => {
request
.put(`${config.apiPrefix}scripts`, {
data: {
filename: scriptInfo.filename,
path: scriptInfo.parent || '',
content,
},
filename: scriptInfo.filename,
path: scriptInfo.parent || '',
content,
})
.then(({ code, data }) => {
if (code === 200) {
@@ -220,7 +218,7 @@ const CronDetailModal = ({
),
onOk() {
request
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
.put(`${config.apiPrefix}crons/run`, [currentCron.id])
.then(({ code, data }) => {
if (code === 200) {
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
@@ -250,7 +248,7 @@ const CronDetailModal = ({
),
onOk() {
request
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
.put(`${config.apiPrefix}crons/stop`, [currentCron.id] )
.then(({ code, data }) => {
if (code === 200) {
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
@@ -282,9 +280,7 @@ const CronDetailModal = ({
`${config.apiPrefix}crons/${
currentCron.isDisabled === 1 ? 'enable' : 'disable'
}`,
{
data: [currentCron.id],
},
[currentCron.id],
)
.then(({ code, data }) => {
if (code === 200) {
@@ -320,9 +316,7 @@ const CronDetailModal = ({
`${config.apiPrefix}crons/${
currentCron.isPinned === 1 ? 'unpin' : 'pin'
}`,
{
data: [currentCron.id],
},
[currentCron.id],
)
.then(({ code, data }) => {
if (code === 200) {
+13 -56
View File
@@ -54,51 +54,11 @@ import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import { getCommandScript, parseCrontab } from '@/utils';
import { ColumnProps } from 'antd/lib/table';
import { useVT } from 'virtualizedtableforantd4';
import { ICrontab, OperationName, OperationPath, CrontabStatus } from './type';
const { Text, Paragraph } = Typography;
const { Search } = Input;
export enum CrontabStatus {
'running',
'idle',
'disabled',
'queued',
}
const CrontabSort: any = { 0: 0, 5: 1, 3: 2, 1: 3, 4: 4 };
enum OperationName {
'启用',
'禁用',
'运行',
'停止',
'置顶',
'取消置顶',
}
enum OperationPath {
'enable',
'disable',
'run',
'stop',
'pin',
'unpin',
}
export interface ICrontab {
name: string;
command: string;
schedule: string;
id: number;
status: number;
isDisabled?: 1 | 0;
isPinned?: 1 | 0;
labels?: string[];
last_running_time?: number;
last_execution_time?: number;
nextRunTime: Date;
}
const Crontab = () => {
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
const columns: ColumnProps<ICrontab>[] = [
@@ -264,19 +224,19 @@ const Crontab = () => {
filters: [
{
text: '运行中',
value: 0,
value: CrontabStatus.running,
},
{
text: '空闲中',
value: 1,
value: CrontabStatus.idle,
},
{
text: '已禁用',
value: 2,
value: CrontabStatus.disabled,
},
{
text: '队列中',
value: 3,
value: CrontabStatus.queued,
},
],
render: (text, record) => (
@@ -497,7 +457,7 @@ const Crontab = () => {
),
onOk() {
request
.put(`${config.apiPrefix}crons/run`, { data: [record.id] })
.put(`${config.apiPrefix}crons/run`, [record.id])
.then(({ code, data }) => {
if (code === 200) {
const result = [...value];
@@ -532,7 +492,7 @@ const Crontab = () => {
),
onOk() {
request
.put(`${config.apiPrefix}crons/stop`, { data: [record.id] })
.put(`${config.apiPrefix}crons/stop`, [record.id])
.then(({ code, data }) => {
if (code === 200) {
const result = [...value];
@@ -573,9 +533,7 @@ const Crontab = () => {
`${config.apiPrefix}crons/${
record.isDisabled === 1 ? 'enable' : 'disable'
}`,
{
data: [record.id],
},
[record.id],
)
.then(({ code, data }) => {
if (code === 200) {
@@ -617,9 +575,7 @@ const Crontab = () => {
`${config.apiPrefix}crons/${
record.isPinned === 1 ? 'unpin' : 'pin'
}`,
{
data: [record.id],
},
[record.id],
)
.then(({ code, data }) => {
if (code === 200) {
@@ -765,9 +721,10 @@ const Crontab = () => {
content: <>{OperationName[operationStatus]}</>,
onOk() {
request
.put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
data: selectedRowIds,
})
.put(
`${config.apiPrefix}crons/${OperationPath[operationStatus]}`,
selectedRowIds,
)
.then(({ code, data }) => {
if (code === 200) {
getCrons();
+2 -33
View File
@@ -8,13 +8,8 @@ import {
} from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-layout';
import { logEnded } from '@/utils';
import { CrontabStatus } from './type';
enum CrontabStatus {
'running',
'idle',
'disabled',
'queued',
}
const { Countdown } = Statistic;
const CronLogModal = ({
@@ -51,10 +46,7 @@ const CronLogModal = ({
const log = data as string;
setValue(log || '暂无日志');
const hasNext = Boolean(
log &&
!logEnded(log) &&
!log.includes('重启面板') &&
!log.includes('任务未运行'),
log && !logEnded(log) && !log.includes('任务未运行'),
);
setExecuting(hasNext);
autoScroll();
@@ -63,29 +55,6 @@ const CronLogModal = ({
getCronLog();
}, 2000);
}
if (
log &&
log.includes('重启面板') &&
cron.status === CrontabStatus.running
) {
message.warning({
content: (
<span>
<Countdown
className="inline-countdown"
format="ss"
value={Date.now() + 1000 * 30}
/>
</span>
),
duration: 10,
});
setTimeout(() => {
window.location.reload();
}, 30000);
}
}
})
.finally(() => {
+5 -6
View File
@@ -25,9 +25,10 @@ const CronModal = ({
payload.id = cron.id;
}
try {
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
data: payload,
});
const { code, data } = await request[method](
`${config.apiPrefix}crons`,
payload,
);
if (code === 200) {
message.success(cron ? '更新Cron成功' : '新建Cron成功');
@@ -130,9 +131,7 @@ const CronLabelModal = ({
try {
const { code, data } = await request[action](
`${config.apiPrefix}crons/labels`,
{
data: payload,
},
payload,
);
if (code === 200) {
+38
View File
@@ -0,0 +1,38 @@
export enum CrontabStatus {
'running' = 0,
'queued' = 0.5,
'idle' = 1,
'disabled',
}
export enum OperationName {
'启用',
'禁用',
'运行',
'停止',
'置顶',
'取消置顶',
}
export enum OperationPath {
'enable',
'disable',
'run',
'stop',
'pin',
'unpin',
}
export interface ICrontab {
name: string;
command: string;
schedule: string;
id: number;
status: number;
isDisabled?: 1 | 0;
isPinned?: 1 | 0;
labels?: string[];
last_running_time?: number;
last_execution_time?: number;
nextRunTime: Date;
}
+5 -6
View File
@@ -14,6 +14,7 @@ import config from '@/utils/config';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import IconFont from '@/components/iconfont';
import get from 'lodash/get';
import { CrontabStatus } from './type';
const PROPERTIES = [
{ name: '命令', value: 'command' },
@@ -47,9 +48,9 @@ const SORTTYPES = [
const STATUS_MAP = {
status: [
{ name: '运行中', value: 0 },
{ name: '空闲中', value: 1 },
{ name: '已禁用', value: 2 },
{ name: '运行中', value: CrontabStatus.running },
{ name: '空闲中', value: CrontabStatus.idle },
{ name: '已禁用', value: CrontabStatus.disabled },
],
};
@@ -79,9 +80,7 @@ const ViewCreateModal = ({
try {
const { code, data } = await request[method](
`${config.apiPrefix}crons/views`,
{
data: view ? { ...values, id: view.id } : values,
},
view ? { ...values, id: view.id } : values,
);
if (code === 200) {
+6 -4
View File
@@ -168,9 +168,9 @@ const ViewManageModal = ({
const onShowChange = (checked: boolean, record: any, index: number) => {
request
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
data: [record.id],
})
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, [
record.id,
])
.then(({ code, data }) => {
if (code === 200) {
const _list = [...list];
@@ -195,7 +195,9 @@ const ViewManageModal = ({
const dragRow = list[dragIndex];
request
.put(`${config.apiPrefix}crons/views/move`, {
data: { fromIndex: dragIndex, toIndex: hoverIndex, id: dragRow.id },
fromIndex: dragIndex,
toIndex: hoverIndex,
id: dragRow.id,
})
.then(({ code, data }) => {
if (code === 200) {
+15 -25
View File
@@ -33,6 +33,7 @@ import DependenceLogModal from './logModal';
import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import dayjs from 'dayjs';
const { Text } = Typography;
const { Search } = Input;
@@ -123,27 +124,20 @@ const Dependence = () => {
dataIndex: 'remark',
key: 'remark',
},
{
title: '更新时间',
key: 'updatedAt',
dataIndex: 'updatedAt',
render: (text: string) => {
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
},
},
{
title: '创建时间',
key: 'timestamp',
dataIndex: 'timestamp',
render: (text: string, record: any) => {
const language = navigator.language || navigator.languages[0];
const time = record.createdAt || record.timestamp;
const date = new Date(time)
.toLocaleString(language, {
hour12: false,
})
.replace(' 24:', ' 00:');
return (
<Tooltip
placement="topLeft"
title={date}
trigger={['hover', 'click']}
>
<span>{date}</span>
</Tooltip>
);
key: 'createdAt',
dataIndex: 'createdAt',
render: (text: string) => {
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
},
},
{
@@ -275,9 +269,7 @@ const Dependence = () => {
),
onOk() {
request
.put(`${config.apiPrefix}dependencies/reinstall`, {
data: [record.id],
})
.put(`${config.apiPrefix}dependencies/reinstall`, [record.id])
.then(({ code, data }) => {
if (code === 200) {
handleDependence(data[0]);
@@ -348,9 +340,7 @@ const Dependence = () => {
content: <></>,
onOk() {
request
.put(`${config.apiPrefix}dependencies/reinstall`, {
data: selectedRowIds,
})
.put(`${config.apiPrefix}dependencies/reinstall`, selectedRowIds)
.then(({ code, data }) => {
if (code === 200) {
setSelectedRowIds([]);
+6 -4
View File
@@ -48,9 +48,7 @@ const DependenceModal = ({
try {
const { code, data } = await request[method](
`${config.apiPrefix}dependencies`,
{
data: payload,
},
payload,
);
if (code === 200) {
@@ -122,7 +120,11 @@ const DependenceModal = ({
name="name"
label="名称"
rules={[
{ required: true, message: '请输入依赖名称', whitespace: true },
{
required: true,
message: '请输入依赖名称,支持指定版本',
whitespace: true,
},
]}
>
<Input.TextArea
+2 -1
View File
@@ -48,7 +48,8 @@ const Diff = () => {
request
.post(`${config.apiPrefix}configs/save`, {
data: { content, name: current },
content,
name: current,
})
.then(({ code, data }) => {
if (code === 200) {
+2 -4
View File
@@ -19,10 +19,8 @@ const EditNameModal = ({
setLoading(true);
try {
const { code, data } = await request.put(`${config.apiPrefix}envs/name`, {
data: {
ids,
name: values.name,
},
ids,
name: values.name,
});
if (code === 200) {
+8 -10
View File
@@ -248,9 +248,7 @@ const Env = () => {
`${config.apiPrefix}envs/${
record.status === Status. ? 'enable' : 'disable'
}`,
{
data: [record.id],
},
[record.id],
)
.then(({ code, data }) => {
if (code === 200) {
@@ -388,7 +386,8 @@ const Env = () => {
const dragRow = value[dragIndex];
request
.put(`${config.apiPrefix}envs/${dragRow.id}/move`, {
data: { fromIndex: dragIndex, toIndex: hoverIndex },
fromIndex: dragIndex,
toIndex: hoverIndex,
})
.then(({ code, data }) => {
if (code === 200) {
@@ -438,9 +437,10 @@ const Env = () => {
content: <>{OperationName[operationStatus]}</>,
onOk() {
request
.put(`${config.apiPrefix}envs/${OperationPath[operationStatus]}`, {
data: selectedRowIds,
})
.put(
`${config.apiPrefix}envs/${OperationPath[operationStatus]}`,
selectedRowIds,
)
.then(({ code, data }) => {
if (code === 200) {
getEnvs();
@@ -477,9 +477,7 @@ const Env = () => {
try {
const { code, data } = await request.post(
`${config.apiPrefix}envs/upload`,
{
data: formData,
},
formData,
);
if (code === 200) {
+4 -3
View File
@@ -37,9 +37,10 @@ const EnvModal = ({
payload = { ...values, id: env.id };
}
try {
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
data: payload,
});
const { code, data } = await request[method](
`${config.apiPrefix}envs`,
payload,
);
if (code === 200) {
message.success(env ? '更新变量成功' : '新建变量成功');
-1
View File
@@ -1,6 +1,5 @@
.error-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
+1 -1
View File
@@ -81,7 +81,7 @@ const Error = () => {
</Typography.Paragraph>
</div>
) : (
<PageLoading style={{ paddingTop: 0 }} tip="启动中,请稍后..." />
<PageLoading tip="启动中,请稍后..." />
)}
</div>
);
+3 -7
View File
@@ -36,10 +36,8 @@ const Initialization = () => {
setLoading(true);
request
.put(`${config.apiPrefix}user/init`, {
data: {
username: values.username,
password: values.password,
},
username: values.username,
password: values.password,
})
.then(({ code, data }) => {
if (code === 200) {
@@ -53,9 +51,7 @@ const Initialization = () => {
setLoading(true);
request
.put(`${config.apiPrefix}user/notification/init`, {
data: {
...values,
},
values,
})
.then(({ code, data }) => {
if (code === 200) {
+4 -5
View File
@@ -35,10 +35,8 @@ const Login = () => {
setWaitTime(null);
request
.post(`${config.apiPrefix}user/login`, {
data: {
username: values.username,
password: values.password,
},
username: values.username,
password: values.password,
})
.then((data) => {
checkResponse(data, values);
@@ -54,7 +52,8 @@ const Login = () => {
setVerifying(true);
request
.put(`${config.apiPrefix}user/two-factor/login`, {
data: { ...loginInfo, code: values.code },
...loginInfo,
code: values.code,
})
.then((data: any) => {
checkResponse(data);
+6 -10
View File
@@ -86,11 +86,9 @@ const EditModal = ({
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
request
.put(`${config.apiPrefix}scripts/run`, {
data: {
filename: cNode.title,
path: cNode.parent || '',
content,
},
filename: cNode.title,
path: cNode.parent || '',
content,
})
.then(({ code, data }) => {
if (code === 200) {
@@ -106,11 +104,9 @@ const EditModal = ({
}
request
.put(`${config.apiPrefix}scripts/stop`, {
data: {
filename: cNode.title,
path: cNode.parent || '',
pid: currentPid,
},
filename: cNode.title,
path: cNode.parent || '',
pid: currentPid,
})
.then(({ code, data }) => {
if (code === 200) {
+1 -3
View File
@@ -44,9 +44,7 @@ const EditScriptNameModal = ({
formData.append('content', '');
formData.append('directory', directory);
request
.post(`${config.apiPrefix}scripts`, {
data: formData,
})
.post(`${config.apiPrefix}scripts`, formData)
.then(({ code, data }) => {
if (code === 200) {
message.success(directory ? '新建文件夹成功' : '新建文件成功');
+4 -8
View File
@@ -223,11 +223,9 @@ const Script = () => {
return new Promise((resolve, reject) => {
request
.put(`${config.apiPrefix}scripts`, {
data: {
filename: currentNode.title,
path: currentNode.parent || '',
content,
},
filename: currentNode.title,
path: currentNode.parent || '',
content,
})
.then(({ code, data }) => {
if (code === 200) {
@@ -341,9 +339,7 @@ const Script = () => {
const downloadFile = () => {
request
.post(`${config.apiPrefix}scripts/download`, {
data: {
filename: currentNode.title,
},
filename: currentNode.title,
})
.then(({ code, data }) => {
if (code === 200) {
+3 -5
View File
@@ -21,11 +21,9 @@ const RenameModal = ({
const { code, data } = await request.put(
`${config.apiPrefix}scripts/rename`,
{
data: {
filename: currentNode.title,
path: currentNode.parent || '',
newFilename: values.name,
},
filename: currentNode.title,
path: currentNode.parent || '',
newFilename: values.name,
},
);
+1 -3
View File
@@ -19,9 +19,7 @@ const SaveModal = ({
setLoading(true);
const payload = { ...file, ...values, originFilename: file.title };
request
.post(`${config.apiPrefix}scripts`, {
data: payload,
})
.post(`${config.apiPrefix}scripts`, payload)
.then(({ code, data }) => {
if (code === 200) {
message.success('保存文件成功');
+1 -3
View File
@@ -19,9 +19,7 @@ const SettingModal = ({
setLoading(true);
const payload = { ...file, ...values };
request
.post(`${config.apiPrefix}scripts`, {
data: payload,
})
.post(`${config.apiPrefix}scripts`, payload)
.then(({ code, data }) => {
if (code === 200) {
message.success('保存文件成功');
+2 -5
View File
@@ -31,13 +31,10 @@ const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
{TVersion[systemInfo.branch]} v{systemInfo.version}
</Descriptions.Item>
<Descriptions.Item label="更新时间" span={3}>
{dayjs(systemInfo.lastCommitTime * 1000).format(
'YYYY-MM-DD HH:mm:ss',
{dayjs(systemInfo.publishTime * 1000).format(
'YYYY-MM-DD HH:mm',
)}
</Descriptions.Item>
<Descriptions.Item label="更新ID" span={3}>
{systemInfo.lastCommitId}
</Descriptions.Item>
<Descriptions.Item label="更新日志" span={3}>
<Link
href={`https://qn.whyour.cn/version.yaml?t=${Date.now()}`}
+4 -3
View File
@@ -23,9 +23,10 @@ const AppModal = ({
payload.id = app.id;
}
try {
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
data: payload,
});
const { code, data } = await request[method](
`${config.apiPrefix}apps`,
payload,
);
if (code === 200) {
message.success(app ? '更新应用成功' : '新建应用成功');
+51 -21
View File
@@ -47,7 +47,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
</div>
</>
),
okText: '强制更新',
okText: '重新下载',
onOk() {
showUpdatingModal();
request
@@ -82,7 +82,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
{lastLog}
</pre>
),
okText: '更新',
okText: '下载更新',
cancelText: '以后再说',
onOk() {
showUpdatingModal();
@@ -104,7 +104,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
closable: false,
keyboard: false,
okButtonProps: { disabled: true },
title: '更新中...',
title: '下载更新中...',
centered: true,
content: (
<pre
@@ -119,6 +119,50 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
});
};
const showReloadModal = () => {
Modal.confirm({
width: 600,
maskClosable: false,
title: '确认重启',
centered: true,
content: '系统安装包下载成功,确认重启',
okText: '重启',
onOk() {
request
.put(`${config.apiPrefix}system/reload`, { type: 'system' })
.then((_data: any) => {
message.success({
content: (
<span>
<Countdown
className="inline-countdown"
format="ss"
value={Date.now() + 1000 * 30}
/>
</span>
),
duration: 30,
});
setTimeout(() => {
window.location.reload();
}, 30000);
})
.catch((error: any) => {
console.log(error);
});
},
onCancel() {
modalRef.current.update({
maskClosable: true,
closable: true,
okButtonProps: { disabled: false },
});
},
});
};
useEffect(() => {
if (!modalRef.current || !socketMessage) {
return;
@@ -130,7 +174,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
}
const newMessage = `${value}${_message}`;
const updateFailed = newMessage.includes('失败,请检查');
const updateFailed = newMessage.includes('失败');
modalRef.current.update({
maskClosable: updateFailed,
@@ -162,24 +206,10 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
.getElementById('log-identifier')!
.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
if (_message.includes('重启面板')) {
message.warning({
content: (
<span>
<Countdown
className="inline-countdown"
format="ss"
value={Date.now() + 1000 * 30}
/>
</span>
),
duration: 30,
});
if (_message.includes('更新包下载成功')) {
setTimeout(() => {
window.location.reload();
}, 30000);
showReloadModal();
}, 1000);
}
}, [socketMessage]);
+1 -3
View File
@@ -20,9 +20,7 @@ const NotificationSetting = ({ data }: any) => {
request
.put(`${config.apiPrefix}user/notification`, {
data: {
...values,
},
values,
})
.then(({ code, data }) => {
if (code === 200) {
+148 -26
View File
@@ -1,11 +1,25 @@
import React, { useState, useEffect } from 'react';
import { Button, InputNumber, Form, Radio, message, Input } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
InputNumber,
Form,
Radio,
message,
Input,
Upload,
Modal,
Progress,
} from 'antd';
import * as DarkReader from '@umijs/ssr-darkreader';
import config from '@/utils/config';
import { request } from '@/utils/http';
import CheckUpdate from './checkUpdate';
import { SharedContext } from '@/layouts';
import { saveAs } from 'file-saver';
import './index.less';
import { UploadOutlined } from '@ant-design/icons';
import Countdown from 'antd/lib/statistic/Countdown';
import useProgress from './progress';
const optionsWithDisabled = [
{ label: '亮色', value: 'light' },
@@ -19,8 +33,15 @@ const Other = ({
reloadTheme,
}: Pick<SharedContext, 'socketMessage' | 'reloadTheme' | 'systemInfo'>) => {
const defaultTheme = localStorage.getItem('qinglong_dark_theme') || 'auto';
const [logRemoveFrequency, setLogRemoveFrequency] = useState<number | null>();
const [systemConfig, setSystemConfig] = useState<{
logRemoveFrequency?: number | null;
cronConcurrency?: number | null;
}>();
const [form] = Form.useForm();
const modalRef = useRef<any>();
const [exportLoading, setExportLoading] = useState(false);
const showUploadProgress = useProgress('上传');
const showDownloadProgress = useProgress('下载');
const {
enable: enableDarkMode,
@@ -45,13 +66,12 @@ const Other = ({
reloadTheme();
};
const getLogRemoveFrequency = () => {
const getSystemConfig = () => {
request
.get(`${config.apiPrefix}system/log/remove`)
.get(`${config.apiPrefix}system/config`)
.then(({ code, data }) => {
if (code === 200 && data.info) {
const { frequency } = data.info;
setLogRemoveFrequency(frequency);
setSystemConfig(data.info);
}
})
.catch((error: any) => {
@@ -59,25 +79,83 @@ const Other = ({
});
};
const updateRemoveLogFrequency = () => {
setTimeout(() => {
request
.put(`${config.apiPrefix}system/log/remove`, {
data: { frequency: logRemoveFrequency },
})
.then(({ code, data }) => {
if (code === 200) {
message.success('更新成功');
}
})
.catch((error: any) => {
console.log(error);
});
const updateSystemConfig = () => {
request
.put(`${config.apiPrefix}system/config`, systemConfig)
.then(({ code, data }) => {
if (code === 200) {
message.success('更新成功');
}
})
.catch((error: any) => {
console.log(error);
});
};
const exportData = () => {
setExportLoading(true);
request
.put<Blob>(
`${config.apiPrefix}system/data/export`,
{},
{
responseType: 'blob',
timeout: 86400000,
onDownloadProgress: (e) => {
if (e.progress) {
showDownloadProgress(parseFloat((e.progress * 100).toFixed(1)));
}
},
},
)
.then((res) => {
saveAs(res, 'data.tgz');
})
.catch((error: any) => {
console.log(error);
})
.finally(() => setExportLoading(false));
};
const showReloadModal = () => {
Modal.confirm({
width: 600,
maskClosable: false,
title: '确认重启',
centered: true,
content: '备份数据上传成功,确认覆盖数据',
okText: '重启',
onOk() {
request
.put(`${config.apiPrefix}system/reload`, { type: 'data' })
.then(() => {
message.success({
content: (
<span>
<Countdown
className="inline-countdown"
format="ss"
value={Date.now() + 1000 * 30}
/>
</span>
),
duration: 30,
});
setTimeout(() => {
window.location.reload();
}, 30000);
})
.catch((error: any) => {
console.log(error);
});
},
});
};
useEffect(() => {
getLogRemoveFrequency();
getSystemConfig();
}, []);
return (
@@ -100,16 +178,60 @@ const Other = ({
<InputNumber
addonBefore="每"
addonAfter="天"
style={{ width: 150 }}
style={{ width: 142 }}
min={0}
value={logRemoveFrequency}
onChange={(value) => setLogRemoveFrequency(value)}
value={systemConfig?.logRemoveFrequency}
onChange={(value) => {
setSystemConfig({ ...systemConfig, logRemoveFrequency: value });
}}
/>
<Button type="primary" onClick={updateRemoveLogFrequency}>
<Button type="primary" onClick={updateSystemConfig}>
</Button>
</Input.Group>
</Form.Item>
<Form.Item label="定时任务并发数" name="frequency">
<Input.Group compact>
<InputNumber
style={{ width: 142 }}
min={1}
value={systemConfig?.cronConcurrency}
onChange={(value) => {
setSystemConfig({ ...systemConfig, cronConcurrency: value });
}}
/>
<Button type="primary" onClick={updateSystemConfig}>
</Button>
</Input.Group>
</Form.Item>
<Form.Item label="数据备份还原" name="frequency">
<Button type="primary" onClick={exportData} loading={exportLoading}>
</Button>
<Upload
method="put"
showUploadList={false}
maxCount={1}
action="/api/system/data/import"
onChange={(e) => {
if (e.event?.percent) {
showUploadProgress(parseFloat(e.event?.percent.toFixed(1)));
if (e.event?.percent === 100) {
showReloadModal();
}
}
}}
name="data"
headers={{
Authorization: `Bearer ${localStorage.getItem(config.authKey)}`,
}}
>
<Button icon={<UploadOutlined />} style={{ marginLeft: 8 }}>
</Button>
</Upload>
</Form.Item>
<Form.Item label="检查更新" name="update">
<CheckUpdate systemInfo={systemInfo} socketMessage={socketMessage} />
</Form.Item>
+33
View File
@@ -0,0 +1,33 @@
import { Modal, Progress } from 'antd';
import { useRef } from 'react';
export default function useProgress(title: string) {
const modalRef = useRef<ReturnType<typeof Modal.info>>();
const ProgressElement = ({ percent }: { percent: number }) => (
<Progress
style={{ display: 'flex', justifyContent: 'center' }}
type="circle"
percent={percent}
/>
);
const showProgress = (percent: number) => {
if (modalRef.current) {
modalRef.current.update({
title: `${title}${percent >= 100 ? '成功' : '中...'}`,
content: <ProgressElement percent={percent} />,
});
} else {
modalRef.current = Modal.info({
width: 600,
maskClosable: false,
title: `${title}${percent >= 100 ? '成功' : '中...'}`,
centered: true,
content: <ProgressElement percent={percent} />,
});
}
};
return showProgress;
}
+4 -6
View File
@@ -22,10 +22,8 @@ const SecuritySettings = ({ user, userChange }: any) => {
const handleOk = (values: any) => {
request
.put(`${config.apiPrefix}user`, {
data: {
username: values.username,
password: values.password,
},
username: values.username,
password: values.password,
})
.then(({ code, data }) => {
if (code === 200) {
@@ -64,7 +62,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
const completeTowFactor = () => {
setLoading(true);
request
.put(`${config.apiPrefix}user/two-factor/active`, { data: { code } })
.put(`${config.apiPrefix}user/two-factor/active`, { code })
.then(({ code, data }) => {
if (code === 200) {
if (data) {
@@ -241,7 +239,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
</div>
<Avatar size={128} shape="square" icon={<UserOutlined />} src={avatar} />
<ImgCrop rotate>
<ImgCrop rotationSlider>
<Upload
method="put"
showUploadList={false}
+3 -5
View File
@@ -254,7 +254,7 @@ const Subscription = () => {
),
onOk() {
request
.put(`${config.apiPrefix}subscriptions/run`, { data: [record.id] })
.put(`${config.apiPrefix}subscriptions/run`, [record.id])
.then(({ code, data }) => {
if (code === 200) {
const result = [...value];
@@ -289,7 +289,7 @@ const Subscription = () => {
),
onOk() {
request
.put(`${config.apiPrefix}subscriptions/stop`, { data: [record.id] })
.put(`${config.apiPrefix}subscriptions/stop`, [record.id])
.then(({ code, data }) => {
if (code === 200) {
const result = [...value];
@@ -386,9 +386,7 @@ const Subscription = () => {
`${config.apiPrefix}subscriptions/${
record.is_disabled === 1 ? 'enable' : 'disable'
}`,
{
data: [record.id],
},
[record.id],
)
.then(({ code, data }) => {
if (code === 200) {
+1 -3
View File
@@ -47,9 +47,7 @@ const SubscriptionModal = ({
try {
const { code, data } = await request[method](
`${config.apiPrefix}subscriptions`,
{
data: payload,
},
payload,
);
if (code === 200) {
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
+68 -25
View File
@@ -1,17 +1,36 @@
import { extend } from 'umi-request';
import { message } from 'antd';
import config from './config';
import { history } from '@umijs/max';
import axios, {
AxiosError,
AxiosInstance,
AxiosRequestConfig,
} from 'axios';
interface IResponseData {
code?: number;
data?: any;
message?: string;
}
type Override<
T,
K extends Partial<{ [P in keyof T]: any }> | string,
> = K extends string
? Omit<T, K> & { [P in keyof T]: T[P] | unknown }
: Omit<T, keyof K> & K;
message.config({
duration: 2,
});
const time = Date.now();
const errorHandler = function (error: any) {
const errorHandler = function (
error: AxiosError,
) {
if (error.response) {
const msg = error.data
? error.data.message || error.message || error.data
const msg = error.response.data
? error.response.data.message || error.message || error.response.data
: error.response.statusText;
const responseStatus = error.response.status;
if ([502, 504].includes(responseStatus)) {
@@ -32,10 +51,14 @@ const errorHandler = function (error: any) {
console.log(error.message);
}
throw error; // 如果throw. 错误将继续抛出.
return Promise.reject(error);
};
const _request = extend({ timeout: 60000, params: { t: time }, errorHandler });
let _request = axios.create({
timeout: 60000,
params: { t: time },
});
const apiWhiteList = [
'/api/user/login',
'/open/auth/token',
@@ -45,15 +68,13 @@ const apiWhiteList = [
'/api/user/notification/init',
];
_request.interceptors.request.use((url, options) => {
_request.interceptors.request.use((_config) => {
const token = localStorage.getItem(config.authKey);
if (token && !apiWhiteList.includes(url)) {
const headers = {
Authorization: `Bearer ${token}`,
};
return { url, options: { ...options, headers } };
if (token && !apiWhiteList.includes(_config.url!)) {
_config.headers.Authorization = `Bearer ${token}`;
return _config;
}
return { url, options };
return _config;
});
_request.interceptors.response.use(async (response) => {
@@ -66,18 +87,40 @@ _request.interceptors.response.use(async (response) => {
history.push('/login');
}
} else {
const res = await response.clone().json();
if (res.code !== 200) {
const msg = res.message || res.data;
msg &&
message.error({
content: msg,
style: { maxWidth: 500, margin: '0 auto' },
});
}
return res;
try {
const res = response.data;
if (res.code !== 200) {
const msg = res.message || res.data;
msg &&
message.error({
content: msg,
style: { maxWidth: 500, margin: '0 auto' },
});
}
return res;
} catch (error) { }
return response;
}
return response;
});
}, errorHandler);
export const request = _request;
export const request = _request as Override<AxiosInstance, {
get<T = IResponseData, D = any>(
url: string,
config?: AxiosRequestConfig<D>,
): Promise<T>;
delete<T = IResponseData, D = any>(
url: string,
config?: AxiosRequestConfig<D>,
): Promise<T>;
post<T = IResponseData, D = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D>,
): Promise<T>;
put<T = IResponseData, D = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D>,
): Promise<T>;
}>;
+6 -10
View File
@@ -1,11 +1,7 @@
version: 2.15.16
changeLogLink: https://t.me/jiao_long/378
version: 2.15.18
changeLogLink: https://t.me/jiao_long/384
publishTime: 2023-07-19 21:21
changeLog: |
1. 企业微信通知增加代理地址配置 QYWX_ORIGIN
2. 重构任务并发执行逻辑,依赖并发安装逻辑
3. 修复关闭全部任务视图,默认视图筛选错误
4. 增加初始化文件写入,修复参数含有空格影响 task/ql 命令
5. 修复初始化界面、侧边栏、错误页样式
6. 修复拉取订阅文件包含空格出错
7. 修改 api 限流策略,修复检查检查日志
8. 修复环境变量列表搜索字符转码
1. 增加备份恢复功能
2. 重构系统检查更新逻辑
3. 修复系统设置自动删除日志启动时失效