Compare commits

...
30 Commits
Author SHA1 Message Date
whyour 742d0b2135 更新版本 v2.16.2 2023-09-02 00:41:16 +08:00
whyour 2081364325 修改定时任务置顶样式 2023-09-02 00:41:13 +08:00
whyour 39dc31916e 修复日志弹框样式 2023-09-01 23:16:12 +08:00
whyour 5395cebdb4 修复环境变量有空格时并发数量错误 2023-09-01 22:35:15 +08:00
whyour fe934fa4af 修复环境变量中单引号转义 2023-09-01 15:15:25 +08:00
whyour 461fa8e131 修改系统任务日志 2023-08-29 23:29:02 +08:00
whyour 4618a19c04 修改本地监听 ip 2023-08-29 21:50:23 +08:00
whyour e2bd15683e 修复 JSON.parse 错误,修复删除环境变量名称过长 2023-08-27 12:41:06 +08:00
whyour 648b9c4520 修改系统日志时间打印 2023-08-26 20:51:17 +08:00
whyour b4e4e84bbd 修复多语言翻译 2023-08-26 15:29:44 +08:00
whyour aa5d6f3cb6 系统设置增加系统语言切换 2023-08-26 00:16:15 +08:00
whyour e9416c23df 增加任务运行数日志 2023-08-25 19:25:12 +08:00
NANSIYVandGitHub ef4999be55 修复 python 企业微信推送错误 (#2087)
close #2086
2023-08-25 10:37:03 +08:00
whyour e7be4999b0 修复定时任务日志滚动 2023-08-25 09:05:32 +08:00
whyour 5907553670 修改定时任务置顶样式 2023-08-25 00:46:28 +08:00
whyour 0b9066525a 修复任务运行错误时没有日志,修改 tooltip 样式 2023-08-24 23:14:22 +08:00
whyour ac904cae61 修复停止订阅执行日志 2023-08-23 22:57:18 +08:00
whyour 4d5fa320ea 修改私有仓库 ssh 配置 2023-08-23 18:06:28 +08:00
whyour 003defedcf 修复环境变量中$变量被计算 2023-08-23 14:55:57 +08:00
whyour 40e8041401 修复退出调试自动删除调试日志目录 2023-08-22 22:18:18 +08:00
whyour a8174e89be 更新版本 v2.16.1 2023-08-21 22:51:00 +08:00
whyour cce83f7a4c 修复日志中多语言文本判断 2023-08-21 22:50:57 +08:00
whyour 4f7649f157 系统设置增加系统运行日志 2023-08-21 00:10:43 +08:00
whyour b002cbef3a 修复手机端日志弹框样式 2023-08-19 09:29:35 +08:00
whyour 1d93fe0de0 修复添加订阅粘贴 ql 链接 2023-08-18 09:17:18 +08:00
whyour 2d936f1341 修复定时任务页面样式 2023-08-17 08:26:48 +08:00
whyour b40673e9ac 修改日志弹框样式 2023-08-16 22:14:45 +08:00
whyour 637874e426 初始化数据增加错误捕获 2023-08-11 22:50:53 +08:00
whyour de71d1a258 修复任务筛选条件切换 2023-08-11 22:25:14 +08:00
whyour e174e190ee 修复 python 企业微信推送,debian 版本关于页面报错 2023-08-11 21:40:01 +08:00
72 changed files with 1453 additions and 629 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ src/.umi
src/.umi-production
src/.umi-test
.env.local
env
.env
history
version.ts
config
+1
View File
@@ -4,6 +4,7 @@ const CompressionPlugin = require('compression-webpack-plugin');
const baseUrl = process.env.QlBaseUrl || '/';
export default defineConfig({
hash: true,
jsMinifier: 'terser',
antd: {},
locale: {
antd: true,
+2 -1
View File
@@ -6,6 +6,7 @@ import { celebrate, Joi } from 'celebrate';
import multer from 'multer';
import config from '../config';
import fs from 'fs';
import { safeJSONParse } from '../config/util';
const route = Router();
const storage = multer.diskStorage({
@@ -200,7 +201,7 @@ export default (app: Router) => {
try {
const envService = Container.get(EnvService);
const fileContent = await fs.promises.readFile(req!.file!.path, 'utf8');
const parseContent = JSON.parse(fileContent);
const parseContent = safeJSONParse(fileContent);
const data = Array.isArray(parseContent)
? parseContent
: [parseContent];
+2
View File
@@ -265,9 +265,11 @@ export default (app: Router) => {
let { filename, path, pid } = req.body;
const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
const logPath = join(config.logPath, path, `${name}.swap`);
const scriptService = Container.get(ScriptService);
const result = await scriptService.stopScript(filePath, pid);
emptyDir(logPath);
res.send(result);
} catch (e) {
return next(e);
+17 -1
View File
@@ -174,7 +174,10 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
try {
const systemService = Container.get(SystemService);
const uniqPath = await getUniqPath(req.body.command);
const command = req.body.command
const idStr = `cat ${config.crontabFile} | grep -E "${command}" | perl -pe "s|.*ID=(.*) ${command}.*|\\1|" | head -1 | awk -F " " '{print $1}' | xargs echo -n`;
let id = await promiseExec(idStr);
const uniqPath = await getUniqPath(command, id);
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
const logPath = `${uniqPath}/${logTime}.log`;
res.setHeader('Content-type', 'application/octet-stream');
@@ -249,4 +252,17 @@ export default (app: Router) => {
}
},
);
route.get(
'/log',
async (req: Request, res: Response, next: NextFunction) => {
try {
const systemService = Container.get(SystemService);
await systemService.getSystemLog(res);
} catch (e) {
return next(e);
}
},
);
};
+2
View File
@@ -18,10 +18,12 @@ async function startServer() {
const server = app
.listen(config.port, () => {
Logger.debug(`✌️ 后端服务启动成功!`);
console.debug(`✌️ 后端服务启动成功!`);
process.send?.('ready');
})
.on('error', (err) => {
Logger.error(err);
console.error(err);
process.exit(1);
});
+3 -1
View File
@@ -1,6 +1,6 @@
import dotenv from 'dotenv';
import path from 'path';
import { createRandomString } from './util';
import { createRandomString } from './share';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
@@ -30,6 +30,7 @@ const logPath = path.join(dataPath, 'log/');
const dbPath = path.join(dataPath, 'db/');
const uploadPath = path.join(dataPath, 'upload/');
const sshdPath = path.join(dataPath, 'ssh.d/');
const systemLogPath = path.join(dataPath, 'syslog/');
const envFile = path.join(configPath, 'env.sh');
const confFile = path.join(configPath, 'config.sh');
@@ -110,4 +111,5 @@ export default {
lastVersionFile,
sqliteFile,
sshdPath,
systemLogPath,
};
+84
View File
@@ -0,0 +1,84 @@
export function createRandomString(min: number, max: number): string {
const num = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const english = [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
];
const ENGLISH = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
];
const special = ['-', '_'];
const config = num.concat(english).concat(ENGLISH).concat(special);
const arr = [];
arr.push(getOne(num));
arr.push(getOne(english));
arr.push(getOne(ENGLISH));
arr.push(getOne(special));
const len = min + Math.floor(Math.random() * (max - min + 1));
for (let i = 4; i < len; i++) {
arr.push(config[Math.floor(Math.random() * config.length)]);
}
const newArr = [];
for (let j = 0; j < len; j++) {
newArr.push(arr.splice(Math.random() * arr.length, 1)[0]);
}
function getOne(arr: any[]) {
return arr[Math.floor(Math.random() * arr.length)];
}
return newArr.join('');
}
+30 -95
View File
@@ -9,6 +9,9 @@ import { promisify } from 'util';
import { load } from 'js-yaml';
import config from './index';
import { TASK_COMMAND } from './const';
import Logger from '../loaders/logger';
export * from './share';
export function getFileContentByName(fileName: string) {
if (fs.existsSync(fileName)) {
@@ -36,91 +39,6 @@ export function getLastModifyFilePath(dir: string) {
return filePath;
}
export function createRandomString(min: number, max: number): string {
const num = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const english = [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
];
const ENGLISH = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
];
const special = ['-', '_'];
const config = num.concat(english).concat(ENGLISH).concat(special);
const arr = [];
arr.push(getOne(num));
arr.push(getOne(english));
arr.push(getOne(ENGLISH));
arr.push(getOne(special));
const len = min + Math.floor(Math.random() * (max - min + 1));
for (let i = 4; i < len; i++) {
arr.push(config[Math.floor(Math.random() * config.length)]);
}
const newArr = [];
for (let j = 0; j < len; j++) {
newArr.push(arr.splice(Math.random() * arr.length, 1)[0]);
}
function getOne(arr: any[]) {
return arr[Math.floor(Math.random() * arr.length)];
}
return newArr.join('');
}
export function getToken(req: any) {
const { authorization = '' } = req.headers;
if (authorization && authorization.split(' ')[0] === 'Bearer') {
@@ -307,22 +225,28 @@ interface IFile {
type: 'directory' | 'file';
parent: string;
mtime: number;
size?: number;
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;
export function dirSort(a: IFile, b: IFile): number {
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
} else {
return 0;
}
}
export function readDirs(
dir: string,
baseDir: string = '',
blacklist: string[] = [],
) {
): IFile[] {
const relativePath = path.relative(baseDir, dir);
const files = fs.readdirSync(dir);
const result: any = files
const result: IFile[] = files
.filter((x) => !blacklist.includes(x))
.map((file: string) => {
const subPath = path.join(dir, file);
@@ -344,6 +268,7 @@ export function readDirs(
isLeaf: true,
key,
parent: relativePath,
size: stats.size,
mtime: stats.mtime.getTime(),
};
});
@@ -522,11 +447,8 @@ export async function parseContentVersion(content: string): Promise<IVersion> {
return load(content) as IVersion;
}
export async function getUniqPath(command: string): Promise<string> {
const idStr = `cat ${config.crontabFile} | grep -E "${command}" | perl -pe "s|.*ID=(.*) ${command}.*|\\1|" | head -1 | awk -F " " '{print $1}' | xargs echo -n`;
let id = await promiseExec(idStr);
if (/^\d\d*\d$/.test(id)) {
export async function getUniqPath(command: string, id: string): Promise<string> {
if (/^\d+$/.test(id)) {
id = `_${id}`;
} else {
id = '';
@@ -558,3 +480,16 @@ export async function getUniqPath(command: string): Promise<string> {
return `${str}${id}`;
}
export function safeJSONParse(value?: string) {
if (!value) {
return {};
}
try {
return JSON.parse(value);
} catch (error) {
Logger.error('[JSON.parse失败]', error)
return {};
}
}
+20 -10
View File
@@ -7,18 +7,28 @@ import linkDeps from './deps';
import initTask from './initTask';
export default async ({ expressApp }: { expressApp: Application }) => {
await depInjectorLoader();
Logger.info('✌️ Dependency Injector loaded');
try {
depInjectorLoader();
Logger.info('✌️ Dependency Injector loaded');
console.log('✌️ Dependency Injector loaded');
await expressLoader({ app: expressApp });
Logger.info('✌️ Express loaded');
expressLoader({ app: expressApp });
Logger.info('✌️ Express loaded');
console.log('✌️ Express loaded');
await initData();
Logger.info('✌️ init data loaded');
await initData();
Logger.info('✌️ init data loaded');
console.log('✌️ init data loaded');
await linkDeps();
Logger.info('✌️ link deps loaded');
await linkDeps();
Logger.info('✌️ link deps loaded');
console.log('✌️ link deps loaded');
initTask();
Logger.info('✌️ init task loaded');
initTask();
Logger.info('✌️ init task loaded');
console.log('✌️ init task loaded');
} catch (error) {
Logger.error(`✌️ depInjectorLoader expressLoader initData linkDeps failed, ${error}`);
console.error(`✌️ depInjectorLoader expressLoader initData linkDeps failed ${error}`);
}
};
+3 -2
View File
@@ -122,9 +122,10 @@ export default async () => {
});
}
console.log('✌️ DB loaded');
Logger.info('✌️ DB loaded');
} catch (error) {
Logger.info('✌️ DB load failed');
Logger.info(error);
console.error('✌️ DB load failed');
Logger.error(error);
}
};
+6 -4
View File
@@ -5,7 +5,7 @@ import routes from '../api';
import config from '../config';
import jwt, { UnauthorizedError } from 'express-jwt';
import fs from 'fs';
import { getPlatform, getToken } from '../config/util';
import { getPlatform, getToken, safeJSONParse } from '../config/util';
import Container from 'typedi';
import OpenService from '../services/open';
import rewrite from 'express-urlrewrite';
@@ -15,6 +15,7 @@ import { EnvModel } from '../data/env';
import { errors } from 'celebrate';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { serveEnv } from '../config/serverEnv';
import Logger from './logger';
export default ({ app }: { app: Application }) => {
app.enable('trust proxy');
@@ -25,9 +26,10 @@ export default ({ app }: { app: Application }) => {
app.use(
'/api/public',
createProxyMiddleware({
target: `http://localhost:${config.publicPort}/api`,
target: `http://0.0.0.0:${config.publicPort}/api`,
changeOrigin: true,
pathRewrite: { '/api/public': '' },
logProvider: () => Logger
}),
);
@@ -36,7 +38,7 @@ export default ({ app }: { app: Application }) => {
app.use(
jwt({
secret: config.secret as string,
secret: config.secret,
algorithms: ['HS384'],
}).unless({
path: [...config.apiWhiteList, /^\/open\//],
@@ -83,7 +85,7 @@ export default ({ app }: { app: Application }) => {
const data = fs.readFileSync(config.authConfigFile, 'utf8');
if (data && headerToken) {
const { token = '', tokens = {} } = JSON.parse(data);
const { token = '', tokens = {} } = safeJSONParse(data);
if (headerToken === token || tokens[req.platform] === headerToken) {
return next();
}
+7
View File
@@ -28,6 +28,7 @@ const TaskAfterFile = path.join(configPath, 'task_after.sh');
const homedir = os.homedir();
const sshPath = path.resolve(homedir, '.ssh');
const sshdPath = path.join(dataPath, 'ssh.d');
const systemLogPath = path.join(dataPath, 'syslog');
export default async () => {
const authFileExist = await fileExist(authConfigFile);
@@ -39,6 +40,7 @@ export default async () => {
const sshDirExist = await fileExist(sshPath);
const bakDirExist = await fileExist(bakPath);
const sshdDirExist = await fileExist(sshdPath);
const systemLogDirExist = await fileExist(systemLogPath);
const tmpDirExist = await fileExist(tmpPath);
const scriptNotifyJsFileExist = await fileExist(scriptNotifyJsFile);
const scriptNotifyPyFileExist = await fileExist(scriptNotifyPyFile);
@@ -77,6 +79,10 @@ export default async () => {
fs.mkdirSync(sshdPath);
}
if (!systemLogDirExist) {
fs.mkdirSync(systemLogPath);
}
// 初始化文件
if (!authFileExist) {
fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile));
@@ -105,4 +111,5 @@ export default async () => {
dotenv.config({ path: confFile });
Logger.info('✌️ Init file down');
console.log('✌️ Init file down');
};
+27 -21
View File
@@ -1,32 +1,38 @@
import winston from 'winston';
import 'winston-daily-rotate-file';
import config from '../config';
import path from 'path';
const transports = [];
if (process.env.NODE_ENV !== 'development') {
transports.push(new winston.transports.Console());
} else {
transports.push(
new winston.transports.Console({
format: winston.format.combine(
winston.format.cli(),
winston.format.splat(),
),
}),
);
const levelMap: Record<string, string> = {
info: '🔵',
warn: '🟡',
error: '🔴',
debug: '🔶'
}
const customFormat = winston.format.combine(
winston.format.splat(),
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.align(),
winston.format.printf((i) => `[${levelMap[i.level]}${i.level}] [${[i.timestamp]}]: ${i.message}`),
);
const defaultOptions = {
format: customFormat,
datePattern: "YYYY-MM-DD",
maxSize: "20m",
maxFiles: "7d",
};
const LoggerInstance = winston.createLogger({
level: config.logs.level,
levels: winston.config.npm.levels,
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json(),
),
transports,
transports: [
new winston.transports.DailyRotateFile({
filename: path.join(config.systemLogPath, '%DATE%.log'),
...defaultOptions,
})
],
});
export default LoggerInstance;
+1
View File
@@ -29,4 +29,5 @@ export default async ({ expressApp }: { expressApp: Application }) => {
expressApp.use(Sentry.Handlers.tracingHandler());
Logger.info('✌️ Sentry loaded');
console.log('✌️ Sentry loaded');
};
+5 -1
View File
@@ -10,9 +10,11 @@ export default async ({ server }: { server: Server }) => {
process.on('SIGINT', (singal) => {
Logger.warn(`Server need close, singal ${singal}`);
console.warn(`Server need close, singal ${singal}`);
exitTime++;
if (exitTime >= 3) {
Logger.warn('Forcing server close');
console.warn('Forcing server close');
clearTimeout(timer);
process.exit(1);
}
@@ -28,11 +30,13 @@ export default async ({ server }: { server: Server }) => {
process.on('uncaughtException', (error) => {
Logger.error('Uncaught exception:', error);
console.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
Logger.error('Unhandled rejection:', reason);
Logger.error('Unhandled rejection:', reason, promise);
console.error('Unhandled rejection:', reason, promise);
process.exit(1);
});
};
+2 -2
View File
@@ -5,7 +5,7 @@ import { Container } from 'typedi';
import SockService from '../services/sock';
import config from '../config/index';
import fs from 'fs';
import { getPlatform } from '../config/util';
import { getPlatform, safeJSONParse } from '../config/util';
export default async ({ server }: { server: Server }) => {
const echo = sockJs.createServer({ prefix: '/api/ws', log: () => {} });
@@ -20,7 +20,7 @@ export default async ({ server }: { server: Server }) => {
const platform = getPlatform(conn.headers['user-agent'] || '') || 'desktop';
const headerToken = conn.url.replace(`${conn.pathname}?token=`, '');
if (data) {
const { token = '', tokens = {} } = JSON.parse(data);
const { token = '', tokens = {} } = safeJSONParse(data);
if (headerToken === token || tokens[platform] === headerToken) {
conn.write(JSON.stringify({ type: 'ping', message: 'hanhh' }));
sockService.addClient(conn);
+3 -1
View File
@@ -6,7 +6,7 @@ import { credentials } from '@grpc/grpc-js';
const app = express();
const client = new HealthClient(
`localhost:${config.cronPort}`,
`0.0.0.0:${config.cronPort}`,
credentials.createInsecure(),
);
@@ -25,9 +25,11 @@ app
await require('./loaders/db').default();
Logger.debug(`✌️ 公共服务启动成功!`);
console.debug(`✌️ 公共服务启动成功!`);
process.send?.('ready');
})
.on('error', (err) => {
Logger.error(err);
console.error(err);
process.exit(1);
});
+8 -3
View File
@@ -22,13 +22,18 @@ const addCron = (
cmdStr = `${TASK_PREFIX}${cmdStr}`;
}
Logger.info(
'[schedule][创建定时任务], 任务ID: %s, cron: %s, 执行命令: %s',
id,
schedule,
command,
);
scheduleStacks.set(
id,
nodeSchedule.scheduleJob(id, schedule, async () => {
Logger.info(
`当前时间: ${dayjs().format(
'YYYY-MM-DD HH:mm:ss',
)},运行命令: ${cmdStr}`,
`[schedule][准备运行任务] 命令: ${cmdStr}`,
);
runCron(`ID=${id} ${cmdStr}`);
}),
+1 -1
View File
@@ -10,7 +10,7 @@ import config from '../config';
class Client {
private client = new CronClient(
`localhost:${config.cronPort}`,
`0.0.0.0:${config.cronPort}`,
credentials.createInsecure(),
);
+5
View File
@@ -1,6 +1,7 @@
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
import { DeleteCronRequest, DeleteCronResponse } from '../protos/cron';
import { scheduleStacks } from './data';
import Logger from '../loaders/logger';
const delCron = (
call: ServerUnaryCall<DeleteCronRequest, DeleteCronResponse>,
@@ -8,6 +9,10 @@ const delCron = (
) => {
for (const id of call.request.ids) {
if (scheduleStacks.has(id)) {
Logger.info(
'[schedule][取消定时任务], 任务ID: %s',
id,
);
scheduleStacks.get(id)?.cancel();
scheduleStacks.delete(id);
}
+1 -1
View File
@@ -10,7 +10,7 @@ const check = async (
switch (call.request.service) {
case 'cron':
const res = await promiseExec(
`curl -s http://localhost:${config.port}/api/system`,
`curl -s http://0.0.0.0:${config.port}/api/system`,
);
if (res.includes('200')) {
+2 -1
View File
@@ -11,7 +11,7 @@ const server = new Server();
server.addService(HealthService, { check });
server.addService(CronService, { addCron, delCron });
server.bindAsync(
`localhost:${config.cronPort}`,
`0.0.0.0:${config.cronPort}`,
ServerCredentials.createInsecure(),
(err, port) => {
if (err) {
@@ -19,6 +19,7 @@ server.bindAsync(
}
server.start();
Logger.debug(`✌️ 定时服务启动成功!`);
console.debug(`✌️ 定时服务启动成功!`);
process.send?.('ready');
},
);
+22 -20
View File
@@ -5,7 +5,7 @@ import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
import { exec, execSync } from 'child_process';
import fs from 'fs';
import cron_parser from 'cron-parser';
import { getFileContentByName, fileExist, killTask } from '../config/util';
import { getFileContentByName, fileExist, killTask, getUniqPath, safeJSONParse } from '../config/util';
import { promises, existsSync } from 'fs';
import { Op, where, col as colFn, FindOptions, fn } from 'sequelize';
import path from 'path';
@@ -13,10 +13,11 @@ import { TASK_PREFIX, QL_PREFIX } from '../config/const';
import cronClient from '../schedule/client';
import taskLimit from '../shared/pLimit';
import { spawn } from 'cross-spawn';
import dayjs from 'dayjs';
@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;
@@ -161,7 +162,7 @@ export default class CronService {
case 'In':
q[Op.or] = [
{
[property]: value,
[property]: Array.isArray(value) ? value : [value],
},
property === 'status' && value.includes(2)
? { isDisabled: 1 }
@@ -172,7 +173,7 @@ export default class CronService {
q[Op.and] = [
{
[property]: {
[Op.notIn]: value,
[Op.notIn]: Array.isArray(value) ? value : [value],
},
},
property === 'status' && value.includes(2)
@@ -308,9 +309,9 @@ export default class CronService {
const searchText = params?.searchValue;
const page = Number(params?.page || '0');
const size = Number(params?.size || '0');
const viewQuery = JSON.parse(params?.queryString || '{}');
const filterQuery = JSON.parse(params?.filters || '{}');
const sorterQuery = JSON.parse(params?.sorter || '{}');
const viewQuery = safeJSONParse(params?.queryString);
const filterQuery = safeJSONParse(params?.filters);
const sorterQuery = safeJSONParse(params?.sorter);
let query: any = {};
let order = [
@@ -370,7 +371,7 @@ export default class CronService {
try {
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
this.logger.error(error);
}
}
}
@@ -391,8 +392,14 @@ export default class CronService {
}
let { id, command, log_path } = cron;
const absolutePath = path.resolve(config.logPath, `${log_path}`);
const logFileExist = log_path && (await fileExist(absolutePath));
const uniqPath = await getUniqPath(command, `${id}`);
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
if (log_path?.split('/')?.every(x => x !== uniqPath)) {
fs.mkdirSync(logDirPath, { recursive: true });
}
const logPath = `${uniqPath}/${logTime}.log`;
const absolutePath = path.resolve(config.logPath, `${logPath}`);
this.logger.silly('Running job');
this.logger.silly('ID: ' + id);
@@ -412,26 +419,22 @@ export default class CronService {
cmdStr = `${cmdStr} now`;
}
const cp = spawn(`ID=${id} ${cmdStr}`, { shell: '/bin/bash' });
const cp = spawn(`real_log_path=${logPath} ID=${id} ${cmdStr}`, { shell: '/bin/bash' });
await CrontabModel.update(
{ status: CrontabStatus.running, pid: cp.pid },
{ status: CrontabStatus.running, pid: cp.pid, log_path: logPath },
{ where: { id } },
);
cp.stderr.on('data', (data) => {
if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${data.toString()}`);
}
fs.appendFileSync(`${absolutePath}`, `${data.toString()}`);
});
cp.on('error', (err) => {
if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
}
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
});
cp.on('exit', async (code, signal) => {
this.logger.info(
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
`[panel][任务退出] 任务 ${command} 进程id: ${cp.pid}, 退出码 ${code}`,
);
});
cp.on('close', async (code) => {
@@ -530,7 +533,6 @@ export default class CronService {
}
});
this.logger.silly(crontab_string);
fs.writeFileSync(config.crontabFile, crontab_string);
execSync(`crontab ${config.crontabFile}`);
+2 -4
View File
@@ -202,11 +202,9 @@ export default class EnvService {
let value = group
.map((x) => x.value)
.join('&')
.replace(/(\\)[^n]/g, '\\\\')
.replace(/(\\$)/, '\\\\')
.replace(/"/g, '\\"')
.replace(/'/g, "'\\''")
.trim();
env_string += `export ${key}="${value}"\n`;
env_string += `export ${key}='${value}'\n`;
}
}
}
+10 -14
View File
@@ -66,9 +66,8 @@ export default class ScheduleService {
cp.stderr.on('data', async (data) => {
this.logger.info(
'[执行任务失败] %s,时间:%s, 错误信息%j',
'[panel][执行任务失败] 命令: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
data.toString(),
);
await callbacks.onError?.(data.toString());
@@ -76,9 +75,8 @@ export default class ScheduleService {
cp.on('error', async (err) => {
this.logger.error(
'[创建任务失败] %s,时间:%s, 错误信息%j',
'[panel][创建任务失败] 命令: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
err,
);
await callbacks.onError?.(JSON.stringify(err));
@@ -86,7 +84,7 @@ export default class ScheduleService {
cp.on('exit', async (code, signal) => {
this.logger.info(
`[任务退出] ${command} 进程id: ${cp.pid}退出码 ${code}`,
`[panel][任务退出] ${command} 进程id: ${cp.pid}, 退出码 ${code}`,
);
});
@@ -100,10 +98,9 @@ export default class ScheduleService {
resolve(null);
});
} catch (error) {
await this.logger.error(
'执行任务%s失败,时间:%s, 错误信息%j',
this.logger.error(
'[panel][执行任务失败] 命令: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
error,
);
await callbacks.onError?.(JSON.stringify(error));
@@ -119,7 +116,7 @@ export default class ScheduleService {
) {
const _id = this.formatId(id);
this.logger.info(
'[创建cron任务]任务ID: %scron: %s任务名: %s执行命令: %s',
'[panel][创建cron任务], 任务ID: %s, cron: %s, 任务名: %s, 执行命令: %s',
_id,
schedule,
name,
@@ -140,7 +137,7 @@ export default class ScheduleService {
async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id);
this.logger.info('[取消定时任务]任务名%s', name);
this.logger.info('[panel][取消定时任务], 任务名: %s', name);
if (this.scheduleStacks.has(_id)) {
this.scheduleStacks.get(_id)?.cancel();
this.scheduleStacks.delete(_id);
@@ -155,7 +152,7 @@ export default class ScheduleService {
) {
const _id = this.formatId(id);
this.logger.info(
'[创建interval任务]任务ID: %s任务名: %s执行命令: %s',
'[panel][创建interval任务], 任务ID: %s, 任务名: %s, 执行命令: %s',
_id,
name,
command,
@@ -167,9 +164,8 @@ export default class ScheduleService {
},
(err) => {
this.logger.error(
'执行任务%s失败,时间:%s, 错误信息%j',
'[执行任务失败] 命令: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
err,
);
},
@@ -190,7 +186,7 @@ export default class ScheduleService {
async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id);
this.logger.info('[取消interval任务]任务ID: %s任务名%s', _id, name);
this.logger.info('[取消interval任务], 任务ID: %s, 任务名: %s', _id, name);
this.intervalSchedule.removeById(_id);
}
+1 -2
View File
@@ -53,10 +53,9 @@ export default class ScriptService {
}
public async stopScript(filePath: string, pid: number) {
let str = '';
if (!pid) {
const relativePath = path.relative(config.scriptPath, filePath);
pid = await getPid(`${TASK_COMMAND} -l ${relativePath} now`);
pid = await getPid(`${TASK_COMMAND} -l ${relativePath} now`) as number;
}
try {
await killTask(pid);
+1 -1
View File
@@ -58,7 +58,7 @@ export default class SshKeyService {
private generateSingleSshConfig(alias: string, host: string, proxy?: string) {
if (host === 'github.com') {
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa\n PubkeyAcceptedAlgorithms +ssh-rsa`;
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa`;
}
const proxyStr = proxy
? ` ProxyCommand nc -v -x ${proxy} %h %p 2>/dev/null\n`
+1 -9
View File
@@ -301,17 +301,9 @@ export default class SubscriptionService {
try {
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
this.logger.error(error);
}
}
const absolutePath = await handleLogPath(doc.log_path as string);
fs.appendFileSync(
`${absolutePath}`,
`\n## 执行结束... ${dayjs().format(
'YYYY-MM-DD HH:mm:ss',
)}${LOG_END_SYMBOL}`,
);
}
await SubscriptionModel.update(
+28
View File
@@ -21,11 +21,14 @@ import {
parseContentVersion,
parseVersion,
promiseExec,
readDirs,
} from '../config/util';
import { TASK_COMMAND } from '../config/const';
import taskLimit from '../shared/pLimit';
import tar from 'tar';
import path from 'path';
import fs from 'fs';
import { sum } from 'lodash';
@Service()
export default class SystemService {
@@ -275,4 +278,29 @@ export default class SystemService {
return { code: 400, message: error.message };
}
}
public async getSystemLog(res: Response) {
const result = readDirs(config.systemLogPath, config.systemLogPath);
const logs = result.reverse().filter((x) => x.title.endsWith('.log'));
res.set({
'Content-Length': sum(logs.map((x) => x.size)),
});
(function sendFiles(res, fileNames) {
if (fileNames.length === 0) {
res.end();
return;
}
const currentLog = fileNames.shift();
if (currentLog) {
const currentFileStream = fs.createReadStream(
path.join(config.systemLogPath, currentLog.title),
);
currentFileStream.on('end', () => {
sendFiles(res, fileNames);
});
currentFileStream.pipe(res, { end: false });
}
})(res, logs);
}
}
+2 -1
View File
@@ -5,6 +5,7 @@ import {
fileExist,
getNetIp,
getPlatform,
safeJSONParse,
} from '../config/util';
import config from '../config';
import * as fs from 'fs';
@@ -327,7 +328,7 @@ export default class UserService {
private getAuthInfo() {
const content = fs.readFileSync(config.authConfigFile, 'utf8');
return JSON.parse(content || '{}');
return safeJSONParse(content);
}
private updateAuthInfo(authInfo: any, info: any) {
+13
View File
@@ -1,12 +1,22 @@
import pLimit from 'p-limit';
import os from 'os';
import { AuthDataType, AuthModel } from '../data/auth';
import Logger from '../loaders/logger';
import dayjs from 'dayjs';
class TaskLimit {
private oneLimit = pLimit(1);
private updateLogLimit = pLimit(1);
private cpuLimit = pLimit(Math.max(os.cpus().length, 4));
get cpuLimitActiveCount() {
return this.cpuLimit.activeCount;
}
get cpuLimitPendingCount() {
return this.cpuLimit.pendingCount;
}
constructor() {
this.setCustomLimit();
}
@@ -26,6 +36,9 @@ class TaskLimit {
}
public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
Logger.info(
`[schedule][任务加入队列] 运行中任务数: ${this.cpuLimitActiveCount}, 等待中任务数: ${this.cpuLimitPendingCount}`,
);
return this.cpuLimit(fn);
}
+4 -6
View File
@@ -5,29 +5,27 @@ import Logger from '../loaders/logger';
export function runCron(cmd: string): Promise<number> {
return taskLimit.runWithCpuLimit(() => {
return new Promise(async (resolve: any) => {
Logger.silly('运行命令: ' + cmd);
Logger.info(`[schedule][开始执行任务] 运行命令: ${cmd}`);
const cp = spawn(cmd, { shell: '/bin/bash' });
cp.stderr.on('data', (data) => {
Logger.info(
'[执行任务失败] %s,时间:%s, 错误信息%j',
'[schedule][执行任务失败] 命令: %s, 错误信息: %j',
cmd,
new Date().toLocaleString(),
data.toString(),
);
});
cp.on('error', (err) => {
Logger.error(
'[创建任务失败] %s,时间:%s, 错误信息%j',
'[schedule][创建任务失败] 命令: %s, 错误信息: %j',
cmd,
new Date().toLocaleString(),
err,
);
});
cp.on('close', async (code) => {
Logger.info(`[任务退出] ${cmd} 进程id: ${cp.pid} 退出退出码 ${code}`);
Logger.info(`[schedule][任务退出] ${cmd} 进程id: ${cp.pid} 退出, 退出码 ${code}`);
resolve();
});
});
+3 -2
View File
@@ -96,6 +96,7 @@
"typedi": "^0.10.0",
"uuid": "^8.3.2",
"winston": "^3.6.0",
"winston-daily-rotate-file": "^4.7.1",
"yargs": "^17.3.1"
},
"devDependencies": {
@@ -127,6 +128,8 @@
"@types/sockjs-client": "^1.5.1",
"@types/tar": "^6.1.5",
"@types/uuid": "^8.3.4",
"@uiw/codemirror-extensions-langs": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9",
"@umijs/max": "^4.0.72",
"@umijs/ssr-darkreader": "^4.9.45",
"ahooks": "^3.7.8",
@@ -134,7 +137,6 @@
"antd": "^4.24.8",
"antd-img-crop": "^4.2.3",
"axios": "^1.4.0",
"codemirror": "^5.65.2",
"compression-webpack-plugin": "9.2.0",
"concurrently": "^7.0.0",
"file-saver": "^2.0.5",
@@ -148,7 +150,6 @@
"rc-tween-one": "^3.0.6",
"rc-virtual-list": "3.5.3",
"react": "18.2.0",
"react-codemirror2": "^7.2.1",
"react-copy-to-clipboard": "^5.1.0",
"react-diff-viewer": "^3.1.1",
"react-dnd": "^14.0.2",
+649 -21
View File
@@ -124,6 +124,9 @@ dependencies:
winston:
specifier: ^3.6.0
version: 3.9.0
winston-daily-rotate-file:
specifier: ^4.7.1
version: 4.7.1(winston@3.9.0)
yargs:
specifier: ^17.3.1
version: 17.7.2
@@ -213,6 +216,12 @@ devDependencies:
'@types/uuid':
specifier: ^8.3.4
version: 8.3.4
'@uiw/codemirror-extensions-langs':
specifier: ^4.21.9
version: 4.21.9(@codemirror/autocomplete@6.9.0)(@codemirror/language-data@6.3.1)(@codemirror/language@6.9.0)(@codemirror/legacy-modes@6.3.3)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/javascript@1.4.5)(@lezer/lr@1.3.10)
'@uiw/react-codemirror':
specifier: ^4.21.9
version: 4.21.9(@babel/runtime@7.22.3)(@codemirror/autocomplete@6.9.0)(@codemirror/language@6.9.0)(@codemirror/lint@6.4.0)(@codemirror/search@6.5.1)(@codemirror/state@6.2.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.16.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0)
'@umijs/max':
specifier: ^4.0.72
version: 4.0.72(@types/node@17.0.45)(@types/react-dom@18.2.4)(@types/react@18.2.8)(prettier@2.8.8)(react-dom@18.2.0)(react@18.2.0)(sockjs-client@1.6.1)(typescript@4.8.4)(webpack@5.85.1)
@@ -234,9 +243,6 @@ devDependencies:
axios:
specifier: ^1.4.0
version: 1.4.0
codemirror:
specifier: ^5.65.2
version: 5.65.13
compression-webpack-plugin:
specifier: 9.2.0
version: 9.2.0(webpack@5.85.1)
@@ -276,9 +282,6 @@ devDependencies:
react:
specifier: 18.2.0
version: 18.2.0
react-codemirror2:
specifier: ^7.2.1
version: 7.2.1(codemirror@5.65.13)(react@18.2.0)
react-copy-to-clipboard:
specifier: ^5.1.0
version: 5.1.0(react@18.2.0)
@@ -2762,6 +2765,298 @@ packages:
tinycolor2: 1.6.0
dev: true
/@codemirror/autocomplete@6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4):
resolution: {integrity: sha512-Fbwm0V/Wn3BkEJZRhr0hi5BhCo5a7eBL6LYaliPjOSwCyfOpnjXY59HruSxOUNV+1OYer0Tgx1zRNQttjXyDog==}
peerDependencies:
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
dependencies:
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
dev: true
/@codemirror/commands@6.2.4:
resolution: {integrity: sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==}
dependencies:
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
dev: true
/@codemirror/lang-angular@0.1.2:
resolution: {integrity: sha512-Nq7lmx9SU+JyoaRcs6SaJs7uAmW2W06HpgJVQYeZptVGNWDzDvzhjwVb/ZuG1rwTlOocY4Y9GwNOBuKCeJbKtw==}
dependencies:
'@codemirror/lang-html': 6.4.5
'@codemirror/lang-javascript': 6.1.9
'@codemirror/language': 6.9.0
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@codemirror/lang-cpp@6.0.2:
resolution: {integrity: sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==}
dependencies:
'@codemirror/language': 6.9.0
'@lezer/cpp': 1.1.1
dev: true
/@codemirror/lang-css@6.2.1(@codemirror/view@6.16.0):
resolution: {integrity: sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@lezer/common': 1.0.4
'@lezer/css': 1.1.3
transitivePeerDependencies:
- '@codemirror/view'
dev: true
/@codemirror/lang-html@6.4.5:
resolution: {integrity: sha512-dUCSxkIw2G+chaUfw3Gfu5kkN83vJQN8gfQDp9iEHsIZluMJA0YJveT12zg/28BJx+uPsbQ6VimKCgx3oJrZxA==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/lang-css': 6.2.1(@codemirror/view@6.16.0)
'@codemirror/lang-javascript': 6.1.9
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
'@lezer/css': 1.1.3
'@lezer/html': 1.3.6
dev: true
/@codemirror/lang-java@6.0.1:
resolution: {integrity: sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==}
dependencies:
'@codemirror/language': 6.9.0
'@lezer/java': 1.0.4
dev: true
/@codemirror/lang-javascript@6.1.9:
resolution: {integrity: sha512-z3jdkcqOEBT2txn2a87A0jSy6Te3679wg/U8QzMeftFt+4KA6QooMwfdFzJiuC3L6fXKfTXZcDocoaxMYfGz0w==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/language': 6.9.0
'@codemirror/lint': 6.4.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
'@lezer/javascript': 1.4.5
dev: true
/@codemirror/lang-json@6.0.1:
resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
dependencies:
'@codemirror/language': 6.9.0
'@lezer/json': 1.0.1
dev: true
/@codemirror/lang-less@6.0.1(@codemirror/view@6.16.0):
resolution: {integrity: sha512-ABcsKBjLbyPZwPR5gePpc8jEKCQrFF4pby2WlMVdmJOOr7OWwwyz8DZonPx/cKDE00hfoSLc8F7yAcn/d6+rTQ==}
dependencies:
'@codemirror/lang-css': 6.2.1(@codemirror/view@6.16.0)
'@codemirror/language': 6.9.0
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
transitivePeerDependencies:
- '@codemirror/view'
dev: true
/@codemirror/lang-lezer@6.0.1:
resolution: {integrity: sha512-WHwjI7OqKFBEfkunohweqA5B/jIlxaZso6Nl3weVckz8EafYbPZldQEKSDb4QQ9H9BUkle4PVELP4sftKoA0uQ==}
dependencies:
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@lezer/common': 1.0.4
'@lezer/lezer': 1.1.2
dev: true
/@codemirror/lang-markdown@6.2.0:
resolution: {integrity: sha512-deKegEQVzfBAcLPqsJEa+IxotqPVwWZi90UOEvQbfa01NTAw8jNinrykuYPTULGUj+gha0ZG2HBsn4s5d64Qrg==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/lang-html': 6.4.5
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
'@lezer/markdown': 1.1.0
dev: true
/@codemirror/lang-php@6.0.1:
resolution: {integrity: sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==}
dependencies:
'@codemirror/lang-html': 6.4.5
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@lezer/common': 1.0.4
'@lezer/php': 1.0.1
dev: true
/@codemirror/lang-python@6.1.3(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4):
resolution: {integrity: sha512-S9w2Jl74hFlD5nqtUMIaXAq9t5WlM0acCkyuQWUUSvZclk1sV+UfnpFiZzuZSG+hfEaOmxKR5UxY/Uxswn7EhQ==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/language': 6.9.0
'@lezer/python': 1.1.8
transitivePeerDependencies:
- '@codemirror/state'
- '@codemirror/view'
- '@lezer/common'
dev: true
/@codemirror/lang-rust@6.0.1:
resolution: {integrity: sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==}
dependencies:
'@codemirror/language': 6.9.0
'@lezer/rust': 1.0.1
dev: true
/@codemirror/lang-sass@6.0.2(@codemirror/view@6.16.0):
resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==}
dependencies:
'@codemirror/lang-css': 6.2.1(@codemirror/view@6.16.0)
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@lezer/common': 1.0.4
'@lezer/sass': 1.0.3
transitivePeerDependencies:
- '@codemirror/view'
dev: true
/@codemirror/lang-sql@6.5.4(@codemirror/view@6.16.0)(@lezer/common@1.0.4):
resolution: {integrity: sha512-5Gq7fYtT/5HbNyIG7a8vYaqOYQU3JbgtBe3+derkrFUXRVcjkf8WVgz++PIbMFAQsOFMDdDR+uiNM8ZRRuXH+w==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
transitivePeerDependencies:
- '@codemirror/view'
- '@lezer/common'
dev: true
/@codemirror/lang-vue@0.1.2:
resolution: {integrity: sha512-D4YrefiRBAr+CfEIM4S3yvGSbYW+N69mttIfGMEf7diHpRbmygDxS+R/5xSqjgtkY6VO6qmUrre1GkRcWeZa9A==}
dependencies:
'@codemirror/lang-html': 6.4.5
'@codemirror/lang-javascript': 6.1.9
'@codemirror/language': 6.9.0
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@codemirror/lang-wast@6.0.1:
resolution: {integrity: sha512-sQLsqhRjl2MWG3rxZysX+2XAyed48KhLBHLgq9xcKxIJu3npH/G+BIXW5NM5mHeDUjG0jcGh9BcjP0NfMStuzA==}
dependencies:
'@codemirror/language': 6.9.0
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@codemirror/lang-xml@6.0.2(@codemirror/view@6.16.0):
resolution: {integrity: sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@lezer/common': 1.0.4
'@lezer/xml': 1.0.2
transitivePeerDependencies:
- '@codemirror/view'
dev: true
/@codemirror/language-data@6.3.1(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4):
resolution: {integrity: sha512-p6jhJmvhGe1TG1EGNhwH7nFWWFSTJ8NDKnB2fVx5g3t+PpO0+63R7GJNxjS0TmmH3cdMxZbzejsik+rlEh1EyQ==}
dependencies:
'@codemirror/lang-angular': 0.1.2
'@codemirror/lang-cpp': 6.0.2
'@codemirror/lang-css': 6.2.1(@codemirror/view@6.16.0)
'@codemirror/lang-html': 6.4.5
'@codemirror/lang-java': 6.0.1
'@codemirror/lang-javascript': 6.1.9
'@codemirror/lang-json': 6.0.1
'@codemirror/lang-less': 6.0.1(@codemirror/view@6.16.0)
'@codemirror/lang-markdown': 6.2.0
'@codemirror/lang-php': 6.0.1
'@codemirror/lang-python': 6.1.3(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/lang-rust': 6.0.1
'@codemirror/lang-sass': 6.0.2(@codemirror/view@6.16.0)
'@codemirror/lang-sql': 6.5.4(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/lang-vue': 0.1.2
'@codemirror/lang-wast': 6.0.1
'@codemirror/lang-xml': 6.0.2(@codemirror/view@6.16.0)
'@codemirror/language': 6.9.0
'@codemirror/legacy-modes': 6.3.3
transitivePeerDependencies:
- '@codemirror/state'
- '@codemirror/view'
- '@lezer/common'
dev: true
/@codemirror/language@6.9.0:
resolution: {integrity: sha512-nFu311/0ne/qGuGCL3oKuktBgzVOaxCHZPZv1tLSZkNjPYxxvkjSbzno3MlErG2tgw1Yw1yF8BxMCegeMXqpiw==}
dependencies:
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
style-mod: 4.0.3
dev: true
/@codemirror/legacy-modes@6.3.3:
resolution: {integrity: sha512-X0Z48odJ0KIoh/HY8Ltz75/4tDYc9msQf1E/2trlxFaFFhgjpVHjZ/BCXe1Lk7s4Gd67LL/CeEEHNI+xHOiESg==}
dependencies:
'@codemirror/language': 6.9.0
dev: true
/@codemirror/lint@6.4.0:
resolution: {integrity: sha512-6VZ44Ysh/Zn07xrGkdtNfmHCbGSHZzFBdzWi0pbd7chAQ/iUcpLGX99NYRZTa7Ugqg4kEHCqiHhcZnH0gLIgSg==}
dependencies:
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
crelt: 1.0.6
dev: true
/@codemirror/search@6.5.1:
resolution: {integrity: sha512-4jupk4JwkeVbrN2pStY74q6OJEYqwosB4koA66nyLeVedadtX9MHI38j2vbYmnfDGurDApP3OZO46MrWalcjiQ==}
dependencies:
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
crelt: 1.0.6
dev: true
/@codemirror/state@6.2.1:
resolution: {integrity: sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==}
dev: true
/@codemirror/theme-one-dark@6.1.2:
resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==}
dependencies:
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/highlight': 1.1.6
dev: true
/@codemirror/view@6.16.0:
resolution: {integrity: sha512-1Z2HkvkC3KR/oEZVuW9Ivmp8TWLzGEd8T8TA04TTwPvqogfkHBdYSlflytDOqmkUxM2d1ywTg7X2dU5mC+SXvg==}
dependencies:
'@codemirror/state': 6.2.1
style-mod: 4.0.3
w3c-keyname: 2.2.8
dev: true
/@colors/colors@1.5.0:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
@@ -3562,6 +3857,114 @@ packages:
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
dev: true
/@lezer/common@1.0.4:
resolution: {integrity: sha512-lZHlk8p67x4aIDtJl6UQrXSOP6oi7dQR3W/geFVrENdA1JDaAJWldnVqVjPMJupbTKbzDfFcePfKttqVidS/dg==}
dev: true
/@lezer/cpp@1.1.1:
resolution: {integrity: sha512-eS1M3L3U2mDowoFVPG7tEp01SWu9/68Nx3HEBgLJVn3N9ku7g5S7WdFv0jzmcTipAyONYfZJ+7x4WRkfdB2Ung==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/css@1.1.3:
resolution: {integrity: sha512-SjSM4pkQnQdJDVc80LYzEaMiNy9txsFbI7HsMgeVF28NdLaAdHNtQ+kB/QqDUzRBV/75NTXjJ/R5IdC8QQGxMg==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/highlight@1.1.6:
resolution: {integrity: sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==}
dependencies:
'@lezer/common': 1.0.4
dev: true
/@lezer/html@1.3.6:
resolution: {integrity: sha512-Kk9HJARZTc0bAnMQUqbtuhFVsB4AnteR2BFUWfZV7L/x1H0aAKz6YabrfJ2gk/BEgjh9L3hg5O4y2IDZRBdzuQ==}
dependencies:
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/java@1.0.4:
resolution: {integrity: sha512-POc53LHf2AuNeRXjqZbXNu88GKj0KZTjjSx0L7tYeXlrEHF+3NAQx+dEwKVuCbkl0ZMtpRy2VsDYOV7KKV0oyg==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/javascript@1.4.5:
resolution: {integrity: sha512-FmBUHz8K1V22DgjTd6SrIG9owbzOYZ1t3rY6vGEmw+e2RVBd7sqjM8uXEVRFmfxKFn1Mx2ABJehHjrN3G2ZpmA==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/json@1.0.1:
resolution: {integrity: sha512-nkVC27qiEZEjySbi6gQRuMwa2sDu2PtfjSgz0A4QF81QyRGm3kb2YRzLcOPcTEtmcwvrX/cej7mlhbwViA4WJw==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/lezer@1.1.2:
resolution: {integrity: sha512-O8yw3CxPhzYHB1hvwbdozjnAslhhR8A5BH7vfEMof0xk3p+/DFDfZkA9Tde6J+88WgtwaHy4Sy6ThZSkaI0Evw==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/lr@1.3.10:
resolution: {integrity: sha512-BZfVvf7Re5BIwJHlZXbJn9L8lus5EonxQghyn+ih8Wl36XMFBPTXC0KM0IdUtj9w/diPHsKlXVgL+AlX2jYJ0Q==}
dependencies:
'@lezer/common': 1.0.4
dev: true
/@lezer/markdown@1.1.0:
resolution: {integrity: sha512-JYOI6Lkqbl83semCANkO3CKbKc0pONwinyagBufWBm+k4yhIcqfCF8B8fpEpvJLmIy7CAfwiq7dQ/PzUZA340g==}
dependencies:
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
dev: true
/@lezer/php@1.0.1:
resolution: {integrity: sha512-aqdCQJOXJ66De22vzdwnuC502hIaG9EnPK2rSi+ebXyUd+j7GAX1mRjWZOVOmf3GST1YUfUCu6WXDiEgDGOVwA==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/python@1.1.8:
resolution: {integrity: sha512-1T/XsmeF57ijrjpC0Zmrf9YeO5mn2zC1XeSNrOnc0KB+6PgxJ5m7kWKt0CnwyS74oHQXbJxUUL+QDQJR26c1Gw==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/rust@1.0.1:
resolution: {integrity: sha512-j+ToFKM6Wpglv3OQ4ebHYdYIMT2dh0ziCCV0rTf47AWiHOVhR0WjaKrBq+yuvDQNEhr5sxPxVI7+naJIgpqcsQ==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/sass@1.0.3:
resolution: {integrity: sha512-n4l2nVOB7gWiGU/Cg2IVxpt2Ic9Hgfgy/7gk+p/XJibAsPXs0lSbsfGwQgwsAw9B/euYo3oS6lEFr9WytoqcZg==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@lezer/xml@1.0.2:
resolution: {integrity: sha512-dlngsWceOtQBMuBPw5wtHpaxdPJ71aVntqjbpGkFtWsp4WtQmCnuTjQGocviymydN6M18fhj6UQX3oiEtSuY7w==}
dependencies:
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@loadable/component@5.15.2(react@18.1.0):
resolution: {integrity: sha512-ryFAZOX5P2vFkUdzaAtTG88IGnr9qxSdvLRvJySXcUA4B4xVWurUNADu3AnKPksxOZajljqTrDEDcYjeL4lvLw==}
engines: {node: '>=8'}
@@ -3644,6 +4047,19 @@ packages:
state-local: 1.0.7
dev: true
/@nextjournal/lang-clojure@1.0.0:
resolution: {integrity: sha512-gOCV71XrYD0DhwGoPMWZmZ0r92/lIHsqQu9QWdpZYYBwiChNwMO4sbVMP7eTuAqffFB2BTtCSC+1skSH9d3bNg==}
dependencies:
'@codemirror/language': 6.9.0
'@nextjournal/lezer-clojure': 1.0.0
dev: true
/@nextjournal/lezer-clojure@1.0.0:
resolution: {integrity: sha512-VZyuGu4zw5mkTOwQBTaGVNWmsOZAPw5ZRxu1/Knk/Xfs7EDBIogwIs5UXTYkuECX5ZQB8eOB+wKA2pc7VyqaZQ==}
dependencies:
'@lezer/lr': 1.3.10
dev: true
/@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3:
resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==}
requiresBuild: true
@@ -3878,6 +4294,82 @@ packages:
react: 18.2.0
dev: true
/@replit/codemirror-lang-csharp@6.1.0(@codemirror/autocomplete@6.9.0)(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/lr@1.3.10):
resolution: {integrity: sha512-Dtyk9WVrdPPgkgTp8MUX9HyXd87O7UZnFrE647gjHUZY8p0UN+z0m6dPfk6rJMsTTvMcl7YbDUykxfeqB6EQOQ==}
peerDependencies:
'@codemirror/autocomplete': ^6.0.0
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
'@lezer/highlight': ^1.0.0
'@lezer/lr': ^1.0.0
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.9.0)(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/lr@1.3.10):
resolution: {integrity: sha512-lvzjoYn9nfJzBD5qdm3Ut6G3+Or2wEacYIDJ49h9+19WSChVnxv4ojf+rNmQ78ncuxIt/bfbMvDLMeMP0xze6g==}
peerDependencies:
'@codemirror/autocomplete': ^6.0.0
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
'@lezer/highlight': ^1.0.0
'@lezer/lr': ^1.0.0
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/@replit/codemirror-lang-solidity@6.0.1(@codemirror/language@6.9.0):
resolution: {integrity: sha512-kDnak0xZelGmvzJwKTpMTl6gYSfFq9hnxrkbLaMV0CARq/MFvDQJmcmYon/k8uZqXy6DfzewKDV8tx9kY2WUZg==}
peerDependencies:
'@codemirror/language': ^6.0.0
dependencies:
'@codemirror/language': 6.9.0
dev: true
/@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.9.0)(@codemirror/lang-css@6.2.1)(@codemirror/lang-html@6.4.5)(@codemirror/lang-javascript@6.1.9)(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/javascript@1.4.5)(@lezer/lr@1.3.10):
resolution: {integrity: sha512-U2OqqgMM6jKelL0GNWbAmqlu1S078zZNoBqlJBW+retTc5M4Mha6/Y2cf4SVg6ddgloJvmcSpt4hHrVoM4ePRA==}
peerDependencies:
'@codemirror/autocomplete': ^6.0.0
'@codemirror/lang-css': ^6.0.1
'@codemirror/lang-html': ^6.2.0
'@codemirror/lang-javascript': ^6.1.1
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
'@lezer/highlight': ^1.0.0
'@lezer/javascript': ^1.2.0
'@lezer/lr': ^1.0.0
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/lang-css': 6.2.1(@codemirror/view@6.16.0)
'@codemirror/lang-html': 6.4.5
'@codemirror/lang-javascript': 6.1.9
'@codemirror/language': 6.9.0
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
'@lezer/common': 1.0.4
'@lezer/highlight': 1.1.6
'@lezer/javascript': 1.4.5
'@lezer/lr': 1.3.10
dev: true
/@sentry-internal/tracing@7.54.0:
resolution: {integrity: sha512-JsyhZ0wWZ+VqbHJg+azqRGdYJDkcI5R9+pnkO6SzbzxrRewqMAIwzkpPee3oI7vG99uhMEkOkMjHu0nQGwkOQw==}
engines: {node: '>=8'}
@@ -4828,6 +5320,101 @@ packages:
eslint-visitor-keys: 3.4.1
dev: true
/@uiw/codemirror-extensions-basic-setup@4.21.9(@codemirror/autocomplete@6.9.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.9.0)(@codemirror/lint@6.4.0)(@codemirror/search@6.5.1)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0):
resolution: {integrity: sha512-TQT6aF8brxZpFnk/K4fm/K/9k9eF3PMav/KKjHlYrGUT8BTNk/qL+ximLtIzvTUhmBFchjM1lrqSJdvpVom7/w==}
peerDependencies:
'@codemirror/autocomplete': '>=6.0.0'
'@codemirror/commands': '>=6.0.0'
'@codemirror/language': '>=6.0.0'
'@codemirror/lint': '>=6.0.0'
'@codemirror/search': '>=6.0.0'
'@codemirror/state': '>=6.0.0'
'@codemirror/view': '>=6.0.0'
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/commands': 6.2.4
'@codemirror/language': 6.9.0
'@codemirror/lint': 6.4.0
'@codemirror/search': 6.5.1
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
dev: true
/@uiw/codemirror-extensions-langs@4.21.9(@codemirror/autocomplete@6.9.0)(@codemirror/language-data@6.3.1)(@codemirror/language@6.9.0)(@codemirror/legacy-modes@6.3.3)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/javascript@1.4.5)(@lezer/lr@1.3.10):
resolution: {integrity: sha512-s1VT1rss0iyvrtRl7BZtC5H7U5uQtCKTaD8wxjQrgZz5un9wHVvy9twU97aJGQR0FwbKWqK8/1iiICRJTRCoZA==}
peerDependencies:
'@codemirror/language-data': '>=6.0.0'
'@codemirror/legacy-modes': '>=6.0.0'
dependencies:
'@codemirror/lang-angular': 0.1.2
'@codemirror/lang-cpp': 6.0.2
'@codemirror/lang-css': 6.2.1(@codemirror/view@6.16.0)
'@codemirror/lang-html': 6.4.5
'@codemirror/lang-java': 6.0.1
'@codemirror/lang-javascript': 6.1.9
'@codemirror/lang-json': 6.0.1
'@codemirror/lang-less': 6.0.1(@codemirror/view@6.16.0)
'@codemirror/lang-lezer': 6.0.1
'@codemirror/lang-markdown': 6.2.0
'@codemirror/lang-php': 6.0.1
'@codemirror/lang-python': 6.1.3(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/lang-rust': 6.0.1
'@codemirror/lang-sass': 6.0.2(@codemirror/view@6.16.0)
'@codemirror/lang-sql': 6.5.4(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/lang-vue': 0.1.2
'@codemirror/lang-wast': 6.0.1
'@codemirror/lang-xml': 6.0.2(@codemirror/view@6.16.0)
'@codemirror/language-data': 6.3.1(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/legacy-modes': 6.3.3
'@nextjournal/lang-clojure': 1.0.0
'@replit/codemirror-lang-csharp': 6.1.0(@codemirror/autocomplete@6.9.0)(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/lr@1.3.10)
'@replit/codemirror-lang-nix': 6.0.1(@codemirror/autocomplete@6.9.0)(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/lr@1.3.10)
'@replit/codemirror-lang-solidity': 6.0.1(@codemirror/language@6.9.0)
'@replit/codemirror-lang-svelte': 6.0.0(@codemirror/autocomplete@6.9.0)(@codemirror/lang-css@6.2.1)(@codemirror/lang-html@6.4.5)(@codemirror/lang-javascript@6.1.9)(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)(@lezer/highlight@1.1.6)(@lezer/javascript@1.4.5)(@lezer/lr@1.3.10)
codemirror-lang-mermaid: 0.2.2
transitivePeerDependencies:
- '@codemirror/autocomplete'
- '@codemirror/language'
- '@codemirror/state'
- '@codemirror/view'
- '@lezer/common'
- '@lezer/highlight'
- '@lezer/javascript'
- '@lezer/lr'
dev: true
/@uiw/react-codemirror@4.21.9(@babel/runtime@7.22.3)(@codemirror/autocomplete@6.9.0)(@codemirror/language@6.9.0)(@codemirror/lint@6.4.0)(@codemirror/search@6.5.1)(@codemirror/state@6.2.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.16.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-aeLegPz2iCvqJjhzXp2WUMqpMZDqxsTnF3rX9kGRlfY6vQLsrjoctj0cQ29uxEtFYJChOVjtCOtnQUlyIuNAHQ==}
peerDependencies:
'@babel/runtime': '>=7.11.0'
'@codemirror/state': '>=6.0.0'
'@codemirror/theme-one-dark': '>=6.0.0'
'@codemirror/view': '>=6.0.0'
codemirror: '>=6.0.0'
react: '>=16.8.0 || 18'
react-dom: '>=16.8.0 || 18'
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
dependencies:
'@babel/runtime': 7.22.3
'@codemirror/commands': 6.2.4
'@codemirror/state': 6.2.1
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.16.0
'@uiw/codemirror-extensions-basic-setup': 4.21.9(@codemirror/autocomplete@6.9.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.9.0)(@codemirror/lint@6.4.0)(@codemirror/search@6.5.1)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)
codemirror: 6.0.1(@lezer/common@1.0.4)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@codemirror/autocomplete'
- '@codemirror/language'
- '@codemirror/lint'
- '@codemirror/search'
dev: true
/@umijs/ast@4.0.72:
resolution: {integrity: sha512-WatRvU09vsx4Hlu5hemPA7a+QK4pJvzmQz/9LxN/KVgn+wZXi717qHFLu5eoV6XO7HlFZaEBGq2aHpDj0ngA8w==}
dependencies:
@@ -6657,8 +7244,26 @@ packages:
mimic-response: 1.0.1
dev: false
/codemirror@5.65.13:
resolution: {integrity: sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==}
/codemirror-lang-mermaid@0.2.2:
resolution: {integrity: sha512-AqSzkQgfWsjBbifio3dy/zDj6WXEw4g52Mq6bltIWLMWryWWRMpFwjQSlHtCGOol1FENYObUF5KI4ofiv8bjXA==}
dependencies:
'@codemirror/language': 6.9.0
'@lezer/highlight': 1.1.6
'@lezer/lr': 1.3.10
dev: true
/codemirror@6.0.1(@lezer/common@1.0.4):
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
dependencies:
'@codemirror/autocomplete': 6.9.0(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.4)
'@codemirror/commands': 6.2.4
'@codemirror/language': 6.9.0
'@codemirror/lint': 6.4.0
'@codemirror/search': 6.5.1
'@codemirror/state': 6.2.1
'@codemirror/view': 6.16.0
transitivePeerDependencies:
- '@lezer/common'
dev: true
/color-convert@1.9.3:
@@ -6970,6 +7575,10 @@ packages:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
dev: true
/cron-parser@4.8.1:
resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==}
engines: {node: '>=12.0.0'}
@@ -8273,6 +8882,12 @@ packages:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: true
/file-stream-rotator@0.6.1:
resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==}
dependencies:
moment: 2.29.4
dev: false
/file-uri-to-path@2.0.0:
resolution: {integrity: sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==}
engines: {node: '>= 6'}
@@ -10787,6 +11402,11 @@ packages:
engines: {node: '>= 0.10.0'}
dev: true
/object-hash@2.2.0:
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
engines: {node: '>= 6'}
dev: false
/object-inspect@1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
@@ -12903,19 +13523,6 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: true
/react-codemirror2@7.2.1(codemirror@5.65.13)(react@18.2.0):
resolution: {integrity: sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==}
peerDependencies:
codemirror: 5.x
react: '>=15.5 <=16.x || 18'
peerDependenciesMeta:
react:
optional: true
dependencies:
codemirror: 5.65.13
react: 18.2.0
dev: true
/react-copy-to-clipboard@5.1.0(react@18.2.0):
resolution: {integrity: sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==}
peerDependencies:
@@ -14307,6 +14914,10 @@ packages:
engines: {node: '>=8'}
dev: true
/style-mod@4.0.3:
resolution: {integrity: sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==}
dev: true
/style-search@0.1.0:
resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
dev: true
@@ -15308,6 +15919,10 @@ packages:
acorn-walk: 8.2.0
dev: true
/w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
dev: true
/walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
dependencies:
@@ -15489,6 +16104,19 @@ packages:
semver: 5.7.1
dev: true
/winston-daily-rotate-file@4.7.1(winston@3.9.0):
resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==}
engines: {node: '>=8'}
peerDependencies:
winston: ^3
dependencies:
file-stream-rotator: 0.6.1
object-hash: 2.2.0
triple-beam: 1.3.0
winston: 3.9.0
winston-transport: 4.5.0
dev: false
/winston-transport@4.5.0:
resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==}
engines: {node: '>= 6.4.0'}
+1 -1
View File
@@ -456,7 +456,7 @@ class WeCom:
return respone["errmsg"]
def send_mpnews(self, title, message, media_id, touser="@all"):
send_url = f"https://{self.HOST}/cgi-bin/message/send?access_token={self.get_access_token()}"
send_url = f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}"
send_values = {
"touser": touser,
"msgtype": "mpnews",
+1 -1
View File
@@ -36,7 +36,7 @@ add_cron_api() {
local sub_id="$4"
fi
if [[ ! $sub_id ]];then
if [[ ! $sub_id ]]; then
sub_id="null"
fi
+19 -11
View File
@@ -86,6 +86,11 @@ check_server() {
fi
}
env_str_to_array() {
local IFS="&"
read -ra array <<<"${!env_param}"
}
## 正常运行单个脚本,$1:传入参数
run_normal() {
local file_param=$1
@@ -113,8 +118,7 @@ run_concurrent() {
exit 1
fi
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
env_str_to_array
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[@]}))
@@ -125,11 +129,13 @@ run_concurrent() {
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
local cookieStr=$(
IFS="&"
echo "${array_run[*]}"
)
[[ ! -z $cookieStr ]] && export "${env_param}=${cookieStr}"
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
env_str_to_array
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%3N")
cd $dir_scripts
@@ -139,7 +145,7 @@ run_concurrent() {
file_param=${file_param/$relative_path\//}
fi
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
export "${env_param}=${array[i]}"
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval $timeoutCmd $which_program $file_param &>$single_log_path &
done
@@ -161,8 +167,7 @@ run_designated() {
exit 1
fi
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
env_str_to_array
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[@]}))
@@ -173,8 +178,11 @@ run_designated() {
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
local cookieStr=$(
IFS="&"
echo "${array_run[*]}"
)
[[ ! -z $cookieStr ]] && export "${env_param}=${cookieStr}"
cd $dir_scripts
local relative_path="${file_param%/*}"
+1 -1
View File
@@ -296,7 +296,7 @@ git_clone_scripts() {
set_proxy "$proxy"
git clone --depth=1 $part_cmd $url $dir
git clone --depth=1 $part_cmd $url $dir 2>&1
exit_status=$?
unset_proxy
+3 -3
View File
@@ -31,7 +31,7 @@ output_list_add_drop() {
local list=$1
local type=$2
if [[ -s $list ]]; then
echo -e "检测到有${type}的定时任务\n"
echo -e "检测到有${type}的定时任务:"
cat $list
echo
fi
@@ -134,10 +134,10 @@ update_repo() {
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}"
if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新${repo_path}成功...\n"
echo -e "\n拉取 ${uniq_path} 成功...\n"
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
else
echo -e "\n更新${repo_path}失败,请检查网络...\n"
echo -e "\n拉取 ${uniq_path} 失败,请检查网络...\n"
fi
}
+3
View File
@@ -1,4 +1,5 @@
const baseUrl = window.__ENV__QlBaseUrl || '/';
import { setLocale } from '@umijs/max';
import intl from 'react-intl-universal';
export function rootContainer(container: any) {
@@ -9,6 +10,7 @@ export function rootContainer(container: any) {
let currentLocale = intl.determineLocale({
urlLocaleKey: 'lang',
cookieLocaleKey: 'lang',
localStorageLocaleKey: 'lang',
}).slice(0, 2);
if (!currentLocale || !Object.keys(locales).includes(currentLocale)) {
@@ -16,6 +18,7 @@ export function rootContainer(container: any) {
}
intl.init({ currentLocale, locales });
setLocale(currentLocale === 'zh' ? 'zh-CN' : 'en-US');
return container;
}
+31 -9
View File
@@ -1,6 +1,5 @@
@import '~antd/es/style/themes/default.less';
@import '~@/styles/variable.less';
@import '~codemirror/lib/codemirror.css';
@font-face {
font-family: 'Source Code Pro';
@@ -32,9 +31,24 @@ body {
}
.log-modal {
.ant-modal {
padding-bottom: 0 !important;
width: 580px !important;
&.ant-modal {
max-width: 1000px !important;
width: 80vw !important;
}
.ant-modal-body {
overflow-y: auto;
min-height: 300px;
max-height: calc(80vh - 110px);
max-height: calc(80vh - var(--vh-offset, 110px));
padding: 0;
display: flex;
.log-container {
width: 100%;
padding: 24px;
overflow-y: auto;
}
}
pre {
@@ -87,10 +101,12 @@ body {
}
.ant-tooltip {
max-width: 500px !important;
max-width: 300px !important;
.ant-tooltip-inner {
word-break: break-all !important;
max-height: 300px !important;
overflow-y: auto !important;
}
}
@@ -182,8 +198,14 @@ body {
}
}
.log-modal {
&.ant-modal {
width: calc(100vw - 16px) !important;
}
}
.ant-tooltip {
max-width: 250px !important;
max-width: 300px !important;
}
}
@@ -368,9 +390,9 @@ select:-webkit-autofill:focus {
}
pre {
word-break: break-all !important;
white-space: break-spaces !important;
padding: 0 !important;
word-break: break-all;
white-space: break-spaces;
padding: 0;
}
.virtuallist {
-3
View File
@@ -32,9 +32,6 @@ import {
import SockJS from 'sockjs-client';
import * as Sentry from '@sentry/react';
import { init } from '../utils/init';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/python/python';
import 'codemirror/mode/shell/shell';
export interface SharedContext {
headerStyle: React.CSSProperties;
+4 -1
View File
@@ -448,5 +448,8 @@
"编辑订阅": "Edit Subscription",
"Subscription表达式格式有误": "Incorrect Subscription Expression Format",
"一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "The 'Group Code' for One-to-Many Push (Below One-to-Many Push->Your Group (if not, create one)->Group Code, if you are the creator of the group, you also need to click 'View QR Code' to scan and bind, otherwise you cannot receive group messages)",
"登录已过期,请重新登录": "Login session has expired, please log in again"
"登录已过期,请重新登录": "Login session has expired, please log in again",
"系统日志": "System Logs",
"主题": "Theme",
"语言": "Language"
}
+12 -9
View File
@@ -337,7 +337,7 @@
"飞书机器人": "飞书机器人",
"自定义通知": "自定义通知",
"已关闭": "已关闭",
"gotify的url地址,例如 https://push.example.de:8080": "gotify的url地址,例如 https://push.example.de:8080",
"gotify的url地址例如 https://push.example.de:8080": "gotify的url地址例如 https://push.example.de:8080",
"gotify的消息应用token码": "gotify的消息应用token码",
"推送消息的优先级": "推送消息的优先级",
"chat的url地址": "chat的url地址",
@@ -349,21 +349,21 @@
"PushDeer的Keyhttps://github.com/easychen/pushdeer": "PushDeer的Keyhttps://github.com/easychen/pushdeer",
"PushDeer的自架API endpoint,默认是 https://api2.pushdeer.com/message/push": "PushDeer的自架API endpoint,默认是 https://api2.pushdeer.com/message/push",
"Bark的信息IP/设备码,例如:https://api.day.app/XXXXXXXX": "Bark的信息IP/设备码,例如:https://api.day.app/XXXXXXXX",
"BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)": "BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)",
"BARK推送铃声,铃声列表去APP查看复制填写": "BARK推送铃声,铃声列表去APP查看复制填写",
"BARK推送消息的分组, 默认为qinglong": "BARK推送消息的分组, 默认为qinglong",
"BARK推送图标自定义推送图标 (需iOS15或以上才能显示)": "BARK推送图标自定义推送图标 (需iOS15或以上才能显示)",
"BARK推送铃声铃声列表去APP查看复制填写": "BARK推送铃声铃声列表去APP查看复制填写",
"BARK推送消息的分组默认为qinglong": "BARK推送消息的分组默认为qinglong",
"telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw": "telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw",
"telegram用户的id,例如:129xxx206": "telegram用户的id,例如:129xxx206",
"代理IP": "代理IP",
"代理端口": "代理端口",
"telegram代理配置认证参数, 用户名与密码用英文冒号连接 user:password": "telegram代理配置认证参数, 用户名与密码用英文冒号连接 user:password",
"telegram代理配置认证参数用户名与密码用英文冒号连接 user:password": "telegram代理配置认证参数, 用户名与密码用英文冒号连接 user:password",
"telegram api自建的反向代理地址,默认tg官方api": "telegram api自建的反向代理地址,默认tg官方api",
"钉钉机器人webhook token,例如:5a544165465465645d0f31dca676e7bd07415asdasd": "钉钉机器人webhook token,例如:5a544165465465645d0f31dca676e7bd07415asdasd",
"密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串": "密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串",
"企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa": "企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa",
"企业微信机器人的webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa": "企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa",
"企业微信代理地址": "企业微信代理地址",
"corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号),例如:wwcfrs,B-76WERQ,qinglong,1000001,2COat": "corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号),例如:wwcfrs,B-76WERQ,qinglong,1000001,2COat",
"密钥key,智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql": "密钥key,智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql",
"corpidcorpsecrettouser(注:多个成员ID使用|隔开)agentid消息类型(选填不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号),例如:wwcfrs,B-76WERQ,qinglong,1000001,2COat": "corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号),例如:wwcfrs,B-76WERQ,qinglong,1000001,2COat",
"密钥key智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql": "密钥key,智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql",
"发送的目标,群组或者好友": "发送的目标,群组或者好友",
"请输入要发送的目标": "请输入要发送的目标",
"群聊": "群聊",
@@ -448,5 +448,8 @@
"编辑订阅": "编辑订阅",
"Subscription表达式格式有误": "Subscription表达式格式有误",
"一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)",
"登录已过期,请重新登录": "登录已过期,请重新登录"
"登录已过期,请重新登录": "登录已过期,请重新登录",
"系统日志": "系统日志",
"主题": "主题",
"语言": "语言"
}
+5 -9
View File
@@ -11,9 +11,10 @@ import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
import { request } from '@/utils/http';
import Editor from '@monaco-editor/react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import CodeMirror from '@uiw/react-codemirror';
import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
import { langs } from '@uiw/codemirror-extensions-langs';
const Config = () => {
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
@@ -104,16 +105,11 @@ const Config = () => {
{isPhone ? (
<CodeMirror
value={value}
options={{
lineNumbers: true,
styleActiveLine: true,
matchBrackets: true,
mode: 'shell',
}}
onBeforeChange={(editor, data, value) => {
theme={theme.includes('dark') ? 'dark' : 'light'}
extensions={[langs.shell()]}
onChange={(value) => {
setValue(value);
}}
onChange={(editor, data, value) => {}}
/>
) : (
<Editor
+5 -13
View File
@@ -94,7 +94,7 @@
.log-item {
cursor: pointer;
&:hover {
background: #fafafa;
background: #f2f2f2;
}
}
@@ -186,18 +186,6 @@ tr.drop-over-upward td {
}
}
.log-modal {
.log-container {
overflow-y: auto;
min-height: 300px;
max-height: calc(80vh - 110px);
max-height: calc(80vh - var(--vh-offset, 110px));
padding: 24px;
margin: -24px;
}
}
body[data-mode='desktop'] {
.crontab-wrapper {
tbody .ant-table-cell {
@@ -205,3 +193,7 @@ body[data-mode='desktop'] {
}
}
}
.cron.pinned-cron > td {
background: #f2f2f2;
}
+38 -72
View File
@@ -68,53 +68,25 @@ const Crontab = () => {
title: intl.get('名称'),
dataIndex: 'name',
key: 'name',
fixed: isPhone ? undefined : 'left',
fixed: 'left',
width: 120,
render: (text: string, record: any) => (
<>
<Paragraph
style={{
wordBreak: 'break-all',
marginBottom: 0,
}}
ellipsis={{ tooltip: text, rows: 2 }}
>
<a
onClick={() => {
setDetailCron(record);
setIsDetailModalVisible(true);
}}
>
{record.labels?.length > 0 && record.labels[0] !== '' && false ? (
<Popover
placement="right"
trigger={isPhone ? 'click' : 'hover'}
content={
<div>
{record.labels?.map((label: string) => (
<Tag
color="blue"
key={label}
style={{ cursor: 'point' }}
onClick={(e) => {
e.stopPropagation();
setSearchValue(`label:${label}`);
setSearchText(`label:${label}`);
}}
>
<a>{label}</a>
</Tag>
))}
</div>
}
>
{record.name || '-'}
</Popover>
) : (
record.name || '-'
)}
{record.isPinned ? (
<span>
<PushpinOutlined />
</span>
) : (
''
)}
{record.name || '-'}
</a>
</>
</Paragraph>
),
sorter: {
compare: (a, b) => a?.name?.localeCompare(b?.name),
@@ -124,7 +96,7 @@ const Crontab = () => {
title: intl.get('命令/脚本'),
dataIndex: 'command',
key: 'command',
width: 200,
width: 240,
render: (text, record) => {
return (
<Paragraph
@@ -214,7 +186,7 @@ const Crontab = () => {
},
{
title: intl.get('最后运行时长'),
width: 180,
width: 167,
dataIndex: 'last_running_time',
key: 'last_running_time',
sorter: {
@@ -232,7 +204,7 @@ const Crontab = () => {
title: intl.get('最后运行时间'),
dataIndex: 'last_execution_time',
key: 'last_execution_time',
width: 150,
width: 141,
sorter: {
compare: (a, b) => {
return (a.last_execution_time || 0) - (b.last_execution_time || 0);
@@ -259,7 +231,7 @@ const Crontab = () => {
},
{
title: intl.get('下次运行时间'),
width: 150,
width: 144,
sorter: {
compare: (a: any, b: any) => {
return a.nextRunTime - b.nextRunTime;
@@ -276,7 +248,7 @@ const Crontab = () => {
},
{
title: intl.get('关联订阅'),
width: 190,
width: 185,
render: (text, record: any) =>
record.sub_id ? (
<Name
@@ -292,46 +264,40 @@ const Crontab = () => {
{
title: intl.get('操作'),
key: 'action',
width: 130,
width: 140,
fixed: isPhone ? undefined : 'right',
render: (text, record, index) => {
const isPc = !isPhone;
return (
<Space size="middle">
{record.status === CrontabStatus.idle && (
<Tooltip title={isPc ? intl.get('运行') : ''}>
<a
onClick={(e) => {
e.stopPropagation();
runCron(record, index);
}}
>
<PlayCircleOutlined />
</a>
</Tooltip>
)}
{record.status !== CrontabStatus.idle && (
<Tooltip title={isPc ? intl.get('停止') : ''}>
<a
onClick={(e) => {
e.stopPropagation();
stopCron(record, index);
}}
>
<PauseCircleOutlined />
</a>
</Tooltip>
)}
<Tooltip title={isPc ? intl.get('日志') : ''}>
<a
onClick={(e) => {
e.stopPropagation();
setLogCron({ ...record, timestamp: Date.now() });
runCron(record, index);
}}
>
<FileTextOutlined />
{intl.get('运行')}
</a>
</Tooltip>
)}
{record.status !== CrontabStatus.idle && (
<a
onClick={(e) => {
e.stopPropagation();
stopCron(record, index);
}}
>
{intl.get('停止')}
</a>
)}
<a
onClick={(e) => {
e.stopPropagation();
setLogCron({ ...record, timestamp: Date.now() });
}}
>
{intl.get('日志')}
</a>
<MoreBtn key="more" record={record} index={index} />
</Space>
);
@@ -1041,7 +1007,7 @@ const Crontab = () => {
dataSource={value}
rowKey="id"
size="middle"
scroll={{ x: 1000, y: tableScrollHeight }}
scroll={{ x: 1200, y: tableScrollHeight }}
loading={loading}
rowSelection={rowSelection}
rowClassName={getRowClassName}
+2 -6
View File
@@ -50,8 +50,8 @@ const CronLogModal = ({
log && !logEnded(log) && !log.includes('任务未运行'),
);
setExecuting(hasNext);
autoScroll();
if (hasNext) {
autoScroll();
setTimeout(() => {
getCronLog();
}, 2000);
@@ -74,7 +74,7 @@ const CronLogModal = ({
document
.querySelector('#log-flag')!
.scrollIntoView({ behavior: 'smooth' });
}, 1000);
}, 600);
};
const cancel = () => {
@@ -124,9 +124,6 @@ const CronLogModal = ({
open={visible}
centered
className="log-modal"
bodyStyle={{
minHeight: '300px',
}}
forceRender
onOk={() => cancel()}
onCancel={() => cancel()}
@@ -145,7 +142,6 @@ const CronLogModal = ({
isPhone
? {
fontFamily: 'Source Code Pro',
width: 375,
zoom: 0.83,
}
: {}
+1 -1
View File
@@ -83,7 +83,7 @@ const CronModal = ({
>
<Input.TextArea
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get(
'支持输入脚本路径/任意系统可执行命令/task 脚本路径',
)}
+6 -6
View File
@@ -400,7 +400,7 @@ const Dependence = () => {
const { type, message, references } = socketMessage;
if (
type === 'installDependence' &&
message.includes(intl.get('开始时间')) &&
message.includes('开始时间') &&
references.length > 0
) {
const result = [...value];
@@ -409,7 +409,7 @@ const Dependence = () => {
if (index !== -1) {
result.splice(index, 1, {
...value[index],
status: message.includes(intl.get('安装')) ? Status.安装中 : Status.删除中,
status: message.includes('安装') ? Status.安装中 : Status.删除中,
});
}
}
@@ -417,14 +417,14 @@ const Dependence = () => {
}
if (
type === 'installDependence' &&
message.includes(intl.get('结束时间')) &&
message.includes('结束时间') &&
references.length > 0
) {
let status;
if (message.includes(intl.get('安装'))) {
status = message.includes(intl.get('成功')) ? Status.已安装 : Status.安装失败;
if (message.includes('安装')) {
status = message.includes('成功') ? Status.已安装 : Status.安装失败;
} else {
status = message.includes(intl.get('成功')) ? Status.已删除 : Status.删除失败;
status = message.includes('成功') ? Status.已删除 : Status.删除失败;
}
const result = [...value];
for (let i = 0; i < references.length; i++) {
+22 -26
View File
@@ -56,8 +56,8 @@ const DependenceLogModal = ({
) {
const log = (data.log || []).join('') as string;
setValue(log);
setExecuting(!log.includes(intl.get('结束时间')));
setIsRemoveFailed(log.includes(intl.get('删除失败')));
setExecuting(!log.includes('结束时间'));
setIsRemoveFailed(log.includes('删除失败'));
}
})
.finally(() => {
@@ -103,9 +103,9 @@ const DependenceLogModal = ({
references.length > 0 &&
references.includes(dependence.id)
) {
if (message.includes(intl.get('结束时间'))) {
if (message.includes('结束时间')) {
setExecuting(false);
setIsRemoveFailed(message.includes(intl.get('删除失败')));
setIsRemoveFailed(message.includes('删除失败'));
}
setValue(`${value}${message}`);
}
@@ -121,11 +121,6 @@ const DependenceLogModal = ({
open={visible}
centered
className="log-modal"
bodyStyle={{
overflowY: 'auto',
maxHeight: 'calc(70vh - var(--vh-offset, 0px))',
minHeight: '300px',
}}
forceRender
onOk={() => cancel()}
onCancel={() => cancel()}
@@ -135,23 +130,24 @@ const DependenceLogModal = ({
</Button>,
]}
>
{loading ? (
<PageLoading />
) : (
<pre
style={
isPhone
? {
fontFamily: 'Source Code Pro',
width: 375,
zoom: 0.83,
}
: {}
}
>
<Ansi>{value}</Ansi>
</pre>
)}
<div className="log-container">
{loading ? (
<PageLoading />
) : (
<pre
style={
isPhone
? {
fontFamily: 'Source Code Pro',
zoom: 0.83,
}
: {}
}
>
<Ansi>{value}</Ansi>
</pre>
)}
</div>
</Modal>
);
};
+1 -1
View File
@@ -130,7 +130,7 @@ const DependenceModal = ({
>
<Input.TextArea
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入依赖名称')}
/>
</Form.Item>
+44 -14
View File
@@ -1,4 +1,4 @@
import intl from 'react-intl-universal'
import intl from 'react-intl-universal';
import React, {
useCallback,
useRef,
@@ -42,7 +42,7 @@ import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import Copy from '../../components/copy';
import { useVT } from 'virtualizedtableforantd4';
const { Text } = Typography;
const { Paragraph } = Typography;
const { Search } = Input;
enum Status {
@@ -149,7 +149,7 @@ const Env = () => {
title: intl.get('状态'),
key: 'status',
dataIndex: 'status',
width: 80,
width: 100,
filters: [
{
text: intl.get('已启用'),
@@ -186,7 +186,11 @@ const Env = () => {
</Tooltip>
<Tooltip
title={
isPc ? (record.status === Status. ? intl.get('启用') : intl.get('禁用')) : ''
isPc
? record.status === Status.
? intl.get('启用')
: intl.get('禁用')
: ''
}
>
<a onClick={() => enabledOrDisabledEnv(record, index)}>
@@ -232,14 +236,24 @@ const Env = () => {
const enabledOrDisabledEnv = (record: any, index: number) => {
Modal.confirm({
title: `确认${record.status === Status. ? intl.get('启用') : intl.get('禁用')}`,
title: `确认${
record.status === Status. ? intl.get('启用') : intl.get('禁用')
}`,
content: (
<>
{intl.get('确认')}{record.status === Status. ? intl.get('启用') : intl.get('禁用')}
{intl.get('确认')}
{record.status === Status.
? intl.get('启用')
: intl.get('禁用')}
Env{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
<Paragraph
style={{ wordBreak: 'break-all', display: 'inline' }}
ellipsis={{ rows: 6, expandable: true }}
type="warning"
copyable
>
{record.value}
</Text>{' '}
</Paragraph>{' '}
{intl.get('吗')}
</>
),
@@ -254,7 +268,11 @@ const Env = () => {
.then(({ code, data }) => {
if (code === 200) {
message.success(
`${record.status === Status. ? intl.get('启用') : intl.get('禁用')}${intl.get('成功')}`,
`${
record.status === Status.
? intl.get('启用')
: intl.get('禁用')
}${intl.get('成功')}`,
);
const newStatus =
record.status === Status. ? Status.已启用 : Status.已禁用;
@@ -289,9 +307,14 @@ const Env = () => {
content: (
<>
{intl.get('确认删除变量')}{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
<Paragraph
style={{ wordBreak: 'break-all', display: 'inline' }}
ellipsis={{ rows: 6, expandable: true }}
type="warning"
copyable
>
{record.name}: {record.value}
</Text>{' '}
</Paragraph>{' '}
{intl.get('吗')}
</>
),
@@ -435,7 +458,13 @@ const Env = () => {
const operateEnvs = (operationStatus: number) => {
Modal.confirm({
title: `确认${OperationName[operationStatus]}`,
content: <>{intl.get('确认')}{OperationName[operationStatus]}{intl.get('选中的变量吗')}</>,
content: (
<>
{intl.get('确认')}
{OperationName[operationStatus]}
{intl.get('选中的变量吗')}
</>
),
onOk() {
request
.put(
@@ -567,7 +596,8 @@ const Env = () => {
</Button>
<span style={{ marginLeft: 8 }}>
{intl.get('已选择')}
<a>{selectedRowIds?.length}</a>{intl.get('项')}
<a>{selectedRowIds?.length}</a>
{intl.get('项')}
</span>
</div>
)}
@@ -579,7 +609,7 @@ const Env = () => {
dataSource={value}
rowKey="id"
size="middle"
scroll={{ x: 1000, y: tableScrollHeight }}
scroll={{ x: 1200, y: tableScrollHeight }}
components={vt}
loading={loading}
onRow={(record: any, index: number | undefined) => {
+15 -6
View File
@@ -1,4 +1,4 @@
import intl from 'react-intl-universal'
import intl from 'react-intl-universal';
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Radio } from 'antd';
import { request } from '@/utils/http';
@@ -44,7 +44,9 @@ const EnvModal = ({
);
if (code === 200) {
message.success(env ? intl.get('更新变量成功') : intl.get('创建变量成功'));
message.success(
env ? intl.get('更新变量成功') : intl.get('创建变量成功'),
);
handleCancel(data);
}
setLoading(false);
@@ -82,7 +84,11 @@ const EnvModal = ({
name="name"
label={intl.get('名称')}
rules={[
{ required: true, message: intl.get('请输入环境变量名称'), whitespace: true },
{
required: true,
message: intl.get('请输入环境变量名称'),
whitespace: true,
},
{
pattern: /^[a-zA-Z_][0-9a-zA-Z_]*$/,
message: intl.get('只能输入字母数字下划线,且不能以数字开头'),
@@ -108,12 +114,15 @@ const EnvModal = ({
name="value"
label={intl.get('值')}
rules={[
{ required: true, message: intl.get('请输入环境变量值'), whitespace: true },
{
required: true,
message: intl.get('请输入环境变量值'),
whitespace: true,
},
]}
>
<Input.TextArea
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 8 }}
placeholder={intl.get('请输入环境变量值')}
/>
</Form.Item>
+1 -1
View File
@@ -185,7 +185,7 @@ const Initialization = () => {
style={{ maxWidth: 400 }}
>
<Input.TextArea
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={`请输入${x.label}`}
/>
</Form.Item>
+4 -10
View File
@@ -16,7 +16,7 @@ import { PageContainer } from '@ant-design/pro-layout';
import Editor from '@monaco-editor/react';
import { request } from '@/utils/http';
import styles from './index.module.less';
import { Controlled as CodeMirror } from 'react-codemirror2';
import CodeMirror from '@uiw/react-codemirror';
import SplitPane from 'react-split-pane';
import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
@@ -278,17 +278,11 @@ const Log = () => {
{isPhone && (
<CodeMirror
value={value}
options={{
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: true,
}}
onBeforeChange={(editor, data, value) => {
readOnly={true}
theme={theme.includes('dark') ? 'dark' : 'light'}
onChange={(value, viewUpdate) => {
setValue(value);
}}
onChange={(editor, data, value) => {}}
/>
)}
</div>
+1 -1
View File
@@ -224,6 +224,7 @@ const EditModal = ({
minSize={200}
defaultSize="50%"
style={{ height: 'calc(100vh - 55px)' }}
pane2Style={{ overflowY: 'auto' }}
>
<Editor
language={language}
@@ -241,7 +242,6 @@ const EditModal = ({
/>
<pre
style={{
height: '100%',
padding: '0 15px',
}}
>
+8 -11
View File
@@ -20,7 +20,7 @@ import Editor from '@monaco-editor/react';
import { request } from '@/utils/http';
import styles from './index.module.less';
import EditModal from './editModal';
import { Controlled as CodeMirror } from 'react-codemirror2';
import CodeMirror from '@uiw/react-codemirror';
import SplitPane from 'react-split-pane';
import {
DeleteOutlined,
@@ -43,6 +43,7 @@ import useFilterTreeData from '@/hooks/useFilterTreeData';
import uniq from 'lodash/uniq';
import IconFont from '@/components/iconfont';
import RenameModal from './renameModal';
import { langs } from '@uiw/codemirror-extensions-langs';
const { Text } = Typography;
@@ -580,18 +581,14 @@ const Script = () => {
{isPhone && (
<CodeMirror
value={value}
options={{
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true,
matchBrackets: true,
mode,
readOnly: !isEditing,
}}
onBeforeChange={(editor, data, value) => {
extensions={
mode ? [langs[mode as keyof typeof langs]()] : undefined
}
theme={theme.includes('dark') ? 'dark' : 'light'}
readOnly={!isEditing}
onChange={(value) => {
setValue(value);
}}
onChange={(editor, data, value) => {}}
/>
)}
<EditModal
+1
View File
@@ -10,6 +10,7 @@ const { Link } = Typography;
enum TVersion {
'develop' = '开发版',
'master' = '正式版',
'debian' = '正式版'
}
const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
+6 -31
View File
@@ -75,16 +75,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
</div>
</>
),
content: (
<pre
style={{
fontSize: 12,
fontWeight: 400,
}}
>
{lastLog}
</pre>
),
content: <pre>{lastLog}</pre>,
okText: intl.get('下载更新'),
cancelText: intl.get('以后再说'),
onOk() {
@@ -109,16 +100,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
okButtonProps: { disabled: true },
title: intl.get('下载更新中...'),
centered: true,
content: (
<pre
style={{
fontSize: 12,
fontWeight: 400,
}}
>
{value}
</pre>
),
content: <pre>{value}</pre>,
});
};
@@ -177,7 +159,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
}
const newMessage = `${value}${_message}`;
const updateFailed = newMessage.includes(intl.get('失败'));
const updateFailed = newMessage.includes('失败');
modalRef.current.update({
maskClosable: updateFailed,
@@ -185,20 +167,13 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
okButtonProps: { disabled: !updateFailed },
content: (
<>
<pre
style={{
fontSize: 12,
fontWeight: 400,
}}
>
{newMessage}
</pre>
<pre>{newMessage}</pre>
<div id="log-identifier" style={{ paddingBottom: 5 }}></div>
</>
),
});
if (updateFailed && !value.includes(intl.get('失败,请检查'))) {
if (updateFailed && !value.includes('失败,请检查')) {
message.error(intl.get('更新失败,请检查网络及日志或稍后再试'));
}
@@ -209,7 +184,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
.getElementById('log-identifier')!
.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
if (_message.includes(intl.get('更新包下载成功'))) {
if (_message.includes('更新包下载成功')) {
setTimeout(() => {
showReloadModal();
}, 1000);
+111 -61
View File
@@ -1,5 +1,5 @@
import intl from 'react-intl-universal';
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
InputNumber,
@@ -32,6 +32,8 @@ import About from './about';
import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
import './index.less';
import CodeMirror from '@uiw/react-codemirror';
import useResizeObserver from '@react-hook/resize-observer';
const { Text } = Typography;
const isDemoEnv = window.__ENV__DeployEnv === 'demo';
@@ -41,6 +43,7 @@ const Setting = () => {
headerStyle,
isPhone,
user,
theme,
reloadUser,
reloadTheme,
socketMessage,
@@ -113,7 +116,17 @@ const Setting = () => {
const [editedApp, setEditedApp] = useState<any>();
const [tabActiveKey, setTabActiveKey] = useState('security');
const [loginLogData, setLoginLogData] = useState<any[]>([]);
const [systemLogData, setSystemLogData] = useState<string>('');
const [notificationInfo, setNotificationInfo] = useState<any>();
const containergRef = useRef<HTMLDivElement>(null);
const [height, setHeight] = useState<number>(0);
useResizeObserver(containergRef, (entry) => {
const _height = entry.target.parentElement?.parentElement?.offsetHeight;
if (_height && height !== _height - 66) {
setHeight(_height - 66);
}
});
const getApps = () => {
setLoading(true);
@@ -232,6 +245,19 @@ const Setting = () => {
});
};
const getSystemLog = () => {
request
.get<Blob>(`${config.apiPrefix}system/log`, {
responseType: 'blob',
})
.then(async (res) => {
setSystemLogData(await res.text());
})
.catch((error: any) => {
console.log(error);
});
};
const tabChange = (activeKey: string) => {
setTabActiveKey(activeKey);
if (activeKey === 'app') {
@@ -240,6 +266,8 @@ const Setting = () => {
getLoginLog();
} else if (activeKey === 'notification') {
getNotification();
} else if (activeKey === 'syslog') {
getSystemLog();
}
};
@@ -279,66 +307,88 @@ const Setting = () => {
: []
}
>
<Tabs
defaultActiveKey="security"
size="small"
tabPosition="top"
onChange={tabChange}
items={[
...(!isDemoEnv
? [
{
key: 'security',
label: intl.get('安全设置'),
children: (
<SecuritySettings user={user} userChange={reloadUser} />
),
},
]
: []),
{
key: 'app',
label: intl.get('应用设置'),
children: (
<Table
columns={columns}
pagination={false}
dataSource={dataSource}
rowKey="id"
size="middle"
scroll={{ x: 768 }}
loading={loading}
/>
),
},
{
key: 'notification',
label: intl.get('通知设置'),
children: <NotificationSetting data={notificationInfo} />,
},
{
key: 'login',
label: intl.get('登录日志'),
children: <LoginLog data={loginLogData} />,
},
{
key: 'other',
label: intl.get('其他设置'),
children: (
<Other
reloadTheme={reloadTheme}
socketMessage={socketMessage}
systemInfo={systemInfo}
/>
),
},
{
key: 'about',
label: intl.get('关于'),
children: <About systemInfo={systemInfo} />,
},
]}
></Tabs>
<div ref={containergRef}>
<Tabs
defaultActiveKey="security"
size="small"
tabPosition="top"
onChange={tabChange}
items={[
...(!isDemoEnv
? [
{
key: 'security',
label: intl.get('安全设置'),
children: (
<SecuritySettings user={user} userChange={reloadUser} />
),
},
]
: []),
{
key: 'app',
label: intl.get('应用设置'),
children: (
<Table
columns={columns}
pagination={false}
dataSource={dataSource}
rowKey="id"
size="middle"
scroll={{ x: 768 }}
loading={loading}
/>
),
},
{
key: 'notification',
label: intl.get('通知设置'),
children: <NotificationSetting data={notificationInfo} />,
},
{
key: 'syslog',
label: intl.get('系统日志'),
children: (
<CodeMirror
maxHeight={`${height}px`}
value={systemLogData}
onCreateEditor={(view) => {
setTimeout(() => {
view.scrollDOM.scrollTo({
top: view.scrollDOM.scrollHeight,
behavior: 'smooth',
});
}, 300);
}}
readOnly={true}
theme={theme.includes('dark') ? 'dark' : 'light'}
/>
),
},
{
key: 'login',
label: intl.get('登录日志'),
children: <LoginLog data={loginLogData} />,
},
{
key: 'other',
label: intl.get('其他设置'),
children: (
<Other
reloadTheme={reloadTheme}
socketMessage={socketMessage}
systemInfo={systemInfo}
/>
),
},
{
key: 'about',
label: intl.get('关于'),
children: <About systemInfo={systemInfo} />,
},
]}
></Tabs>
</div>
<AppModal
visible={isModalVisible}
handleCancel={handleCancel}
+2 -2
View File
@@ -19,7 +19,7 @@ enum LoginStatusColor {
const columns = [
{
title: intl.get('序号'),
width: 40,
width: 50,
render: (text: string, record: any, index: number) => {
return index + 1;
},
@@ -75,7 +75,7 @@ const LoginLog = ({ data }: any) => {
dataSource={data}
rowKey="id"
size="middle"
scroll={{ x: 768 }}
scroll={{ x: 1000 }}
sticky
/>
</>
+1 -1
View File
@@ -88,7 +88,7 @@ const NotificationSetting = ({ data }: any) => {
) : (
<Input.TextArea
disabled={loading}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={x.placeholder || `请输入${x.label}`}
/>
)}
+48 -12
View File
@@ -9,7 +9,7 @@ import {
Input,
Upload,
Modal,
Progress,
Select,
} from 'antd';
import * as DarkReader from '@umijs/ssr-darkreader';
import config from '@/utils/config';
@@ -22,12 +22,6 @@ import { UploadOutlined } from '@ant-design/icons';
import Countdown from 'antd/lib/statistic/Countdown';
import useProgress from './progress';
const optionsWithDisabled = [
{ label: intl.get('亮色'), value: 'light' },
{ label: intl.get('暗色'), value: 'dark' },
{ label: intl.get('跟随系统'), value: 'auto' },
];
const Other = ({
systemInfo,
socketMessage,
@@ -67,6 +61,13 @@ const Other = ({
reloadTheme();
};
const handleLangChange = (v: string) => {
localStorage.setItem('lang', v);
setTimeout(() => {
window.location.reload();
}, 500);
};
const getSystemConfig = () => {
request
.get(`${config.apiPrefix}system/config`)
@@ -167,12 +168,27 @@ const Other = ({
initialValue={defaultTheme}
>
<Radio.Group
options={optionsWithDisabled}
onChange={themeChange}
value={defaultTheme}
optionType="button"
buttonStyle="solid"
/>
>
<Radio.Button
value="light"
style={{ width: 70, textAlign: 'center' }}
>
{intl.get('亮色')}
</Radio.Button>
<Radio.Button value="dark" style={{ width: 66, textAlign: 'center' }}>
{intl.get('暗色')}
</Radio.Button>
<Radio.Button
value="auto"
style={{ width: 129, textAlign: 'center' }}
>
{intl.get('跟随系统')}
</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item
label={intl.get('日志删除频率')}
@@ -190,7 +206,11 @@ const Other = ({
setSystemConfig({ ...systemConfig, logRemoveFrequency: value });
}}
/>
<Button type="primary" onClick={updateSystemConfig}>
<Button
type="primary"
onClick={updateSystemConfig}
style={{ width: 84 }}
>
{intl.get('确认')}
</Button>
</Input.Group>
@@ -198,18 +218,34 @@ const Other = ({
<Form.Item label={intl.get('定时任务并发数')} name="frequency">
<Input.Group compact>
<InputNumber
style={{ width: 150 }}
style={{ width: 180 }}
min={1}
value={systemConfig?.cronConcurrency}
onChange={(value) => {
setSystemConfig({ ...systemConfig, cronConcurrency: value });
}}
/>
<Button type="primary" onClick={updateSystemConfig}>
<Button
type="primary"
onClick={updateSystemConfig}
style={{ width: 84 }}
>
{intl.get('确认')}
</Button>
</Input.Group>
</Form.Item>
<Form.Item label={intl.get('语言')} name="lang">
<Select
defaultValue={localStorage.getItem('lang') || ''}
style={{ width: 264 }}
onChange={handleLangChange}
options={[
{ value: '', label: intl.get('跟随系统') },
{ value: 'zh', label: '简体中文' },
{ value: 'en', label: 'English' },
]}
/>
</Form.Item>
<Form.Item label={intl.get('数据备份还原')} name="frequency">
<Button type="primary" onClick={exportData} loading={exportLoading}>
{exportLoading ? intl.get('生成数据中...') : intl.get('备份')}
+6 -2
View File
@@ -188,7 +188,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
hasFeedback
style={{ maxWidth: 300 }}
>
<Input placeholder={intl.get('用户名')} />
<Input autoComplete="username" placeholder={intl.get('用户名')} />
</Form.Item>
<Form.Item
label={intl.get('密码')}
@@ -203,7 +203,11 @@ const SecuritySettings = ({ user, userChange }: any) => {
hasFeedback
style={{ maxWidth: 300 }}
>
<Input type="password" placeholder={intl.get('密码')} />
<Input
type="password"
autoComplete="current-password"
placeholder={intl.get('密码')}
/>
</Form.Item>
<Button type="primary" htmlType="submit">
{intl.get('保存')}
+22 -28
View File
@@ -184,45 +184,39 @@ const Subscription = () => {
{
title: intl.get('操作'),
key: 'action',
width: 130,
width: 140,
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
return (
<Space size="middle">
{record.status === SubscriptionStatus.idle && (
<Tooltip title={isPc ? intl.get('运行') : ''}>
<a
onClick={(e) => {
e.stopPropagation();
runSubscription(record, index);
}}
>
<PlayCircleOutlined />
</a>
</Tooltip>
)}
{record.status !== SubscriptionStatus.idle && (
<Tooltip title={isPc ? intl.get('停止') : ''}>
<a
onClick={(e) => {
e.stopPropagation();
stopSubsciption(record, index);
}}
>
<PauseCircleOutlined />
</a>
</Tooltip>
)}
<Tooltip title={isPc ? intl.get('日志') : ''}>
<a
onClick={(e) => {
e.stopPropagation();
setLogSubscription({ ...record, timestamp: Date.now() });
runSubscription(record, index);
}}
>
<FileTextOutlined />
{intl.get('运行')}
</a>
</Tooltip>
)}
{record.status !== SubscriptionStatus.idle && (
<a
onClick={(e) => {
e.stopPropagation();
stopSubsciption(record, index);
}}
>
{intl.get('停止')}
</a>
)}
<a
onClick={(e) => {
e.stopPropagation();
setLogSubscription({ ...record, timestamp: Date.now() });
}}
>
{intl.get('日志')}
</a>
<MoreBtn key="more" record={record} index={index} />
</Space>
);
+18 -20
View File
@@ -99,9 +99,6 @@ const SubscriptionLogModal = ({
open={visible}
centered
className="log-modal"
bodyStyle={{
minHeight: '300px',
}}
forceRender
onOk={() => cancel()}
onCancel={() => cancel()}
@@ -111,23 +108,24 @@ const SubscriptionLogModal = ({
</Button>,
]}
>
{loading ? (
<PageLoading />
) : (
<pre
style={
isPhone
? {
fontFamily: 'Source Code Pro',
width: 375,
zoom: 0.83,
}
: {}
}
>
{value}
</pre>
)}
<div className="log-container">
{loading ? (
<PageLoading />
) : (
<pre
style={
isPhone
? {
fontFamily: 'Source Code Pro',
zoom: 0.83,
}
: {}
}
>
{value}
</pre>
)}
</div>
</Modal>
);
};
+23 -13
View File
@@ -241,10 +241,9 @@ const SubscriptionModal = ({
const onNamePaste = useCallback((e) => {
const text = e.clipboardData.getData('text') as string;
if (text.startsWith('ql ')) {
if (text.includes('ql repo') || text.includes('ql raw')) {
e.preventDefault();
}
onPaste(e);
}, []);
const formatParams = (sub) => {
@@ -327,10 +326,9 @@ const SubscriptionModal = ({
]}
>
<Input.TextArea
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入订阅链接')}
onPaste={onUrlChange}
onPaste={onNamePaste}
onChange={onUrlChange}
/>
</Form.Item>
@@ -338,7 +336,7 @@ const SubscriptionModal = ({
<Form.Item name="branch" label={intl.get('分支')}>
<Input
placeholder={intl.get('请输入分支')}
onPaste={onBranchChange}
onPaste={onNamePaste}
onChange={onBranchChange}
/>
</Form.Item>
@@ -401,7 +399,10 @@ const SubscriptionModal = ({
{scheduleType === 'interval' ? (
<IntervalSelect />
) : (
<Input placeholder={intl.get('秒(可选) 分 时 天 月 周')} />
<Input
onPaste={onNamePaste}
placeholder={intl.get('秒(可选) 分 时 天 月 周')}
/>
)}
</Form.Item>
{type !== 'file' && (
@@ -413,10 +414,11 @@ const SubscriptionModal = ({
>
<Input.TextArea
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get(
'请输入脚本筛选白名单关键词,多个关键词竖线分割',
)}
onPaste={onNamePaste}
/>
</Form.Item>
<Form.Item
@@ -426,10 +428,11 @@ const SubscriptionModal = ({
>
<Input.TextArea
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get(
'请输入脚本筛选黑名单关键词,多个关键词竖线分割',
)}
onPaste={onNamePaste}
/>
</Form.Item>
<Form.Item
@@ -439,10 +442,11 @@ const SubscriptionModal = ({
>
<Input.TextArea
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get(
'请输入脚本依赖文件关键词,多个关键词竖线分割',
)}
onPaste={onNamePaste}
/>
</Form.Item>
<Form.Item
@@ -452,7 +456,10 @@ const SubscriptionModal = ({
'仓库需要拉取的文件后缀,多个后缀空格分隔,默认使用配置文件中的RepoFileExtensions',
)}
>
<Input placeholder={intl.get('请输入文件后缀')} />
<Input
onPaste={onNamePaste}
placeholder={intl.get('请输入文件后缀')}
/>
</Form.Item>
<Form.Item
name="sub_before"
@@ -462,8 +469,9 @@ const SubscriptionModal = ({
)}
>
<Input.TextArea
onPaste={onNamePaste}
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入运行订阅前要执行的命令')}
/>
</Form.Item>
@@ -475,8 +483,9 @@ const SubscriptionModal = ({
)}
>
<Input.TextArea
onPaste={onNamePaste}
rows={4}
autoSize={true}
autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入运行订阅后要执行的命令')}
/>
</Form.Item>
@@ -490,6 +499,7 @@ const SubscriptionModal = ({
)}
>
<Input
onPaste={onNamePaste}
placeholder={
type === 'private-repo'
? 'SOCK5代理,例如 IP:PORT'
+1 -1
View File
@@ -174,7 +174,7 @@ export default {
},
{
label: 'barkGroup',
tip: intl.get('BARK推送消息的分组, 默认为qinglong'),
tip: intl.get('BARK推送消息的分组默认为qinglong'),
},
],
telegramBot: [
+12 -11
View File
@@ -1,12 +1,13 @@
version: 2.16.0
changeLogLink: https://t.me/jiao_long/388
publishTime: 2023-08-06 16:00
version: 2.16.2
changeLogLink: https://t.me/jiao_long/393
publishTime: 2023-09-02 07:00
changeLog: |
1. 多语言支持英文界面
2. 定时任务增加关联订阅
3. 删除订阅支持自动删除任务和脚本
4. 定时任务支持运行 mjs 后缀文件
5. PushMe消息通道增加 params 参数
6. 修复 6 位 cron 不以 task 开头定时任务运行失败
7. 修任务详情日志列表过多卡顿
8. 修复定时任务列表虚拟滚动
1. 系统设置增加语言设置
2. 修复环境变量有空格时并发数量错误
3. 修复环境变量特殊字符转义
4. 修复仓库订阅 ssh 配置
5. 修复停止订阅执行日志
6. 修改定时任务置顶样式
7. 修任务日志样式
8. 修复环境变量值 tip 样式
9. 其他 bug 修复