mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-06 08:44:32 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e07d2b6639 | |||
| 220a87627d | |||
| 87bca2ac4e | |||
| c320906149 | |||
| 27958a1a90 | |||
| 82c7011522 |
@@ -94,7 +94,7 @@ export default (app: Router) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const dependenceService = Container.get(DependenceService);
|
||||
const data = await dependenceService.removeDb(req.body);
|
||||
const data = await dependenceService.remove(req.body, true);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createRandomString } from './util';
|
||||
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
const lastVersionFile = 'http://qn.whyour.cn/version.ts?v=2.12.1';
|
||||
const lastVersionFile = `http://qn.whyour.cn/version.ts?v=${Date.now()}`;
|
||||
|
||||
const envFound = dotenv.config();
|
||||
const rootPath = process.cwd();
|
||||
|
||||
@@ -37,13 +37,13 @@ export enum DependenceTypes {
|
||||
}
|
||||
|
||||
export enum InstallDependenceCommandTypes {
|
||||
'npm i -g --force',
|
||||
'npm i -g -f --loglevel warn',
|
||||
'pip3 install',
|
||||
'apk add --no-cache -f',
|
||||
}
|
||||
|
||||
export enum unInstallDependenceCommandTypes {
|
||||
'npm uninstall -g --force',
|
||||
'npm uninstall -g -f --loglevel warn',
|
||||
'pip3 uninstall -y',
|
||||
'apk del -f',
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ export class SockMessage {
|
||||
export type SockMessageType =
|
||||
| 'ping'
|
||||
| 'installDependence'
|
||||
| 'uninstallDependence'
|
||||
| 'updateSystemVersion'
|
||||
| 'manuallyRunScript';
|
||||
|
||||
+18
-16
@@ -56,13 +56,13 @@ export default class DependenceService {
|
||||
return await this.getDb({ id: payload.id });
|
||||
}
|
||||
|
||||
public async remove(ids: number[]) {
|
||||
public async remove(ids: number[], force = false): Promise<Dependence[]> {
|
||||
await DependenceModel.update(
|
||||
{ status: DependenceStatus.removing, log: [] },
|
||||
{ where: { id: ids } },
|
||||
);
|
||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||
this.installOrUninstallDependencies(docs, false);
|
||||
this.installOrUninstallDependencies(docs, false, force);
|
||||
return docs;
|
||||
}
|
||||
|
||||
@@ -128,12 +128,16 @@ export default class DependenceService {
|
||||
public installOrUninstallDependencies(
|
||||
dependencies: Dependence[],
|
||||
isInstall: boolean = true,
|
||||
force: boolean = false,
|
||||
) {
|
||||
return new Promise(async (resolve) => {
|
||||
if (dependencies.length === 0) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const socketMessageType = !force
|
||||
? 'installDependence'
|
||||
: 'uninstallDependence';
|
||||
const depNames = dependencies.map((x) => x.name).join(' ');
|
||||
const depRunCommand = (
|
||||
isInstall
|
||||
@@ -145,21 +149,21 @@ export default class DependenceService {
|
||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||
const startTime = Date.now();
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
type: socketMessageType,
|
||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}`,
|
||||
).toLocaleString()}\n\n`,
|
||||
references: depIds,
|
||||
});
|
||||
await this.updateLog(
|
||||
depIds,
|
||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}\n`,
|
||||
).toLocaleString()}\n\n`,
|
||||
);
|
||||
cp.stdout.on('data', async (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
type: socketMessageType,
|
||||
message: data.toString(),
|
||||
references: depIds,
|
||||
});
|
||||
@@ -168,7 +172,7 @@ export default class DependenceService {
|
||||
|
||||
cp.stderr.on('data', async (data) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
type: socketMessageType,
|
||||
message: data.toString(),
|
||||
references: depIds,
|
||||
});
|
||||
@@ -177,7 +181,7 @@ export default class DependenceService {
|
||||
|
||||
cp.on('error', async (err) => {
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
type: socketMessageType,
|
||||
message: JSON.stringify(err),
|
||||
references: depIds,
|
||||
});
|
||||
@@ -191,15 +195,15 @@ export default class DependenceService {
|
||||
const resultText = isSucceed ? '成功' : '失败';
|
||||
|
||||
this.sockService.sendMessage({
|
||||
type: 'installDependence',
|
||||
message: `依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
type: socketMessageType,
|
||||
message: `\n依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
references: depIds,
|
||||
});
|
||||
await this.updateLog(
|
||||
depIds,
|
||||
`依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
`\n依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
);
|
||||
@@ -216,11 +220,9 @@ export default class DependenceService {
|
||||
}
|
||||
await DependenceModel.update({ status }, { where: { id: depIds } });
|
||||
|
||||
// 如果删除依赖成功,3秒后删除数据库记录
|
||||
if (isSucceed && !isInstall) {
|
||||
setTimeout(() => {
|
||||
this.removeDb(depIds);
|
||||
}, 5000);
|
||||
// 如果删除依赖成功或者强制删除
|
||||
if ((isSucceed || force) && !isInstall) {
|
||||
this.removeDb(depIds);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
|
||||
@@ -168,13 +168,9 @@ export default class EnvService {
|
||||
.filter((x) => x.status !== EnvStatus.disabled)
|
||||
.map('value')
|
||||
.join('&')
|
||||
.replace(/ /g, '');
|
||||
if (/"/.test(value)) {
|
||||
value = `'${value}'`;
|
||||
} else {
|
||||
value = `"${value}"`;
|
||||
}
|
||||
env_string += `export ${key}=${value}\n`;
|
||||
.replace(/"/g, '\"')
|
||||
.trim();
|
||||
env_string += `export ${key}="${value}"\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+41
-33
@@ -5,8 +5,8 @@ import { Crontab } from '../data/cron';
|
||||
import { exec } from 'child_process';
|
||||
import {
|
||||
ToadScheduler,
|
||||
SimpleIntervalJob,
|
||||
Task,
|
||||
LongIntervalJob,
|
||||
AsyncTask,
|
||||
SimpleIntervalSchedule,
|
||||
} from 'toad-scheduler';
|
||||
|
||||
@@ -87,43 +87,51 @@ export default class ScheduleService {
|
||||
name,
|
||||
command,
|
||||
);
|
||||
const task = new Task(name, async () => {
|
||||
try {
|
||||
exec(
|
||||
command,
|
||||
{ maxBuffer: this.maxBuffer },
|
||||
async (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
const task = new AsyncTask(
|
||||
name,
|
||||
async () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
exec(
|
||||
command,
|
||||
{ maxBuffer: this.maxBuffer },
|
||||
async (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
await this.logger.info(
|
||||
if (stderr) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
error,
|
||||
err,
|
||||
);
|
||||
} finally {
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const job = new SimpleIntervalJob({ ...schedule }, task, _id);
|
||||
const job = new LongIntervalJob({ ...schedule }, task, _id);
|
||||
|
||||
this.intervalSchedule.addIntervalJob(job);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,9 @@ export default class SystemService {
|
||||
}
|
||||
|
||||
public async updateLogRemoveFrequency(frequency: number) {
|
||||
const oDoc = await this.getLogRemoveFrequency();
|
||||
const result = await this.updateAuthDb({
|
||||
...oDoc,
|
||||
type: AuthDataType.removeLogFrequency,
|
||||
info: { frequency },
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createFromIconfontCN } from '@ant-design/icons';
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: ['//at.alicdn.com/t/font_3354854_pk18p04ny1a.js'],
|
||||
});
|
||||
|
||||
export default IconFont;
|
||||
+238
-30
@@ -10,6 +10,7 @@ import {
|
||||
List,
|
||||
Divider,
|
||||
Typography,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import {
|
||||
ClockCircleOutlined,
|
||||
@@ -17,6 +18,8 @@ import {
|
||||
FieldTimeOutlined,
|
||||
Loading3QuartersOutlined,
|
||||
FileOutlined,
|
||||
PlayCircleOutlined,
|
||||
PauseCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { CrontabStatus } from './index';
|
||||
import { diffTime } from '@/utils/date';
|
||||
@@ -24,6 +27,7 @@ import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
import CronLogModal from './logModal';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import IconFont from '@/components/iconfont';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -68,6 +72,7 @@ const CronDetailModal = ({
|
||||
const [scriptInfo, setScriptInfo] = useState<any>({});
|
||||
const [logUrl, setLogUrl] = useState('');
|
||||
const [validTabs, setValidTabs] = useState(tabList);
|
||||
const [currentCron, setCurrentCron] = useState<any>({});
|
||||
|
||||
const contentList: any = {
|
||||
log: (
|
||||
@@ -103,7 +108,7 @@ const CronDetailModal = ({
|
||||
};
|
||||
|
||||
const onClickItem = (item: LogItem) => {
|
||||
localStorage.setItem('logCron', cron.id);
|
||||
localStorage.setItem('logCron', currentCron.id);
|
||||
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
|
||||
request
|
||||
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
|
||||
@@ -196,8 +201,150 @@ const CronDetailModal = ({
|
||||
});
|
||||
};
|
||||
|
||||
const runCron = () => {
|
||||
Modal.confirm({
|
||||
title: '确认运行',
|
||||
content: (
|
||||
<>
|
||||
确认运行定时任务{' '}
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{currentCron.name}
|
||||
</Text>{' '}
|
||||
吗
|
||||
</>
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
|
||||
setTimeout(() => {
|
||||
getLogs();
|
||||
}, 1000);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const stopCron = () => {
|
||||
Modal.confirm({
|
||||
title: '确认停止',
|
||||
content: (
|
||||
<>
|
||||
确认停止定时任务{' '}
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{currentCron.name}
|
||||
</Text>{' '}
|
||||
吗
|
||||
</>
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const enabledOrDisabledCron = () => {
|
||||
Modal.confirm({
|
||||
title: `确认${currentCron.isDisabled === 1 ? '启用' : '禁用'}`,
|
||||
content: (
|
||||
<>
|
||||
确认{currentCron.isDisabled === 1 ? '启用' : '禁用'}
|
||||
定时任务{' '}
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{currentCron.name}
|
||||
</Text>{' '}
|
||||
吗
|
||||
</>
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.put(
|
||||
`${config.apiPrefix}crons/${
|
||||
currentCron.isDisabled === 1 ? 'enable' : 'disable'
|
||||
}`,
|
||||
{
|
||||
data: [currentCron.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
setCurrentCron({
|
||||
...currentCron,
|
||||
isDisabled: currentCron.isDisabled === 1 ? 0 : 1,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const pinOrUnPinCron = () => {
|
||||
Modal.confirm({
|
||||
title: `确认${currentCron.isPinned === 1 ? '取消置顶' : '置顶'}`,
|
||||
content: (
|
||||
<>
|
||||
确认{currentCron.isPinned === 1 ? '取消置顶' : '置顶'}
|
||||
定时任务{' '}
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{currentCron.name}
|
||||
</Text>{' '}
|
||||
吗
|
||||
</>
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.put(
|
||||
`${config.apiPrefix}crons/${
|
||||
currentCron.isPinned === 1 ? 'unpin' : 'pin'
|
||||
}`,
|
||||
{
|
||||
data: [currentCron.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
setCurrentCron({
|
||||
...currentCron,
|
||||
isPinned: currentCron.isPinned === 1 ? 0 : 1,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (cron && cron.id) {
|
||||
setCurrentCron(cron);
|
||||
getLogs();
|
||||
getScript();
|
||||
}
|
||||
@@ -206,19 +353,76 @@ const CronDetailModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<>
|
||||
<span>{cron.name}</span>
|
||||
{cron.labels?.length > 0 && cron.labels[0] !== '' && (
|
||||
<Divider type="vertical"></Divider>
|
||||
)}
|
||||
{cron.labels?.length > 0 &&
|
||||
cron.labels[0] !== '' &&
|
||||
cron.labels?.map((label: string, i: number) => (
|
||||
<Tag color="blue" style={{ marginRight: 5 }}>
|
||||
{label}
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
<div className="crontab-title-wrapper">
|
||||
<div>
|
||||
<span>{currentCron.name}</span>
|
||||
{currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && (
|
||||
<Divider type="vertical"></Divider>
|
||||
)}
|
||||
{currentCron.labels?.length > 0 &&
|
||||
currentCron.labels[0] !== '' &&
|
||||
currentCron.labels?.map((label: string, i: number) => (
|
||||
<Tag color="blue" style={{ marginRight: 5 }}>
|
||||
{label}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="operations">
|
||||
<Tooltip
|
||||
title={
|
||||
currentCron.status === CrontabStatus.idle ? '运行' : '停止'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
icon={
|
||||
currentCron.status === CrontabStatus.idle ? (
|
||||
<PlayCircleOutlined />
|
||||
) : (
|
||||
<PauseCircleOutlined />
|
||||
)
|
||||
}
|
||||
size="small"
|
||||
onClick={
|
||||
currentCron.status === CrontabStatus.idle ? runCron : stopCron
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={currentCron.isDisabled === 1 ? '启用' : '禁用'}>
|
||||
<Button
|
||||
type="link"
|
||||
icon={
|
||||
<IconFont
|
||||
type={
|
||||
currentCron.isDisabled === 1
|
||||
? 'ql-icon-qiyong'
|
||||
: 'ql-icon-jinyong'
|
||||
}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
onClick={enabledOrDisabledCron}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={currentCron.isPinned === 1 ? '取消置顶' : '置顶'}>
|
||||
<Button
|
||||
type="link"
|
||||
icon={
|
||||
<IconFont
|
||||
type={
|
||||
currentCron.isPinned === 1
|
||||
? 'ql-icon-quxiaozhiding'
|
||||
: 'ql-icon-zhiding'
|
||||
}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
onClick={pinOrUnPinCron}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
centered
|
||||
visible={visible}
|
||||
@@ -232,21 +436,22 @@ const CronDetailModal = ({
|
||||
<Card>
|
||||
<div className="cron-detail-info-item">
|
||||
<div className="cron-detail-info-title">任务</div>
|
||||
<div className="cron-detail-info-value">{cron.command}</div>
|
||||
<div className="cron-detail-info-value">{currentCron.command}</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card style={{ marginTop: 10 }}>
|
||||
<div className="cron-detail-info-item">
|
||||
<div className="cron-detail-info-title">状态</div>
|
||||
<div className="cron-detail-info-value">
|
||||
{(!cron.isDisabled || cron.status !== CrontabStatus.idle) && (
|
||||
{(!currentCron.isDisabled ||
|
||||
currentCron.status !== CrontabStatus.idle) && (
|
||||
<>
|
||||
{cron.status === CrontabStatus.idle && (
|
||||
{currentCron.status === CrontabStatus.idle && (
|
||||
<Tag icon={<ClockCircleOutlined />} color="default">
|
||||
空闲中
|
||||
</Tag>
|
||||
)}
|
||||
{cron.status === CrontabStatus.running && (
|
||||
{currentCron.status === CrontabStatus.running && (
|
||||
<Tag
|
||||
icon={<Loading3QuartersOutlined spin />}
|
||||
color="processing"
|
||||
@@ -254,29 +459,30 @@ const CronDetailModal = ({
|
||||
运行中
|
||||
</Tag>
|
||||
)}
|
||||
{cron.status === CrontabStatus.queued && (
|
||||
{currentCron.status === CrontabStatus.queued && (
|
||||
<Tag icon={<FieldTimeOutlined />} color="default">
|
||||
队列中
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{cron.isDisabled === 1 && cron.status === CrontabStatus.idle && (
|
||||
<Tag icon={<CloseCircleOutlined />} color="error">
|
||||
已禁用
|
||||
</Tag>
|
||||
)}
|
||||
{currentCron.isDisabled === 1 &&
|
||||
currentCron.status === CrontabStatus.idle && (
|
||||
<Tag icon={<CloseCircleOutlined />} color="error">
|
||||
已禁用
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="cron-detail-info-item">
|
||||
<div className="cron-detail-info-title">定时</div>
|
||||
<div className="cron-detail-info-value">{cron.schedule}</div>
|
||||
<div className="cron-detail-info-value">{currentCron.schedule}</div>
|
||||
</div>
|
||||
<div className="cron-detail-info-item">
|
||||
<div className="cron-detail-info-title">最后运行时间</div>
|
||||
<div className="cron-detail-info-value">
|
||||
{cron.last_execution_time
|
||||
? new Date(cron.last_execution_time * 1000)
|
||||
{currentCron.last_execution_time
|
||||
? new Date(currentCron.last_execution_time * 1000)
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
@@ -287,14 +493,16 @@ const CronDetailModal = ({
|
||||
<div className="cron-detail-info-item">
|
||||
<div className="cron-detail-info-title">最后运行时长</div>
|
||||
<div className="cron-detail-info-value">
|
||||
{cron.last_running_time ? diffTime(cron.last_running_time) : '-'}
|
||||
{currentCron.last_running_time
|
||||
? diffTime(currentCron.last_running_time)
|
||||
: '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="cron-detail-info-item">
|
||||
<div className="cron-detail-info-title">下次运行时间</div>
|
||||
<div className="cron-detail-info-value">
|
||||
{cron.nextRunTime &&
|
||||
cron.nextRunTime
|
||||
{currentCron.nextRunTime &&
|
||||
currentCron.nextRunTime
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
|
||||
@@ -77,6 +77,22 @@
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.crontab-title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-right: 32px;
|
||||
|
||||
.operations {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ant-btn:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.log-item {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
DeleteOutlined,
|
||||
SyncOutlined,
|
||||
CheckCircleOutlined,
|
||||
StopOutlined,
|
||||
DeleteFilled,
|
||||
BugOutlined,
|
||||
FileTextOutlined,
|
||||
} from '@ant-design/icons';
|
||||
@@ -120,6 +120,15 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
const isPc = !isPhone;
|
||||
return (
|
||||
<Space size="middle">
|
||||
<Tooltip title={isPc ? '日志' : ''}>
|
||||
<a
|
||||
onClick={() => {
|
||||
setLogDependence({ ...record, timestamp: Date.now() });
|
||||
}}
|
||||
>
|
||||
<FileTextOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
{record.status !== Status.安装中 &&
|
||||
record.status !== Status.删除中 && (
|
||||
<>
|
||||
@@ -133,17 +142,13 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
<DeleteOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={isPc ? '强制删除' : ''}>
|
||||
<a onClick={() => deleteDependence(record, index, true)}>
|
||||
<DeleteFilled />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<Tooltip title={isPc ? '日志' : ''}>
|
||||
<a
|
||||
onClick={() => {
|
||||
setLogDependence({ ...record, timestamp: Date.now() });
|
||||
}}
|
||||
>
|
||||
<FileTextOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
@@ -182,7 +187,11 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const deleteDependence = (record: any, index: number) => {
|
||||
const deleteDependence = (
|
||||
record: any,
|
||||
index: number,
|
||||
force: boolean = false,
|
||||
) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: (
|
||||
@@ -196,10 +205,19 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}dependencies`, { data: [record.id] })
|
||||
.delete(`${config.apiPrefix}dependencies${force ? '/force' : ''}`, {
|
||||
data: [record.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
handleDependence(data.data[0]);
|
||||
if (force) {
|
||||
const i = value.findIndex((x) => x.id === data.data[0].id);
|
||||
if (i !== -1) {
|
||||
const result = [...value];
|
||||
result.splice(i, 1);
|
||||
setValue(result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
@@ -275,13 +293,16 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
onChange: onSelectChange,
|
||||
};
|
||||
|
||||
const delDependencies = () => {
|
||||
const delDependencies = (force: boolean) => {
|
||||
const forceUrl = force ? '/force' : '';
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: <>确认删除选中的依赖吗</>,
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}dependencies`, { data: selectedRowIds })
|
||||
.delete(`${config.apiPrefix}dependencies${forceUrl}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
setSelectedRowIds([]);
|
||||
@@ -377,10 +398,17 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||
onClick={delDependencies}
|
||||
onClick={() => delDependencies(false)}
|
||||
>
|
||||
批量删除
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||
onClick={() => delDependencies(true)}
|
||||
>
|
||||
批量强制删除
|
||||
</Button>
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
已选择
|
||||
<a>{selectedRowIds?.length}</a>项
|
||||
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
export const version = '2.12.1';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/289';
|
||||
export const changeLog = `2.12.1 版本说明
|
||||
1. 修复定时任务详情获取日志
|
||||
2. 修复系统通知pushdeer
|
||||
3. 修复python pushDeer推送,感谢 https://github.com/chen310 PR
|
||||
3. nodejs telegram推送改为json方式,感谢 https://github.com/kan3Git PR
|
||||
4. 修复日志目录拼接规则
|
||||
5. 其他bug修复
|
||||
export const version = '2.12.2';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/290';
|
||||
export const changeLog = `2.12.2 版本说明
|
||||
1. 任务详情支持运行、禁用、置顶操作
|
||||
2. 依赖管理增加直接强制删除
|
||||
3. 修复环境变量引号转义逻辑,感谢 https://github.com/JerryWn12 PR
|
||||
4. 修复定时删除日志设置,支持设置为24天以上
|
||||
5. 修复拉取脚本,shell发送通知
|
||||
6. 修复shell获取日志目录
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user