mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 22:36:06 +08:00
依赖管理支持取消安装和状态筛选
This commit is contained in:
parent
892d91edc4
commit
14cb1f7788
|
@ -134,4 +134,20 @@ export default (app: Router) => {
|
|||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/cancel',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.number().required()),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const dependenceService = Container.get(DependenceService);
|
||||
await dependenceService.cancel(req.body);
|
||||
return res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
|
@ -435,8 +435,8 @@ export async function killTask(pid: number) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getPid(name: string) {
|
||||
const taskCommand = `ps -eo pid,command | grep "${name}" | grep -v grep | awk '{print $1}' | head -1 | xargs echo -n`;
|
||||
export async function getPid(cmd: string) {
|
||||
const taskCommand = `ps -eo pid,command | grep "${cmd}" | grep -v grep | awk '{print $1}' | head -1 | xargs echo -n`;
|
||||
const pid = await promiseExec(taskCommand);
|
||||
return pid ? Number(pid) : undefined;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@ import {
|
|||
import { spawn } from 'cross-spawn';
|
||||
import SockService from './sock';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
import { fileExist, promiseExecSuccess } from '../config/util';
|
||||
import {
|
||||
fileExist,
|
||||
getPid,
|
||||
killTask,
|
||||
promiseExecSuccess,
|
||||
} from '../config/util';
|
||||
import dayjs from 'dayjs';
|
||||
import taskLimit from '../shared/pLimit';
|
||||
|
||||
|
@ -86,11 +91,21 @@ export default class DependenceService {
|
|||
}
|
||||
|
||||
public async dependencies(
|
||||
{ searchValue, type }: { searchValue: string; type: string },
|
||||
sort: any = { position: -1 },
|
||||
{
|
||||
searchValue,
|
||||
type,
|
||||
status,
|
||||
}: { searchValue: string; type: string; status: string },
|
||||
sort: any = [],
|
||||
query: any = {},
|
||||
): Promise<Dependence[]> {
|
||||
let condition = { ...query, type: DependenceTypes[type as any] };
|
||||
let condition = {
|
||||
...query,
|
||||
type: DependenceTypes[type as any],
|
||||
};
|
||||
if (status) {
|
||||
condition.status = status.split(',').map(Number);
|
||||
}
|
||||
if (searchValue) {
|
||||
const encodeText = encodeURI(searchValue);
|
||||
const reg = {
|
||||
|
@ -106,7 +121,7 @@ export default class DependenceService {
|
|||
};
|
||||
}
|
||||
try {
|
||||
const result = await this.find(condition);
|
||||
const result = await this.find(condition, sort);
|
||||
return result as any;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
@ -134,6 +149,18 @@ export default class DependenceService {
|
|||
return docs;
|
||||
}
|
||||
|
||||
public async cancel(ids: number[]) {
|
||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||
for (const doc of docs) {
|
||||
taskLimit.removeQueuedDependency(doc);
|
||||
const depRunCommand = InstallDependenceCommandTypes[doc.type];
|
||||
const cmd = `${depRunCommand} ${doc.name.trim()}`;
|
||||
const pid = await getPid(cmd);
|
||||
pid && (await killTask(pid));
|
||||
}
|
||||
await this.removeDb(ids);
|
||||
}
|
||||
|
||||
private async find(query: any, sort: any = []): Promise<Dependence[]> {
|
||||
const docs = await DependenceModel.findAll({
|
||||
where: { ...query },
|
||||
|
@ -168,8 +195,14 @@ export default class DependenceService {
|
|||
isInstall: boolean = true,
|
||||
force: boolean = false,
|
||||
) {
|
||||
return taskLimit.runOneByOne(() => {
|
||||
return taskLimit.runDependeny(dependency, () => {
|
||||
return new Promise(async (resolve) => {
|
||||
if (taskLimit.firstDependencyId !== dependency.id) {
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
taskLimit.removeQueuedDependency(dependency);
|
||||
|
||||
const depIds = [dependency.id!];
|
||||
const status = isInstall
|
||||
? DependenceStatus.installing
|
||||
|
|
|
@ -2,11 +2,19 @@ import PQueue, { QueueAddOptions } from 'p-queue-cjs';
|
|||
import os from 'os';
|
||||
import { AuthDataType, SystemModel } from '../data/system';
|
||||
import Logger from '../loaders/logger';
|
||||
import { Dependence } from '../data/dependence';
|
||||
|
||||
interface IDependencyFn<T> {
|
||||
(): Promise<T>;
|
||||
dependency?: Dependence;
|
||||
}
|
||||
class TaskLimit {
|
||||
private oneLimit = new PQueue({ concurrency: 1 });
|
||||
private dependenyLimit = new PQueue({ concurrency: 1 });
|
||||
private queuedDependencyIds = new Set<number>([]);
|
||||
private updateLogLimit = new PQueue({ concurrency: 1 });
|
||||
private cronLimit = new PQueue({ concurrency: Math.max(os.cpus().length, 4) });
|
||||
private cronLimit = new PQueue({
|
||||
concurrency: Math.max(os.cpus().length, 4),
|
||||
});
|
||||
|
||||
get cronLimitActiveCount() {
|
||||
return this.cronLimit.pending;
|
||||
|
@ -16,6 +24,10 @@ class TaskLimit {
|
|||
return this.cronLimit.size;
|
||||
}
|
||||
|
||||
get firstDependencyId() {
|
||||
return [...this.queuedDependencyIds.values()][0];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.setCustomLimit();
|
||||
this.handleEvents();
|
||||
|
@ -26,21 +38,19 @@ class TaskLimit {
|
|||
Logger.info(
|
||||
`[schedule][任务加入队列] 运行中任务数: ${this.cronLimitActiveCount}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
||||
);
|
||||
})
|
||||
});
|
||||
this.cronLimit.on('active', () => {
|
||||
Logger.info(
|
||||
`[schedule][开始处理任务] 运行中任务数: ${this.cronLimitActiveCount + 1}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
||||
);
|
||||
})
|
||||
this.cronLimit.on('completed', (param) => {
|
||||
Logger.info(
|
||||
`[schedule][任务处理成功] 参数 ${JSON.stringify(param)}`,
|
||||
`[schedule][开始处理任务] 运行中任务数: ${
|
||||
this.cronLimitActiveCount + 1
|
||||
}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
||||
);
|
||||
});
|
||||
this.cronLimit.on('error', error => {
|
||||
Logger.error(
|
||||
`[schedule][任务处理错误] 参数 ${JSON.stringify(error)}`,
|
||||
);
|
||||
this.cronLimit.on('completed', (param) => {
|
||||
Logger.info(`[schedule][任务处理成功] 参数 ${JSON.stringify(param)}`);
|
||||
});
|
||||
this.cronLimit.on('error', (error) => {
|
||||
Logger.error(`[schedule][任务处理错误] 参数 ${JSON.stringify(error)}`);
|
||||
});
|
||||
this.cronLimit.on('next', () => {
|
||||
Logger.info(
|
||||
|
@ -48,12 +58,16 @@ class TaskLimit {
|
|||
);
|
||||
});
|
||||
this.cronLimit.on('idle', () => {
|
||||
Logger.info(
|
||||
`[schedule][任务队列] 空闲中...`,
|
||||
);
|
||||
Logger.info(`[schedule][任务队列] 空闲中...`);
|
||||
});
|
||||
}
|
||||
|
||||
public removeQueuedDependency(dependency: Dependence) {
|
||||
if (this.queuedDependencyIds.has(dependency.id!)) {
|
||||
this.queuedDependencyIds.delete(dependency.id!);
|
||||
}
|
||||
}
|
||||
|
||||
public async setCustomLimit(limit?: number) {
|
||||
if (limit) {
|
||||
this.cronLimit.concurrency = limit;
|
||||
|
@ -68,15 +82,27 @@ class TaskLimit {
|
|||
}
|
||||
}
|
||||
|
||||
public async runWithCronLimit<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
||||
public async runWithCronLimit<T>(
|
||||
fn: () => Promise<T>,
|
||||
options?: Partial<QueueAddOptions>,
|
||||
): Promise<T | void> {
|
||||
return this.cronLimit.add(fn, options);
|
||||
}
|
||||
|
||||
public runOneByOne<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
||||
return this.oneLimit.add(fn, options);
|
||||
public runDependeny<T>(
|
||||
dependency: Dependence,
|
||||
fn: IDependencyFn<T>,
|
||||
options?: Partial<QueueAddOptions>,
|
||||
): Promise<T | void> {
|
||||
this.queuedDependencyIds.add(dependency.id!);
|
||||
fn.dependency = dependency;
|
||||
return this.dependenyLimit.add(fn, options);
|
||||
}
|
||||
|
||||
public updateDepLog<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
||||
public updateDepLog<T>(
|
||||
fn: () => Promise<T>,
|
||||
options?: Partial<QueueAddOptions>,
|
||||
): Promise<T | void> {
|
||||
return this.updateLogLimit.add(fn, options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { createFromIconfontCN } from '@ant-design/icons';
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_ob5y15ewlyq.js'],
|
||||
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_lc939gab1iq.js'],
|
||||
});
|
||||
|
||||
export default IconFont;
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
"创建时间": "Creation Time",
|
||||
"确认删除依赖": "Confirm to delete the dependency",
|
||||
"确认重新安装": "Confirm to reinstall",
|
||||
"确认取消安装": "Confirm to cancel install",
|
||||
"确认删除选中的依赖吗": "Confirm to delete the selected dependencies?",
|
||||
"确认重新安装选中的依赖吗": "Confirm to reinstall the selected dependencies?",
|
||||
"请输入名称": "Please enter a name",
|
||||
|
@ -394,6 +395,7 @@
|
|||
"系统": "System",
|
||||
"个人": "Personal",
|
||||
"重新安装": "Reinstall",
|
||||
"取消安装": "Cancel Install",
|
||||
"强制删除": "Force Delete",
|
||||
"全部任务": "All Tasks",
|
||||
"关联订阅": "Associate Subscription",
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
"创建时间": "创建时间",
|
||||
"确认删除依赖": "确认删除依赖",
|
||||
"确认重新安装": "确认重新安装",
|
||||
"确认取消安装": "确认取消安装",
|
||||
"确认删除选中的依赖吗": "确认删除选中的依赖吗",
|
||||
"确认重新安装选中的依赖吗": "确认重新安装选中的依赖吗",
|
||||
"请输入名称": "请输入名称",
|
||||
|
@ -394,6 +395,7 @@
|
|||
"系统": "系统",
|
||||
"个人": "个人",
|
||||
"重新安装": "重新安装",
|
||||
"取消安装": "取消安装",
|
||||
"强制删除": "强制删除",
|
||||
"全部任务": "全部任务",
|
||||
"关联订阅": "关联订阅",
|
||||
|
|
|
@ -36,6 +36,8 @@ import { SharedContext } from '@/layouts';
|
|||
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
||||
import dayjs from 'dayjs';
|
||||
import WebSocketManager from '@/utils/websocket';
|
||||
import { DependenceStatus } from './type';
|
||||
import IconFont from '@/components/iconfont';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Search } = Input;
|
||||
|
@ -108,6 +110,36 @@ const Dependence = () => {
|
|||
key: 'status',
|
||||
width: 120,
|
||||
dataIndex: 'status',
|
||||
filters: [
|
||||
{
|
||||
text: intl.get('队列中'),
|
||||
value: DependenceStatus.queued,
|
||||
},
|
||||
{
|
||||
text: intl.get('安装中'),
|
||||
value: DependenceStatus.installing,
|
||||
},
|
||||
{
|
||||
text: intl.get('已安装'),
|
||||
value: DependenceStatus.installed,
|
||||
},
|
||||
{
|
||||
text: intl.get('安装失败'),
|
||||
value: DependenceStatus.installFailed,
|
||||
},
|
||||
{
|
||||
text: intl.get('删除中'),
|
||||
value: DependenceStatus.removing,
|
||||
},
|
||||
{
|
||||
text: intl.get('已删除'),
|
||||
value: DependenceStatus.removed,
|
||||
},
|
||||
{
|
||||
text: intl.get('删除失败'),
|
||||
value: DependenceStatus.removeFailed,
|
||||
},
|
||||
],
|
||||
render: (text: string, record: any, index: number) => {
|
||||
return (
|
||||
<Space size="middle" style={{ cursor: 'text' }}>
|
||||
|
@ -163,26 +195,32 @@ const Dependence = () => {
|
|||
<FileTextOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
{record.status !== Status.安装中 &&
|
||||
record.status !== Status.删除中 && (
|
||||
<>
|
||||
<Tooltip title={isPc ? intl.get('重新安装') : ''}>
|
||||
<a onClick={() => reInstallDependence(record, index)}>
|
||||
<BugOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={isPc ? intl.get('删除') : ''}>
|
||||
<a onClick={() => deleteDependence(record, index)}>
|
||||
<DeleteOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={isPc ? intl.get('强制删除') : ''}>
|
||||
<a onClick={() => deleteDependence(record, index, true)}>
|
||||
<DeleteFilled />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{[Status.队列中, Status.安装中].includes(record.status) && (
|
||||
<Tooltip title={isPc ? intl.get('取消安装') : ''}>
|
||||
<a onClick={() => cancelDependence(record)}>
|
||||
<IconFont type="ql-icon-quxiaoanzhuang" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{![Status.安装中, Status.删除中].includes(record.status) && (
|
||||
<>
|
||||
<Tooltip title={isPc ? intl.get('重新安装') : ''}>
|
||||
<a onClick={() => reInstallDependence(record, index)}>
|
||||
<BugOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={isPc ? intl.get('删除') : ''}>
|
||||
<a onClick={() => deleteDependence(record, index)}>
|
||||
<DeleteOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={isPc ? intl.get('强制删除') : ''}>
|
||||
<a onClick={() => deleteDependence(record, index, true)}>
|
||||
<DeleteFilled />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
|
@ -200,11 +238,15 @@ const Dependence = () => {
|
|||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const tableScrollHeight = useTableScrollHeight(tableRef, 59);
|
||||
|
||||
const getDependencies = () => {
|
||||
const getDependencies = (status?: number[]) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(
|
||||
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
|
||||
`${
|
||||
config.apiPrefix
|
||||
}dependencies?searchValue=${searchText}&type=${type}&status=${
|
||||
status || ''
|
||||
}`,
|
||||
)
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
|
@ -289,6 +331,31 @@ const Dependence = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const cancelDependence = (record: any) => {
|
||||
Modal.confirm({
|
||||
title: intl.get('确认取消安装'),
|
||||
content: (
|
||||
<>
|
||||
{intl.get('确认取消安装')}{' '}
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{record.name}
|
||||
</Text>{' '}
|
||||
{intl.get('吗')}
|
||||
</>
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}dependencies/cancel`, [record.id])
|
||||
.then(() => {
|
||||
getDependencies();
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = (dependence?: any[]) => {
|
||||
setIsModalVisible(false);
|
||||
dependence && handleDependence(dependence);
|
||||
|
@ -538,6 +605,9 @@ const Dependence = () => {
|
|||
size="middle"
|
||||
scroll={{ x: 768, y: tableScrollHeight }}
|
||||
loading={loading}
|
||||
onChange={(pagination, filters) => {
|
||||
getDependencies(filters?.status as number[]);
|
||||
}}
|
||||
/>
|
||||
</DndProvider>
|
||||
</div>
|
||||
|
|
9
src/pages/dependence/type.ts
Normal file
9
src/pages/dependence/type.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export enum DependenceStatus {
|
||||
'installing',
|
||||
'installed',
|
||||
'installFailed',
|
||||
'removing',
|
||||
'removed',
|
||||
'removeFailed',
|
||||
'queued',
|
||||
}
|
Loading…
Reference in New Issue
Block a user