qinglong/src/pages/subscription/modal.tsx

457 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useCallback, useEffect, useState } from 'react';
import { Modal, message, InputNumber, Form, Radio, Select, Input } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import cron_parser from 'cron-parser';
const { Option } = Select;
const repoUrlRegx = /[^\/\:]+\/[^\/]+(?=\.git)/;
const fileUrlRegx = /[^\/\:]+\/[^\/]+$/;
const SubscriptionModal = ({
subscription,
handleCancel,
visible,
}: {
subscription?: any;
visible: boolean;
handleCancel: (needUpdate?: boolean) => void;
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [type, setType] = useState('public-repo');
const [scheduleType, setScheduleType] = useState('crontab');
const [pullType, setPullType] = useState<'ssh-key' | 'user-pwd'>('ssh-key');
const handleOk = async (values: any) => {
setLoading(true);
const method = subscription ? 'put' : 'post';
const payload = { ...values };
if (subscription) {
payload.id = subscription.id;
}
try {
const { code, data } = await request[method](
`${config.apiPrefix}subscriptions`,
{
data: payload,
},
);
if (code === 200) {
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
} else {
message.error(data);
}
setLoading(false);
handleCancel(data);
} catch (error: any) {
setLoading(false);
}
};
const typeChange = (e) => {
setType(e.target.value);
const _url = form.getFieldValue('url');
const _branch = form.getFieldValue('branch');
form.setFieldsValue({
alias: formatAlias(_url, _branch, e.target.value),
});
if (_url) {
form.validateFields(['url']);
}
};
const scheduleTypeChange = (e) => {
setScheduleType(e.target.value);
form.setFieldsValue({ schedule: '' });
};
const pullTypeChange = (e) => {
setPullType(e.target.value);
};
const onUrlChange = (e) => {
const _branch = form.getFieldValue('branch');
form.setFieldsValue({
alias: formatAlias(e.target.value, _branch),
});
};
const onBranchChange = (e) => {
const _url = form.getFieldValue('url');
form.setFieldsValue({
alias: formatAlias(_url, e.target.value),
});
};
const formatAlias = (_url: string, _branch: string, _type = type) => {
let _alias = '';
const _regx = _type === 'file' ? fileUrlRegx : repoUrlRegx;
if (_regx.test(_url)) {
_alias = _url.match(_regx)![0].replaceAll('/', '_').replaceAll('.', '_');
}
if (_branch) {
_alias = _alias + '_' + _branch;
}
return _alias;
};
const IntervalSelect = ({
value,
onChange,
}: {
value?: any;
onChange?: (param: any) => void;
}) => {
const [intervalType, setIntervalType] = useState('days');
const [intervalNumber, setIntervalNumber] = useState<number>();
const intervalTypeChange = (type: string) => {
setIntervalType(type);
onChange?.({ type, value: intervalNumber });
};
const numberChange = (value: number) => {
setIntervalNumber(value);
onChange?.({ type: intervalType, value });
};
useEffect(() => {
if (value) {
setIntervalType(value.type);
setIntervalNumber(value.value);
}
}, [value]);
return (
<Input.Group compact>
<InputNumber
addonBefore="每"
precision={0}
min={1}
value={intervalNumber}
style={{ width: 'calc(100% - 58px)' }}
onChange={numberChange}
/>
<Select value={intervalType} onChange={intervalTypeChange}>
<Option value="days"></Option>
<Option value="hours"></Option>
<Option value="minutes"></Option>
<Option value="seconds"></Option>
</Select>
</Input.Group>
);
};
const PullOptions = ({
value,
onChange,
type,
}: {
value?: any;
type: 'ssh-key' | 'user-pwd';
onChange?: (param: any) => void;
}) => {
return type === 'ssh-key' ? (
<Form.Item
name={['pull_option', 'private_key']}
label="私钥"
rules={[{ required: true }]}
>
<Input.TextArea
rows={4}
autoSize={{ minRows: 1, maxRows: 6 }}
placeholder="请输入私钥"
/>
</Form.Item>
) : (
<>
<Form.Item
name={['pull_option', 'username']}
label="用户名"
rules={[{ required: true }]}
>
<Input placeholder="请输入认证用户名" />
</Form.Item>
<Form.Item
name={['pull_option', 'password']}
tooltip="Github已不支持密码认证请使用Token方式"
label="密码/Token"
rules={[{ required: true }]}
>
<Input placeholder="请输入密码或者Token" />
</Form.Item>
</>
);
};
const onPaste = useCallback((e: any) => {
const text = e.clipboardData.getData('text');
if (!subscription && text.includes('ql ')) {
const [
,
type,
url,
whitelist,
blacklist,
dependences,
branch,
extensions,
] = text
.split(' ')
.map((x) => x.trim().replace(/\"/g, '').replace(/\'/, ''));
form.setFieldsValue({
type:
type === 'raw'
? 'file'
: url.startsWith('http')
? 'public-repo'
: 'private-repo',
url,
whitelist,
blacklist,
dependences,
branch,
extensions,
});
form.validateFields(['url']);
}
}, []);
useEffect(() => {
if (visible) {
window.addEventListener('paste', onPaste);
} else {
window.removeEventListener('paste', onPaste);
}
}, [visible]);
useEffect(() => {
form.setFieldsValue(subscription || {});
setType((subscription && subscription.type) || 'public-repo');
setScheduleType((subscription && subscription.schedule_type) || 'crontab');
setPullType((subscription && subscription.pull_type) || 'ssh-key');
if (!subscription) {
form.resetFields();
}
}, [subscription, visible]);
const isFirefox = navigator.userAgent.includes('Firefox');
const isSafari =
navigator.userAgent.includes('Safari') &&
!navigator.userAgent.includes('Chrome');
const isQQBrowser = navigator.userAgent.includes('QQBrowser');
return (
<Modal
title={
subscription ? (
'编辑订阅'
) : (
<span>
<small
style={{
color: '#999',
fontWeight: 400,
fontSize: isFirefox ? 9 : 12,
marginLeft: 2,
zoom: isSafari ? 0.66 : 0.8,
letterSpacing: isQQBrowser ? -2 : 0,
}}
>
repo/raw命令
</small>
</span>
)
}
visible={visible}
forceRender
centered
maskClosable={false}
onOk={() => {
form
.validateFields()
.then((values) => {
handleOk(values);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
onCancel={() => handleCancel()}
confirmLoading={loading}
>
<Form form={form} name="form_in_modal" layout="vertical">
<Form.Item name="name" label="名称">
<Input placeholder="请输入订阅名" />
</Form.Item>
<Form.Item
name="type"
label="类型"
rules={[{ required: true }]}
initialValue={'public-repo'}
>
<Radio.Group onChange={typeChange}>
<Radio value="public-repo"></Radio>
<Radio value="private-repo"></Radio>
<Radio value="file"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="url"
label="链接"
rules={[
{ required: true },
{ pattern: type === 'file' ? fileUrlRegx : repoUrlRegx },
]}
>
<Input.TextArea
rows={4}
autoSize={true}
placeholder="请输入订阅链接"
onPaste={onUrlChange}
onChange={onUrlChange}
/>
</Form.Item>
{type !== 'file' && (
<Form.Item name="branch" label="分支">
<Input
placeholder="请输入分支"
onPaste={onBranchChange}
onChange={onBranchChange}
/>
</Form.Item>
)}
<Form.Item
name="alias"
label="唯一值"
rules={[{ required: true, message: '' }]}
tooltip="唯一值用于日志目录和私钥别名"
>
<Input placeholder="自动生成" disabled />
</Form.Item>
{type === 'private-repo' && (
<>
<Form.Item
name="pull_type"
label="拉取方式"
initialValue={'ssh-key'}
rules={[{ required: true }]}
>
<Radio.Group onChange={pullTypeChange}>
<Radio value="ssh-key"></Radio>
<Radio value="user-pwd">/Token</Radio>
</Radio.Group>
</Form.Item>
<PullOptions type={pullType} />
</>
)}
<Form.Item
name="schedule_type"
label="定时类型"
initialValue={'crontab'}
rules={[{ required: true }]}
>
<Radio.Group onChange={scheduleTypeChange}>
<Radio value="crontab">crontab</Radio>
<Radio value="interval">interval</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name={scheduleType === 'crontab' ? 'schedule' : 'interval_schedule'}
label="定时规则"
rules={[
{ required: true },
{
validator: (rule, value) => {
if (
scheduleType === 'interval' ||
!value ||
cron_parser.parseExpression(value).hasNext()
) {
return Promise.resolve();
} else {
return Promise.reject('Subscription表达式格式有误');
}
},
},
]}
>
{scheduleType === 'interval' ? (
<IntervalSelect />
) : (
<Input placeholder="秒(可选) 分 时 天 月 周" />
)}
</Form.Item>
{type !== 'file' && (
<>
<Form.Item
name="whitelist"
label="白名单"
tooltip="多个关键词竖线分割,支持正则表达式"
>
<Input.TextArea
rows={4}
autoSize={true}
placeholder="请输入脚本筛选白名单关键词,多个关键词竖线分割"
/>
</Form.Item>
<Form.Item
name="blacklist"
label="黑名单"
tooltip="多个关键词竖线分割,支持正则表达式"
>
<Input.TextArea
rows={4}
autoSize={true}
placeholder="请输入脚本筛选黑名单关键词,多个关键词竖线分割"
/>
</Form.Item>
<Form.Item
name="dependences"
label="依赖文件"
tooltip="多个关键词竖线分割,支持正则表达式"
>
<Input.TextArea
rows={4}
autoSize={true}
placeholder="请输入脚本依赖文件关键词,多个关键词竖线分割"
/>
</Form.Item>
<Form.Item
name="extensions"
label="文件后缀"
tooltip="仓库需要拉取的文件后缀多个后缀空格分隔默认使用配置文件中的RepoFileExtensions"
>
<Input placeholder="请输入文件后缀" />
</Form.Item>
<Form.Item
name="sub_before"
label="执行前"
tooltip="运行订阅前执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js"
>
<Input.TextArea
rows={4}
autoSize={true}
placeholder="请输入运行订阅前要执行的命令"
/>
</Form.Item>
<Form.Item
name="sub_after"
label="执行后"
tooltip="运行订阅后执行的命令,比如 cp/mv/python3 xxx.py/node xxx.js"
>
<Input.TextArea
rows={4}
autoSize={true}
placeholder="请输入运行订阅后要执行的命令"
/>
</Form.Item>
</>
)}
</Form>
</Modal>
);
};
export default SubscriptionModal;