Compare commits

..

16 Commits

Author SHA1 Message Date
whyour 98dfa4c425 更新版本 v2.15.9 2023-03-04 15:48:29 +08:00
whyour 5bf08b76de 修复系统更新源判断,回退 @monaco-editor/react 2023-03-03 23:45:38 +08:00
whyour 5c5b4f804e 更新 antd 2023-03-03 22:52:44 +08:00
whyour 6455245d11 更新 @umijs/max 2023-03-03 22:14:03 +08:00
pharaoh2012 2ff3d9c601 根据标题跳过消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔 (#1833) 2023-03-03 21:59:44 +08:00
whyour d342fbf29f 修改登录日志表格样式 2023-03-03 11:58:06 +08:00
whyour f865018cd6 修改表格样式 2023-03-02 23:31:46 +08:00
whyour 3b9a4f0834 修复定时任务状态筛选 2023-03-02 23:16:18 +08:00
whyour 67bc305950 修复拉取仓库重置操作 2023-03-01 22:12:49 +08:00
whyour 97f128d59f 修改 ql extra 执行逻辑 2023-02-28 22:56:06 +08:00
whyour e25885c4d9 修改 nginx ipv6 配置 2023-02-27 23:30:00 +08:00
whyour c285026339 修改删除日志逻辑 2023-02-27 23:25:08 +08:00
whyour 6c34045a48 修改订阅名称必填 2023-02-26 23:36:32 +08:00
whyour e1e6261b4f 修复定时任务/视图/环境变量更新逻辑 2023-02-24 23:21:08 +08:00
whyour 697bcb5922 暂时去掉 vlist 2023-02-24 23:04:18 +08:00
whyour e05b0a2491 修复文件类型订阅重复添加任务 2023-02-18 14:38:13 +08:00
29 changed files with 2424 additions and 1570 deletions
+15
View File
@@ -152,6 +152,21 @@ export default (app: Router) => {
}
});
route.get(
'/detail',
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cronService = Container.get(CronService);
const data = await cronService.find(req.query as any);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.post(
'/',
celebrate({
+19 -7
View File
@@ -18,7 +18,7 @@ import { TASK_PREFIX, QL_PREFIX } from '../config/const';
@Service()
export default class CronService {
constructor(@Inject('logger') private logger: winston.Logger) { }
constructor(@Inject('logger') private logger: winston.Logger) {}
private isSixCron(cron: Crontab) {
const { schedule } = cron;
@@ -41,8 +41,9 @@ export default class CronService {
}
public async update(payload: Crontab): Promise<Crontab> {
payload.saved = false;
const newDoc = await this.updateDb(payload);
const tab = new Crontab(payload);
tab.saved = false;
const newDoc = await this.updateDb(tab);
await this.set_crontab();
return newDoc;
}
@@ -244,8 +245,11 @@ export default class CronService {
const filterKeys: any = Object.keys(filterQuery);
for (const key of filterKeys) {
let q: any = {};
if (filterKeys[key]) {
q[key] = filterKeys[key];
if (!filterQuery[key]) continue;
if (key === 'status' && filterQuery[key].includes(2)) {
q = { [Op.or]: [{ [key]: filterQuery[key] }, { isDisabled: 1 }] };
} else {
q[key] = filterQuery[key];
}
query[Op.and].push(q);
}
@@ -260,6 +264,15 @@ export default class CronService {
}
}
public async find(params: { log_path: string }): Promise<Crontab | null> {
try {
const result = await CrontabModel.findOne({ where: { ...params } });
return result;
} catch (error) {
throw error;
}
}
public async crontabs(params?: {
searchValue: string;
page: string;
@@ -428,7 +441,7 @@ export default class CronService {
if (logFileExist) {
return getFileContentByName(`${absolutePath}`);
} else {
return '任务未运行或运行失败,请尝试手动运行';
return '任务未运行';
}
}
@@ -452,7 +465,6 @@ export default class CronService {
} else {
return [];
}
}
private make_command(tab: Crontab) {
+1 -1
View File
@@ -31,7 +31,7 @@ export default class CronViewService {
}
public async update(payload: CrontabView): Promise<CrontabView> {
const newDoc = await this.updateDb(payload);
const newDoc = await this.updateDb(new CrontabView(payload));
return newDoc;
}
+7 -3
View File
@@ -16,7 +16,7 @@ import { Op } from 'sequelize';
@Service()
export default class EnvService {
constructor(@Inject('logger') private logger: winston.Logger) { }
constructor(@Inject('logger') private logger: winston.Logger) {}
public async create(payloads: Env[]): Promise<Env[]> {
const envs = await this.envs();
@@ -49,7 +49,7 @@ export default class EnvService {
}
public async update(payload: Env): Promise<Env> {
const newDoc = await this.updateDb(payload);
const newDoc = await this.updateDb(new Env(payload));
await this.set_envs();
return newDoc;
}
@@ -98,7 +98,11 @@ export default class EnvService {
private async checkPosition(position: number, edge: number = 0) {
const precisionPosition = parseFloat(position.toPrecision(16));
if (precisionPosition < minPosition || precisionPosition > maxPosition || Math.abs(precisionPosition - edge) < minPosition) {
if (
precisionPosition < minPosition ||
precisionPosition > maxPosition ||
Math.abs(precisionPosition - edge) < minPosition
) {
const envs = await this.envs();
let position = initPosition;
for (const env of envs) {
+2 -2
View File
@@ -42,13 +42,13 @@ echo -e "定时任务启动成功...\n"
if [[ $AutoStartBot == true ]]; then
echo -e "======================7. 启动bot========================\n"
nohup ql bot >$dir_log/bot.log 2>&1 &
nohup ql -l bot >$dir_log/bot.log 2>&1 &
echo -e "bot后台启动中...\n"
fi
if [[ $EnableExtraShell == true ]]; then
echo -e "======================8. 执行自定义脚本========================\n"
nohup ql extra >$dir_log/extra.log 2>&1 &
nohup ql -l extra >$dir_log/extra.log 2>&1 &
echo -e "自定义脚本后台执行中...\n"
fi
+1
View File
@@ -13,6 +13,7 @@ map $http_upgrade $connection_upgrade {
server {
listen 5700;
listen [::]:5700 ipv6only=on;
root /ql/static/dist;
ssl_session_timeout 5m;
+3 -4
View File
@@ -92,7 +92,7 @@
"devDependencies": {
"@ant-design/icons": "^4.7.0",
"@ant-design/pro-layout": "6.38.22",
"@monaco-editor/react": "4.4.6",
"@monaco-editor/react": "4.2.1",
"@react-hook/resize-observer": "^1.2.6",
"@sentry/react": "^7.12.1",
"@types/body-parser": "^1.19.2",
@@ -115,10 +115,10 @@
"@types/sockjs": "^0.3.33",
"@types/sockjs-client": "^1.5.1",
"@types/uuid": "^8.3.4",
"@umijs/max": "^4.0.42",
"@umijs/max": "^4.0.55",
"@umijs/ssr-darkreader": "^4.9.45",
"ansi-to-react": "^6.1.6",
"antd": "^4.24.7",
"antd": "^4.24.8",
"antd-img-crop": "^4.2.3",
"codemirror": "^5.65.2",
"compression-webpack-plugin": "9.2.0",
@@ -145,7 +145,6 @@
"typescript": "4.8.4",
"umi-request": "^1.4.0",
"vh-check": "^2.0.5",
"virtuallist-antd": "^0.7.6",
"webpack": "^5.70.0",
"yorkie": "^2.0.0"
}
+1727 -1380
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -296,6 +296,16 @@ async function sendNotify(
) {
//提供6种通知
desp += author; //增加作者信息,防止被贩卖等
// 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔
let skipTitle = process.env.SKIP_PUSH_TITLE
if(skipTitle) {
if(skipTitle.split('\n').includes(text)) {
console.info(text + "在SKIP_PUSH_TITLE环境变量内,跳过推送!");
return
}
}
await Promise.all([
serverNotify(text, desp), //微信server酱
pushPlusNotify(text, desp), //pushplus(推送加)
+7
View File
@@ -647,6 +647,13 @@ def send(title: str, content: str) -> None:
print(f"{title} 推送内容为空!")
return
# 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔
skipTitle = os.getenv("SKIP_PUSH_TITLE")
if skipTitle:
if (title in re.split("\n", skipTitle)):
print(f"{title} 在SKIP_PUSH_TITLE环境变量内,跳过推送!")
return
hitokoto = push_config.get("HITOKOTO")
text = one() if hitokoto else ""
+23
View File
@@ -199,4 +199,27 @@ notify_api() {
fi
}
find_cron_api() {
local params=$1
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/detail?$params&t=$currentTimeStamp" \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--compressed
)
data=$(echo "$api" | jq -r .data)
if [[ $data == 'null' ]]; then
echo -e ""
else
name=$(echo "$api" | jq -r .data.name)
echo -e "$name"
fi
}
get_token
+8 -2
View File
@@ -6,7 +6,7 @@ dir_shell=$QL_DIR/shell
days=$1
## 删除运行js脚本的旧日志
## 删除运行脚本的旧日志
remove_js_log() {
local log_full_path_list=$(find $dir_log/ -name "*.log")
local diff_time
@@ -18,7 +18,13 @@ remove_js_log() {
else
diff_time=$(($(date +%s) - $(date +%s -d "$log_date")))
fi
[[ $diff_time -gt $((${days} * 86400)) ]] && rm -vf $log
if [[ $diff_time -gt $((${days} * 86400)) ]]; then
local log_path=$(echo "$log" | sed "s,${dir_log},,g")
local result=$(find_cron_api "log_path=$log_path")
if [[ $result ]]; then
rm -vf $log
fi
fi
fi
done
}
+2 -2
View File
@@ -330,8 +330,8 @@ git_pull_scripts() {
set_proxy "$proxy"
git fetch --all
git pull 1>/dev/null
exit_status=$?
git pull &>/dev/null
unset_proxy
cd $dir_current
@@ -357,7 +357,7 @@ reset_romote_url() {
reset_branch() {
local branch="$1"
local part_cmd=""
local part_cmd="HEAD"
if [[ $branch ]]; then
part_cmd="origin/${branch}"
git checkout -B "$branch" &>/dev/null
+11 -14
View File
@@ -169,11 +169,11 @@ update_raw() {
if [[ $? -eq 0 ]]; then
mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}"
echo -e "下载 ${raw_file_name} 成功...\n"
cd $dir_raw
local filename="raw_${raw_file_name}"
local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* $filename" | perl -pe "s|.*ID=(.*) $cmd_task.* $filename\.*|\1|" | head -1 | awk -F " " '{print $1}')
cp -f $raw_file_name $dir_scripts/${filename}
if [[ -z $cron_id ]] && [[ ${autoAddCron} == true ]]; then
cd $dir_raw
local filename="raw_${raw_file_name}"
local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* $filename" | perl -pe "s|.*ID=(.*) $cmd_task.* $filename\.*|\1|" | head -1 | awk -F " " '{print $1}')
cp -f $raw_file_name $dir_scripts/${filename}
cron_line=$(
perl -ne "{
print if /.*([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*]( |,|\").*$raw_file_name/
@@ -203,13 +203,10 @@ update_raw() {
## 调用用户自定义的extra.sh
run_extra_shell() {
if [[ ${EnableExtraShell} == true ]]; then
if [[ -f $file_extra_shell ]]; then
echo -e "--------------------------------------------------------------\n"
. $file_extra_shell
else
echo -e "$file_extra_shell文件不存在,跳过执行...\n"
fi
if [[ -f $file_extra_shell ]]; then
. $file_extra_shell
else
echo -e "$file_extra_shell文件不存在,跳过执行...\n"
fi
}
@@ -229,10 +226,10 @@ usage() {
## 更新qinglong
update_qinglong() {
local mirror="github"
local githubStatus=$(curl -s -m 2 -IL "https://github.com" | grep 200)
local mirror="gitee"
local githubStatus=$(curl -s -m 2 -IL "https://google.com" | grep 200)
if [[ ! -z $githubStatus ]]; then
mirror="gitee"
mirror="github"
fi
echo -e "使用 ${mirror} 源更新...\n"
export isFirstStartServer=false
+404
View File
@@ -0,0 +1,404 @@
import React, {
useRef,
useEffect,
useContext,
createContext,
useReducer,
useState,
useMemo,
} from 'react';
import { throttle, isNumber, debounce } from 'lodash';
const initialState = {
// 行高度
rowHeight: 0,
// 当前的scrollTop
curScrollTop: 0,
// 总行数
totalLen: 0,
};
function reducer(state, action) {
const { curScrollTop, totalLen, ifScrollTopClear, scrollTop } = action;
let stateScrollTop = state.curScrollTop;
switch (action.type) {
// 改变trs 即 改变渲染的列表trs
case 'changeTrs':
return {
...state,
curScrollTop,
};
// 更改totalLen
case 'changeTotalLen':
if (totalLen === 0) {
stateScrollTop = 0;
}
return {
...state,
totalLen,
curScrollTop: stateScrollTop,
};
case 'reset':
return {
...state,
curScrollTop: ifScrollTopClear ? 0 : scrollTop ?? state.curScrollTop,
};
default:
throw new Error();
}
}
// ==============全局常量 ================== //
const DEFAULT_VID = 'vtable';
const vidMap = new Map();
let preData = 0;
// ===============context ============== //
const ScrollContext = createContext({
dispatch: undefined,
renderLen: 1,
start: 0,
offsetStart: 0,
// =============
rowHeight: initialState.rowHeight,
totalLen: 0,
vid: DEFAULT_VID,
});
// =============组件 =================== //
function VCell(props: any): JSX.Element {
const { children, ...restProps } = props;
return (
<td {...restProps}>
<div>{children[1]}</div>
</td>
);
}
function VRow(props: any, ref: any): JSX.Element {
const { rowHeight } = useContext(ScrollContext);
const { children, style, ...restProps } = props;
const trRef = useRef<HTMLTableRowElement>(null);
return (
<tr
{...restProps}
ref={Object.prototype.hasOwnProperty.call(ref, 'current') ? ref : trRef}
style={{
...style,
height: rowHeight || 'auto',
boxSizing: 'border-box',
}}
>
{children}
</tr>
);
}
function VWrapper(props: any): JSX.Element {
const { children, ...restProps } = props;
const { renderLen, start, dispatch, vid, totalLen } =
useContext(ScrollContext);
const contents = useMemo(() => {
return children[1];
}, [children]);
const contentsLen = useMemo(() => {
return contents?.length ?? 0;
}, [contents]);
useEffect(() => {
if (totalLen !== contentsLen) {
dispatch({
type: 'changeTotalLen',
totalLen: contentsLen ?? 0,
});
}
}, [contentsLen, dispatch, vid, totalLen]);
let tempNode = null;
if (Array.isArray(contents) && contents.length) {
tempNode = [
children[0],
contents.slice(start, start + (renderLen ?? 1)).map((item) => {
if (Array.isArray(item)) {
// 兼容antd v4.3.5 --- rc-table 7.8.1及以下
return item[0];
}
// 处理antd ^v4.4.0 --- rc-table ^7.8.2
return item;
}),
];
} else {
tempNode = children;
}
return <tbody {...restProps}>{tempNode}</tbody>;
}
function VTable(props: any, otherParams): JSX.Element {
const { style, children, ...rest } = props;
const { width, ...rest_style } = style;
const { vid, scrollY, resetScrollTopWhenDataChange, rowHeight, scrollTop } =
otherParams ?? {};
const [state, dispatch] = useReducer(reducer, {
...initialState,
curScrollTop: scrollTop,
rowHeight,
});
const wrap_tableRef = useRef<HTMLDivElement>(null);
const tableRef = useRef<HTMLTableElement>(null);
const ifChangeRef = useRef(false);
// 数据的总条数
const [totalLen, setTotalLen] = useState<number>(
children[1]?.props?.data?.length ?? 0,
);
useEffect(() => {
setTotalLen(state.totalLen);
}, [state.totalLen]);
// 组件卸载的清除操作
useEffect(() => {
return () => {
vidMap.delete(vid);
};
}, [vid]);
// 数据变更
useEffect(() => {
ifChangeRef.current = true;
// console.log('数据变更')
if (isNumber(children[1]?.props?.data?.length)) {
dispatch({
type: 'changeTotalLen',
totalLen: children[1]?.props?.data?.length ?? 0,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [children[1].props.data]);
// table总高度
const tableHeight = useMemo<string | number>(() => {
let temp: string | number = 'auto';
if (rowHeight && totalLen) {
temp = rowHeight * totalLen;
}
return temp;
}, [totalLen]);
// table的scrollY值
const [tableScrollY, setTableScrollY] = useState(0);
// tableScrollY 随scrollY / tableHeight 进行变更
useEffect(() => {
let temp = 0;
if (typeof scrollY === 'string') {
temp =
(wrap_tableRef.current?.parentNode as HTMLElement)?.offsetHeight ?? 0;
} else {
temp = scrollY;
}
// if (isNumber(tableHeight) && tableHeight < temp) {
// temp = tableHeight;
// }
// 处理tableScrollY <= 0的情况
if (temp <= 0) {
temp = 0;
}
setTableScrollY(temp);
}, [scrollY, tableHeight]);
// 渲染的条数
const renderLen = useMemo<number>(() => {
let temp = 1;
if (rowHeight && totalLen && tableScrollY) {
if (tableScrollY <= 0) {
temp = 0;
} else {
const tempRenderLen = ((tableScrollY / rowHeight) | 0) + 10;
// console.log('tempRenderLen', tempRenderLen)
// temp = tempRenderLen > totalLen ? totalLen : tempRenderLen;
temp = tempRenderLen;
}
}
return temp;
}, [totalLen, tableScrollY]);
// 渲染中的第一条
let start = rowHeight ? (state.curScrollTop / rowHeight) | 0 : 0;
start = start < 5 ? 0 : start - 5 + 1;
// 偏移量
let offsetStart = state.curScrollTop % (rowHeight * 5);
if (start > 0) {
offsetStart = offsetStart % rowHeight;
}
// console.log(offsetStart)
// offsetStart= offsetStart%rowHeight
// 用来优化向上滚动出现的空白
if (state.curScrollTop && state.curScrollTop >= rowHeight * 5) {
// start -= 1
// if (offsetStart >= rowHeight) {
// offsetStart +=
// } else {
offsetStart += rowHeight * 4;
// }
} else {
start = 0;
}
// console.log(state.curScrollTop, start, offsetStart)
// 数据变更 操作scrollTop
useEffect(() => {
const scrollNode = wrap_tableRef.current?.parentNode as HTMLElement;
if (ifChangeRef?.current) {
// console.log(scrollNode)
ifChangeRef.current = false;
if (resetScrollTopWhenDataChange) {
// 重置scrollTop
if (scrollNode) {
scrollNode.scrollTop = 0;
}
dispatch({ type: 'reset', ifScrollTopClear: true });
} else {
// console.log(preData)
// scrollNode.scrollTop = preData+53
// 不重置scrollTop 不清空curScrollTop
dispatch({ type: 'reset', ifScrollTopClear: false });
}
}
if (vidMap.has(vid)) {
vidMap.set(vid, {
...vidMap.get(vid),
scrollNode,
});
}
}, [totalLen, resetScrollTopWhenDataChange, vid, children]);
useEffect(() => {
const throttleScroll = throttle((e) => {
const scrollTop: number = e?.target?.scrollTop ?? 0;
// const scrollHeight: number = e?.target?.scrollHeight ?? 0
// const clientHeight: number = e?.target?.clientHeight ?? 0
if (scrollTop) {
preData = scrollTop;
}
// 到底了 没有滚动条就不会触发reachEnd. 建议设置scrolly高度少点或者数据量多点.
// 若renderLen大于totalLen, 置空curScrollTop. => table paddingTop会置空.
dispatch({
type: 'changeTrs',
curScrollTop: renderLen <= totalLen ? scrollTop : 0,
});
}, 60);
const ref = wrap_tableRef?.current?.parentNode as HTMLElement;
if (ref) {
ref.addEventListener('scroll', throttleScroll, { passive: true });
}
return () => {
ref.removeEventListener('scroll', throttleScroll);
};
}, [renderLen, totalLen]);
return (
<div
className="virtuallist"
ref={wrap_tableRef}
style={{
width: '100%',
position: 'relative',
height: tableHeight,
boxSizing: 'border-box',
paddingTop: state.curScrollTop,
}}
>
<ScrollContext.Provider
value={{
dispatch,
start,
offsetStart,
renderLen,
totalLen,
vid,
rowHeight,
}}
>
<table
{...rest}
ref={tableRef}
style={{
...rest_style,
width,
position: 'relative',
transform: `translateY(-${offsetStart}px)`,
}}
>
{children}
</table>
</ScrollContext.Provider>
</div>
);
}
// ================导出===================
export function VList(props: {
height: number;
// 唯一标识
vid?: string;
rowHeight: number | string;
reset?: boolean;
scrollTop?: number | string;
}): any {
const { vid = DEFAULT_VID, rowHeight, height, reset, scrollTop } = props;
const resetScrollTopWhenDataChange = reset ?? true;
if (!vidMap.has(vid)) {
vidMap.set(vid, { _id: vid });
}
return {
table: (p) =>
VTable(p, {
vid,
scrollY: height,
rowHeight,
resetScrollTopWhenDataChange,
scrollTop,
}),
body: {
wrapper: VWrapper,
row: VRow,
cell: VCell,
},
};
}
+13 -10
View File
@@ -1,16 +1,19 @@
import { MutableRefObject, useLayoutEffect, useState } from 'react';
import useResizeObserver from '@react-hook/resize-observer'
import useResizeObserver from '@react-hook/resize-observer';
import { getTableScroll } from '@/utils';
export default <T extends HTMLElement>(target: MutableRefObject<T>, extraHeight?: number) => {
const [height, setHeight] = useState<number>()
export default <T extends HTMLElement>(
target: MutableRefObject<T>,
extraHeight?: number,
) => {
const [height, setHeight] = useState<number>(0);
useResizeObserver(target, (entry) => {
let _targe = entry.target as any
if (!_targe.classList.contains('ant-table-wrapper')) {
_targe = entry.target.querySelector('.ant-table-wrapper')
let _target = entry.target as any;
if (!_target.classList.contains('ant-table-wrapper')) {
_target = entry.target.querySelector('.ant-table-wrapper');
}
setHeight(getTableScroll({ extraHeight, target: _targe as HTMLElement }))
})
return height
}
setHeight(getTableScroll({ extraHeight, target: _target as HTMLElement }));
});
return height;
};
+35 -2
View File
@@ -9,6 +9,11 @@
url('../assets/fonts/SourceCodePro-Regular.ttf') format('truetype');
}
#root {
height: 100vh;
height: calc(100vh - var(--vh-offset, 0px));
}
.ant-modal-body {
max-height: calc(80vh - 110px);
max-height: calc(80vh - var(--vh-offset, 110px));
@@ -350,6 +355,34 @@ pre {
padding: 0 !important;
}
.virtuallist .ant-table-tbody > tr > td > div {
white-space: unset !important;
.virtuallist {
.ant-table-tbody > tr > td > div {
white-space: unset !important;
}
}
.virtuallist .ant-table-tbody > tr > td > div {
box-sizing: border-box;
white-space: nowrap;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.virtuallist .ant-table-tbody > tr > td.ant-table-row-expand-icon-cell > div {
overflow: inherit;
}
.ant-table-bordered .virtuallist > table > .ant-table-tbody > tr > td {
border-right: 1px solid #f0f0f0;
}
.ant-table-column-title {
flex: unset;
}
.ant-table-column-sorters,
.ant-table-filter-column {
justify-content: unset;
}
+26 -15
View File
@@ -53,7 +53,7 @@ import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import { getCommandScript, parseCrontab } from '@/utils';
import { ColumnProps } from 'antd/lib/table';
import { VList } from 'virtuallist-antd';
import { VList } from '../../components/vlist';
const { Text, Paragraph } = Typography;
const { Search } = Input;
@@ -107,7 +107,6 @@ const Crontab = () => {
dataIndex: 'name',
key: 'name',
width: 150,
align: 'center' as const,
render: (text: string, record: any) => (
<>
<a
@@ -163,14 +162,12 @@ const Crontab = () => {
dataIndex: 'command',
key: 'command',
width: 300,
align: 'center' as const,
render: (text, record) => {
return (
<Paragraph
style={{
wordBreak: 'break-all',
marginBottom: 0,
textAlign: 'left',
}}
ellipsis={{ tooltip: text, rows: 2 }}
>
@@ -193,14 +190,12 @@ const Crontab = () => {
dataIndex: 'schedule',
key: 'schedule',
width: 110,
align: 'center' as const,
sorter: {
compare: (a, b) => a.schedule.localeCompare(b.schedule),
},
},
{
title: '最后运行时间',
align: 'center' as const,
dataIndex: 'last_execution_time',
key: 'last_execution_time',
width: 150,
@@ -230,7 +225,6 @@ const Crontab = () => {
},
{
title: '最后运行时长',
align: 'center' as const,
width: 120,
dataIndex: 'last_running_time',
key: 'last_running_time',
@@ -247,7 +241,6 @@ const Crontab = () => {
},
{
title: '下次运行时间',
align: 'center' as const,
width: 150,
sorter: {
compare: (a: any, b: any) => {
@@ -267,7 +260,6 @@ const Crontab = () => {
title: '状态',
key: 'status',
dataIndex: 'status',
align: 'center' as const,
width: 88,
filters: [
{
@@ -329,7 +321,6 @@ const Crontab = () => {
{
title: '操作',
key: 'action',
align: 'center' as const,
width: 100,
render: (text, record, index) => {
const isPc = !isPhone;
@@ -339,6 +330,7 @@ const Crontab = () => {
<Tooltip title={isPc ? '运行' : ''}>
<a
onClick={(e) => {
setReset(false);
e.stopPropagation();
runCron(record, index);
}}
@@ -351,6 +343,7 @@ const Crontab = () => {
<Tooltip title={isPc ? '停止' : ''}>
<a
onClick={(e) => {
setReset(false);
e.stopPropagation();
stopCron(record, index);
}}
@@ -362,6 +355,7 @@ const Crontab = () => {
<Tooltip title={isPc ? '日志' : ''}>
<a
onClick={(e) => {
setReset(false);
e.stopPropagation();
setLogCron({ ...record, timestamp: Date.now() });
}}
@@ -405,6 +399,10 @@ const Crontab = () => {
const [moreMenuActive, setMoreMenuActive] = useState(false);
const tableRef = useRef<any>();
const tableScrollHeight = useTableScrollHeight(tableRef);
const resetRef = useRef<boolean>(true);
const setReset = (v) => {
resetRef.current = v;
};
const goToScriptManager = (record: any) => {
const result = getCommandScript(record.command);
@@ -424,9 +422,9 @@ const Crontab = () => {
}crons?searchValue=${searchText}&page=${page}&size=${size}&filters=${JSON.stringify(
filters,
)}`;
if (sorter && sorter.field) {
if (sorter && sorter.column && sorter.order) {
url += `&sorter=${JSON.stringify({
field: sorter.field,
field: sorter.column.key,
type: sorter.order === 'ascend' ? 'ASC' : 'DESC',
})}`;
}
@@ -688,6 +686,7 @@ const Crontab = () => {
items: getMenuItems(record),
onClick: ({ key, domEvent }) => {
domEvent.stopPropagation();
setReset(false);
action(key, record, index);
},
}}
@@ -723,6 +722,7 @@ const Crontab = () => {
};
const onSearch = (value: string) => {
setReset(true);
setSearchText(value.trim());
};
@@ -750,6 +750,10 @@ const Crontab = () => {
setSelectedRowIds(selectedIds);
};
useEffect(() => {
setReset(false);
}, [selectedRowIds]);
const rowSelection = {
selectedRowKeys: selectedRowIds,
onChange: onSelectChange,
@@ -777,6 +781,7 @@ const Crontab = () => {
};
const operateCrons = (operationStatus: number) => {
setReset(false);
Modal.confirm({
title: `确认${OperationName[operationStatus]}`,
content: <>{OperationName[operationStatus]}</>,
@@ -803,6 +808,7 @@ const Crontab = () => {
sorter: SorterResult<any> | SorterResult<any>[],
) => {
const { current, pageSize } = pagination;
setReset(true);
setPageConf({
page: current as number,
size: pageSize as number,
@@ -917,16 +923,21 @@ const Crontab = () => {
const tabClick = (key: string) => {
const view = enabledCronViews.find((x) => x.id == key);
setSelectedRowIds([]);
setReset(true);
setPageConf({ ...pageConf, page: 1 });
setViewConf(view ? view : null);
};
const vComponents = useMemo(() => {
return VList({
height: tableScrollHeight!,
resetTopWhenDataChange: false,
height: tableScrollHeight,
reset: resetRef.current,
rowHeight: 69,
scrollTop: resetRef.current
? 0
: tableRef.current?.querySelector('.ant-table-body')?.scrollTop,
});
}, [tableScrollHeight]);
}, [tableScrollHeight, resetRef.current]);
return (
<PageContainer
+1 -1
View File
@@ -54,7 +54,7 @@ const CronLogModal = ({
log &&
!logEnded(log) &&
!log.includes('重启面板') &&
!log.includes('任务未运行或运行失败,请尝试手动运行'),
!log.includes('任务未运行'),
);
setExecuting(hasNext);
autoScroll();
-7
View File
@@ -80,23 +80,17 @@ const ViewManageModal = ({
title: '名称',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
render: (text) => (
<div style={{ textAlign: 'left', paddingLeft: 30 }}>{text}</div>
),
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
align: 'center' as const,
render: (v) => (v === 1 ? '系统' : '个人'),
},
{
title: '显示',
key: 'isDisabled',
dataIndex: 'isDisabled',
align: 'center' as const,
width: 100,
render: (text: string, record: any, index: number) => {
return (
@@ -111,7 +105,6 @@ const ViewManageModal = ({
title: '操作',
key: 'action',
width: 100,
align: 'center' as const,
render: (text: string, record: any, index: number) => {
return record.type !== 1 ? (
<Space size="middle">
+1 -8
View File
@@ -32,7 +32,6 @@ import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
const { Text } = Typography;
const { Search } = Input;
@@ -57,7 +56,6 @@ const Dependence = () => {
const columns: any = [
{
title: '序号',
align: 'center' as const,
width: 50,
render: (text: string, record: any, index: number) => {
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
@@ -67,13 +65,11 @@ const Dependence = () => {
title: '名称',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
align: 'center' as const,
render: (text: string, record: any, index: number) => {
return (
<Space size="middle" style={{ cursor: 'text' }}>
@@ -91,13 +87,11 @@ const Dependence = () => {
title: '备注',
dataIndex: 'remark',
key: 'remark',
align: 'center' as const,
},
{
title: '创建时间',
key: 'timestamp',
dataIndex: 'timestamp',
align: 'center' as const,
render: (text: string, record: any) => {
const language = navigator.language || navigator.languages[0];
const time = record.createdAt || record.timestamp;
@@ -120,7 +114,6 @@ const Dependence = () => {
{
title: '操作',
key: 'action',
align: 'center' as const,
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
return (
@@ -169,7 +162,7 @@ const Dependence = () => {
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [type, setType] = useState('nodejs');
const tableRef = useRef<any>();
const tableScrollHeight = useTableScrollHeight(tableRef, 59)
const tableScrollHeight = useTableScrollHeight(tableRef, 59);
const getDependencies = () => {
setLoading(true);
+86 -73
View File
@@ -39,7 +39,7 @@ import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import Copy from '../../components/copy';
import { VList } from 'virtuallist-antd';
import { VList } from '../../components/vlist';
const { Text } = Typography;
const { Search } = Input;
@@ -71,7 +71,6 @@ const Env = () => {
const columns: any = [
{
title: '序号',
align: 'center' as const,
width: 60,
render: (text: string, record: any, index: number) => {
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
@@ -81,14 +80,12 @@ const Env = () => {
title: '名称',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
sorter: (a: any, b: any) => a.name.localeCompare(b.name),
},
{
title: '值',
dataIndex: 'value',
key: 'value',
align: 'center' as const,
width: '35%',
render: (text: string, record: any) => {
return (
@@ -105,7 +102,6 @@ const Env = () => {
title: '备注',
dataIndex: 'remarks',
key: 'remarks',
align: 'center' as const,
render: (text: string, record: any) => {
return (
<Tooltip title={text} placement="topLeft">
@@ -118,7 +114,6 @@ const Env = () => {
title: '更新时间',
dataIndex: 'timestamp',
key: 'timestamp',
align: 'center' as const,
width: 165,
ellipsis: {
showTitle: false,
@@ -153,7 +148,6 @@ const Env = () => {
title: '状态',
key: 'status',
dataIndex: 'status',
align: 'center' as const,
width: 70,
filters: [
{
@@ -180,7 +174,6 @@ const Env = () => {
title: '操作',
key: 'action',
width: 120,
align: 'center' as const,
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
return (
@@ -223,6 +216,10 @@ const Env = () => {
const [importLoading, setImportLoading] = useState(false);
const tableRef = useRef<any>();
const tableScrollHeight = useTableScrollHeight(tableRef, 59);
const resetRef = useRef<boolean>(true);
const setReset = (v) => {
resetRef.current = v;
};
const getEnvs = () => {
setLoading(true);
@@ -237,6 +234,7 @@ const Env = () => {
};
const enabledOrDisabledEnv = (record: any, index: number) => {
setReset(false);
Modal.confirm({
title: `确认${record.status === Status. ? '启用' : '禁用'}`,
content: (
@@ -282,16 +280,19 @@ const Env = () => {
};
const addEnv = () => {
setReset(false);
setEditedEnv(null as any);
setIsModalVisible(true);
};
const editEnv = (record: any, index: number) => {
setReset(false);
setEditedEnv(record);
setIsModalVisible(true);
};
const deleteEnv = (record: any, index: number) => {
setReset(false);
Modal.confirm({
title: '确认删除',
content: (
@@ -334,9 +335,13 @@ const Env = () => {
const vComponents = useMemo(() => {
return VList({
height: tableScrollHeight!,
resetTopWhenDataChange: false,
reset: resetRef.current,
rowHeight: 48,
scrollTop: resetRef.current
? 0
: tableRef.current?.querySelector('.ant-table-body')?.scrollTop,
});
}, [tableScrollHeight]);
}, [tableScrollHeight, resetRef.current]);
const DragableBodyRow = (props: any) => {
const { index, moveRow, className, style, ...restProps } = props;
@@ -420,12 +425,17 @@ const Env = () => {
setSelectedRowIds(selectedIds);
};
useEffect(() => {
setReset(false);
}, [selectedRowIds]);
const rowSelection = {
selectedRowKeys: selectedRowIds,
onChange: onSelectChange,
};
const delEnvs = () => {
setReset(false);
Modal.confirm({
title: '确认删除',
content: <></>,
@@ -447,6 +457,7 @@ const Env = () => {
};
const operateEnvs = (operationStatus: number) => {
setReset(false);
Modal.confirm({
title: `确认${OperationName[operationStatus]}`,
content: <>{OperationName[operationStatus]}</>,
@@ -479,6 +490,7 @@ const Env = () => {
};
const onSearch = (value: string) => {
setReset(true);
setSearchText(value.trim());
};
@@ -542,69 +554,70 @@ const Env = () => {
style: headerStyle,
}}
>
{selectedRowIds.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
style={{ marginBottom: 5 }}
onClick={modifyName}
>
</Button>
<Button
type="primary"
style={{ marginBottom: 5, marginLeft: 8 }}
onClick={delEnvs}
>
</Button>
<Button
type="primary"
onClick={() => exportEnvs()}
style={{ marginLeft: 8, marginRight: 8 }}
>
</Button>
<Button
type="primary"
onClick={() => operateEnvs(0)}
style={{ marginLeft: 8, marginBottom: 5 }}
>
</Button>
<Button
type="primary"
onClick={() => operateEnvs(1)}
style={{ marginLeft: 8, marginRight: 8 }}
>
</Button>
<span style={{ marginLeft: 8 }}>
<a>{selectedRowIds?.length}</a>
</span>
</div>
)}
<DndProvider backend={HTML5Backend}>
<Table
ref={tableRef}
columns={columns}
rowSelection={rowSelection}
pagination={false}
dataSource={value}
rowKey="id"
size="middle"
scroll={{ x: 1000, y: tableScrollHeight }}
components={components}
loading={loading}
onRow={(record: any, index: number | undefined) => {
return {
index,
moveRow,
} as any;
}}
/>
</DndProvider>
<div ref={tableRef}>
{selectedRowIds.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
style={{ marginBottom: 5 }}
onClick={modifyName}
>
</Button>
<Button
type="primary"
style={{ marginBottom: 5, marginLeft: 8 }}
onClick={delEnvs}
>
</Button>
<Button
type="primary"
onClick={() => exportEnvs()}
style={{ marginLeft: 8, marginRight: 8 }}
>
</Button>
<Button
type="primary"
onClick={() => operateEnvs(0)}
style={{ marginLeft: 8, marginBottom: 5 }}
>
</Button>
<Button
type="primary"
onClick={() => operateEnvs(1)}
style={{ marginLeft: 8, marginRight: 8 }}
>
</Button>
<span style={{ marginLeft: 8 }}>
<a>{selectedRowIds?.length}</a>
</span>
</div>
)}
<DndProvider backend={HTML5Backend}>
<Table
columns={columns}
rowSelection={rowSelection}
pagination={false}
dataSource={value}
rowKey="id"
size="middle"
scroll={{ x: 1000, y: tableScrollHeight }}
components={components}
loading={loading}
onRow={(record: any, index: number | undefined) => {
return {
index,
moveRow,
} as any;
}}
/>
</DndProvider>
</div>
<EnvModal
visible={isModalVisible}
handleCancel={handleCancel}
+3 -12
View File
@@ -49,13 +49,11 @@ const Setting = () => {
title: '名称',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
},
{
title: 'Client ID',
dataIndex: 'client_id',
key: 'client_id',
align: 'center' as const,
render: (text: string, record: any) => {
return <Text copyable>{record.client_id}</Text>;
},
@@ -64,7 +62,6 @@ const Setting = () => {
title: 'Client Secret',
dataIndex: 'client_secret',
key: 'client_secret',
align: 'center' as const,
render: (text: string, record: any) => {
return <Text copyable={{ text: record.client_secret }}>*******</Text>;
},
@@ -73,22 +70,16 @@ const Setting = () => {
title: '权限',
dataIndex: 'scopes',
key: 'scopes',
align: 'center' as const,
width: '40%',
render: (text: string, record: any) => {
return (
<div style={{ textAlign: 'left' }}>
{record.scopes.map((scope: any) => {
return <Tag key={scope}>{(config.scopesMap as any)[scope]}</Tag>;
})}
</div>
);
return record.scopes.map((scope: any) => {
return <Tag key={scope}>{(config.scopesMap as any)[scope]}</Tag>;
});
},
},
{
title: '操作',
key: 'action',
align: 'center' as const,
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
return (
+2 -6
View File
@@ -18,7 +18,6 @@ enum LoginStatusColor {
const columns = [
{
title: '序号',
align: 'center' as const,
width: 50,
render: (text: string, record: any, index: number) => {
return index + 1;
@@ -28,7 +27,6 @@ const columns = [
title: '登录时间',
dataIndex: 'timestamp',
key: 'timestamp',
align: 'center' as const,
render: (text: string, record: any) => {
return new Date(record.timestamp).toLocaleString();
},
@@ -37,25 +35,23 @@ const columns = [
title: '登录地址',
dataIndex: 'address',
key: 'address',
align: 'center' as const,
},
{
title: '登录IP',
dataIndex: 'ip',
key: 'ip',
align: 'center' as const,
},
{
title: '登录设备',
dataIndex: 'platform',
key: 'platform',
align: 'center' as const,
width: 80,
},
{
title: '登录状态',
dataIndex: 'status',
key: 'status',
align: 'center' as const,
width: 80,
render: (text: string, record: any) => {
return (
<Tag color={LoginStatusColor[record.status]} style={{ marginRight: 0 }}>
-8
View File
@@ -68,7 +68,6 @@ const Subscription = () => {
dataIndex: 'name',
key: 'name',
width: 150,
align: 'center' as const,
sorter: {
compare: (a: any, b: any) => a.name.localeCompare(b.name),
multiple: 2,
@@ -78,7 +77,6 @@ const Subscription = () => {
title: '链接',
dataIndex: 'url',
key: 'url',
align: 'center' as const,
sorter: {
compare: (a: any, b: any) => a.name.localeCompare(b.name),
multiple: 2,
@@ -89,7 +87,6 @@ const Subscription = () => {
style={{
wordBreak: 'break-all',
marginBottom: 0,
textAlign: 'left',
}}
ellipsis={{ tooltip: text, rows: 2 }}
>
@@ -103,7 +100,6 @@ const Subscription = () => {
dataIndex: 'type',
key: 'type',
width: 130,
align: 'center' as const,
render: (text: string, record: any) => {
return (SubscriptionType as any)[record.type];
},
@@ -113,7 +109,6 @@ const Subscription = () => {
dataIndex: 'branch',
key: 'branch',
width: 130,
align: 'center' as const,
render: (text: string, record: any) => {
return record.branch || '-';
},
@@ -121,7 +116,6 @@ const Subscription = () => {
{
title: '定时规则',
width: 180,
align: 'center' as const,
render: (text: string, record: any) => {
if (record.schedule_type === 'interval') {
const { type, value } = record.interval_schedule;
@@ -134,7 +128,6 @@ const Subscription = () => {
title: '状态',
key: 'status',
dataIndex: 'status',
align: 'center' as const,
width: 110,
filters: [
{
@@ -189,7 +182,6 @@ const Subscription = () => {
{
title: '操作',
key: 'action',
align: 'center' as const,
width: 130,
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
+3 -2
View File
@@ -243,6 +243,7 @@ const SubscriptionModal = ({
if (text.startsWith('ql ')) {
e.preventDefault();
}
onPaste(e);
}, []);
const formatParams = (sub) => {
@@ -294,9 +295,9 @@ const SubscriptionModal = ({
confirmLoading={loading}
>
<Form form={form} name="form_in_modal" layout="vertical">
<Form.Item name="name" label="名称">
<Form.Item name="name" label="名称" rules={[{ required: true }]}>
<Input
placeholder="支持拷贝ql repo/raw命令,粘贴导入"
placeholder="支持拷贝 ql repo/raw 命令,粘贴导入"
onPaste={onNamePaste}
/>
</Form.Item>
+1 -1
View File
@@ -183,7 +183,7 @@ export function getTableScroll({
extraHeight,
target,
}: { extraHeight?: number; target?: HTMLElement } = {}) {
if (typeof extraHeight == 'undefined') {
if (typeof extraHeight === 'undefined') {
// 47 + 40 + 12
extraHeight = 99;
}
+3 -2
View File
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/react';
import { Integrations } from '@sentry/tracing';
import { loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';
export function init(version: string) {
// sentry监控 init
@@ -27,7 +26,9 @@ export function init(version: string) {
// monaco 编辑器配置cdn和locale
loader.config({
monaco,
paths: {
vs: 'https://cdn.staticfile.org/monaco-editor/0.33.0/min/vs',
},
'vs/nls': {
availableLanguages: {
'*': 'zh-cn',
+10 -8
View File
@@ -1,10 +1,12 @@
version: 2.15.8
version: 2.15.9
changeLogLink: https://t.me/jiao_long/364
changeLog: |
1. 修复环境变量位置重置逻辑
2. 日志名称增加毫秒
3. 修复添加文件类型订阅
4. 修复订阅生成 ssh 配置逻辑
5. 任务增加关联订阅
6. 修复订阅自动增加/删除任务参数默认值
7. 其他优化
1. 通知脚本增加环境变量 SKIP_PUSH_TITLE,设置需要跳过推送的标题,多个换行符分割分隔,感谢 https://github.com/pharaoh2012
2. nginx 增加 ipv6 配置
3. 对比工具增加通知文件对比
4. 修复文件类型订阅重复添加任务
5. 修改自动删除日志逻辑
6. 修复定时任务状态筛选,排序
7. 修改表格样式
8. 修复切换导航,编辑器页面可能崩溃
9. 其他 bug 修复