Compare commits

..

19 Commits

Author SHA1 Message Date
whyour e93cad91b4 更新版本 v2.15.4 2022-12-28 11:15:20 +08:00
whyour 08ee61b179 修改 pub 脚本 2022-12-28 11:14:57 +08:00
whyour 0ab756665e 修改版本文件 2022-12-28 11:06:47 +08:00
whyour 3570cddce0 关于增加更新日志查看 2022-12-27 17:10:21 +08:00
whyour a2d9f1a5db 脚本管理支持重命名文件/文件夹 2022-12-27 16:02:15 +08:00
whyour 0c6a214e55 修复启动public服务命令 2022-12-27 12:24:24 +08:00
whyour 095fcdbe69 修复任务脚本快捷跳转 2022-12-17 12:46:26 +08:00
whyour 3afb1d18f6 更新版本 v2.15.3 2022-12-10 19:06:39 +08:00
whyour 4526c84330 任务视图支持标签筛选 2022-12-10 19:00:03 +08:00
whyour a9cc1cb4b9 修改token获取逻辑 2022-12-10 17:12:45 +08:00
whyour 7acf1eace3 修改添加任务视图筛选条件交互 2022-12-09 23:34:23 +08:00
whyour bb2e8cb287 修复调试保存文件默认目录和任务视图创建 2022-12-09 22:59:38 +08:00
whyour 3b389259c1 修复调试脚本日志 2022-12-06 00:22:05 +08:00
whyour 310ce55b09 修改默认基础镜像 2022-12-05 23:49:33 +08:00
CoolBoy 4662e3fec2 修复notify.js中智能微秘书 (#1736) 2022-12-05 23:44:34 +08:00
whyour 10fab9d415 修改进程默认退出信号 2022-12-05 21:00:14 +08:00
whyour acecf01cbe 修复 killTask 返回值 2022-12-05 15:38:21 +08:00
whyour b95fb9cda4 修改退出进程逻辑 2022-12-05 15:26:22 +08:00
whyour 23fd595582 修改 timeout 命令默认信号 2022-12-05 13:18:10 +08:00
39 changed files with 483 additions and 302 deletions
+32 -5
View File
@@ -170,7 +170,7 @@ export default (app: Router) => {
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().allow(''),
type: Joi.string().optional()
type: Joi.string().optional(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@@ -183,7 +183,7 @@ export default (app: Router) => {
};
const filePath = join(config.scriptPath, path, filename);
if (type === 'directory') {
emptyDir(filePath);
emptyDir(filePath);
} else {
fs.unlinkSync(filePath);
}
@@ -255,23 +255,50 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
filename: Joi.string().required(),
content: Joi.string().optional().allow(''),
path: Joi.string().optional().allow(''),
pid: Joi.number().optional().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
let { filename, content, path } = req.body;
let { filename, path, pid } = req.body;
const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
const scriptService = Container.get(ScriptService);
const result = await scriptService.stopScript(filePath);
const result = await scriptService.stopScript(filePath, pid);
res.send(result);
} catch (e) {
return next(e);
}
},
);
route.put(
'/rename',
celebrate({
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().allow(''),
newFilename: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
try {
let { filename, path, type, newFilename } = req.body as {
filename: string;
path: string;
type: string;
newFilename: string;
};
const filePath = join(config.scriptPath, path, filename);
const newPath = join(config.scriptPath, path, newFilename);
fs.renameSync(filePath, newPath);
res.send({ code: 200 });
} catch (e) {
return next(e);
}
},
);
};
+6 -5
View File
@@ -7,7 +7,7 @@ import SystemService from '../services/system';
import { celebrate, Joi } from 'celebrate';
import UserService from '../services/user';
import { EnvModel } from '../data/env';
import { promiseExec } from '../config/util';
import { parseVersion, promiseExec } from '../config/util';
import dayjs from 'dayjs';
const route = Router();
@@ -21,10 +21,9 @@ export default (app: Router) => {
const userService = Container.get(UserService);
const authInfo = await userService.getUserInfo();
const envCount = await EnvModel.count();
const versionRegx = /.*export const version = \'(.*)\'\;/;
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
const version = currentVersionFile.match(versionRegx)![1];
const { version, changeLog, changeLogLink } = await parseVersion(
config.versionFile,
);
const lastCommitTime = (
await promiseExec(
`cd ${config.rootPath} && git show -s --format=%ai | head -1`,
@@ -56,6 +55,8 @@ export default (app: Router) => {
lastCommitTime: dayjs(lastCommitTime).unix(),
lastCommitId,
branch,
changeLog,
changeLogLink,
},
});
} catch (e) {
+2 -2
View File
@@ -14,7 +14,7 @@ if (!process.env.QL_DIR) {
process.env.QL_DIR = qlHomePath.replace(/\/$/g, '');
}
const lastVersionFile = `https://qn.whyour.cn/version.ts`;
const lastVersionFile = `https://qn.whyour.cn/version.yaml`;
const rootPath = process.env.QL_DIR as string;
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
@@ -40,7 +40,7 @@ const sqliteFile = path.join(samplePath, 'database.sqlite');
const authError = '错误的用户名密码,请重试';
const loginFaild = '请先登录!';
const configString = 'config sample crontab shareCode diy';
const versionFile = path.join(rootPath, 'src/version.ts');
const versionFile = path.join(rootPath, 'version.yaml');
if (envFound.error) {
throw new Error("⚠️ Couldn't find .env file ⚠️");
+49 -4
View File
@@ -4,6 +4,9 @@ import got from 'got';
import iconv from 'iconv-lite';
import { exec } from 'child_process';
import FormData from 'form-data';
import psTreeFun from 'pstree.remy';
import { promisify } from 'util';
import { load } from 'js-yaml';
export function getFileContentByName(fileName: string) {
if (fs.existsSync(fileName)) {
@@ -287,15 +290,15 @@ enum FileType {
interface IFile {
title: string;
key: string;
type: 'directory' | 'file',
type: 'directory' | 'file';
parent: string;
mtime: number;
children?: IFile[],
children?: IFile[];
}
export function dirSort(a: IFile, b: IFile) {
if (a.type !== b.type) return FileType[a.type] < FileType[b.type] ? -1 : 1
else if (a.mtime !== b.mtime) return a.mtime > b.mtime ? -1 : 1
if (a.type !== b.type) return FileType[a.type] < FileType[b.type] ? -1 : 1;
else if (a.mtime !== b.mtime) return a.mtime > b.mtime ? -1 : 1;
}
export function readDirs(
@@ -452,3 +455,45 @@ export function parseBody(
return parsed;
}
export function psTree(pid: number): Promise<number[]> {
return new Promise((resolve, reject) => {
psTreeFun(pid, (err: any, pids: number[]) => {
if (err) {
reject(err);
}
resolve(pids.filter((x) => !isNaN(x)));
});
});
}
export async function killTask(pid: number) {
const pids = await psTree(pid);
// SIGALRM 14 时钟信号
if (pids.length) {
process.kill(pids[0], 14);
} else {
process.kill(pid, 14);
}
}
export async function getPid(name: string) {
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
const execAsync = promisify(exec);
let pid = (await execAsync(taskCommand)).stdout;
return Number(pid);
}
interface IVersion {
version: string;
changeLogLink: string;
changeLog: string;
}
export async function parseVersion(path: string): Promise<IVersion> {
return load(await promisify(fs.readFile)(path, 'utf8')) as IVersion;
}
export async function parseContentVersion(content: string): Promise<IVersion> {
return load(content) as IVersion;
}
+4 -6
View File
@@ -4,13 +4,11 @@ import * as Tracing from '@sentry/tracing';
import Logger from './logger';
import config from '../config';
import fs from 'fs';
import { parseVersion } from '../config/util';
export default ({ expressApp }: { expressApp: Application }) => {
const versionRegx = /.*export const version = \'(.*)\'\;/;
export default async ({ expressApp }: { expressApp: Application }) => {
const { version } = await parseVersion(config.versionFile);
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
const currentVersion = currentVersionFile.match(versionRegx)![1];
Sentry.init({
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
integrations: [
@@ -18,7 +16,7 @@ export default ({ expressApp }: { expressApp: Application }) => {
new Tracing.Integrations.Express({ app: expressApp }),
],
tracesSampleRate: 0.1,
release: currentVersion,
release: version,
});
expressApp.use(Sentry.Handlers.requestHandler());
+25 -65
View File
@@ -5,13 +5,15 @@ import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
import { exec, execSync, spawn } from 'child_process';
import fs from 'fs';
import cron_parser from 'cron-parser';
import { getFileContentByName, concurrentRun, fileExist } from '../config/util';
import {
getFileContentByName,
concurrentRun,
fileExist,
killTask,
} from '../config/util';
import { promises, existsSync } from 'fs';
import { promisify } from 'util';
import { Op } from 'sequelize';
import { Op, where, col as colFn } from 'sequelize';
import path from 'path';
import dayjs from 'dayjs';
import { LOG_END_SYMBOL } from '../config/const';
@Service()
export default class CronService {
@@ -161,9 +163,23 @@ export default class CronService {
}
if (operate && operate2) {
q[property] = {
[operate2]: [
{ [operate]: `%${value}%` },
{ [operate]: `%${encodeURIComponent(value)}%` },
[Op.or]: [
{
[operate2]: [
{ [operate]: `%${value}%` },
{ [operate]: `%${encodeURIComponent(value)}%` },
],
},
{
[operate2]: [
where(colFn(property), operate, `%${value}%`),
where(
colFn(property),
operate,
`%${encodeURIComponent(value)}%`,
),
],
},
],
};
}
@@ -315,31 +331,11 @@ export default class CronService {
for (const doc of docs) {
if (doc.pid) {
try {
process.kill(-doc.pid);
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
}
}
const err = await this.killTask(doc.command);
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
const logFileExist = doc.log_path && (await fileExist(absolutePath));
const endTime = dayjs();
const diffTimeStr = doc.last_execution_time
? ` 耗时 ${endTime.diff(
dayjs(doc.last_execution_time * 1000),
'second',
)}`
: '';
if (logFileExist) {
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)}${diffTimeStr}${LOG_END_SYMBOL}`,
);
}
}
await CrontabModel.update(
@@ -348,42 +344,6 @@ export default class CronService {
);
}
public async killTask(name: string) {
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
const execAsync = promisify(exec);
try {
let pid = (await execAsync(taskCommand)).stdout;
if (pid) {
pid = (await execAsync(`pstree -p ${pid}`)).stdout;
} else {
return;
}
let pids = pid.match(/\(\d+/g);
const killLogs = [];
if (pids && pids.length > 0) {
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
pids = pids.slice(0, 3);
for (const id of pids) {
const c = `kill -9 ${id.slice(1)}`;
try {
const { stdout, stderr } = await execAsync(c);
if (stderr) {
killLogs.push(stderr);
}
if (stdout) {
killLogs.push(stdout);
}
} catch (error: any) {
killLogs.push(error.message);
}
}
}
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
} catch (e) {
return JSON.stringify(e);
}
}
private async runSingle(cronId: number): Promise<number> {
return new Promise(async (resolve: any) => {
const cron = await this.getDb({ id: cronId });
+6 -2
View File
@@ -42,7 +42,11 @@ export default class ScheduleService {
constructor(@Inject('logger') private logger: winston.Logger) {}
async runTask(command: string, callbacks: TaskCallbacks = {}) {
async runTask(
command: string,
callbacks: TaskCallbacks = {},
completionTime: 'start' | 'end' = 'end',
) {
return new Promise(async (resolve, reject) => {
try {
const startTime = dayjs();
@@ -52,6 +56,7 @@ export default class ScheduleService {
// TODO:
callbacks.onStart?.(cp, startTime);
completionTime === 'start' && resolve(cp.pid);
cp.stdout.on('data', async (data) => {
await callbacks.onLog?.(data.toString());
@@ -100,7 +105,6 @@ export default class ScheduleService {
error,
);
await callbacks.onError?.(JSON.stringify(error));
resolve(null);
}
});
}
+16 -13
View File
@@ -7,6 +7,7 @@ import CronService from './cron';
import ScheduleService, { TaskCallbacks } from './schedule';
import config from '../config';
import { LOG_END_SYMBOL } from '../config/const';
import { getPid, killTask } from '../config/util';
@Service()
export default class ScriptService {
@@ -42,22 +43,24 @@ export default class ScriptService {
public async runScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const command = `task -l ${relativePath} now`;
this.scheduleService.runTask(command, this.taskCallbacks(filePath));
const pid = await this.scheduleService.runTask(
command,
this.taskCallbacks(filePath),
'start',
);
return { code: 200 };
return { code: 200, data: pid };
}
public async stopScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const err = await this.cronService.killTask(`task -l ${relativePath} now`);
const str = err ? `\n${err}` : '';
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: `${str}\n## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')}${LOG_END_SYMBOL}`,
});
public async stopScript(filePath: string, pid: number) {
let str = '';
if (!pid) {
const relativePath = path.relative(config.scriptPath, filePath);
pid = await getPid(`task -l ${relativePath} now`);
}
try {
await killTask(pid);
} catch (error) {}
return { code: 200 };
}
+1 -1
View File
@@ -14,7 +14,7 @@ export default class SshKeyService {
private generatePrivateKeyFile(alias: string, key: string): void {
try {
fs.writeFileSync(`${this.sshPath}/${alias}`, key, {
fs.writeFileSync(`${this.sshPath}/${alias}`, `${key}${os.EOL}`, {
encoding: 'utf8',
mode: '400',
});
+3 -41
View File
@@ -19,9 +19,9 @@ import {
concurrentRun,
fileExist,
createFile,
killTask,
} from '../config/util';
import { promises, existsSync } from 'fs';
import { promisify } from 'util';
import { Op } from 'sequelize';
import path from 'path';
import ScheduleService, { TaskCallbacks } from './schedule';
@@ -351,19 +351,16 @@ export default class SubscriptionService {
for (const doc of docs) {
if (doc.pid) {
try {
process.kill(-doc.pid);
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
}
}
const command = this.formatCommand(doc);
const err = await this.killTask(command);
const absolutePath = await this.handleLogPath(doc.log_path as string);
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${dayjs().format(
`\n## 执行结束... ${dayjs().format(
'YYYY-MM-DD HH:mm:ss',
)}${LOG_END_SYMBOL}`,
);
@@ -375,41 +372,6 @@ export default class SubscriptionService {
);
}
public async killTask(name: string) {
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
const execAsync = promisify(exec);
try {
let pid = (await execAsync(taskCommand)).stdout;
if (pid) {
pid = (await execAsync(`pstree -p ${pid}`)).stdout;
} else {
return;
}
let pids = pid.match(/\(\d+/g);
const killLogs = [];
if (pids && pids.length > 0) {
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
for (const id of pids) {
const c = `kill -9 ${id.slice(1)}`;
try {
const { stdout, stderr } = await execAsync(c);
if (stderr) {
killLogs.push(stderr);
}
if (stdout) {
killLogs.push(stdout);
}
} catch (error: any) {
killLogs.push(error.message);
}
}
}
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
} catch (e) {
return JSON.stringify(e);
}
}
private async runSingle(subscriptionId: number) {
const subscription = await this.getDb({ id: subscriptionId });
if (subscription.status !== SubscriptionStatus.queued) {
+15 -15
View File
@@ -9,6 +9,7 @@ import ScheduleService from './schedule';
import { spawn } from 'child_process';
import SockService from './sock';
import got from 'got';
import { parseContentVersion, parseVersion } from '../config/util';
@Service()
export default class SystemService {
@@ -78,14 +79,9 @@ export default class SystemService {
public async checkUpdate() {
try {
const versionRegx = /.*export const version = \'(.*)\'\;/;
const logRegx = /.*export const changeLog = \`((.*\n.*)+)\`;/;
const currentVersionContent = await parseVersion(config.versionFile);
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
const currentVersion = currentVersionFile.match(versionRegx)![1];
let lastVersion = '';
let lastLog = '';
let lastVersionContent;
try {
const result = await got.get(
`${config.lastVersionFile}?t=${Date.now()}`,
@@ -93,19 +89,23 @@ export default class SystemService {
timeout: 30000,
},
);
const lastVersionFileContent = result.body;
lastVersion = lastVersionFileContent.match(versionRegx)![1];
lastLog = lastVersionFileContent.match(logRegx)
? lastVersionFileContent.match(logRegx)![1]
: '';
lastVersionContent = await parseContentVersion(result.body);
} catch (error) {}
if (!lastVersionContent) {
lastVersionContent = currentVersionContent;
}
return {
code: 200,
data: {
hasNewVersion: this.checkHasNewVersion(currentVersion, lastVersion),
lastVersion,
lastLog,
hasNewVersion: this.checkHasNewVersion(
currentVersionContent.version,
lastVersionContent.version,
),
lastVersion: lastVersionContent.version,
lastLog: lastVersionContent.changeLog,
lastLogLink: lastVersionContent.changeLogLink,
},
};
} catch (error: any) {
+9 -3
View File
@@ -5,6 +5,7 @@ import LoggerInstance from './loaders/logger';
import fs from 'fs';
import config from './config';
import path from 'path';
import os from 'os';
const tokenFile = path.join(config.configPath, 'token.json');
@@ -25,9 +26,14 @@ async function getToken() {
async function writeFile(data: any) {
return new Promise<void>((resolve, reject) => {
fs.writeFile(tokenFile, JSON.stringify(data), { encoding: 'utf8' }, () => {
resolve();
});
fs.writeFile(
tokenFile,
`${JSON.stringify(data)}${os.EOL}`,
{ encoding: 'utf8' },
() => {
resolve();
},
);
});
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:alpine
FROM python:3.10-alpine
ARG QL_MAINTAINER="whyour"
LABEL maintainer="${QL_MAINTAINER}"
+4 -1
View File
@@ -4,7 +4,7 @@
"start": "concurrently -n w: npm:start:*",
"start:front": "max dev",
"start:back": "nodemon",
"start:public": "ts-node back/public.ts",
"start:public": "ts-node --transpile-only ./back/public.ts",
"build:front": "max build",
"build:back": "tsc -p tsconfig.back.json",
"panel": "npm run build:back && node static/build/app.js",
@@ -69,12 +69,14 @@
"got": "^11.8.2",
"hpagent": "^0.1.2",
"iconv-lite": "^0.6.3",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"multer": "^1.4.4",
"nedb": "^1.8.0",
"node-schedule": "^2.1.0",
"nodemailer": "^6.7.2",
"pstree.remy": "^1.1.8",
"reflect-metadata": "^0.1.13",
"sequelize": "^6.25.5",
"serve-handler": "^6.1.3",
@@ -96,6 +98,7 @@
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/express-jwt": "^6.0.4",
"@types/js-yaml": "^4.0.5",
"@types/jsonwebtoken": "^8.5.8",
"@types/lodash": "^4.14.185",
"@types/multer": "^1.4.7",
+10 -3
View File
@@ -13,6 +13,7 @@ specifiers:
'@types/cors': ^2.8.12
'@types/express': ^4.17.13
'@types/express-jwt': ^6.0.4
'@types/js-yaml': ^4.0.5
'@types/jsonwebtoken': ^8.5.8
'@types/lodash': ^4.14.185
'@types/multer': ^1.4.7
@@ -49,6 +50,7 @@ specifiers:
got: ^11.8.2
hpagent: ^0.1.2
iconv-lite: ^0.6.3
js-yaml: ^4.1.0
jsonwebtoken: ^8.5.1
lint-staged: ^13.0.3
lodash: ^4.17.21
@@ -58,6 +60,7 @@ specifiers:
nodemailer: ^6.7.2
nodemon: ^2.0.15
prettier: ^2.5.1
pstree.remy: ^1.1.8
qiniu: ^7.4.0
qrcode.react: ^1.0.1
query-string: ^7.1.1
@@ -106,12 +109,14 @@ dependencies:
got: 11.8.5
hpagent: 0.1.2
iconv-lite: 0.6.3
js-yaml: 4.1.0
jsonwebtoken: 8.5.1
lodash: 4.17.21
multer: 1.4.4
nedb: 1.8.0
node-schedule: 2.1.0
nodemailer: 6.7.8
pstree.remy: 1.1.8
reflect-metadata: 0.1.13
sequelize: 6.25.6_@louislam+sqlite3@15.0.6
serve-handler: 6.1.3
@@ -133,6 +138,7 @@ devDependencies:
'@types/cors': 2.8.12
'@types/express': 4.17.14
'@types/express-jwt': 6.0.4
'@types/js-yaml': 4.0.5
'@types/jsonwebtoken': 8.5.9
'@types/lodash': 4.14.185
'@types/multer': 1.4.7
@@ -2628,6 +2634,10 @@ packages:
'@types/istanbul-lib-report': 3.0.0
dev: true
/@types/js-yaml/4.0.5:
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
dev: true
/@types/json-schema/7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
@@ -3839,7 +3849,6 @@ packages:
/argparse/2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/aria-hidden/1.2.1_jobltc72ducwijlk5r742icaw4:
resolution: {integrity: sha512-PN344VAf9j1EAi+jyVHOJ8XidQdPVssGco39eNcsGdM4wcsILtxrKLkbuiMfLWYROK1FjRQasMWCBttrhjnr6A==}
@@ -7992,7 +8001,6 @@ packages:
hasBin: true
dependencies:
argparse: 2.0.1
dev: true
/jsbn/0.1.1:
resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
@@ -10352,7 +10360,6 @@ packages:
/pstree.remy/1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
dev: true
/public-encrypt/4.0.3:
resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==}
+2
View File
@@ -980,6 +980,8 @@ function aibotkNotify(text, desp) {
resolve(data);
}
});
} else {
resolve();
}
});
}
+2 -2
View File
@@ -5,14 +5,14 @@ const envFound = dotenv.config();
const accessKey = process.env.QINIU_AK;
const secretKey = process.env.QINIU_SK;
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
const key = 'version.ts';
const key = 'version.yaml';
const options = {
scope: `${process.env.QINIU_SCOPE}:${key}`,
};
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(mac);
const localFile = 'src/version.ts';
const localFile = 'version.yaml';
const config = new qiniu.conf.Config({ zone: qiniu.zone.Zone_z1 });
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra(
+10 -1
View File
@@ -1,7 +1,16 @@
#!/usr/bin/env bash
get_token() {
token=$(cat $file_auth_token | jq -r .value)
if [[ -f $file_auth_token ]]; then
token=$(cat $file_auth_token | jq -r .value)
else
local token_command="ts-node-transpile-only ${dir_root}/back/token.ts"
local token_file="${dir_root}static/build/token.js"
if [[ -f $token_file ]]; then
token_command="node ${token_file}"
fi
token=$(eval "$token_command")
fi
}
add_cron_api() {
+9 -3
View File
@@ -5,6 +5,12 @@ dir_shell=$QL_DIR/shell
. $dir_shell/share.sh
. $dir_shell/api.sh
trap "single_hanle" 2 20 15 14
single_hanle() {
handle_task_after "$@"
exit 1
}
random_delay() {
local random_delay_max=$RandomDelay
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
@@ -115,7 +121,7 @@ handle_task_after() {
local end_time=$(format_time "$time_format" "$etime")
local end_timestamp=$(format_timestamp "$time_format" "$etime")
local diff_time=$(($end_timestamp - $begin_timestamp))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time"
echo -e "\n     "
@@ -150,7 +156,7 @@ run_concurrent() {
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
@@ -198,7 +204,7 @@ run_designated() {
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
+1 -1
View File
@@ -13,7 +13,7 @@ git push
echo -e "更新cdn文件"
ts-node sample/tool.ts
string=$(cat src/version.ts | grep "version" | egrep "[^\']*" -o | egrep "\d\.*")
string=$(cat version.yaml | grep "version" | egrep "[^ ]*" -o | egrep "\d\.*")
version="v$string"
echo -e "当前版本$version"
+1 -1
View File
@@ -56,7 +56,7 @@ format_params() {
time_format="%Y-%m-%d %H:%M:%S"
timeoutCmd=""
if type timeout &>/dev/null; then
timeoutCmd="timeout -k 10s $command_timeout_time "
timeoutCmd="timeout --foreground -s 14 -k 10s $command_timeout_time "
fi
params=$(echo "$@" | sed -E 's/([^ ])&([^ ])/\1\\\&\2/g')
}
+6 -36
View File
@@ -26,33 +26,6 @@ diff_cron() {
fi
}
## 检测配置文件版本
detect_config_version() {
## 识别出两个文件的版本号
ver_config_sample=$(grep " Version: " $file_config_sample | perl -pe "s|.+v((\d+\.?){3})|\1|")
[[ -f $file_config_user ]] && ver_config_user=$(grep " Version: " $file_config_user | perl -pe "s|.+v((\d+\.?){3})|\1|")
## 删除旧的发送记录文件
[[ -f $send_mark ]] && [[ $(cat $send_mark) != $ver_config_sample ]] && rm -f $send_mark
## 识别出更新日期和更新内容
update_date=$(grep " Date: " $file_config_sample | awk -F ": " '{print $2}')
update_content=$(grep " Update Content: " $file_config_sample | awk -F ": " '{print $2}')
## 如果是今天,并且版本号不一致,则发送通知
if [[ -f $file_config_user ]] && [[ $ver_config_user != $ver_config_sample ]] && [[ $update_date == $(date "+%Y-%m-%d") ]]; then
if [[ ! -f $send_mark ]]; then
local notify_title="配置文件更新通知"
local notify_content="更新日期: $update_date\n用户版本: $ver_config_user\n新的版本: $ver_config_sample\n更新内容: $update_content\n更新说明: 如需使用新功能请对照config.sample.sh,将相关新参数手动增加到你自己的config.sh中,否则请无视本消息。本消息只在该新版本配置文件更新当天发送一次。\n"
echo -e $notify_content
notify_api "$notify_title" "$notify_content"
[[ $? -eq 0 ]] && echo $ver_config_sample >$send_mark
fi
else
[[ -f $send_mark ]] && rm -f $send_mark
fi
}
## 输出是否有新的或失效的定时任务,$1:新的或失效的任务清单文件路径,$2:新/失效
output_list_add_drop() {
local list=$1
@@ -188,7 +161,7 @@ update_raw() {
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 | head -1 | awk -F " " '{print $1}')
local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* $filename" | perl -pe "s|.*ID=(.*) $cmd_task.* $filename\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
cp -f $raw_file_name $dir_scripts/${filename}
cron_line=$(
perl -ne "{
@@ -266,7 +239,6 @@ update_qinglong() {
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新青龙源文件成功...\n"
cp -f $file_config_sample $dir_config/config.sample.sh
detect_config_version
update_depend
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
@@ -291,8 +263,6 @@ update_qinglong_static() {
fi
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新青龙静态资源成功...\n"
local static_version=$(cat $dir_root/src/version.ts | perl -pe "s|.*\'(.*)\';\.*|\1|" | head -1)
echo -e "\n当前版本 $static_version...\n"
rm -rf $dir_static/*
cp -rf $ql_static_repo/* $dir_static
@@ -396,12 +366,12 @@ gen_list_repo() {
filename=$(basename $file)
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
echo "${uniq_path}/${filename}" >>"$dir_list_tmp/${uniq_path}_scripts.list"
cron_id=$(cat $list_crontab_user | grep -E "$cmd_task ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
if [[ $cron_id ]]; then
result=$(update_cron_command_api "$cmd_task ${uniq_path}/${filename}:$cron_id")
fi
# cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task.* ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
# if [[ $cron_id ]]; then
# result=$(update_cron_command_api "$cmd_task ${uniq_path}/${filename}:$cron_id")
# fi
done
grep -E "${cmd_task} ${uniq_path}" ${list_crontab_user} | perl -pe "s|.*ID=(.*) ${cmd_task} (${uniq_path}.*)\.*|\2|" | awk -F " " '{print $1}' | sort -u >"$dir_list_tmp/${uniq_path}_user.list"
grep -E "${cmd_task}.* ${uniq_path}" ${list_crontab_user} | perl -pe "s|.*ID=(.*) ${cmd_task}.* (${uniq_path}.*)\.*|\2|" | awk -F " " '{print $1}' | sort -u >"$dir_list_tmp/${uniq_path}_user.list"
cd $dir_current
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_z0d9rbri1ci.js'],
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_ob5y15ewlyq.js'],
});
export default IconFont;
+5 -4
View File
@@ -13,7 +13,6 @@ import config from '@/utils/config';
import { request } from '@/utils/http';
import './index.less';
import vhCheck from 'vh-check';
import { version, changeLogLink, changeLog } from '../version';
import { useCtx, useTheme } from '@/utils/hooks';
import {
message,
@@ -52,6 +51,8 @@ interface TSystemInfo {
lastCommitId: string;
lastCommitTime: number;
version: string;
changeLog: string;
changeLogLink: string;
}
export default function () {
@@ -89,6 +90,7 @@ export default function () {
if (!data.isInitialized) {
history.push('/initialization');
} else {
init(data.version);
getUser();
}
}
@@ -143,7 +145,6 @@ export default function () {
useEffect(() => {
vhCheck();
init();
const _theme = localStorage.getItem('qinglong_dark_theme') || 'auto';
if (typeof window === 'undefined') return;
@@ -269,7 +270,7 @@ export default function () {
<>
<span style={{ fontSize: 16 }}></span>
<a
href={changeLogLink}
href={systemInfo?.changeLogLink}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => {
@@ -289,7 +290,7 @@ export default function () {
letterSpacing: isQQBrowser ? -2 : 0,
}}
>
v{version}
v{systemInfo?.version}
</span>
</Badge>
</Tooltip>
+5 -16
View File
@@ -29,6 +29,7 @@ import config from '@/utils/config';
import CronLogModal from './logModal';
import Editor from '@monaco-editor/react';
import IconFont from '@/components/iconfont';
import { getCommandScript } from '@/utils';
const { Text } = Typography;
@@ -147,22 +148,10 @@ const CronDetailModal = ({
};
const getScript = () => {
const cmd = cron.command.split(' ') as string[];
if (cmd[0] === 'task') {
const result = getCommandScript(cron.command);
if (Array.isArray(result)) {
setValidTabs(validTabs);
if (cmd[1].startsWith('/ql/data/scripts')) {
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
}
let p: string, s: string;
let index = cmd[1].lastIndexOf('/');
if (index >= 0) {
s = cmd[1].slice(index + 1);
p = cmd[1].slice(0, index);
} else {
s = cmd[1];
p = '';
}
const [s, p] = result;
setScriptInfo({ parent: p, filename: s });
request
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
@@ -171,7 +160,7 @@ const CronDetailModal = ({
setValue(data);
}
});
} else {
} else if (result) {
setValidTabs([validTabs[0]]);
}
};
+6 -17
View File
@@ -50,6 +50,7 @@ import ViewManageModal from './viewManageModal';
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import { getCommandScript } from '@/utils';
const { Text, Paragraph } = Typography;
const { Search } = Input;
@@ -390,24 +391,12 @@ const Crontab = () => {
const tableScrollHeight = useTableScrollHeight(tableRef);
const goToScriptManager = (record: any) => {
const cmd = record.command.split(' ') as string[];
if (cmd[0] === 'task') {
if (cmd[1].startsWith('/ql/data/scripts')) {
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
}
let p: string, s: string;
let index = cmd[1].lastIndexOf('/');
if (index >= 0) {
s = cmd[1].slice(index + 1);
p = cmd[1].slice(0, index);
} else {
s = cmd[1];
p = '';
}
const result = getCommandScript(record.command);
if (Array.isArray(result)) {
const [s, p] = result;
history.push(`/script?p=${p}&s=${s}`);
} else if (cmd[1] === 'repo') {
location.href = cmd[2];
} else if (result) {
location.href = result;
}
};
+74 -26
View File
@@ -13,19 +13,27 @@ import { request } from '@/utils/http';
import config from '@/utils/config';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import IconFont from '@/components/iconfont';
import get from 'lodash/get';
const PROPERTIES = [
{ name: '命令', value: 'command' },
{ name: '名称', value: 'name' },
{ name: '定时规则', value: 'schedule' },
{ name: '状态', value: 'status' },
{ name: '标签', value: 'labels' },
];
const EOperation: any = {
Reg: '',
NotReg: '',
In: 'select',
Nin: 'select',
};
const OPERATIONS = [
{ name: '包含', value: 'Reg' },
{ name: '不包含', value: 'NotReg' },
{ name: '属于', value: 'In' },
{ name: '不属于', value: 'Nin' },
{ name: '属于', value: 'In', type: 'select' },
{ name: '不属于', value: 'Nin', type: 'select' },
// { name: '等于', value: 'Eq' },
// { name: '不等于', value: 'Ne' },
// { name: '为空', value: 'IsNull' },
@@ -37,11 +45,13 @@ const SORTTYPES = [
{ name: '倒序', value: 'DESC' },
];
const STATUS = [
{ name: '运行中', value: 0 },
{ name: '空闲中', value: 1 },
{ name: '已禁用', value: 2 },
];
const STATUS_MAP = {
status: [
{ name: '运行中', value: 0 },
{ name: '空闲中', value: 1 },
{ name: '已禁用', value: 2 },
],
};
enum ViewFilterRelation {
'and' = '且',
@@ -125,15 +135,17 @@ const ViewCreateModal = ({
</Select>
);
const statusElement = (
<Select mode="multiple" allowClear placeholder="请选择状态">
{STATUS.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
const statusElement = (property: keyof typeof STATUS_MAP) => {
return (
<Select mode="tags" allowClear placeholder="输入后回车增加自定义选项">
{STATUS_MAP[property]?.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
};
return (
<Modal
@@ -239,17 +251,53 @@ const ViewCreateModal = ({
{operationElement}
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
rules={[{ required: true, message: '请输入内容' }]}
noStyle
shouldUpdate={(prevValues, nextValues) => {
const preOperation =
EOperation[
get(prevValues, ['filters', name, 'operation'])
];
const nextOperation =
EOperation[
get(nextValues, ['filters', name, 'operation'])
];
const flag = preOperation !== nextOperation;
if (flag) {
form.setFieldValue(
['filters', name, 'value'],
nextOperation === 'select' ? [] : '',
);
}
return flag;
}}
>
{['In', 'Nin'].includes(
form.getFieldValue(['filters', index, 'operation']),
) ? (
statusElement
) : (
<Input placeholder="请输入内容" />
)}
{() => {
const property = form.getFieldValue([
'filters',
index,
'property',
]) as 'status';
const operate = form.getFieldValue([
'filters',
name,
'operation',
]);
return (
<Form.Item
{...restField}
name={[name, 'value']}
rules={[
{ required: true, message: '请输入内容' },
]}
>
{EOperation[operate] === 'select' ? (
statusElement(property)
) : (
<Input placeholder="请输入内容" />
)}
</Form.Item>
);
}}
</Form.Item>
{index !== 0 && (
<MinusCircleOutlined onClick={() => remove(name)} />
+4 -3
View File
@@ -49,6 +49,7 @@ const EditModal = ({
const { theme } = useTheme();
const editorRef = useRef<any>(null);
const [isRunning, setIsRunning] = useState(false);
const [currentPid, setCurrentPid] = useState(null);
const cancel = () => {
handleCancel();
@@ -94,6 +95,7 @@ const EditModal = ({
.then(({ code, data }) => {
if (code === 200) {
setIsRunning(true);
setCurrentPid(data);
}
});
};
@@ -102,13 +104,12 @@ const EditModal = ({
if (!cNode || !cNode.title) {
return;
}
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
request
.put(`${config.apiPrefix}scripts/stop`, {
data: {
filename: cNode.title,
path: cNode.parent || '',
content,
pid: currentPid,
},
})
.then(({ code, data }) => {
@@ -271,7 +272,7 @@ const EditModal = ({
content:
editorRef.current &&
editorRef.current.getValue().replace(/\r\n/g, '\n'),
filename: cNode?.title,
...cNode,
}}
/>
<SettingModal
+38 -3
View File
@@ -39,6 +39,8 @@ import { depthFirstSearch } from '@/utils';
import { SharedContext } from '@/layouts';
import useFilterTreeData from '@/hooks/useFilterTreeData';
import uniq from 'lodash/uniq';
import IconFont from '@/components/iconfont';
import RenameModal from './renameModal';
const { Text } = Typography;
@@ -64,20 +66,22 @@ const Script = () => {
const [isEditing, setIsEditing] = useState(false);
const editorRef = useRef<any>(null);
const [isAddFileModalVisible, setIsAddFileModalVisible] = useState(false);
const [isRenameFileModalVisible, setIsRenameFileModalVisible] = useState(false);
const [currentNode, setCurrentNode] = useState<any>();
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const getScripts = () => {
setLoading(true);
const getScripts = (needLoading: boolean = true) => {
needLoading && setLoading(true);
request
.get(`${config.apiPrefix}scripts`)
.then(({ code, data }) => {
if (code === 200) {
setData(data);
initState();
initGetScript();
}
})
.finally(() => setLoading(false));
.finally(() => needLoading && setLoading(false));
};
const getDetail = (node: any) => {
@@ -287,6 +291,15 @@ const Script = () => {
});
};
const renameFile = () => {
setIsRenameFileModalVisible(true);
}
const handleRenameFileCancel = () => {
setIsRenameFileModalVisible(false);
getScripts(false);
}
const addFile = () => {
setIsAddFileModalVisible(true);
};
@@ -381,6 +394,9 @@ const Script = () => {
case 'delete':
deleteFile();
break;
case 'rename':
renameFile();
break;
default:
break;
}
@@ -407,6 +423,12 @@ const Script = () => {
icon: <EditOutlined />,
disabled: !select,
},
{
label: '重命名',
key: 'rename',
icon: <IconFont type="ql-icon-rename" />,
disabled: !select,
},
{
label: '删除',
key: 'delete',
@@ -471,6 +493,14 @@ const Script = () => {
icon={<EditOutlined />}
/>
</Tooltip>,
<Tooltip title="重命名">
<Button
disabled={!select}
type="primary"
onClick={renameFile}
icon={<IconFont type="ql-icon-rename" />}
/>
</Tooltip>,
<Tooltip title="删除">
<Button
type="primary"
@@ -585,6 +615,11 @@ const Script = () => {
treeData={data}
handleCancel={addFileModalClose}
/>
<RenameModal
visible={isRenameFileModalVisible}
handleCancel={handleRenameFileCancel}
currentNode={currentNode}
/>
</div>
</PageContainer>
);
+79
View File
@@ -0,0 +1,79 @@
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
const RenameModal = ({
currentNode,
handleCancel,
visible,
}: {
currentNode?: any;
visible: boolean;
handleCancel: () => void;
}) => {
console.log(currentNode);
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleOk = async (values: any) => {
setLoading(true);
try {
const { code, data } = await request.put(
`${config.apiPrefix}scripts/rename`,
{
data: {
filename: currentNode.title,
path: currentNode.parent || '',
newFilename: values.name,
},
},
);
if (code === 200) {
message.success('更新名称成功');
handleCancel();
}
setLoading(false);
} catch (error) {
setLoading(false);
}
};
useEffect(() => {
form.resetFields();
}, [currentNode, visible]);
return (
<Modal
title="重命名"
open={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} layout="vertical" name="edit_name_modal">
<Form.Item
name="name"
rules={[{ required: true, message: '请输入新名称' }]}
>
<Input placeholder="请输入新名称" />
</Form.Item>
</Form>
</Modal>
);
};
export default RenameModal;
+2 -2
View File
@@ -17,7 +17,7 @@ const SaveModal = ({
const handleOk = async (values: any) => {
setLoading(true);
const payload = { ...file, ...values, originFilename: file.filename };
const payload = { ...file, ...values, originFilename: file.title };
request
.post(`${config.apiPrefix}scripts`, {
data: payload,
@@ -60,7 +60,7 @@ const SaveModal = ({
form={form}
layout="vertical"
name="script_modal"
initialValues={file}
initialValues={{ filename: file?.title, path: file?.parent || '' }}
>
<Form.Item
name="filename"
+8
View File
@@ -38,6 +38,14 @@ const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
<Descriptions.Item label="更新ID" span={3}>
{systemInfo.lastCommitId}
</Descriptions.Item>
<Descriptions.Item label="更新日志" span={3}>
<Link
href={`https://qn.whyour.cn/version.yaml?t=${Date.now()}`}
target="_blank"
>
</Link>
</Descriptions.Item>
</Descriptions>
<div>
<Link
+5 -7
View File
@@ -2,11 +2,10 @@ import React, { useEffect, useState, useRef } from 'react';
import { Statistic, Modal, Tag, Button, Spin, message } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import { version } from '../../version';
const { Countdown } = Statistic;
const CheckUpdate = ({ socketMessage }: any) => {
const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
const [updateLoading, setUpdateLoading] = useState(false);
const [value, setValue] = useState('');
const modalRef = useRef<any>();
@@ -23,7 +22,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
if (data.hasNewVersion) {
showConfirmUpdateModal(data);
} else {
showForceUpdateModal();
showForceUpdateModal(data);
}
}
})
@@ -36,7 +35,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
});
};
const showForceUpdateModal = () => {
const showForceUpdateModal = (data: any) => {
Modal.confirm({
width: 500,
title: '更新',
@@ -44,7 +43,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
<>
<div></div>
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
{version}
{data.lastVersion}
</div>
</>
),
@@ -70,14 +69,13 @@ const CheckUpdate = ({ socketMessage }: any) => {
<>
<div></div>
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
{lastVersion}使{version}
{lastVersion} 使 {systemInfo.version}
</div>
</>
),
content: (
<pre
style={{
paddingTop: 15,
fontSize: 12,
fontWeight: 400,
}}
+4 -1
View File
@@ -416,7 +416,10 @@ const Setting = () => {
</Input.Group>
</Form.Item>
<Form.Item label="检查更新" name="update">
<CheckUpdate socketMessage={socketMessage} />
<CheckUpdate
systemInfo={systemInfo}
socketMessage={socketMessage}
/>
</Form.Item>
</Form>
),
+29 -1
View File
@@ -198,7 +198,7 @@ export function getTableScroll({
if (tHeader) {
mainTop = tHeader.getBoundingClientRect().top;
}
//窗体高度-表格内容顶部的高度-表格内容底部的高度
let height = document.body.clientHeight - mainTop - extraHeight;
return height;
@@ -276,3 +276,31 @@ export function logEnded(log: string): boolean {
const endTips = [LOG_END_SYMBOL, '执行结束'];
return endTips.some((x) => log.includes(x));
}
export function getCommandScript(
command: string,
): [string, string] | string | undefined {
const cmd = command.split(' ') as string[];
if (cmd[0] === 'task') {
let scriptsPart = cmd.find((x) =>
['.js', '.ts', '.sh', '.py'].some((y) => x.endsWith(y)),
);
if (!scriptsPart) return;
if (scriptsPart.startsWith('/ql/data/scripts')) {
scriptsPart = scriptsPart.replace('/ql/data/scripts/', '');
}
let p: string, s: string;
let index = scriptsPart.lastIndexOf('/');
if (index >= 0) {
s = scriptsPart.slice(index + 1);
p = scriptsPart.slice(0, index);
} else {
s = scriptsPart;
p = '';
}
return [s, p];
} else if (cmd[1] === 'repo') {
return cmd[2];
}
}
+1 -2
View File
@@ -1,9 +1,8 @@
import * as Sentry from '@sentry/react';
import { Integrations } from '@sentry/tracing';
import { loader } from '@monaco-editor/react';
import { version } from '../version';
export function init() {
export function init(version: string) {
// sentry监控 init
Sentry.init({
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
-8
View File
@@ -1,8 +0,0 @@
export const version = '2.15.2';
export const changeLogLink = 'https://t.me/jiao_long/353';
export const changeLog = `2.15.2 版本说明
1. 任务视图增加系统视图排序
2. task 命令增加 -m 参数,支持设置任务超时时间,格式参考 CommandTimeoutTime。例如 task -m 1h xxx.js
3. 修复资源占用检查逻辑
4. 其他优化
`;
+2
View File
@@ -8,3 +8,5 @@ declare module '*.svg' {
const url: string;
export default url;
}
declare module 'pstree.remy';
+6
View File
@@ -0,0 +1,6 @@
version: 2.15.4
changeLogLink: https://t.me/jiao_long/355
changeLog: |
1. 脚本管理支持重命名文件/文件夹
2. 关于增加更新日志查看
3. 修复定时任务脚本识别和跳转