mirror of
https://github.com/whyour/qinglong.git
synced 2026-07-17 01:14:50 +08:00
全新青龙2.0 (#65)
* 重构shell (#17) * 更新正则 * 更新update命令 * 移除测试代码 * 重构删除日志命令 * 更新entrypoint * 更新dockerfile * 完善shell调用 * 修复share shell引用 * 修复entrypoint * 修复share shell * 修复share.sh * 修改依赖重装逻辑 * 更新docker entrypoint * curl 使用静默模式 * 更新ql raw * 修复添加单个任务 * 修复shell语法 * 添加定时任务进程 * 更新默认定时任务 * 更新定时任务重启schedule * 更新青龙重启逻辑 * 修复定时任务列表创建 * 修复schedule进程 * 修复entrypoint * 更新task命令 * pm2 restart替换成reload * 修复task命令参数引入 * 完善ql repo命令 * 修复update.sh * 更新ql repo命令 * ql repo添加登录验证,修复package.json示例 * 修复定时任务命令补全 * 修改默认cron端口 * 修复cron日志弹框异常 * 修改cron新建label * 修复ql repo命令 * 修复cron格式验证 * 修改日志目录格式 * 修改青龙remote url * 修复添加定时cron匹配 * 添加定时任务超时时间设置 * 暂时移除timeout命令 * 恢复定时任务timeout * 修复cookie.sh引用 * 修复shell变量自加 * 修复ck更新状态同步 * 增加tg bot测试,修改增删任务通知 * 修复shell函数返回值 * 修改添加任务日志打印 * 修改entrypoint日志 * 修复api日志打印 * 修改api日志打印 * 定时任务支持批量启用禁用删除运行 * 修改cron管理操作按钮响应样式 * 更新bot启动脚本 * 更新bot启动脚本 * 增加timeout默认值,修改session管理逻辑 * 更新config示例和通知日志 * 更新bot.sh * 更新启动bot命令 * 更新启动bot命令 * 修复task运行参数合并 * 增加停止定时任务功能 * 修复停止定时任务api * 更新停止定时任务日志 * 更新停止任务日志 * 修复删除cron api * 更新删除cron通知文本 * 更新命令提示 * 更新bot启动脚本
This commit is contained in:
+22
-24
@@ -105,17 +105,17 @@ const Config = () => {
|
||||
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'pin',
|
||||
key: 'pin',
|
||||
align: 'center' as const,
|
||||
render: (text: string, record: any) => {
|
||||
const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/);
|
||||
const val = (match && match[1]) || '未匹配用户名';
|
||||
return <span style={{ cursor: 'text' }}>{decodeUrl(val)}</span>;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '用户名',
|
||||
// dataIndex: 'pin',
|
||||
// key: 'pin',
|
||||
// align: 'center' as const,
|
||||
// render: (text: string, record: any) => {
|
||||
// const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/);
|
||||
// const val = (match && match[1]) || '未匹配用户名';
|
||||
// return <span style={{ cursor: 'text' }}>{decodeUrl(val)}</span>;
|
||||
// },
|
||||
// },
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
@@ -231,11 +231,11 @@ const Config = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (value && loading) {
|
||||
asyncUpdateStatus();
|
||||
}
|
||||
}, [value]);
|
||||
// useEffect(() => {
|
||||
// if (value && loading) {
|
||||
// asyncUpdateStatus();
|
||||
// }
|
||||
// }, [value]);
|
||||
|
||||
const asyncUpdateStatus = async () => {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
@@ -362,7 +362,7 @@ const Config = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = (cookies: any[]) => {
|
||||
const handleCancel = (cookies?: any[]) => {
|
||||
setIsModalVisible(false);
|
||||
if (cookies && cookies.length > 0) {
|
||||
handleCookies(cookies);
|
||||
@@ -370,10 +370,10 @@ const Config = () => {
|
||||
};
|
||||
|
||||
const handleCookies = (cookies: any[]) => {
|
||||
const result = [...value];
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i];
|
||||
const index = value.findIndex((x) => x._id === cookie._id);
|
||||
const result = [...value];
|
||||
if (index === -1) {
|
||||
result.push(cookie);
|
||||
} else {
|
||||
@@ -381,9 +381,8 @@ const Config = () => {
|
||||
...cookie,
|
||||
});
|
||||
}
|
||||
setValue(result);
|
||||
refreshStatus(cookie, index);
|
||||
}
|
||||
setValue(result);
|
||||
};
|
||||
|
||||
const components = {
|
||||
@@ -432,9 +431,8 @@ const Config = () => {
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
className="cookie-wrapper"
|
||||
title="Cookie管理"
|
||||
loading={loading}
|
||||
className="session-wrapper"
|
||||
title="Session管理"
|
||||
extra={[
|
||||
<Button key="2" type="primary" onClick={() => addCookie()}>
|
||||
添加Cookie
|
||||
@@ -463,9 +461,9 @@ const Config = () => {
|
||||
dataSource={value}
|
||||
rowKey="value"
|
||||
size="middle"
|
||||
bordered
|
||||
scroll={{ x: 768 }}
|
||||
components={components}
|
||||
loading={loading}
|
||||
onRow={(record, index) => {
|
||||
return {
|
||||
index,
|
||||
|
||||
@@ -10,7 +10,7 @@ const CookieModal = ({
|
||||
}: {
|
||||
cookie?: any;
|
||||
visible: boolean;
|
||||
handleCancel: (needUpdate?: boolean) => void;
|
||||
handleCancel: (cks?: any[]) => void;
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -46,7 +46,7 @@ const CookieModal = ({
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
handleCancel(cookie ? [data] : data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -92,7 +92,7 @@ const CookieModal = ({
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
autoSize={true}
|
||||
placeholder="请输入cookie,多个cookie换行输入"
|
||||
placeholder="请输入cookie,可直接换行输入多个cookie"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
+205
-39
@@ -23,6 +23,7 @@ import {
|
||||
EditOutlined,
|
||||
StopOutlined,
|
||||
DeleteOutlined,
|
||||
PauseCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
@@ -39,6 +40,20 @@ enum CrontabStatus {
|
||||
'disabled',
|
||||
}
|
||||
|
||||
enum OperationName {
|
||||
'启用',
|
||||
'禁用',
|
||||
'运行',
|
||||
'停止',
|
||||
}
|
||||
|
||||
enum OperationPath {
|
||||
'enable',
|
||||
'disable',
|
||||
'run',
|
||||
'stop',
|
||||
}
|
||||
|
||||
const Crontab = () => {
|
||||
const columns = [
|
||||
{
|
||||
@@ -108,15 +123,28 @@ const Crontab = () => {
|
||||
align: 'center' as const,
|
||||
render: (text: string, record: any, index: number) => (
|
||||
<Space size="middle">
|
||||
<Tooltip title="运行">
|
||||
<a
|
||||
onClick={() => {
|
||||
runCron(record, index);
|
||||
}}
|
||||
>
|
||||
<PlayCircleOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
{record.status !== CrontabStatus.running && (
|
||||
<Tooltip title="运行">
|
||||
<a
|
||||
onClick={() => {
|
||||
runCron(record, index);
|
||||
}}
|
||||
>
|
||||
<PlayCircleOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{record.status === CrontabStatus.running && (
|
||||
<Tooltip title="停止">
|
||||
<a
|
||||
onClick={() => {
|
||||
stopCron(record, index);
|
||||
}}
|
||||
>
|
||||
<PauseCircleOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title="日志">
|
||||
<a
|
||||
onClick={() => {
|
||||
@@ -142,6 +170,7 @@ const Crontab = () => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||
const [logCron, setLogCron] = useState<any>();
|
||||
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
||||
|
||||
const getCrons = () => {
|
||||
setLoading(true);
|
||||
@@ -177,7 +206,7 @@ const Crontab = () => {
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons/${record._id}`)
|
||||
.delete(`${config.apiPrefix}crons`, { data: [record._id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
notification.success({
|
||||
@@ -213,7 +242,7 @@ const Crontab = () => {
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/${record._id}/run`)
|
||||
.put(`${config.apiPrefix}crons/run`, { data: [record._id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
const result = [...value];
|
||||
@@ -235,6 +264,42 @@ const Crontab = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const stopCron = (record: any, index: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认停止',
|
||||
content: (
|
||||
<>
|
||||
确认停止定时任务{' '}
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{record.name}
|
||||
</Text>{' '}
|
||||
吗
|
||||
</>
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/stop`, { data: [record._id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
const result = [...value];
|
||||
result.splice(index, 1, {
|
||||
...record,
|
||||
status: CrontabStatus.idle,
|
||||
});
|
||||
setValue(result);
|
||||
} else {
|
||||
notification.error({
|
||||
message: data,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const enabledOrDisabledCron = (record: any, index: number) => {
|
||||
Modal.confirm({
|
||||
title: `确认${
|
||||
@@ -252,21 +317,16 @@ const Crontab = () => {
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.get(
|
||||
`${config.apiPrefix}crons/${record._id}/${
|
||||
.put(
|
||||
`${config.apiPrefix}crons/${
|
||||
record.status === CrontabStatus.disabled ? 'enable' : 'disable'
|
||||
}`,
|
||||
{
|
||||
data: { _id: record._id },
|
||||
data: [record._id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
notification.success({
|
||||
message: `${
|
||||
record.status === CrontabStatus.disabled ? '启用' : '禁用'
|
||||
}成功`,
|
||||
});
|
||||
const newStatus =
|
||||
record.status === CrontabStatus.disabled
|
||||
? CrontabStatus.idle
|
||||
@@ -296,30 +356,28 @@ const Crontab = () => {
|
||||
}> = ({ record, index }) => (
|
||||
<Dropdown
|
||||
arrow
|
||||
trigger={['click', 'hover']}
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<Menu onClick={({ key }) => action(key, record, index)}>
|
||||
<Menu.Item key="edit" icon={<EditOutlined />}>
|
||||
编辑
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="enableordisable"
|
||||
icon={
|
||||
record.status === CrontabStatus.disabled ? (
|
||||
<CheckCircleOutlined />
|
||||
) : (
|
||||
<StopOutlined />
|
||||
)
|
||||
}
|
||||
>
|
||||
{record.status === CrontabStatus.disabled ? '启用' : '禁用'}
|
||||
</Menu.Item>
|
||||
{record.isSystem !== 1 && (
|
||||
<>
|
||||
<Menu.Item
|
||||
key="enableordisable"
|
||||
icon={
|
||||
record.status === CrontabStatus.disabled ? (
|
||||
<CheckCircleOutlined />
|
||||
) : (
|
||||
<StopOutlined />
|
||||
)
|
||||
}
|
||||
>
|
||||
{record.status === CrontabStatus.disabled ? '启用' : '禁用'}
|
||||
</Menu.Item>
|
||||
<Menu.Item key="delete" icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Menu.Item>
|
||||
</>
|
||||
<Menu.Item key="delete" icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
@@ -385,8 +443,78 @@ const Crontab = () => {
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const onSelectChange = (selectedIds: any[]) => {
|
||||
setSelectedRowIds(selectedIds);
|
||||
};
|
||||
|
||||
const rowSelection = {
|
||||
selectedRowIds,
|
||||
onChange: onSelectChange,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE,
|
||||
],
|
||||
};
|
||||
|
||||
const delCrons = () => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: <>确认删除选中的定时任务吗</>,
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons`, { data: selectedRowIds })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
notification.success({
|
||||
message: '批量删除成功',
|
||||
});
|
||||
setSelectedRowIds([]);
|
||||
getCrons();
|
||||
} else {
|
||||
notification.error({
|
||||
message: data,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const operateCrons = (operationStatus: number) => {
|
||||
Modal.confirm({
|
||||
title: `确认${OperationName[operationStatus]}`,
|
||||
content: <>确认{OperationName[operationStatus]}选中的定时任务吗</>,
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
notification.success({
|
||||
message: `批量${OperationName[operationStatus]}成功`,
|
||||
});
|
||||
getCrons();
|
||||
} else {
|
||||
notification.error({
|
||||
message: data,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (logCron) {
|
||||
localStorage.setItem('logCron', logCron._id);
|
||||
setIsLogModalVisible(true);
|
||||
}
|
||||
}, [logCron]);
|
||||
@@ -411,7 +539,6 @@ const Crontab = () => {
|
||||
<PageContainer
|
||||
className="code-mirror-wrapper"
|
||||
title="定时任务"
|
||||
loading={loading}
|
||||
extra={[
|
||||
<Search
|
||||
placeholder="请输入名称或者关键词"
|
||||
@@ -436,7 +563,45 @@ const Crontab = () => {
|
||||
marginLeft,
|
||||
},
|
||||
}}
|
||||
style={{
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
{selectedRowIds.length > 0 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
|
||||
批量删除
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => operateCrons(0)}
|
||||
style={{ marginLeft: 8, marginBottom: 5 }}
|
||||
>
|
||||
批量启用
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => operateCrons(1)}
|
||||
style={{ marginLeft: 8, marginRight: 8 }}
|
||||
>
|
||||
批量禁用
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => operateCrons(2)}
|
||||
>
|
||||
批量运行
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => operateCrons(3)}>
|
||||
批量停止
|
||||
</Button>
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
已选择
|
||||
<a>{selectedRowIds?.length}</a>项
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<Table
|
||||
columns={columns}
|
||||
pagination={{
|
||||
@@ -447,8 +612,9 @@ const Crontab = () => {
|
||||
dataSource={value}
|
||||
rowKey="_id"
|
||||
size="middle"
|
||||
bordered
|
||||
scroll={{ x: 768 }}
|
||||
loading={loading}
|
||||
rowSelection={rowSelection}
|
||||
/>
|
||||
<CronLogModal
|
||||
visible={isLogModalVisible}
|
||||
|
||||
@@ -28,12 +28,14 @@ const CronLogModal = ({
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/${cron._id}/log`)
|
||||
.then((data: any) => {
|
||||
const log = data.data as string;
|
||||
setValue(log || '暂无日志');
|
||||
if (log && !log.includes('执行结束')) {
|
||||
setTimeout(() => {
|
||||
getCronLog();
|
||||
}, 2000);
|
||||
if (localStorage.getItem('logCron') === cron._id) {
|
||||
const log = data.data as string;
|
||||
setValue(log || '暂无日志');
|
||||
if (log && !log.includes('执行结束')) {
|
||||
setTimeout(() => {
|
||||
getCronLog();
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -68,19 +68,19 @@ const CronModal = ({
|
||||
>
|
||||
<Form form={form} layout="vertical" name="form_in_modal" preserve={false}>
|
||||
<Form.Item name="name" label="名称">
|
||||
<Input />
|
||||
<Input placeholder="请输入任务名称" />
|
||||
</Form.Item>
|
||||
<Form.Item name="command" label="任务" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
<Form.Item name="command" label="命令" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入要执行的命令" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="schedule"
|
||||
label="时间"
|
||||
label="定时规则"
|
||||
rules={[
|
||||
{ required: true },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
if (cronParse.parseString(value).expressions.length > 0) {
|
||||
if (cronParse.parseExpression(value).hasNext()) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject('Cron表达式格式有误');
|
||||
@@ -89,7 +89,7 @@ const CronModal = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder="秒(可选) 分 时 天 月 周" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -87,7 +87,7 @@ const Crontab = () => {
|
||||
newValue={sample}
|
||||
splitView={true}
|
||||
leftTitle="config.sh"
|
||||
rightTitle="config.sh.sample"
|
||||
rightTitle="config.sample.sh"
|
||||
disableWordDiff={true}
|
||||
/>
|
||||
{/* <CodeDiff
|
||||
|
||||
Reference in New Issue
Block a user