Compare commits

..

6 Commits

Author SHA1 Message Date
whyour e07d2b6639 修复获取最新version文件 2022-04-24 18:43:09 +08:00
whyour 220a87627d 更新版本 v2.12.2 2022-04-24 17:50:03 +08:00
whyour 87bca2ac4e 任务详情增加运行、禁用、置顶操作 2022-04-24 17:45:50 +08:00
whyour c320906149 依赖管理增加直接强制删除 2022-04-23 15:02:54 +08:00
JerryWn 27958a1a90 修复双引号转义逻辑 #1331 (#1337) 2022-04-18 22:55:37 +08:00
whyour 82c7011522 修复定时删除日志设置,主持设置大于24天 2022-04-18 22:29:14 +08:00
13 changed files with 383 additions and 115 deletions
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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();
+2 -2
View File
@@ -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',
}
+1
View File
@@ -13,5 +13,6 @@ export class SockMessage {
export type SockMessageType =
| 'ping'
| 'installDependence'
| 'uninstallDependence'
| 'updateSystemVersion'
| 'manuallyRunScript';
+18 -16
View File
@@ -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);
+3 -7
View File
@@ -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
View File
@@ -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);
}
+2
View File
@@ -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 },
});
+7
View File
@@ -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
View File
@@ -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,
})
+16
View File
@@ -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 {
+44 -16
View File
@@ -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
View File
@@ -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获取日志目录
`;