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-production
src/.umi-test src/.umi-test
.env.local .env.local
env .env
history history
version.ts version.ts
config config
+1
View File
@@ -4,6 +4,7 @@ const CompressionPlugin = require('compression-webpack-plugin');
const baseUrl = process.env.QlBaseUrl || '/'; const baseUrl = process.env.QlBaseUrl || '/';
export default defineConfig({ export default defineConfig({
hash: true, hash: true,
jsMinifier: 'terser',
antd: {}, antd: {},
locale: { locale: {
antd: true, antd: true,
+2 -1
View File
@@ -6,6 +6,7 @@ import { celebrate, Joi } from 'celebrate';
import multer from 'multer'; import multer from 'multer';
import config from '../config'; import config from '../config';
import fs from 'fs'; import fs from 'fs';
import { safeJSONParse } from '../config/util';
const route = Router(); const route = Router();
const storage = multer.diskStorage({ const storage = multer.diskStorage({
@@ -200,7 +201,7 @@ export default (app: Router) => {
try { try {
const envService = Container.get(EnvService); const envService = Container.get(EnvService);
const fileContent = await fs.promises.readFile(req!.file!.path, 'utf8'); const fileContent = await fs.promises.readFile(req!.file!.path, 'utf8');
const parseContent = JSON.parse(fileContent); const parseContent = safeJSONParse(fileContent);
const data = Array.isArray(parseContent) const data = Array.isArray(parseContent)
? parseContent ? parseContent
: [parseContent]; : [parseContent];
+2
View File
@@ -265,9 +265,11 @@ export default (app: Router) => {
let { filename, path, pid } = req.body; let { filename, path, pid } = req.body;
const { name, ext } = parse(filename); const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`); const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
const logPath = join(config.logPath, path, `${name}.swap`);
const scriptService = Container.get(ScriptService); const scriptService = Container.get(ScriptService);
const result = await scriptService.stopScript(filePath, pid); const result = await scriptService.stopScript(filePath, pid);
emptyDir(logPath);
res.send(result); res.send(result);
} catch (e) { } catch (e) {
return next(e); return next(e);
+17 -1
View File
@@ -174,7 +174,10 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
const systemService = Container.get(SystemService); 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 logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
const logPath = `${uniqPath}/${logTime}.log`; const logPath = `${uniqPath}/${logTime}.log`;
res.setHeader('Content-type', 'application/octet-stream'); 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 const server = app
.listen(config.port, () => { .listen(config.port, () => {
Logger.debug(`✌️ 后端服务启动成功!`); Logger.debug(`✌️ 后端服务启动成功!`);
console.debug(`✌️ 后端服务启动成功!`);
process.send?.('ready'); process.send?.('ready');
}) })
.on('error', (err) => { .on('error', (err) => {
Logger.error(err); Logger.error(err);
console.error(err);
process.exit(1); process.exit(1);
}); });
+3 -1
View File
@@ -1,6 +1,6 @@
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import path from 'path'; import path from 'path';
import { createRandomString } from './util'; import { createRandomString } from './share';
process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 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 dbPath = path.join(dataPath, 'db/');
const uploadPath = path.join(dataPath, 'upload/'); const uploadPath = path.join(dataPath, 'upload/');
const sshdPath = path.join(dataPath, 'ssh.d/'); const sshdPath = path.join(dataPath, 'ssh.d/');
const systemLogPath = path.join(dataPath, 'syslog/');
const envFile = path.join(configPath, 'env.sh'); const envFile = path.join(configPath, 'env.sh');
const confFile = path.join(configPath, 'config.sh'); const confFile = path.join(configPath, 'config.sh');
@@ -110,4 +111,5 @@ export default {
lastVersionFile, lastVersionFile,
sqliteFile, sqliteFile,
sshdPath, 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 { load } from 'js-yaml';
import config from './index'; import config from './index';
import { TASK_COMMAND } from './const'; import { TASK_COMMAND } from './const';
import Logger from '../loaders/logger';
export * from './share';
export function getFileContentByName(fileName: string) { export function getFileContentByName(fileName: string) {
if (fs.existsSync(fileName)) { if (fs.existsSync(fileName)) {
@@ -36,91 +39,6 @@ export function getLastModifyFilePath(dir: string) {
return filePath; 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) { export function getToken(req: any) {
const { authorization = '' } = req.headers; const { authorization = '' } = req.headers;
if (authorization && authorization.split(' ')[0] === 'Bearer') { if (authorization && authorization.split(' ')[0] === 'Bearer') {
@@ -307,22 +225,28 @@ interface IFile {
type: 'directory' | 'file'; type: 'directory' | 'file';
parent: string; parent: string;
mtime: number; mtime: number;
size?: number;
children?: IFile[]; children?: IFile[];
} }
export function dirSort(a: IFile, b: IFile) { export function dirSort(a: IFile, b: IFile): number {
if (a.type !== b.type) return FileType[a.type] < FileType[b.type] ? -1 : 1; if (a.type !== b.type) {
else if (a.mtime !== b.mtime) return a.mtime > b.mtime ? -1 : 1; 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( export function readDirs(
dir: string, dir: string,
baseDir: string = '', baseDir: string = '',
blacklist: string[] = [], blacklist: string[] = [],
) { ): IFile[] {
const relativePath = path.relative(baseDir, dir); const relativePath = path.relative(baseDir, dir);
const files = fs.readdirSync(dir); const files = fs.readdirSync(dir);
const result: any = files const result: IFile[] = files
.filter((x) => !blacklist.includes(x)) .filter((x) => !blacklist.includes(x))
.map((file: string) => { .map((file: string) => {
const subPath = path.join(dir, file); const subPath = path.join(dir, file);
@@ -344,6 +268,7 @@ export function readDirs(
isLeaf: true, isLeaf: true,
key, key,
parent: relativePath, parent: relativePath,
size: stats.size,
mtime: stats.mtime.getTime(), mtime: stats.mtime.getTime(),
}; };
}); });
@@ -522,11 +447,8 @@ export async function parseContentVersion(content: string): Promise<IVersion> {
return load(content) as IVersion; return load(content) as IVersion;
} }
export async function getUniqPath(command: string): Promise<string> { export async function getUniqPath(command: string, id: 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`; if (/^\d+$/.test(id)) {
let id = await promiseExec(idStr);
if (/^\d\d*\d$/.test(id)) {
id = `_${id}`; id = `_${id}`;
} else { } else {
id = ''; id = '';
@@ -558,3 +480,16 @@ export async function getUniqPath(command: string): Promise<string> {
return `${str}${id}`; return `${str}${id}`;
} }
export function safeJSONParse(value?: string) {
if (!value) {
return {};
}
try {
return JSON.parse(value);
} catch (error) {
Logger.error('[JSON.parse失败]', error)
return {};
}
}
+12 -2
View File
@@ -7,18 +7,28 @@ import linkDeps from './deps';
import initTask from './initTask'; import initTask from './initTask';
export default async ({ expressApp }: { expressApp: Application }) => { export default async ({ expressApp }: { expressApp: Application }) => {
await depInjectorLoader(); try {
depInjectorLoader();
Logger.info('✌️ Dependency Injector loaded'); Logger.info('✌️ Dependency Injector loaded');
console.log('✌️ Dependency Injector loaded');
await expressLoader({ app: expressApp }); expressLoader({ app: expressApp });
Logger.info('✌️ Express loaded'); Logger.info('✌️ Express loaded');
console.log('✌️ Express loaded');
await initData(); await initData();
Logger.info('✌️ init data loaded'); Logger.info('✌️ init data loaded');
console.log('✌️ init data loaded');
await linkDeps(); await linkDeps();
Logger.info('✌️ link deps loaded'); Logger.info('✌️ link deps loaded');
console.log('✌️ link deps loaded');
initTask(); initTask();
Logger.info('✌️ init task loaded'); 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'); Logger.info('✌️ DB loaded');
} catch (error) { } catch (error) {
Logger.info('✌️ DB load failed'); console.error('✌️ DB load failed');
Logger.info(error); Logger.error(error);
} }
}; };
+6 -4
View File
@@ -5,7 +5,7 @@ import routes from '../api';
import config from '../config'; import config from '../config';
import jwt, { UnauthorizedError } from 'express-jwt'; import jwt, { UnauthorizedError } from 'express-jwt';
import fs from 'fs'; import fs from 'fs';
import { getPlatform, getToken } from '../config/util'; import { getPlatform, getToken, safeJSONParse } from '../config/util';
import Container from 'typedi'; import Container from 'typedi';
import OpenService from '../services/open'; import OpenService from '../services/open';
import rewrite from 'express-urlrewrite'; import rewrite from 'express-urlrewrite';
@@ -15,6 +15,7 @@ import { EnvModel } from '../data/env';
import { errors } from 'celebrate'; import { errors } from 'celebrate';
import { createProxyMiddleware } from 'http-proxy-middleware'; import { createProxyMiddleware } from 'http-proxy-middleware';
import { serveEnv } from '../config/serverEnv'; import { serveEnv } from '../config/serverEnv';
import Logger from './logger';
export default ({ app }: { app: Application }) => { export default ({ app }: { app: Application }) => {
app.enable('trust proxy'); app.enable('trust proxy');
@@ -25,9 +26,10 @@ export default ({ app }: { app: Application }) => {
app.use( app.use(
'/api/public', '/api/public',
createProxyMiddleware({ createProxyMiddleware({
target: `http://localhost:${config.publicPort}/api`, target: `http://0.0.0.0:${config.publicPort}/api`,
changeOrigin: true, changeOrigin: true,
pathRewrite: { '/api/public': '' }, pathRewrite: { '/api/public': '' },
logProvider: () => Logger
}), }),
); );
@@ -36,7 +38,7 @@ export default ({ app }: { app: Application }) => {
app.use( app.use(
jwt({ jwt({
secret: config.secret as string, secret: config.secret,
algorithms: ['HS384'], algorithms: ['HS384'],
}).unless({ }).unless({
path: [...config.apiWhiteList, /^\/open\//], path: [...config.apiWhiteList, /^\/open\//],
@@ -83,7 +85,7 @@ export default ({ app }: { app: Application }) => {
const data = fs.readFileSync(config.authConfigFile, 'utf8'); const data = fs.readFileSync(config.authConfigFile, 'utf8');
if (data && headerToken) { if (data && headerToken) {
const { token = '', tokens = {} } = JSON.parse(data); const { token = '', tokens = {} } = safeJSONParse(data);
if (headerToken === token || tokens[req.platform] === headerToken) { if (headerToken === token || tokens[req.platform] === headerToken) {
return next(); return next();
} }
+7
View File
@@ -28,6 +28,7 @@ const TaskAfterFile = path.join(configPath, 'task_after.sh');
const homedir = os.homedir(); const homedir = os.homedir();
const sshPath = path.resolve(homedir, '.ssh'); const sshPath = path.resolve(homedir, '.ssh');
const sshdPath = path.join(dataPath, 'ssh.d'); const sshdPath = path.join(dataPath, 'ssh.d');
const systemLogPath = path.join(dataPath, 'syslog');
export default async () => { export default async () => {
const authFileExist = await fileExist(authConfigFile); const authFileExist = await fileExist(authConfigFile);
@@ -39,6 +40,7 @@ export default async () => {
const sshDirExist = await fileExist(sshPath); const sshDirExist = await fileExist(sshPath);
const bakDirExist = await fileExist(bakPath); const bakDirExist = await fileExist(bakPath);
const sshdDirExist = await fileExist(sshdPath); const sshdDirExist = await fileExist(sshdPath);
const systemLogDirExist = await fileExist(systemLogPath);
const tmpDirExist = await fileExist(tmpPath); const tmpDirExist = await fileExist(tmpPath);
const scriptNotifyJsFileExist = await fileExist(scriptNotifyJsFile); const scriptNotifyJsFileExist = await fileExist(scriptNotifyJsFile);
const scriptNotifyPyFileExist = await fileExist(scriptNotifyPyFile); const scriptNotifyPyFileExist = await fileExist(scriptNotifyPyFile);
@@ -77,6 +79,10 @@ export default async () => {
fs.mkdirSync(sshdPath); fs.mkdirSync(sshdPath);
} }
if (!systemLogDirExist) {
fs.mkdirSync(systemLogPath);
}
// 初始化文件 // 初始化文件
if (!authFileExist) { if (!authFileExist) {
fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile)); fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile));
@@ -105,4 +111,5 @@ export default async () => {
dotenv.config({ path: confFile }); dotenv.config({ path: confFile });
Logger.info('✌️ Init file down'); Logger.info('✌️ Init file down');
console.log('✌️ Init file down');
}; };
+27 -21
View File
@@ -1,32 +1,38 @@
import winston from 'winston'; import winston from 'winston';
import 'winston-daily-rotate-file';
import config from '../config'; import config from '../config';
import path from 'path';
const transports = []; const levelMap: Record<string, string> = {
if (process.env.NODE_ENV !== 'development') { info: '🔵',
transports.push(new winston.transports.Console()); warn: '🟡',
} else { error: '🔴',
transports.push( debug: '🔶'
new winston.transports.Console({
format: winston.format.combine(
winston.format.cli(),
winston.format.splat(),
),
}),
);
} }
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({ const LoggerInstance = winston.createLogger({
level: config.logs.level, level: config.logs.level,
levels: winston.config.npm.levels, levels: winston.config.npm.levels,
format: winston.format.combine( transports: [
winston.format.timestamp({ new winston.transports.DailyRotateFile({
format: 'YYYY-MM-DD HH:mm:ss', filename: path.join(config.systemLogPath, '%DATE%.log'),
}), ...defaultOptions,
winston.format.errors({ stack: true }), })
winston.format.splat(), ],
winston.format.json(),
),
transports,
}); });
export default LoggerInstance; export default LoggerInstance;
+1
View File
@@ -29,4 +29,5 @@ export default async ({ expressApp }: { expressApp: Application }) => {
expressApp.use(Sentry.Handlers.tracingHandler()); expressApp.use(Sentry.Handlers.tracingHandler());
Logger.info('✌️ Sentry loaded'); 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) => { process.on('SIGINT', (singal) => {
Logger.warn(`Server need close, singal ${singal}`); Logger.warn(`Server need close, singal ${singal}`);
console.warn(`Server need close, singal ${singal}`);
exitTime++; exitTime++;
if (exitTime >= 3) { if (exitTime >= 3) {
Logger.warn('Forcing server close'); Logger.warn('Forcing server close');
console.warn('Forcing server close');
clearTimeout(timer); clearTimeout(timer);
process.exit(1); process.exit(1);
} }
@@ -28,11 +30,13 @@ export default async ({ server }: { server: Server }) => {
process.on('uncaughtException', (error) => { process.on('uncaughtException', (error) => {
Logger.error('Uncaught exception:', error); Logger.error('Uncaught exception:', error);
console.error('Uncaught exception:', error);
process.exit(1); process.exit(1);
}); });
process.on('unhandledRejection', (reason, promise) => { 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); process.exit(1);
}); });
}; };
+2 -2
View File
@@ -5,7 +5,7 @@ import { Container } from 'typedi';
import SockService from '../services/sock'; import SockService from '../services/sock';
import config from '../config/index'; import config from '../config/index';
import fs from 'fs'; import fs from 'fs';
import { getPlatform } from '../config/util'; import { getPlatform, safeJSONParse } from '../config/util';
export default async ({ server }: { server: Server }) => { export default async ({ server }: { server: Server }) => {
const echo = sockJs.createServer({ prefix: '/api/ws', log: () => {} }); 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 platform = getPlatform(conn.headers['user-agent'] || '') || 'desktop';
const headerToken = conn.url.replace(`${conn.pathname}?token=`, ''); const headerToken = conn.url.replace(`${conn.pathname}?token=`, '');
if (data) { if (data) {
const { token = '', tokens = {} } = JSON.parse(data); const { token = '', tokens = {} } = safeJSONParse(data);
if (headerToken === token || tokens[platform] === headerToken) { if (headerToken === token || tokens[platform] === headerToken) {
conn.write(JSON.stringify({ type: 'ping', message: 'hanhh' })); conn.write(JSON.stringify({ type: 'ping', message: 'hanhh' }));
sockService.addClient(conn); sockService.addClient(conn);
+3 -1
View File
@@ -6,7 +6,7 @@ import { credentials } from '@grpc/grpc-js';
const app = express(); const app = express();
const client = new HealthClient( const client = new HealthClient(
`localhost:${config.cronPort}`, `0.0.0.0:${config.cronPort}`,
credentials.createInsecure(), credentials.createInsecure(),
); );
@@ -25,9 +25,11 @@ app
await require('./loaders/db').default(); await require('./loaders/db').default();
Logger.debug(`✌️ 公共服务启动成功!`); Logger.debug(`✌️ 公共服务启动成功!`);
console.debug(`✌️ 公共服务启动成功!`);
process.send?.('ready'); process.send?.('ready');
}) })
.on('error', (err) => { .on('error', (err) => {
Logger.error(err); Logger.error(err);
console.error(err);
process.exit(1); process.exit(1);
}); });
+8 -3
View File
@@ -22,13 +22,18 @@ const addCron = (
cmdStr = `${TASK_PREFIX}${cmdStr}`; cmdStr = `${TASK_PREFIX}${cmdStr}`;
} }
Logger.info(
'[schedule][创建定时任务], 任务ID: %s, cron: %s, 执行命令: %s',
id,
schedule,
command,
);
scheduleStacks.set( scheduleStacks.set(
id, id,
nodeSchedule.scheduleJob(id, schedule, async () => { nodeSchedule.scheduleJob(id, schedule, async () => {
Logger.info( Logger.info(
`当前时间: ${dayjs().format( `[schedule][准备运行任务] 命令: ${cmdStr}`,
'YYYY-MM-DD HH:mm:ss',
)},运行命令: ${cmdStr}`,
); );
runCron(`ID=${id} ${cmdStr}`); runCron(`ID=${id} ${cmdStr}`);
}), }),
+1 -1
View File
@@ -10,7 +10,7 @@ import config from '../config';
class Client { class Client {
private client = new CronClient( private client = new CronClient(
`localhost:${config.cronPort}`, `0.0.0.0:${config.cronPort}`,
credentials.createInsecure(), credentials.createInsecure(),
); );
+5
View File
@@ -1,6 +1,7 @@
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js'; import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
import { DeleteCronRequest, DeleteCronResponse } from '../protos/cron'; import { DeleteCronRequest, DeleteCronResponse } from '../protos/cron';
import { scheduleStacks } from './data'; import { scheduleStacks } from './data';
import Logger from '../loaders/logger';
const delCron = ( const delCron = (
call: ServerUnaryCall<DeleteCronRequest, DeleteCronResponse>, call: ServerUnaryCall<DeleteCronRequest, DeleteCronResponse>,
@@ -8,6 +9,10 @@ const delCron = (
) => { ) => {
for (const id of call.request.ids) { for (const id of call.request.ids) {
if (scheduleStacks.has(id)) { if (scheduleStacks.has(id)) {
Logger.info(
'[schedule][取消定时任务], 任务ID: %s',
id,
);
scheduleStacks.get(id)?.cancel(); scheduleStacks.get(id)?.cancel();
scheduleStacks.delete(id); scheduleStacks.delete(id);
} }
+1 -1
View File
@@ -10,7 +10,7 @@ const check = async (
switch (call.request.service) { switch (call.request.service) {
case 'cron': case 'cron':
const res = await promiseExec( 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')) { if (res.includes('200')) {
+2 -1
View File
@@ -11,7 +11,7 @@ const server = new Server();
server.addService(HealthService, { check }); server.addService(HealthService, { check });
server.addService(CronService, { addCron, delCron }); server.addService(CronService, { addCron, delCron });
server.bindAsync( server.bindAsync(
`localhost:${config.cronPort}`, `0.0.0.0:${config.cronPort}`,
ServerCredentials.createInsecure(), ServerCredentials.createInsecure(),
(err, port) => { (err, port) => {
if (err) { if (err) {
@@ -19,6 +19,7 @@ server.bindAsync(
} }
server.start(); server.start();
Logger.debug(`✌️ 定时服务启动成功!`); Logger.debug(`✌️ 定时服务启动成功!`);
console.debug(`✌️ 定时服务启动成功!`);
process.send?.('ready'); process.send?.('ready');
}, },
); );
+20 -18
View File
@@ -5,7 +5,7 @@ import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
import { exec, execSync } from 'child_process'; import { exec, execSync } from 'child_process';
import fs from 'fs'; import fs from 'fs';
import cron_parser from 'cron-parser'; 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 { promises, existsSync } from 'fs';
import { Op, where, col as colFn, FindOptions, fn } from 'sequelize'; import { Op, where, col as colFn, FindOptions, fn } from 'sequelize';
import path from 'path'; import path from 'path';
@@ -13,10 +13,11 @@ import { TASK_PREFIX, QL_PREFIX } from '../config/const';
import cronClient from '../schedule/client'; import cronClient from '../schedule/client';
import taskLimit from '../shared/pLimit'; import taskLimit from '../shared/pLimit';
import { spawn } from 'cross-spawn'; import { spawn } from 'cross-spawn';
import dayjs from 'dayjs';
@Service() @Service()
export default class CronService { export default class CronService {
constructor(@Inject('logger') private logger: winston.Logger) {} constructor(@Inject('logger') private logger: winston.Logger) { }
private isSixCron(cron: Crontab) { private isSixCron(cron: Crontab) {
const { schedule } = cron; const { schedule } = cron;
@@ -161,7 +162,7 @@ export default class CronService {
case 'In': case 'In':
q[Op.or] = [ q[Op.or] = [
{ {
[property]: value, [property]: Array.isArray(value) ? value : [value],
}, },
property === 'status' && value.includes(2) property === 'status' && value.includes(2)
? { isDisabled: 1 } ? { isDisabled: 1 }
@@ -172,7 +173,7 @@ export default class CronService {
q[Op.and] = [ q[Op.and] = [
{ {
[property]: { [property]: {
[Op.notIn]: value, [Op.notIn]: Array.isArray(value) ? value : [value],
}, },
}, },
property === 'status' && value.includes(2) property === 'status' && value.includes(2)
@@ -308,9 +309,9 @@ export default class CronService {
const searchText = params?.searchValue; const searchText = params?.searchValue;
const page = Number(params?.page || '0'); const page = Number(params?.page || '0');
const size = Number(params?.size || '0'); const size = Number(params?.size || '0');
const viewQuery = JSON.parse(params?.queryString || '{}'); const viewQuery = safeJSONParse(params?.queryString);
const filterQuery = JSON.parse(params?.filters || '{}'); const filterQuery = safeJSONParse(params?.filters);
const sorterQuery = JSON.parse(params?.sorter || '{}'); const sorterQuery = safeJSONParse(params?.sorter);
let query: any = {}; let query: any = {};
let order = [ let order = [
@@ -370,7 +371,7 @@ export default class CronService {
try { try {
await killTask(doc.pid); await killTask(doc.pid);
} catch (error) { } catch (error) {
this.logger.silly(error); this.logger.error(error);
} }
} }
} }
@@ -391,8 +392,14 @@ export default class CronService {
} }
let { id, command, log_path } = cron; let { id, command, log_path } = cron;
const absolutePath = path.resolve(config.logPath, `${log_path}`); const uniqPath = await getUniqPath(command, `${id}`);
const logFileExist = log_path && (await fileExist(absolutePath)); 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('Running job');
this.logger.silly('ID: ' + id); this.logger.silly('ID: ' + id);
@@ -412,26 +419,22 @@ export default class CronService {
cmdStr = `${cmdStr} now`; 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( await CrontabModel.update(
{ status: CrontabStatus.running, pid: cp.pid }, { status: CrontabStatus.running, pid: cp.pid, log_path: logPath },
{ where: { id } }, { where: { id } },
); );
cp.stderr.on('data', (data) => { cp.stderr.on('data', (data) => {
if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${data.toString()}`); fs.appendFileSync(`${absolutePath}`, `${data.toString()}`);
}
}); });
cp.on('error', (err) => { cp.on('error', (err) => {
if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`); fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
}
}); });
cp.on('exit', async (code, signal) => { cp.on('exit', async (code, signal) => {
this.logger.info( this.logger.info(
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`, `[panel][任务退出] 任务 ${command} 进程id: ${cp.pid}, 退出码 ${code}`,
); );
}); });
cp.on('close', async (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); fs.writeFileSync(config.crontabFile, crontab_string);
execSync(`crontab ${config.crontabFile}`); execSync(`crontab ${config.crontabFile}`);
+2 -4
View File
@@ -202,11 +202,9 @@ export default class EnvService {
let value = group let value = group
.map((x) => x.value) .map((x) => x.value)
.join('&') .join('&')
.replace(/(\\)[^n]/g, '\\\\') .replace(/'/g, "'\\''")
.replace(/(\\$)/, '\\\\')
.replace(/"/g, '\\"')
.trim(); .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) => { cp.stderr.on('data', async (data) => {
this.logger.info( this.logger.info(
'[执行任务失败] %s,时间:%s, 错误信息%j', '[panel][执行任务失败] 命令: %s, 错误信息: %j',
command, command,
new Date().toLocaleString(),
data.toString(), data.toString(),
); );
await callbacks.onError?.(data.toString()); await callbacks.onError?.(data.toString());
@@ -76,9 +75,8 @@ export default class ScheduleService {
cp.on('error', async (err) => { cp.on('error', async (err) => {
this.logger.error( this.logger.error(
'[创建任务失败] %s,时间:%s, 错误信息%j', '[panel][创建任务失败] 命令: %s, 错误信息: %j',
command, command,
new Date().toLocaleString(),
err, err,
); );
await callbacks.onError?.(JSON.stringify(err)); await callbacks.onError?.(JSON.stringify(err));
@@ -86,7 +84,7 @@ export default class ScheduleService {
cp.on('exit', async (code, signal) => { cp.on('exit', async (code, signal) => {
this.logger.info( this.logger.info(
`[任务退出] ${command} 进程id: ${cp.pid}退出码 ${code}`, `[panel][任务退出] ${command} 进程id: ${cp.pid}, 退出码 ${code}`,
); );
}); });
@@ -100,10 +98,9 @@ export default class ScheduleService {
resolve(null); resolve(null);
}); });
} catch (error) { } catch (error) {
await this.logger.error( this.logger.error(
'执行任务%s失败,时间:%s, 错误信息%j', '[panel][执行任务失败] 命令: %s, 错误信息: %j',
command, command,
new Date().toLocaleString(),
error, error,
); );
await callbacks.onError?.(JSON.stringify(error)); await callbacks.onError?.(JSON.stringify(error));
@@ -119,7 +116,7 @@ export default class ScheduleService {
) { ) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info( this.logger.info(
'[创建cron任务]任务ID: %scron: %s任务名: %s执行命令: %s', '[panel][创建cron任务], 任务ID: %s, cron: %s, 任务名: %s, 执行命令: %s',
_id, _id,
schedule, schedule,
name, name,
@@ -140,7 +137,7 @@ export default class ScheduleService {
async cancelCronTask({ id = 0, name }: ScheduleTaskType) { async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info('[取消定时任务]任务名%s', name); this.logger.info('[panel][取消定时任务], 任务名: %s', name);
if (this.scheduleStacks.has(_id)) { if (this.scheduleStacks.has(_id)) {
this.scheduleStacks.get(_id)?.cancel(); this.scheduleStacks.get(_id)?.cancel();
this.scheduleStacks.delete(_id); this.scheduleStacks.delete(_id);
@@ -155,7 +152,7 @@ export default class ScheduleService {
) { ) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info( this.logger.info(
'[创建interval任务]任务ID: %s任务名: %s执行命令: %s', '[panel][创建interval任务], 任务ID: %s, 任务名: %s, 执行命令: %s',
_id, _id,
name, name,
command, command,
@@ -167,9 +164,8 @@ export default class ScheduleService {
}, },
(err) => { (err) => {
this.logger.error( this.logger.error(
'执行任务%s失败,时间:%s, 错误信息%j', '[执行任务失败] 命令: %s, 错误信息: %j',
command, command,
new Date().toLocaleString(),
err, err,
); );
}, },
@@ -190,7 +186,7 @@ export default class ScheduleService {
async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) { async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id); 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); this.intervalSchedule.removeById(_id);
} }
+1 -2
View File
@@ -53,10 +53,9 @@ export default class ScriptService {
} }
public async stopScript(filePath: string, pid: number) { public async stopScript(filePath: string, pid: number) {
let str = '';
if (!pid) { if (!pid) {
const relativePath = path.relative(config.scriptPath, filePath); 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 { try {
await killTask(pid); await killTask(pid);
+1 -1
View File
@@ -58,7 +58,7 @@ export default class SshKeyService {
private generateSingleSshConfig(alias: string, host: string, proxy?: string) { private generateSingleSshConfig(alias: string, host: string, proxy?: string) {
if (host === 'github.com') { 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 const proxyStr = proxy
? ` ProxyCommand nc -v -x ${proxy} %h %p 2>/dev/null\n` ? ` ProxyCommand nc -v -x ${proxy} %h %p 2>/dev/null\n`
+1 -9
View File
@@ -301,17 +301,9 @@ export default class SubscriptionService {
try { try {
await killTask(doc.pid); await killTask(doc.pid);
} catch (error) { } 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( await SubscriptionModel.update(
+28
View File
@@ -21,11 +21,14 @@ import {
parseContentVersion, parseContentVersion,
parseVersion, parseVersion,
promiseExec, promiseExec,
readDirs,
} from '../config/util'; } from '../config/util';
import { TASK_COMMAND } from '../config/const'; import { TASK_COMMAND } from '../config/const';
import taskLimit from '../shared/pLimit'; import taskLimit from '../shared/pLimit';
import tar from 'tar'; import tar from 'tar';
import path from 'path'; import path from 'path';
import fs from 'fs';
import { sum } from 'lodash';
@Service() @Service()
export default class SystemService { export default class SystemService {
@@ -275,4 +278,29 @@ export default class SystemService {
return { code: 400, message: error.message }; 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, fileExist,
getNetIp, getNetIp,
getPlatform, getPlatform,
safeJSONParse,
} from '../config/util'; } from '../config/util';
import config from '../config'; import config from '../config';
import * as fs from 'fs'; import * as fs from 'fs';
@@ -327,7 +328,7 @@ export default class UserService {
private getAuthInfo() { private getAuthInfo() {
const content = fs.readFileSync(config.authConfigFile, 'utf8'); const content = fs.readFileSync(config.authConfigFile, 'utf8');
return JSON.parse(content || '{}'); return safeJSONParse(content);
} }
private updateAuthInfo(authInfo: any, info: any) { private updateAuthInfo(authInfo: any, info: any) {
+13
View File
@@ -1,12 +1,22 @@
import pLimit from 'p-limit'; import pLimit from 'p-limit';
import os from 'os'; import os from 'os';
import { AuthDataType, AuthModel } from '../data/auth'; import { AuthDataType, AuthModel } from '../data/auth';
import Logger from '../loaders/logger';
import dayjs from 'dayjs';
class TaskLimit { class TaskLimit {
private oneLimit = pLimit(1); private oneLimit = pLimit(1);
private updateLogLimit = pLimit(1); private updateLogLimit = pLimit(1);
private cpuLimit = pLimit(Math.max(os.cpus().length, 4)); private cpuLimit = pLimit(Math.max(os.cpus().length, 4));
get cpuLimitActiveCount() {
return this.cpuLimit.activeCount;
}
get cpuLimitPendingCount() {
return this.cpuLimit.pendingCount;
}
constructor() { constructor() {
this.setCustomLimit(); this.setCustomLimit();
} }
@@ -26,6 +36,9 @@ class TaskLimit {
} }
public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> { public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
Logger.info(
`[schedule][任务加入队列] 运行中任务数: ${this.cpuLimitActiveCount}, 等待中任务数: ${this.cpuLimitPendingCount}`,
);
return this.cpuLimit(fn); return this.cpuLimit(fn);
} }
+4 -6
View File
@@ -5,29 +5,27 @@ import Logger from '../loaders/logger';
export function runCron(cmd: string): Promise<number> { export function runCron(cmd: string): Promise<number> {
return taskLimit.runWithCpuLimit(() => { return taskLimit.runWithCpuLimit(() => {
return new Promise(async (resolve: any) => { return new Promise(async (resolve: any) => {
Logger.silly('运行命令: ' + cmd); Logger.info(`[schedule][开始执行任务] 运行命令: ${cmd}`);
const cp = spawn(cmd, { shell: '/bin/bash' }); const cp = spawn(cmd, { shell: '/bin/bash' });
cp.stderr.on('data', (data) => { cp.stderr.on('data', (data) => {
Logger.info( Logger.info(
'[执行任务失败] %s,时间:%s, 错误信息%j', '[schedule][执行任务失败] 命令: %s, 错误信息: %j',
cmd, cmd,
new Date().toLocaleString(),
data.toString(), data.toString(),
); );
}); });
cp.on('error', (err) => { cp.on('error', (err) => {
Logger.error( Logger.error(
'[创建任务失败] %s,时间:%s, 错误信息%j', '[schedule][创建任务失败] 命令: %s, 错误信息: %j',
cmd, cmd,
new Date().toLocaleString(),
err, err,
); );
}); });
cp.on('close', async (code) => { cp.on('close', async (code) => {
Logger.info(`[任务退出] ${cmd} 进程id: ${cp.pid} 退出退出码 ${code}`); Logger.info(`[schedule][任务退出] ${cmd} 进程id: ${cp.pid} 退出, 退出码 ${code}`);
resolve(); resolve();
}); });
}); });
+3 -2
View File
@@ -96,6 +96,7 @@
"typedi": "^0.10.0", "typedi": "^0.10.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"winston": "^3.6.0", "winston": "^3.6.0",
"winston-daily-rotate-file": "^4.7.1",
"yargs": "^17.3.1" "yargs": "^17.3.1"
}, },
"devDependencies": { "devDependencies": {
@@ -127,6 +128,8 @@
"@types/sockjs-client": "^1.5.1", "@types/sockjs-client": "^1.5.1",
"@types/tar": "^6.1.5", "@types/tar": "^6.1.5",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@uiw/codemirror-extensions-langs": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9",
"@umijs/max": "^4.0.72", "@umijs/max": "^4.0.72",
"@umijs/ssr-darkreader": "^4.9.45", "@umijs/ssr-darkreader": "^4.9.45",
"ahooks": "^3.7.8", "ahooks": "^3.7.8",
@@ -134,7 +137,6 @@
"antd": "^4.24.8", "antd": "^4.24.8",
"antd-img-crop": "^4.2.3", "antd-img-crop": "^4.2.3",
"axios": "^1.4.0", "axios": "^1.4.0",
"codemirror": "^5.65.2",
"compression-webpack-plugin": "9.2.0", "compression-webpack-plugin": "9.2.0",
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
@@ -148,7 +150,6 @@
"rc-tween-one": "^3.0.6", "rc-tween-one": "^3.0.6",
"rc-virtual-list": "3.5.3", "rc-virtual-list": "3.5.3",
"react": "18.2.0", "react": "18.2.0",
"react-codemirror2": "^7.2.1",
"react-copy-to-clipboard": "^5.1.0", "react-copy-to-clipboard": "^5.1.0",
"react-diff-viewer": "^3.1.1", "react-diff-viewer": "^3.1.1",
"react-dnd": "^14.0.2", "react-dnd": "^14.0.2",
+649 -21
View File
@@ -124,6 +124,9 @@ dependencies:
winston: winston:
specifier: ^3.6.0 specifier: ^3.6.0
version: 3.9.0 version: 3.9.0
winston-daily-rotate-file:
specifier: ^4.7.1
version: 4.7.1(winston@3.9.0)
yargs: yargs:
specifier: ^17.3.1 specifier: ^17.3.1
version: 17.7.2 version: 17.7.2
@@ -213,6 +216,12 @@ devDependencies:
'@types/uuid': '@types/uuid':
specifier: ^8.3.4 specifier: ^8.3.4
version: 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': '@umijs/max':
specifier: ^4.0.72 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) 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: axios:
specifier: ^1.4.0 specifier: ^1.4.0
version: 1.4.0 version: 1.4.0
codemirror:
specifier: ^5.65.2
version: 5.65.13
compression-webpack-plugin: compression-webpack-plugin:
specifier: 9.2.0 specifier: 9.2.0
version: 9.2.0(webpack@5.85.1) version: 9.2.0(webpack@5.85.1)
@@ -276,9 +282,6 @@ devDependencies:
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 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: react-copy-to-clipboard:
specifier: ^5.1.0 specifier: ^5.1.0
version: 5.1.0(react@18.2.0) version: 5.1.0(react@18.2.0)
@@ -2762,6 +2765,298 @@ packages:
tinycolor2: 1.6.0 tinycolor2: 1.6.0
dev: true 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: /@colors/colors@1.5.0:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'} engines: {node: '>=0.1.90'}
@@ -3562,6 +3857,114 @@ packages:
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
dev: true 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): /@loadable/component@5.15.2(react@18.1.0):
resolution: {integrity: sha512-ryFAZOX5P2vFkUdzaAtTG88IGnr9qxSdvLRvJySXcUA4B4xVWurUNADu3AnKPksxOZajljqTrDEDcYjeL4lvLw==} resolution: {integrity: sha512-ryFAZOX5P2vFkUdzaAtTG88IGnr9qxSdvLRvJySXcUA4B4xVWurUNADu3AnKPksxOZajljqTrDEDcYjeL4lvLw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -3644,6 +4047,19 @@ packages:
state-local: 1.0.7 state-local: 1.0.7
dev: true 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: /@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3:
resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==}
requiresBuild: true requiresBuild: true
@@ -3878,6 +4294,82 @@ packages:
react: 18.2.0 react: 18.2.0
dev: true 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: /@sentry-internal/tracing@7.54.0:
resolution: {integrity: sha512-JsyhZ0wWZ+VqbHJg+azqRGdYJDkcI5R9+pnkO6SzbzxrRewqMAIwzkpPee3oI7vG99uhMEkOkMjHu0nQGwkOQw==} resolution: {integrity: sha512-JsyhZ0wWZ+VqbHJg+azqRGdYJDkcI5R9+pnkO6SzbzxrRewqMAIwzkpPee3oI7vG99uhMEkOkMjHu0nQGwkOQw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -4828,6 +5320,101 @@ packages:
eslint-visitor-keys: 3.4.1 eslint-visitor-keys: 3.4.1
dev: true 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: /@umijs/ast@4.0.72:
resolution: {integrity: sha512-WatRvU09vsx4Hlu5hemPA7a+QK4pJvzmQz/9LxN/KVgn+wZXi717qHFLu5eoV6XO7HlFZaEBGq2aHpDj0ngA8w==} resolution: {integrity: sha512-WatRvU09vsx4Hlu5hemPA7a+QK4pJvzmQz/9LxN/KVgn+wZXi717qHFLu5eoV6XO7HlFZaEBGq2aHpDj0ngA8w==}
dependencies: dependencies:
@@ -6657,8 +7244,26 @@ packages:
mimic-response: 1.0.1 mimic-response: 1.0.1
dev: false dev: false
/codemirror@5.65.13: /codemirror-lang-mermaid@0.2.2:
resolution: {integrity: sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==} 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 dev: true
/color-convert@1.9.3: /color-convert@1.9.3:
@@ -6970,6 +7575,10 @@ packages:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true dev: true
/crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
dev: true
/cron-parser@4.8.1: /cron-parser@4.8.1:
resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==} resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -8273,6 +8882,12 @@ packages:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: true 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: /file-uri-to-path@2.0.0:
resolution: {integrity: sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==} resolution: {integrity: sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -10787,6 +11402,11 @@ packages:
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
dev: true dev: true
/object-hash@2.2.0:
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
engines: {node: '>= 6'}
dev: false
/object-inspect@1.12.3: /object-inspect@1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
@@ -12903,19 +13523,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: true 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): /react-copy-to-clipboard@5.1.0(react@18.2.0):
resolution: {integrity: sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==} resolution: {integrity: sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==}
peerDependencies: peerDependencies:
@@ -14307,6 +14914,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/style-mod@4.0.3:
resolution: {integrity: sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==}
dev: true
/style-search@0.1.0: /style-search@0.1.0:
resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
dev: true dev: true
@@ -15308,6 +15919,10 @@ packages:
acorn-walk: 8.2.0 acorn-walk: 8.2.0
dev: true dev: true
/w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
dev: true
/walker@1.0.8: /walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
dependencies: dependencies:
@@ -15489,6 +16104,19 @@ packages:
semver: 5.7.1 semver: 5.7.1
dev: true 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: /winston-transport@4.5.0:
resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==}
engines: {node: '>= 6.4.0'} engines: {node: '>= 6.4.0'}
+1 -1
View File
@@ -456,7 +456,7 @@ class WeCom:
return respone["errmsg"] return respone["errmsg"]
def send_mpnews(self, title, message, media_id, touser="@all"): 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 = { send_values = {
"touser": touser, "touser": touser,
"msgtype": "mpnews", "msgtype": "mpnews",
+1 -1
View File
@@ -36,7 +36,7 @@ add_cron_api() {
local sub_id="$4" local sub_id="$4"
fi fi
if [[ ! $sub_id ]];then if [[ ! $sub_id ]]; then
sub_id="null" sub_id="null"
fi fi
+19 -11
View File
@@ -86,6 +86,11 @@ check_server() {
fi fi
} }
env_str_to_array() {
local IFS="&"
read -ra array <<<"${!env_param}"
}
## 正常运行单个脚本,$1:传入参数 ## 正常运行单个脚本,$1:传入参数
run_normal() { run_normal() {
local file_param=$1 local file_param=$1
@@ -113,8 +118,7 @@ run_concurrent() {
exit 1 exit 1
fi fi
local envs=$(eval echo "\$${env_param}") env_str_to_array
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g") local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr)) local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]})) runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
@@ -125,11 +129,13 @@ run_concurrent() {
let n++ let n++
done done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g') local cookieStr=$(
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr} IFS="&"
echo "${array_run[*]}"
)
[[ ! -z $cookieStr ]] && export "${env_param}=${cookieStr}"
local envs=$(eval echo "\$${env_param}") env_str_to_array
local array=($(echo $envs | sed 's/&/ /g'))
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%3N") single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%3N")
cd $dir_scripts cd $dir_scripts
@@ -139,7 +145,7 @@ run_concurrent() {
file_param=${file_param/$relative_path\//} file_param=${file_param/$relative_path\//}
fi fi
for i in "${!array[@]}"; do 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" single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval $timeoutCmd $which_program $file_param &>$single_log_path & eval $timeoutCmd $which_program $file_param &>$single_log_path &
done done
@@ -161,8 +167,7 @@ run_designated() {
exit 1 exit 1
fi fi
local envs=$(eval echo "\$${env_param}") env_str_to_array
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g") local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr)) local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]})) runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
@@ -173,8 +178,11 @@ run_designated() {
let n++ let n++
done done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g') local cookieStr=$(
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr} IFS="&"
echo "${array_run[*]}"
)
[[ ! -z $cookieStr ]] && export "${env_param}=${cookieStr}"
cd $dir_scripts cd $dir_scripts
local relative_path="${file_param%/*}" local relative_path="${file_param%/*}"
+1 -1
View File
@@ -296,7 +296,7 @@ git_clone_scripts() {
set_proxy "$proxy" set_proxy "$proxy"
git clone --depth=1 $part_cmd $url $dir git clone --depth=1 $part_cmd $url $dir 2>&1
exit_status=$? exit_status=$?
unset_proxy unset_proxy
+3 -3
View File
@@ -31,7 +31,7 @@ output_list_add_drop() {
local list=$1 local list=$1
local type=$2 local type=$2
if [[ -s $list ]]; then if [[ -s $list ]]; then
echo -e "检测到有${type}的定时任务\n" echo -e "检测到有${type}的定时任务:"
cat $list cat $list
echo echo
fi fi
@@ -134,10 +134,10 @@ update_repo() {
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}" git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}"
if [[ $exit_status -eq 0 ]]; then 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" diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
else else
echo -e "\n更新${repo_path}失败,请检查网络...\n" echo -e "\n拉取 ${uniq_path} 失败,请检查网络...\n"
fi fi
} }
+3
View File
@@ -1,4 +1,5 @@
const baseUrl = window.__ENV__QlBaseUrl || '/'; const baseUrl = window.__ENV__QlBaseUrl || '/';
import { setLocale } from '@umijs/max';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
export function rootContainer(container: any) { export function rootContainer(container: any) {
@@ -9,6 +10,7 @@ export function rootContainer(container: any) {
let currentLocale = intl.determineLocale({ let currentLocale = intl.determineLocale({
urlLocaleKey: 'lang', urlLocaleKey: 'lang',
cookieLocaleKey: 'lang', cookieLocaleKey: 'lang',
localStorageLocaleKey: 'lang',
}).slice(0, 2); }).slice(0, 2);
if (!currentLocale || !Object.keys(locales).includes(currentLocale)) { if (!currentLocale || !Object.keys(locales).includes(currentLocale)) {
@@ -16,6 +18,7 @@ export function rootContainer(container: any) {
} }
intl.init({ currentLocale, locales }); intl.init({ currentLocale, locales });
setLocale(currentLocale === 'zh' ? 'zh-CN' : 'en-US');
return container; return container;
} }
+31 -9
View File
@@ -1,6 +1,5 @@
@import '~antd/es/style/themes/default.less'; @import '~antd/es/style/themes/default.less';
@import '~@/styles/variable.less'; @import '~@/styles/variable.less';
@import '~codemirror/lib/codemirror.css';
@font-face { @font-face {
font-family: 'Source Code Pro'; font-family: 'Source Code Pro';
@@ -32,9 +31,24 @@ body {
} }
.log-modal { .log-modal {
.ant-modal { &.ant-modal {
padding-bottom: 0 !important; max-width: 1000px !important;
width: 580px !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 { pre {
@@ -87,10 +101,12 @@ body {
} }
.ant-tooltip { .ant-tooltip {
max-width: 500px !important; max-width: 300px !important;
.ant-tooltip-inner { .ant-tooltip-inner {
word-break: break-all !important; 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 { .ant-tooltip {
max-width: 250px !important; max-width: 300px !important;
} }
} }
@@ -368,9 +390,9 @@ select:-webkit-autofill:focus {
} }
pre { pre {
word-break: break-all !important; word-break: break-all;
white-space: break-spaces !important; white-space: break-spaces;
padding: 0 !important; padding: 0;
} }
.virtuallist { .virtuallist {
-3
View File
@@ -32,9 +32,6 @@ import {
import SockJS from 'sockjs-client'; import SockJS from 'sockjs-client';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { init } from '../utils/init'; import { init } from '../utils/init';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/python/python';
import 'codemirror/mode/shell/shell';
export interface SharedContext { export interface SharedContext {
headerStyle: React.CSSProperties; headerStyle: React.CSSProperties;
+4 -1
View File
@@ -448,5 +448,8 @@
"编辑订阅": "Edit Subscription", "编辑订阅": "Edit Subscription",
"Subscription表达式格式有误": "Incorrect Subscription Expression Format", "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)", "一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "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码", "gotify的消息应用token码": "gotify的消息应用token码",
"推送消息的优先级": "推送消息的优先级", "推送消息的优先级": "推送消息的优先级",
"chat的url地址": "chat的url地址", "chat的url地址": "chat的url地址",
@@ -349,21 +349,21 @@
"PushDeer的Keyhttps://github.com/easychen/pushdeer": "PushDeer的Keyhttps://github.com/easychen/pushdeer", "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", "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的信息IP/设备码,例如:https://api.day.app/XXXXXXXX": "Bark的信息IP/设备码,例如:https://api.day.app/XXXXXXXX",
"BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)": "BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)", "BARK推送图标自定义推送图标 (需iOS15或以上才能显示)": "BARK推送图标自定义推送图标 (需iOS15或以上才能显示)",
"BARK推送铃声,铃声列表去APP查看复制填写": "BARK推送铃声,铃声列表去APP查看复制填写", "BARK推送铃声铃声列表去APP查看复制填写": "BARK推送铃声铃声列表去APP查看复制填写",
"BARK推送消息的分组, 默认为qinglong": "BARK推送消息的分组, 默认为qinglong", "BARK推送消息的分组默认为qinglong": "BARK推送消息的分组默认为qinglong",
"telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw": "telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw", "telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw": "telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw",
"telegram用户的id,例如:129xxx206": "telegram用户的id,例如:129xxx206", "telegram用户的id,例如:129xxx206": "telegram用户的id,例如:129xxx206",
"代理IP": "代理IP", "代理IP": "代理IP",
"代理端口": "代理端口", "代理端口": "代理端口",
"telegram代理配置认证参数, 用户名与密码用英文冒号连接 user:password": "telegram代理配置认证参数, 用户名与密码用英文冒号连接 user:password", "telegram代理配置认证参数用户名与密码用英文冒号连接 user:password": "telegram代理配置认证参数, 用户名与密码用英文冒号连接 user:password",
"telegram api自建的反向代理地址,默认tg官方api": "telegram api自建的反向代理地址,默认tg官方api", "telegram api自建的反向代理地址,默认tg官方api": "telegram api自建的反向代理地址,默认tg官方api",
"钉钉机器人webhook token,例如:5a544165465465645d0f31dca676e7bd07415asdasd": "钉钉机器人webhook token,例如:5a544165465465645d0f31dca676e7bd07415asdasd", "钉钉机器人webhook token,例如:5a544165465465645d0f31dca676e7bd07415asdasd": "钉钉机器人webhook token,例如:5a544165465465645d0f31dca676e7bd07415asdasd",
"密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串": "密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串", "密钥,机器人安全设置页面,加签一栏下面显示的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", "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", "密钥key智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql": "密钥key,智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql",
"发送的目标,群组或者好友": "发送的目标,群组或者好友", "发送的目标,群组或者好友": "发送的目标,群组或者好友",
"请输入要发送的目标": "请输入要发送的目标", "请输入要发送的目标": "请输入要发送的目标",
"群聊": "群聊", "群聊": "群聊",
@@ -448,5 +448,8 @@
"编辑订阅": "编辑订阅", "编辑订阅": "编辑订阅",
"Subscription表达式格式有误": "Subscription表达式格式有误", "Subscription表达式格式有误": "Subscription表达式格式有误",
"一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)", "一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)": "一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)",
"登录已过期,请重新登录": "登录已过期,请重新登录" "登录已过期,请重新登录": "登录已过期,请重新登录",
"系统日志": "系统日志",
"主题": "主题",
"语言": "语言"
} }
+5 -9
View File
@@ -11,9 +11,10 @@ import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout'; import { PageContainer } from '@ant-design/pro-layout';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
import Editor from '@monaco-editor/react'; 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 { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts'; import { SharedContext } from '@/layouts';
import { langs } from '@uiw/codemirror-extensions-langs';
const Config = () => { const Config = () => {
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>(); const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
@@ -104,16 +105,11 @@ const Config = () => {
{isPhone ? ( {isPhone ? (
<CodeMirror <CodeMirror
value={value} value={value}
options={{ theme={theme.includes('dark') ? 'dark' : 'light'}
lineNumbers: true, extensions={[langs.shell()]}
styleActiveLine: true, onChange={(value) => {
matchBrackets: true,
mode: 'shell',
}}
onBeforeChange={(editor, data, value) => {
setValue(value); setValue(value);
}} }}
onChange={(editor, data, value) => {}}
/> />
) : ( ) : (
<Editor <Editor
+5 -13
View File
@@ -94,7 +94,7 @@
.log-item { .log-item {
cursor: pointer; cursor: pointer;
&:hover { &: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'] { body[data-mode='desktop'] {
.crontab-wrapper { .crontab-wrapper {
tbody .ant-table-cell { tbody .ant-table-cell {
@@ -205,3 +193,7 @@ body[data-mode='desktop'] {
} }
} }
} }
.cron.pinned-cron > td {
background: #f2f2f2;
}
+19 -53
View File
@@ -68,53 +68,25 @@ const Crontab = () => {
title: intl.get('名称'), title: intl.get('名称'),
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
fixed: isPhone ? undefined : 'left', fixed: 'left',
width: 120, width: 120,
render: (text: string, record: any) => ( render: (text: string, record: any) => (
<> <Paragraph
style={{
wordBreak: 'break-all',
marginBottom: 0,
}}
ellipsis={{ tooltip: text, rows: 2 }}
>
<a <a
onClick={() => { onClick={() => {
setDetailCron(record); setDetailCron(record);
setIsDetailModalVisible(true); 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 || '-'} {record.name || '-'}
</Popover>
) : (
record.name || '-'
)}
{record.isPinned ? (
<span>
<PushpinOutlined />
</span>
) : (
''
)}
</a> </a>
</> </Paragraph>
), ),
sorter: { sorter: {
compare: (a, b) => a?.name?.localeCompare(b?.name), compare: (a, b) => a?.name?.localeCompare(b?.name),
@@ -124,7 +96,7 @@ const Crontab = () => {
title: intl.get('命令/脚本'), title: intl.get('命令/脚本'),
dataIndex: 'command', dataIndex: 'command',
key: 'command', key: 'command',
width: 200, width: 240,
render: (text, record) => { render: (text, record) => {
return ( return (
<Paragraph <Paragraph
@@ -214,7 +186,7 @@ const Crontab = () => {
}, },
{ {
title: intl.get('最后运行时长'), title: intl.get('最后运行时长'),
width: 180, width: 167,
dataIndex: 'last_running_time', dataIndex: 'last_running_time',
key: 'last_running_time', key: 'last_running_time',
sorter: { sorter: {
@@ -232,7 +204,7 @@ const Crontab = () => {
title: intl.get('最后运行时间'), title: intl.get('最后运行时间'),
dataIndex: 'last_execution_time', dataIndex: 'last_execution_time',
key: 'last_execution_time', key: 'last_execution_time',
width: 150, width: 141,
sorter: { sorter: {
compare: (a, b) => { compare: (a, b) => {
return (a.last_execution_time || 0) - (b.last_execution_time || 0); return (a.last_execution_time || 0) - (b.last_execution_time || 0);
@@ -259,7 +231,7 @@ const Crontab = () => {
}, },
{ {
title: intl.get('下次运行时间'), title: intl.get('下次运行时间'),
width: 150, width: 144,
sorter: { sorter: {
compare: (a: any, b: any) => { compare: (a: any, b: any) => {
return a.nextRunTime - b.nextRunTime; return a.nextRunTime - b.nextRunTime;
@@ -276,7 +248,7 @@ const Crontab = () => {
}, },
{ {
title: intl.get('关联订阅'), title: intl.get('关联订阅'),
width: 190, width: 185,
render: (text, record: any) => render: (text, record: any) =>
record.sub_id ? ( record.sub_id ? (
<Name <Name
@@ -292,46 +264,40 @@ const Crontab = () => {
{ {
title: intl.get('操作'), title: intl.get('操作'),
key: 'action', key: 'action',
width: 130, width: 140,
fixed: isPhone ? undefined : 'right', fixed: isPhone ? undefined : 'right',
render: (text, record, index) => { render: (text, record, index) => {
const isPc = !isPhone; const isPc = !isPhone;
return ( return (
<Space size="middle"> <Space size="middle">
{record.status === CrontabStatus.idle && ( {record.status === CrontabStatus.idle && (
<Tooltip title={isPc ? intl.get('运行') : ''}>
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
runCron(record, index); runCron(record, index);
}} }}
> >
<PlayCircleOutlined /> {intl.get('运行')}
</a> </a>
</Tooltip>
)} )}
{record.status !== CrontabStatus.idle && ( {record.status !== CrontabStatus.idle && (
<Tooltip title={isPc ? intl.get('停止') : ''}>
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
stopCron(record, index); stopCron(record, index);
}} }}
> >
<PauseCircleOutlined /> {intl.get('停止')}
</a> </a>
</Tooltip>
)} )}
<Tooltip title={isPc ? intl.get('日志') : ''}>
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setLogCron({ ...record, timestamp: Date.now() }); setLogCron({ ...record, timestamp: Date.now() });
}} }}
> >
<FileTextOutlined /> {intl.get('日志')}
</a> </a>
</Tooltip>
<MoreBtn key="more" record={record} index={index} /> <MoreBtn key="more" record={record} index={index} />
</Space> </Space>
); );
@@ -1041,7 +1007,7 @@ const Crontab = () => {
dataSource={value} dataSource={value}
rowKey="id" rowKey="id"
size="middle" size="middle"
scroll={{ x: 1000, y: tableScrollHeight }} scroll={{ x: 1200, y: tableScrollHeight }}
loading={loading} loading={loading}
rowSelection={rowSelection} rowSelection={rowSelection}
rowClassName={getRowClassName} rowClassName={getRowClassName}
+2 -6
View File
@@ -50,8 +50,8 @@ const CronLogModal = ({
log && !logEnded(log) && !log.includes('任务未运行'), log && !logEnded(log) && !log.includes('任务未运行'),
); );
setExecuting(hasNext); setExecuting(hasNext);
autoScroll();
if (hasNext) { if (hasNext) {
autoScroll();
setTimeout(() => { setTimeout(() => {
getCronLog(); getCronLog();
}, 2000); }, 2000);
@@ -74,7 +74,7 @@ const CronLogModal = ({
document document
.querySelector('#log-flag')! .querySelector('#log-flag')!
.scrollIntoView({ behavior: 'smooth' }); .scrollIntoView({ behavior: 'smooth' });
}, 1000); }, 600);
}; };
const cancel = () => { const cancel = () => {
@@ -124,9 +124,6 @@ const CronLogModal = ({
open={visible} open={visible}
centered centered
className="log-modal" className="log-modal"
bodyStyle={{
minHeight: '300px',
}}
forceRender forceRender
onOk={() => cancel()} onOk={() => cancel()}
onCancel={() => cancel()} onCancel={() => cancel()}
@@ -145,7 +142,6 @@ const CronLogModal = ({
isPhone isPhone
? { ? {
fontFamily: 'Source Code Pro', fontFamily: 'Source Code Pro',
width: 375,
zoom: 0.83, zoom: 0.83,
} }
: {} : {}
+1 -1
View File
@@ -83,7 +83,7 @@ const CronModal = ({
> >
<Input.TextArea <Input.TextArea
rows={4} rows={4}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get( placeholder={intl.get(
'支持输入脚本路径/任意系统可执行命令/task 脚本路径', '支持输入脚本路径/任意系统可执行命令/task 脚本路径',
)} )}
+6 -6
View File
@@ -400,7 +400,7 @@ const Dependence = () => {
const { type, message, references } = socketMessage; const { type, message, references } = socketMessage;
if ( if (
type === 'installDependence' && type === 'installDependence' &&
message.includes(intl.get('开始时间')) && message.includes('开始时间') &&
references.length > 0 references.length > 0
) { ) {
const result = [...value]; const result = [...value];
@@ -409,7 +409,7 @@ const Dependence = () => {
if (index !== -1) { if (index !== -1) {
result.splice(index, 1, { result.splice(index, 1, {
...value[index], ...value[index],
status: message.includes(intl.get('安装')) ? Status.安装中 : Status.删除中, status: message.includes('安装') ? Status.安装中 : Status.删除中,
}); });
} }
} }
@@ -417,14 +417,14 @@ const Dependence = () => {
} }
if ( if (
type === 'installDependence' && type === 'installDependence' &&
message.includes(intl.get('结束时间')) && message.includes('结束时间') &&
references.length > 0 references.length > 0
) { ) {
let status; let status;
if (message.includes(intl.get('安装'))) { if (message.includes('安装')) {
status = message.includes(intl.get('成功')) ? Status.已安装 : Status.安装失败; status = message.includes('成功') ? Status.已安装 : Status.安装失败;
} else { } else {
status = message.includes(intl.get('成功')) ? Status.已删除 : Status.删除失败; status = message.includes('成功') ? Status.已删除 : Status.删除失败;
} }
const result = [...value]; const result = [...value];
for (let i = 0; i < references.length; i++) { for (let i = 0; i < references.length; i++) {
+6 -10
View File
@@ -56,8 +56,8 @@ const DependenceLogModal = ({
) { ) {
const log = (data.log || []).join('') as string; const log = (data.log || []).join('') as string;
setValue(log); setValue(log);
setExecuting(!log.includes(intl.get('结束时间'))); setExecuting(!log.includes('结束时间'));
setIsRemoveFailed(log.includes(intl.get('删除失败'))); setIsRemoveFailed(log.includes('删除失败'));
} }
}) })
.finally(() => { .finally(() => {
@@ -103,9 +103,9 @@ const DependenceLogModal = ({
references.length > 0 && references.length > 0 &&
references.includes(dependence.id) references.includes(dependence.id)
) { ) {
if (message.includes(intl.get('结束时间'))) { if (message.includes('结束时间')) {
setExecuting(false); setExecuting(false);
setIsRemoveFailed(message.includes(intl.get('删除失败'))); setIsRemoveFailed(message.includes('删除失败'));
} }
setValue(`${value}${message}`); setValue(`${value}${message}`);
} }
@@ -121,11 +121,6 @@ const DependenceLogModal = ({
open={visible} open={visible}
centered centered
className="log-modal" className="log-modal"
bodyStyle={{
overflowY: 'auto',
maxHeight: 'calc(70vh - var(--vh-offset, 0px))',
minHeight: '300px',
}}
forceRender forceRender
onOk={() => cancel()} onOk={() => cancel()}
onCancel={() => cancel()} onCancel={() => cancel()}
@@ -135,6 +130,7 @@ const DependenceLogModal = ({
</Button>, </Button>,
]} ]}
> >
<div className="log-container">
{loading ? ( {loading ? (
<PageLoading /> <PageLoading />
) : ( ) : (
@@ -143,7 +139,6 @@ const DependenceLogModal = ({
isPhone isPhone
? { ? {
fontFamily: 'Source Code Pro', fontFamily: 'Source Code Pro',
width: 375,
zoom: 0.83, zoom: 0.83,
} }
: {} : {}
@@ -152,6 +147,7 @@ const DependenceLogModal = ({
<Ansi>{value}</Ansi> <Ansi>{value}</Ansi>
</pre> </pre>
)} )}
</div>
</Modal> </Modal>
); );
}; };
+1 -1
View File
@@ -130,7 +130,7 @@ const DependenceModal = ({
> >
<Input.TextArea <Input.TextArea
rows={4} rows={4}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入依赖名称')} placeholder={intl.get('请输入依赖名称')}
/> />
</Form.Item> </Form.Item>
+44 -14
View File
@@ -1,4 +1,4 @@
import intl from 'react-intl-universal' import intl from 'react-intl-universal';
import React, { import React, {
useCallback, useCallback,
useRef, useRef,
@@ -42,7 +42,7 @@ import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import Copy from '../../components/copy'; import Copy from '../../components/copy';
import { useVT } from 'virtualizedtableforantd4'; import { useVT } from 'virtualizedtableforantd4';
const { Text } = Typography; const { Paragraph } = Typography;
const { Search } = Input; const { Search } = Input;
enum Status { enum Status {
@@ -149,7 +149,7 @@ const Env = () => {
title: intl.get('状态'), title: intl.get('状态'),
key: 'status', key: 'status',
dataIndex: 'status', dataIndex: 'status',
width: 80, width: 100,
filters: [ filters: [
{ {
text: intl.get('已启用'), text: intl.get('已启用'),
@@ -186,7 +186,11 @@ const Env = () => {
</Tooltip> </Tooltip>
<Tooltip <Tooltip
title={ title={
isPc ? (record.status === Status. ? intl.get('启用') : intl.get('禁用')) : '' isPc
? record.status === Status.
? intl.get('启用')
: intl.get('禁用')
: ''
} }
> >
<a onClick={() => enabledOrDisabledEnv(record, index)}> <a onClick={() => enabledOrDisabledEnv(record, index)}>
@@ -232,14 +236,24 @@ const Env = () => {
const enabledOrDisabledEnv = (record: any, index: number) => { const enabledOrDisabledEnv = (record: any, index: number) => {
Modal.confirm({ Modal.confirm({
title: `确认${record.status === Status. ? intl.get('启用') : intl.get('禁用')}`, title: `确认${
record.status === Status. ? intl.get('启用') : intl.get('禁用')
}`,
content: ( content: (
<> <>
{intl.get('确认')}{record.status === Status. ? intl.get('启用') : intl.get('禁用')} {intl.get('确认')}
{record.status === Status.
? intl.get('启用')
: intl.get('禁用')}
Env{' '} 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} {record.value}
</Text>{' '} </Paragraph>{' '}
{intl.get('吗')} {intl.get('吗')}
</> </>
), ),
@@ -254,7 +268,11 @@ const Env = () => {
.then(({ code, data }) => { .then(({ code, data }) => {
if (code === 200) { if (code === 200) {
message.success( message.success(
`${record.status === Status. ? intl.get('启用') : intl.get('禁用')}${intl.get('成功')}`, `${
record.status === Status.
? intl.get('启用')
: intl.get('禁用')
}${intl.get('成功')}`,
); );
const newStatus = const newStatus =
record.status === Status. ? Status.已启用 : Status.已禁用; record.status === Status. ? Status.已启用 : Status.已禁用;
@@ -289,9 +307,14 @@ const Env = () => {
content: ( content: (
<> <>
{intl.get('确认删除变量')}{' '} {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} {record.name}: {record.value}
</Text>{' '} </Paragraph>{' '}
{intl.get('吗')} {intl.get('吗')}
</> </>
), ),
@@ -435,7 +458,13 @@ const Env = () => {
const operateEnvs = (operationStatus: number) => { const operateEnvs = (operationStatus: number) => {
Modal.confirm({ Modal.confirm({
title: `确认${OperationName[operationStatus]}`, title: `确认${OperationName[operationStatus]}`,
content: <>{intl.get('确认')}{OperationName[operationStatus]}{intl.get('选中的变量吗')}</>, content: (
<>
{intl.get('确认')}
{OperationName[operationStatus]}
{intl.get('选中的变量吗')}
</>
),
onOk() { onOk() {
request request
.put( .put(
@@ -567,7 +596,8 @@ const Env = () => {
</Button> </Button>
<span style={{ marginLeft: 8 }}> <span style={{ marginLeft: 8 }}>
{intl.get('已选择')} {intl.get('已选择')}
<a>{selectedRowIds?.length}</a>{intl.get('项')} <a>{selectedRowIds?.length}</a>
{intl.get('项')}
</span> </span>
</div> </div>
)} )}
@@ -579,7 +609,7 @@ const Env = () => {
dataSource={value} dataSource={value}
rowKey="id" rowKey="id"
size="middle" size="middle"
scroll={{ x: 1000, y: tableScrollHeight }} scroll={{ x: 1200, y: tableScrollHeight }}
components={vt} components={vt}
loading={loading} loading={loading}
onRow={(record: any, index: number | undefined) => { 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 React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Radio } from 'antd'; import { Modal, message, Input, Form, Radio } from 'antd';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
@@ -44,7 +44,9 @@ const EnvModal = ({
); );
if (code === 200) { if (code === 200) {
message.success(env ? intl.get('更新变量成功') : intl.get('创建变量成功')); message.success(
env ? intl.get('更新变量成功') : intl.get('创建变量成功'),
);
handleCancel(data); handleCancel(data);
} }
setLoading(false); setLoading(false);
@@ -82,7 +84,11 @@ const EnvModal = ({
name="name" name="name"
label={intl.get('名称')} label={intl.get('名称')}
rules={[ rules={[
{ required: true, message: intl.get('请输入环境变量名称'), whitespace: true }, {
required: true,
message: intl.get('请输入环境变量名称'),
whitespace: true,
},
{ {
pattern: /^[a-zA-Z_][0-9a-zA-Z_]*$/, pattern: /^[a-zA-Z_][0-9a-zA-Z_]*$/,
message: intl.get('只能输入字母数字下划线,且不能以数字开头'), message: intl.get('只能输入字母数字下划线,且不能以数字开头'),
@@ -108,12 +114,15 @@ const EnvModal = ({
name="value" name="value"
label={intl.get('值')} label={intl.get('值')}
rules={[ rules={[
{ required: true, message: intl.get('请输入环境变量值'), whitespace: true }, {
required: true,
message: intl.get('请输入环境变量值'),
whitespace: true,
},
]} ]}
> >
<Input.TextArea <Input.TextArea
rows={4} autoSize={{ minRows: 1, maxRows: 8 }}
autoSize={true}
placeholder={intl.get('请输入环境变量值')} placeholder={intl.get('请输入环境变量值')}
/> />
</Form.Item> </Form.Item>
+1 -1
View File
@@ -185,7 +185,7 @@ const Initialization = () => {
style={{ maxWidth: 400 }} style={{ maxWidth: 400 }}
> >
<Input.TextArea <Input.TextArea
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={`请输入${x.label}`} placeholder={`请输入${x.label}`}
/> />
</Form.Item> </Form.Item>
+4 -10
View File
@@ -16,7 +16,7 @@ import { PageContainer } from '@ant-design/pro-layout';
import Editor from '@monaco-editor/react'; import Editor from '@monaco-editor/react';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
import styles from './index.module.less'; 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 SplitPane from 'react-split-pane';
import { useOutletContext } from '@umijs/max'; import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts'; import { SharedContext } from '@/layouts';
@@ -278,17 +278,11 @@ const Log = () => {
{isPhone && ( {isPhone && (
<CodeMirror <CodeMirror
value={value} value={value}
options={{ readOnly={true}
lineNumbers: true, theme={theme.includes('dark') ? 'dark' : 'light'}
lineWrapping: true, onChange={(value, viewUpdate) => {
styleActiveLine: true,
matchBrackets: true,
readOnly: true,
}}
onBeforeChange={(editor, data, value) => {
setValue(value); setValue(value);
}} }}
onChange={(editor, data, value) => {}}
/> />
)} )}
</div> </div>
+1 -1
View File
@@ -224,6 +224,7 @@ const EditModal = ({
minSize={200} minSize={200}
defaultSize="50%" defaultSize="50%"
style={{ height: 'calc(100vh - 55px)' }} style={{ height: 'calc(100vh - 55px)' }}
pane2Style={{ overflowY: 'auto' }}
> >
<Editor <Editor
language={language} language={language}
@@ -241,7 +242,6 @@ const EditModal = ({
/> />
<pre <pre
style={{ style={{
height: '100%',
padding: '0 15px', padding: '0 15px',
}} }}
> >
+8 -11
View File
@@ -20,7 +20,7 @@ import Editor from '@monaco-editor/react';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
import styles from './index.module.less'; import styles from './index.module.less';
import EditModal from './editModal'; import EditModal from './editModal';
import { Controlled as CodeMirror } from 'react-codemirror2'; import CodeMirror from '@uiw/react-codemirror';
import SplitPane from 'react-split-pane'; import SplitPane from 'react-split-pane';
import { import {
DeleteOutlined, DeleteOutlined,
@@ -43,6 +43,7 @@ import useFilterTreeData from '@/hooks/useFilterTreeData';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import IconFont from '@/components/iconfont'; import IconFont from '@/components/iconfont';
import RenameModal from './renameModal'; import RenameModal from './renameModal';
import { langs } from '@uiw/codemirror-extensions-langs';
const { Text } = Typography; const { Text } = Typography;
@@ -580,18 +581,14 @@ const Script = () => {
{isPhone && ( {isPhone && (
<CodeMirror <CodeMirror
value={value} value={value}
options={{ extensions={
lineNumbers: true, mode ? [langs[mode as keyof typeof langs]()] : undefined
lineWrapping: true, }
styleActiveLine: true, theme={theme.includes('dark') ? 'dark' : 'light'}
matchBrackets: true, readOnly={!isEditing}
mode, onChange={(value) => {
readOnly: !isEditing,
}}
onBeforeChange={(editor, data, value) => {
setValue(value); setValue(value);
}} }}
onChange={(editor, data, value) => {}}
/> />
)} )}
<EditModal <EditModal
+1
View File
@@ -10,6 +10,7 @@ const { Link } = Typography;
enum TVersion { enum TVersion {
'develop' = '开发版', 'develop' = '开发版',
'master' = '正式版', 'master' = '正式版',
'debian' = '正式版'
} }
const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => { const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
+6 -31
View File
@@ -75,16 +75,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
</div> </div>
</> </>
), ),
content: ( content: <pre>{lastLog}</pre>,
<pre
style={{
fontSize: 12,
fontWeight: 400,
}}
>
{lastLog}
</pre>
),
okText: intl.get('下载更新'), okText: intl.get('下载更新'),
cancelText: intl.get('以后再说'), cancelText: intl.get('以后再说'),
onOk() { onOk() {
@@ -109,16 +100,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
okButtonProps: { disabled: true }, okButtonProps: { disabled: true },
title: intl.get('下载更新中...'), title: intl.get('下载更新中...'),
centered: true, centered: true,
content: ( content: <pre>{value}</pre>,
<pre
style={{
fontSize: 12,
fontWeight: 400,
}}
>
{value}
</pre>
),
}); });
}; };
@@ -177,7 +159,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
} }
const newMessage = `${value}${_message}`; const newMessage = `${value}${_message}`;
const updateFailed = newMessage.includes(intl.get('失败')); const updateFailed = newMessage.includes('失败');
modalRef.current.update({ modalRef.current.update({
maskClosable: updateFailed, maskClosable: updateFailed,
@@ -185,20 +167,13 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
okButtonProps: { disabled: !updateFailed }, okButtonProps: { disabled: !updateFailed },
content: ( content: (
<> <>
<pre <pre>{newMessage}</pre>
style={{
fontSize: 12,
fontWeight: 400,
}}
>
{newMessage}
</pre>
<div id="log-identifier" style={{ paddingBottom: 5 }}></div> <div id="log-identifier" style={{ paddingBottom: 5 }}></div>
</> </>
), ),
}); });
if (updateFailed && !value.includes(intl.get('失败,请检查'))) { if (updateFailed && !value.includes('失败,请检查')) {
message.error(intl.get('更新失败,请检查网络及日志或稍后再试')); message.error(intl.get('更新失败,请检查网络及日志或稍后再试'));
} }
@@ -209,7 +184,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
.getElementById('log-identifier')! .getElementById('log-identifier')!
.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); .scrollIntoView({ behavior: 'smooth', block: 'nearest' });
if (_message.includes(intl.get('更新包下载成功'))) { if (_message.includes('更新包下载成功')) {
setTimeout(() => { setTimeout(() => {
showReloadModal(); showReloadModal();
}, 1000); }, 1000);
+51 -1
View File
@@ -1,5 +1,5 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { import {
Button, Button,
InputNumber, InputNumber,
@@ -32,6 +32,8 @@ import About from './about';
import { useOutletContext } from '@umijs/max'; import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts'; import { SharedContext } from '@/layouts';
import './index.less'; import './index.less';
import CodeMirror from '@uiw/react-codemirror';
import useResizeObserver from '@react-hook/resize-observer';
const { Text } = Typography; const { Text } = Typography;
const isDemoEnv = window.__ENV__DeployEnv === 'demo'; const isDemoEnv = window.__ENV__DeployEnv === 'demo';
@@ -41,6 +43,7 @@ const Setting = () => {
headerStyle, headerStyle,
isPhone, isPhone,
user, user,
theme,
reloadUser, reloadUser,
reloadTheme, reloadTheme,
socketMessage, socketMessage,
@@ -113,7 +116,17 @@ const Setting = () => {
const [editedApp, setEditedApp] = useState<any>(); const [editedApp, setEditedApp] = useState<any>();
const [tabActiveKey, setTabActiveKey] = useState('security'); const [tabActiveKey, setTabActiveKey] = useState('security');
const [loginLogData, setLoginLogData] = useState<any[]>([]); const [loginLogData, setLoginLogData] = useState<any[]>([]);
const [systemLogData, setSystemLogData] = useState<string>('');
const [notificationInfo, setNotificationInfo] = useState<any>(); 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 = () => { const getApps = () => {
setLoading(true); 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) => { const tabChange = (activeKey: string) => {
setTabActiveKey(activeKey); setTabActiveKey(activeKey);
if (activeKey === 'app') { if (activeKey === 'app') {
@@ -240,6 +266,8 @@ const Setting = () => {
getLoginLog(); getLoginLog();
} else if (activeKey === 'notification') { } else if (activeKey === 'notification') {
getNotification(); getNotification();
} else if (activeKey === 'syslog') {
getSystemLog();
} }
}; };
@@ -279,6 +307,7 @@ const Setting = () => {
: [] : []
} }
> >
<div ref={containergRef}>
<Tabs <Tabs
defaultActiveKey="security" defaultActiveKey="security"
size="small" size="small"
@@ -316,6 +345,26 @@ const Setting = () => {
label: intl.get('通知设置'), label: intl.get('通知设置'),
children: <NotificationSetting data={notificationInfo} />, 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', key: 'login',
label: intl.get('登录日志'), label: intl.get('登录日志'),
@@ -339,6 +388,7 @@ const Setting = () => {
}, },
]} ]}
></Tabs> ></Tabs>
</div>
<AppModal <AppModal
visible={isModalVisible} visible={isModalVisible}
handleCancel={handleCancel} handleCancel={handleCancel}
+2 -2
View File
@@ -19,7 +19,7 @@ enum LoginStatusColor {
const columns = [ const columns = [
{ {
title: intl.get('序号'), title: intl.get('序号'),
width: 40, width: 50,
render: (text: string, record: any, index: number) => { render: (text: string, record: any, index: number) => {
return index + 1; return index + 1;
}, },
@@ -75,7 +75,7 @@ const LoginLog = ({ data }: any) => {
dataSource={data} dataSource={data}
rowKey="id" rowKey="id"
size="middle" size="middle"
scroll={{ x: 768 }} scroll={{ x: 1000 }}
sticky sticky
/> />
</> </>
+1 -1
View File
@@ -88,7 +88,7 @@ const NotificationSetting = ({ data }: any) => {
) : ( ) : (
<Input.TextArea <Input.TextArea
disabled={loading} disabled={loading}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={x.placeholder || `请输入${x.label}`} placeholder={x.placeholder || `请输入${x.label}`}
/> />
)} )}
+48 -12
View File
@@ -9,7 +9,7 @@ import {
Input, Input,
Upload, Upload,
Modal, Modal,
Progress, Select,
} from 'antd'; } from 'antd';
import * as DarkReader from '@umijs/ssr-darkreader'; import * as DarkReader from '@umijs/ssr-darkreader';
import config from '@/utils/config'; import config from '@/utils/config';
@@ -22,12 +22,6 @@ import { UploadOutlined } from '@ant-design/icons';
import Countdown from 'antd/lib/statistic/Countdown'; import Countdown from 'antd/lib/statistic/Countdown';
import useProgress from './progress'; import useProgress from './progress';
const optionsWithDisabled = [
{ label: intl.get('亮色'), value: 'light' },
{ label: intl.get('暗色'), value: 'dark' },
{ label: intl.get('跟随系统'), value: 'auto' },
];
const Other = ({ const Other = ({
systemInfo, systemInfo,
socketMessage, socketMessage,
@@ -67,6 +61,13 @@ const Other = ({
reloadTheme(); reloadTheme();
}; };
const handleLangChange = (v: string) => {
localStorage.setItem('lang', v);
setTimeout(() => {
window.location.reload();
}, 500);
};
const getSystemConfig = () => { const getSystemConfig = () => {
request request
.get(`${config.apiPrefix}system/config`) .get(`${config.apiPrefix}system/config`)
@@ -167,12 +168,27 @@ const Other = ({
initialValue={defaultTheme} initialValue={defaultTheme}
> >
<Radio.Group <Radio.Group
options={optionsWithDisabled}
onChange={themeChange} onChange={themeChange}
value={defaultTheme} value={defaultTheme}
optionType="button" optionType="button"
buttonStyle="solid" 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>
<Form.Item <Form.Item
label={intl.get('日志删除频率')} label={intl.get('日志删除频率')}
@@ -190,7 +206,11 @@ const Other = ({
setSystemConfig({ ...systemConfig, logRemoveFrequency: value }); setSystemConfig({ ...systemConfig, logRemoveFrequency: value });
}} }}
/> />
<Button type="primary" onClick={updateSystemConfig}> <Button
type="primary"
onClick={updateSystemConfig}
style={{ width: 84 }}
>
{intl.get('确认')} {intl.get('确认')}
</Button> </Button>
</Input.Group> </Input.Group>
@@ -198,18 +218,34 @@ const Other = ({
<Form.Item label={intl.get('定时任务并发数')} name="frequency"> <Form.Item label={intl.get('定时任务并发数')} name="frequency">
<Input.Group compact> <Input.Group compact>
<InputNumber <InputNumber
style={{ width: 150 }} style={{ width: 180 }}
min={1} min={1}
value={systemConfig?.cronConcurrency} value={systemConfig?.cronConcurrency}
onChange={(value) => { onChange={(value) => {
setSystemConfig({ ...systemConfig, cronConcurrency: value }); setSystemConfig({ ...systemConfig, cronConcurrency: value });
}} }}
/> />
<Button type="primary" onClick={updateSystemConfig}> <Button
type="primary"
onClick={updateSystemConfig}
style={{ width: 84 }}
>
{intl.get('确认')} {intl.get('确认')}
</Button> </Button>
</Input.Group> </Input.Group>
</Form.Item> </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"> <Form.Item label={intl.get('数据备份还原')} name="frequency">
<Button type="primary" onClick={exportData} loading={exportLoading}> <Button type="primary" onClick={exportData} loading={exportLoading}>
{exportLoading ? intl.get('生成数据中...') : intl.get('备份')} {exportLoading ? intl.get('生成数据中...') : intl.get('备份')}
+6 -2
View File
@@ -188,7 +188,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
hasFeedback hasFeedback
style={{ maxWidth: 300 }} style={{ maxWidth: 300 }}
> >
<Input placeholder={intl.get('用户名')} /> <Input autoComplete="username" placeholder={intl.get('用户名')} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={intl.get('密码')} label={intl.get('密码')}
@@ -203,7 +203,11 @@ const SecuritySettings = ({ user, userChange }: any) => {
hasFeedback hasFeedback
style={{ maxWidth: 300 }} style={{ maxWidth: 300 }}
> >
<Input type="password" placeholder={intl.get('密码')} /> <Input
type="password"
autoComplete="current-password"
placeholder={intl.get('密码')}
/>
</Form.Item> </Form.Item>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
{intl.get('保存')} {intl.get('保存')}
+4 -10
View File
@@ -184,45 +184,39 @@ const Subscription = () => {
{ {
title: intl.get('操作'), title: intl.get('操作'),
key: 'action', key: 'action',
width: 130, width: 140,
render: (text: string, record: any, index: number) => { render: (text: string, record: any, index: number) => {
const isPc = !isPhone; const isPc = !isPhone;
return ( return (
<Space size="middle"> <Space size="middle">
{record.status === SubscriptionStatus.idle && ( {record.status === SubscriptionStatus.idle && (
<Tooltip title={isPc ? intl.get('运行') : ''}>
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
runSubscription(record, index); runSubscription(record, index);
}} }}
> >
<PlayCircleOutlined /> {intl.get('运行')}
</a> </a>
</Tooltip>
)} )}
{record.status !== SubscriptionStatus.idle && ( {record.status !== SubscriptionStatus.idle && (
<Tooltip title={isPc ? intl.get('停止') : ''}>
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
stopSubsciption(record, index); stopSubsciption(record, index);
}} }}
> >
<PauseCircleOutlined /> {intl.get('停止')}
</a> </a>
</Tooltip>
)} )}
<Tooltip title={isPc ? intl.get('日志') : ''}>
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setLogSubscription({ ...record, timestamp: Date.now() }); setLogSubscription({ ...record, timestamp: Date.now() });
}} }}
> >
<FileTextOutlined /> {intl.get('日志')}
</a> </a>
</Tooltip>
<MoreBtn key="more" record={record} index={index} /> <MoreBtn key="more" record={record} index={index} />
</Space> </Space>
); );
+2 -4
View File
@@ -99,9 +99,6 @@ const SubscriptionLogModal = ({
open={visible} open={visible}
centered centered
className="log-modal" className="log-modal"
bodyStyle={{
minHeight: '300px',
}}
forceRender forceRender
onOk={() => cancel()} onOk={() => cancel()}
onCancel={() => cancel()} onCancel={() => cancel()}
@@ -111,6 +108,7 @@ const SubscriptionLogModal = ({
</Button>, </Button>,
]} ]}
> >
<div className="log-container">
{loading ? ( {loading ? (
<PageLoading /> <PageLoading />
) : ( ) : (
@@ -119,7 +117,6 @@ const SubscriptionLogModal = ({
isPhone isPhone
? { ? {
fontFamily: 'Source Code Pro', fontFamily: 'Source Code Pro',
width: 375,
zoom: 0.83, zoom: 0.83,
} }
: {} : {}
@@ -128,6 +125,7 @@ const SubscriptionLogModal = ({
{value} {value}
</pre> </pre>
)} )}
</div>
</Modal> </Modal>
); );
}; };
+23 -13
View File
@@ -241,10 +241,9 @@ const SubscriptionModal = ({
const onNamePaste = useCallback((e) => { const onNamePaste = useCallback((e) => {
const text = e.clipboardData.getData('text') as string; const text = e.clipboardData.getData('text') as string;
if (text.startsWith('ql ')) { if (text.includes('ql repo') || text.includes('ql raw')) {
e.preventDefault(); e.preventDefault();
} }
onPaste(e);
}, []); }, []);
const formatParams = (sub) => { const formatParams = (sub) => {
@@ -327,10 +326,9 @@ const SubscriptionModal = ({
]} ]}
> >
<Input.TextArea <Input.TextArea
rows={4} autoSize={{ minRows: 1, maxRows: 5 }}
autoSize={true}
placeholder={intl.get('请输入订阅链接')} placeholder={intl.get('请输入订阅链接')}
onPaste={onUrlChange} onPaste={onNamePaste}
onChange={onUrlChange} onChange={onUrlChange}
/> />
</Form.Item> </Form.Item>
@@ -338,7 +336,7 @@ const SubscriptionModal = ({
<Form.Item name="branch" label={intl.get('分支')}> <Form.Item name="branch" label={intl.get('分支')}>
<Input <Input
placeholder={intl.get('请输入分支')} placeholder={intl.get('请输入分支')}
onPaste={onBranchChange} onPaste={onNamePaste}
onChange={onBranchChange} onChange={onBranchChange}
/> />
</Form.Item> </Form.Item>
@@ -401,7 +399,10 @@ const SubscriptionModal = ({
{scheduleType === 'interval' ? ( {scheduleType === 'interval' ? (
<IntervalSelect /> <IntervalSelect />
) : ( ) : (
<Input placeholder={intl.get('秒(可选) 分 时 天 月 周')} /> <Input
onPaste={onNamePaste}
placeholder={intl.get('秒(可选) 分 时 天 月 周')}
/>
)} )}
</Form.Item> </Form.Item>
{type !== 'file' && ( {type !== 'file' && (
@@ -413,10 +414,11 @@ const SubscriptionModal = ({
> >
<Input.TextArea <Input.TextArea
rows={4} rows={4}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get( placeholder={intl.get(
'请输入脚本筛选白名单关键词,多个关键词竖线分割', '请输入脚本筛选白名单关键词,多个关键词竖线分割',
)} )}
onPaste={onNamePaste}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -426,10 +428,11 @@ const SubscriptionModal = ({
> >
<Input.TextArea <Input.TextArea
rows={4} rows={4}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get( placeholder={intl.get(
'请输入脚本筛选黑名单关键词,多个关键词竖线分割', '请输入脚本筛选黑名单关键词,多个关键词竖线分割',
)} )}
onPaste={onNamePaste}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -439,10 +442,11 @@ const SubscriptionModal = ({
> >
<Input.TextArea <Input.TextArea
rows={4} rows={4}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get( placeholder={intl.get(
'请输入脚本依赖文件关键词,多个关键词竖线分割', '请输入脚本依赖文件关键词,多个关键词竖线分割',
)} )}
onPaste={onNamePaste}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -452,7 +456,10 @@ const SubscriptionModal = ({
'仓库需要拉取的文件后缀,多个后缀空格分隔,默认使用配置文件中的RepoFileExtensions', '仓库需要拉取的文件后缀,多个后缀空格分隔,默认使用配置文件中的RepoFileExtensions',
)} )}
> >
<Input placeholder={intl.get('请输入文件后缀')} /> <Input
onPaste={onNamePaste}
placeholder={intl.get('请输入文件后缀')}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="sub_before" name="sub_before"
@@ -462,8 +469,9 @@ const SubscriptionModal = ({
)} )}
> >
<Input.TextArea <Input.TextArea
onPaste={onNamePaste}
rows={4} rows={4}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入运行订阅前要执行的命令')} placeholder={intl.get('请输入运行订阅前要执行的命令')}
/> />
</Form.Item> </Form.Item>
@@ -475,8 +483,9 @@ const SubscriptionModal = ({
)} )}
> >
<Input.TextArea <Input.TextArea
onPaste={onNamePaste}
rows={4} rows={4}
autoSize={true} autoSize={{ minRows: 1, maxRows: 5 }}
placeholder={intl.get('请输入运行订阅后要执行的命令')} placeholder={intl.get('请输入运行订阅后要执行的命令')}
/> />
</Form.Item> </Form.Item>
@@ -490,6 +499,7 @@ const SubscriptionModal = ({
)} )}
> >
<Input <Input
onPaste={onNamePaste}
placeholder={ placeholder={
type === 'private-repo' type === 'private-repo'
? 'SOCK5代理,例如 IP:PORT' ? 'SOCK5代理,例如 IP:PORT'
+1 -1
View File
@@ -174,7 +174,7 @@ export default {
}, },
{ {
label: 'barkGroup', label: 'barkGroup',
tip: intl.get('BARK推送消息的分组, 默认为qinglong'), tip: intl.get('BARK推送消息的分组默认为qinglong'),
}, },
], ],
telegramBot: [ telegramBot: [
+12 -11
View File
@@ -1,12 +1,13 @@
version: 2.16.0 version: 2.16.2
changeLogLink: https://t.me/jiao_long/388 changeLogLink: https://t.me/jiao_long/393
publishTime: 2023-08-06 16:00 publishTime: 2023-09-02 07:00
changeLog: | changeLog: |
1. 多语言支持英文界面 1. 系统设置增加语言设置
2. 定时任务增加关联订阅 2. 修复环境变量有空格时并发数量错误
3. 删除订阅支持自动删除任务和脚本 3. 修复环境变量特殊字符转义
4. 定时任务支持运行 mjs 后缀文件 4. 修复仓库订阅 ssh 配置
5. PushMe消息通道增加 params 参数 5. 修复停止订阅执行日志
6. 修复 6 位 cron 不以 task 开头定时任务运行失败 6. 修改定时任务置顶样式
7. 修任务详情日志列表过多卡顿 7. 修任务日志样式
8. 修复定时任务列表虚拟滚动 8. 修复环境变量值 tip 样式
9. 其他 bug 修复