增加依赖是否已经安装判断

This commit is contained in:
whyour 2023-07-10 23:48:05 +08:00
parent f3791cbb62
commit b69ff2895e
6 changed files with 105 additions and 44 deletions

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) { export function parseHeaders(headers: string) {
if (!headers) return {}; if (!headers) return {};

View File

@ -4,9 +4,9 @@ import { DataTypes, Model, ModelDefined } from 'sequelize';
export class Dependence { export class Dependence {
timestamp?: string; timestamp?: string;
id?: number; id?: number;
status?: DependenceStatus; status: DependenceStatus;
type?: DependenceTypes; type: DependenceTypes;
name?: number; name: string;
log?: string[]; log?: string[];
remark?: string; remark?: string;
@ -42,13 +42,25 @@ export enum DependenceTypes {
export enum InstallDependenceCommandTypes { export enum InstallDependenceCommandTypes {
'pnpm add -g', 'pnpm add -g',
'pip3 install', 'pip3 install --disable-pip-version-check --root-user-action=ignore',
'apk add', 'apk add',
} }
export enum GetDependenceCommandTypes {
'pnpm ls -g ',
'pip3 list --disable-pip-version-check --root-user-action=ignore',
'apk info',
}
export enum versionDependenceCommandTypes {
'@',
'==',
'=',
}
export enum unInstallDependenceCommandTypes { export enum unInstallDependenceCommandTypes {
'pnpm remove -g', 'pnpm remove -g',
'pip3 uninstall -y', 'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y',
'apk del', 'apk del',
} }

View File

@ -8,11 +8,13 @@ import {
DependenceTypes, DependenceTypes,
unInstallDependenceCommandTypes, unInstallDependenceCommandTypes,
DependenceModel, DependenceModel,
GetDependenceCommandTypes,
versionDependenceCommandTypes,
} from '../data/dependence'; } from '../data/dependence';
import { spawn } from 'cross-spawn'; import { spawn } from 'cross-spawn';
import SockService from './sock'; import SockService from './sock';
import { FindOptions, Op } from 'sequelize'; import { FindOptions, Op } from 'sequelize';
import { concurrentRun } from '../config/util'; import { promiseExecSuccess } from '../config/util';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import taskLimit from '../shared/pLimit'; import taskLimit from '../shared/pLimit';
@ -137,9 +139,17 @@ export default class DependenceService {
} }
private async updateLog(ids: number[], log: string): Promise<void> { private async updateLog(ids: number[], log: string): Promise<void> {
const doc = await DependenceModel.findOne({ 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]; const newLog = doc?.log ? [...doc.log, log] : [log];
await DependenceModel.update({ log: newLog }, { where: { id: ids } }); await DependenceModel.update(
{ log: newLog },
{ where: { id: doc.id } },
);
}
return null;
});
} }
public installOrUninstallDependency( public installOrUninstallDependency(
@ -155,7 +165,7 @@ export default class DependenceService {
: DependenceStatus.removing; : DependenceStatus.removing;
await DependenceModel.update({ status }, { where: { id: depIds } }); await DependenceModel.update({ status }, { where: { id: depIds } });
const socketMessageType = !force const socketMessageType = isInstall
? 'installDependence' ? 'installDependence'
: 'uninstallDependence'; : 'uninstallDependence';
const depName = dependency.name; const depName = dependency.name;
@ -163,7 +173,7 @@ export default class DependenceService {
isInstall isInstall
? InstallDependenceCommandTypes ? InstallDependenceCommandTypes
: unInstallDependenceCommandTypes : unInstallDependenceCommandTypes
)[dependency.type as any]; )[dependency.type];
const actionText = isInstall ? '安装' : '删除'; const actionText = isInstall ? '安装' : '删除';
const startTime = dayjs(); const startTime = dayjs();
@ -175,7 +185,39 @@ export default class DependenceService {
message, message,
references: depIds, 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.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}`, { const cp = spawn(`${depRunCommand} ${depName}`, {
shell: '/bin/bash', shell: '/bin/bash',
@ -187,7 +229,7 @@ export default class DependenceService {
message: data.toString(), message: data.toString(),
references: depIds, references: depIds,
}); });
await this.updateLog(depIds, data.toString()); this.updateLog(depIds, data.toString());
}); });
cp.stderr.on('data', async (data) => { cp.stderr.on('data', async (data) => {
@ -196,7 +238,7 @@ export default class DependenceService {
message: data.toString(), message: data.toString(),
references: depIds, references: depIds,
}); });
await this.updateLog(depIds, data.toString()); this.updateLog(depIds, data.toString());
}); });
cp.on('error', async (err) => { cp.on('error', async (err) => {
@ -205,7 +247,7 @@ export default class DependenceService {
message: JSON.stringify(err), message: JSON.stringify(err),
references: depIds, references: depIds,
}); });
await this.updateLog(depIds, JSON.stringify(err)); this.updateLog(depIds, JSON.stringify(err));
}); });
cp.on('close', async (code) => { cp.on('close', async (code) => {
@ -221,7 +263,7 @@ export default class DependenceService {
message, message,
references: depIds, references: depIds,
}); });
await this.updateLog(depIds, message); this.updateLog(depIds, message);
let status = null; let status = null;
if (isSucceed) { if (isSucceed) {

View File

@ -4,6 +4,7 @@ import { AuthDataType, AuthModel } from "../data/auth";
class TaskLimit { class TaskLimit {
private oneLimit = pLimit(1); private oneLimit = pLimit(1);
private updateLogLimit = pLimit(1);
private cpuLimit = pLimit(Math.max(os.cpus().length, 4)); private cpuLimit = pLimit(Math.max(os.cpus().length, 4));
constructor() { constructor() {
@ -22,15 +23,15 @@ class TaskLimit {
} }
public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> { public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
return this.cpuLimit(() => { return this.cpuLimit(fn);
return fn();
});
} }
public runOneByOne<T>(fn: () => Promise<T>): Promise<T> { public runOneByOne<T>(fn: () => Promise<T>): Promise<T> {
return this.oneLimit(() => { return this.oneLimit(fn);
return fn(); }
});
public updateDepLog<T>(fn: () => Promise<T>): Promise<T> {
return this.updateLogLimit(fn);
} }
} }

View File

@ -33,6 +33,7 @@ import DependenceLogModal from './logModal';
import { useOutletContext } from '@umijs/max'; import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts'; import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight'; import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import dayjs from 'dayjs';
const { Text } = Typography; const { Text } = Typography;
const { Search } = Input; const { Search } = Input;
@ -123,27 +124,20 @@ const Dependence = () => {
dataIndex: 'remark', dataIndex: 'remark',
key: 'remark', key: 'remark',
}, },
{
title: '更新时间',
key: 'updatedAt',
dataIndex: 'updatedAt',
render: (text: string) => {
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
},
},
{ {
title: '创建时间', title: '创建时间',
key: 'timestamp', key: 'createdAt',
dataIndex: 'timestamp', dataIndex: 'createdAt',
render: (text: string, record: any) => { render: (text: string) => {
const language = navigator.language || navigator.languages[0]; return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
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>
);
}, },
}, },
{ {

View File

@ -122,7 +122,7 @@ const DependenceModal = ({
name="name" name="name"
label="名称" label="名称"
rules={[ rules={[
{ required: true, message: '请输入依赖名称', whitespace: true }, { required: true, message: '请输入依赖名称,支持指定版本', whitespace: true },
]} ]}
> >
<Input.TextArea <Input.TextArea