mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 03:10:48 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0977f9e37 | ||
|
|
e9b85347f4 | ||
|
|
325482cbb1 | ||
|
|
45e02a55c3 | ||
|
|
6b8ef75b51 | ||
|
|
f2535ece76 | ||
|
|
b2afa65cb1 | ||
|
|
3a62cc37ff | ||
|
|
93c69826ad | ||
|
|
70727d0e26 | ||
|
|
2578aae0ba | ||
|
|
616e56ea95 | ||
|
|
96ae20de68 | ||
|
|
6975ff4aa1 | ||
|
|
1d8a1e9c4a | ||
|
|
2719280cdc | ||
|
|
575e41172f | ||
|
|
bd50b1e976 | ||
|
|
dfb2572b4f | ||
|
|
7746b18023 | ||
|
|
05c738c7c6 | ||
|
|
e561e12356 | ||
|
|
595b28ba62 | ||
|
|
366caed74e | ||
|
|
2e26a8c24e | ||
|
|
c09c375066 | ||
|
|
4f93905507 |
+3
-3
@@ -7,6 +7,6 @@ LOG_LEVEL='debug'
|
||||
|
||||
SECRET='whyour'
|
||||
|
||||
QINIU_AK = ''
|
||||
QINIU_SK = ''
|
||||
QINIU_SCOPE = ''
|
||||
QINIU_AK=''
|
||||
QINIU_SK=''
|
||||
QINIU_SCOPE=''
|
||||
@@ -1,2 +1,2 @@
|
||||
sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli
|
||||
sentrycli_cdnurl=https://npmmirror.com/mirrors/sentry-cli/
|
||||
strict-peer-dependencies=false
|
||||
+7
-7
@@ -182,9 +182,9 @@ export default (app: Router) => {
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
labels: Joi.array().optional().allow(null),
|
||||
command: Joi.string().optional(),
|
||||
schedule: Joi.string().optional(),
|
||||
name: Joi.string().optional(),
|
||||
command: Joi.string().required(),
|
||||
schedule: Joi.string().required(),
|
||||
name: Joi.string().optional().allow(null),
|
||||
id: Joi.number().required(),
|
||||
}),
|
||||
}),
|
||||
@@ -297,10 +297,10 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
ids: Joi.array().items(Joi.number().required()),
|
||||
status: Joi.string().required(),
|
||||
pid: Joi.string().optional(),
|
||||
log_path: Joi.string().optional(),
|
||||
last_running_time: Joi.number().optional(),
|
||||
last_execution_time: Joi.number().optional(),
|
||||
pid: Joi.string().optional().allow(null),
|
||||
log_path: Joi.string().optional().allow(null),
|
||||
last_running_time: Joi.number().optional().allow(null),
|
||||
last_execution_time: Joi.number().optional().allow(null),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
+1
-1
@@ -170,7 +170,7 @@ export default (app: Router) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const envService = Container.get(EnvService);
|
||||
const data = await envService.getDb(req.params.id);
|
||||
const data = await envService.getDb({ id: req.params.id });
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
|
||||
+5
-1
@@ -6,6 +6,7 @@ import config from '../config';
|
||||
import { getFileContentByName, readDirs } from '../config/util';
|
||||
import { join } from 'path';
|
||||
const route = Router();
|
||||
const blacklist = ['.tmp'];
|
||||
|
||||
export default (app: Router) => {
|
||||
app.use('/logs', route);
|
||||
@@ -13,7 +14,7 @@ export default (app: Router) => {
|
||||
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const result = readDirs(config.logPath, config.logPath);
|
||||
const result = readDirs(config.logPath, config.logPath, blacklist);
|
||||
res.send({
|
||||
code: 200,
|
||||
data: result,
|
||||
@@ -29,6 +30,9 @@ export default (app: Router) => {
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
if (blacklist.includes(req.path)) {
|
||||
return res.send({ code: 403, message: '暂无权限' });
|
||||
}
|
||||
const filePath = join(
|
||||
config.logPath,
|
||||
(req.query.path || '') as string,
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ export default (app: Router) => {
|
||||
'/apps',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().optional().allow(''),
|
||||
name: Joi.string().optional().allow('').disallow('system'),
|
||||
scopes: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
}),
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().optional().allow(''),
|
||||
content: Joi.string().required(),
|
||||
content: Joi.string().required().allow(''),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
@@ -69,6 +69,7 @@ export default {
|
||||
'cookie.sh',
|
||||
'crontab.list',
|
||||
'env.sh',
|
||||
'token.json',
|
||||
],
|
||||
writePathList: [configPath, scriptPath],
|
||||
bakPath,
|
||||
|
||||
+25
-2
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import got from 'got';
|
||||
import iconv from 'iconv-lite';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export function getFileContentByName(fileName: string) {
|
||||
if (fs.existsSync(fileName)) {
|
||||
@@ -277,6 +278,11 @@ export async function concurrentRun(
|
||||
return replyList;
|
||||
}
|
||||
|
||||
enum FileType {
|
||||
'directory',
|
||||
'file',
|
||||
}
|
||||
|
||||
export function readDirs(
|
||||
dir: string,
|
||||
baseDir: string = '',
|
||||
@@ -297,7 +303,10 @@ export function readDirs(
|
||||
type: 'directory',
|
||||
disabled: true,
|
||||
parent: relativePath,
|
||||
children: readDirs(subPath, baseDir),
|
||||
children: readDirs(subPath, baseDir).sort(
|
||||
(a: any, b: any) =>
|
||||
(FileType as any)[a.type] - (FileType as any)[b.type],
|
||||
),
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -307,7 +316,9 @@ export function readDirs(
|
||||
parent: relativePath,
|
||||
};
|
||||
});
|
||||
return result;
|
||||
return result.sort(
|
||||
(a: any, b: any) => (FileType as any)[a.type] - (FileType as any)[b.type],
|
||||
);
|
||||
}
|
||||
|
||||
export function readDir(
|
||||
@@ -332,3 +343,15 @@ export function readDir(
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function promiseExec(command: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
command,
|
||||
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
|
||||
(err, stdout, stderr) => {
|
||||
resolve(stdout || stderr || JSON.stringify(err));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import chokidar from 'chokidar';
|
||||
import config from '../config/index';
|
||||
import { promiseExec } from '../config/util';
|
||||
|
||||
function linkToNodeModule(src: string, dst?: string) {
|
||||
const target = path.join(config.rootPath, 'node_modules', dst || src);
|
||||
@@ -16,7 +17,32 @@ function linkToNodeModule(src: string, dst?: string) {
|
||||
});
|
||||
}
|
||||
|
||||
async function linkCommand() {
|
||||
const commandPath = await promiseExec('which node');
|
||||
const commandDir = path.dirname(commandPath);
|
||||
const linkShell = [
|
||||
{
|
||||
src: 'update.sh',
|
||||
dest: 'ql',
|
||||
},
|
||||
{
|
||||
src: 'task.sh',
|
||||
dest: 'task',
|
||||
},
|
||||
];
|
||||
|
||||
for (const link of linkShell) {
|
||||
const source = path.join(config.rootPath, 'shell', link.src);
|
||||
const target = path.join(commandDir, link.dest);
|
||||
if (fs.existsSync(target)) {
|
||||
fs.unlinkSync(target);
|
||||
}
|
||||
fs.symlink(source, target, (err) => {});
|
||||
}
|
||||
}
|
||||
|
||||
export default async (src: string = 'deps') => {
|
||||
await linkCommand();
|
||||
linkToNodeModule(src);
|
||||
|
||||
const source = path.join(config.rootPath, src);
|
||||
|
||||
@@ -80,13 +80,6 @@ export default ({ app }: { app: Application }) => {
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
const remoteAddress = req.socket.remoteAddress;
|
||||
if (
|
||||
remoteAddress === '::ffff:127.0.0.1' &&
|
||||
originPath === '/api/crons/status'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
||||
if (data) {
|
||||
|
||||
@@ -64,5 +64,13 @@ export default async () => {
|
||||
|
||||
dotenv.config({ path: confFile });
|
||||
|
||||
// 声明QL_DIR环境变量
|
||||
let qlHomePath = path.join(__dirname, '../../');
|
||||
// 生产环境
|
||||
if (qlHomePath.endsWith('/static/')) {
|
||||
qlHomePath = path.join(qlHomePath, '../');
|
||||
}
|
||||
process.env.QL_DIR = qlHomePath;
|
||||
|
||||
Logger.info('✌️ Init file down');
|
||||
};
|
||||
|
||||
+13
-3
@@ -10,6 +10,7 @@ import { promises, existsSync } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { Op } from 'sequelize';
|
||||
import path from 'path';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@Service()
|
||||
export default class CronService {
|
||||
@@ -197,13 +198,21 @@ export default class CronService {
|
||||
const err = await this.killTask(doc.command);
|
||||
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
|
||||
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
||||
|
||||
const endTime = dayjs();
|
||||
const diffTimeStr = doc.last_execution_time
|
||||
? `,耗时 ${endTime.diff(
|
||||
dayjs(doc.last_execution_time * 1000),
|
||||
'second',
|
||||
)}`
|
||||
: '';
|
||||
if (logFileExist) {
|
||||
const str = err ? `\n${err}` : '';
|
||||
fs.appendFileSync(
|
||||
`${absolutePath}`,
|
||||
`${str}\n## 执行结束... ${new Date()
|
||||
.toLocaleString('zh', { hour12: false })
|
||||
.replace(' 24:', ' 00:')} `,
|
||||
`${str}\n## 执行结束... ${endTime.format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)}${diffTimeStr}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -228,6 +237,7 @@ export default class CronService {
|
||||
const killLogs = [];
|
||||
if (pids && pids.length > 0) {
|
||||
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
|
||||
pids = pids.slice(0, 3);
|
||||
for (const id of pids) {
|
||||
const c = `kill -9 ${id.slice(1)}`;
|
||||
try {
|
||||
|
||||
+14
-20
@@ -14,6 +14,7 @@ import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import { Op } from 'sequelize';
|
||||
import { concurrentRun } from '../config/util';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@Service()
|
||||
export default class DependenceService {
|
||||
@@ -161,20 +162,17 @@ export default class DependenceService {
|
||||
)[dependencies[0].type as any];
|
||||
const actionText = isInstall ? '安装' : '删除';
|
||||
const depIds = dependencies.map((x) => x.id) as number[];
|
||||
const startTime = Date.now();
|
||||
const startTime = dayjs();
|
||||
|
||||
const message = `开始${actionText}依赖 ${depNames},开始时间 ${startTime.format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)}\n\n`;
|
||||
this.sockService.sendMessage({
|
||||
type: socketMessageType,
|
||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}\n\n`,
|
||||
message,
|
||||
references: depIds,
|
||||
});
|
||||
await this.updateLog(
|
||||
depIds,
|
||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||
startTime,
|
||||
).toLocaleString()}\n\n`,
|
||||
);
|
||||
await this.updateLog(depIds, message);
|
||||
|
||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||
|
||||
@@ -207,23 +205,19 @@ export default class DependenceService {
|
||||
});
|
||||
|
||||
cp.on('close', async (code) => {
|
||||
const endTime = Date.now();
|
||||
const endTime = dayjs();
|
||||
const isSucceed = code === 0;
|
||||
const resultText = isSucceed ? '成功' : '失败';
|
||||
|
||||
const message = `\n依赖${actionText}${resultText},结束时间 ${endTime.format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)},耗时 ${endTime.diff(startTime, 'second')} 秒`;
|
||||
this.sockService.sendMessage({
|
||||
type: socketMessageType,
|
||||
message: `\n依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
message,
|
||||
references: depIds,
|
||||
});
|
||||
await this.updateLog(
|
||||
depIds,
|
||||
`\n依赖${actionText}${resultText},结束时间 ${new Date(
|
||||
endTime,
|
||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||
);
|
||||
await this.updateLog(depIds, message);
|
||||
|
||||
let status = null;
|
||||
if (isSucceed) {
|
||||
|
||||
+34
-1
@@ -90,7 +90,9 @@ export default class OpenService {
|
||||
}
|
||||
try {
|
||||
const result = await this.find(condition);
|
||||
return result.map((x) => ({ ...x, tokens: [] }));
|
||||
return result
|
||||
.filter((x) => x.name !== 'system')
|
||||
.map((x) => ({ ...x, tokens: [] }));
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@@ -142,4 +144,35 @@ export default class OpenService {
|
||||
return { code: 400, message: 'client_id或client_seret有误' };
|
||||
}
|
||||
}
|
||||
|
||||
public async findSystemToken(): Promise<{
|
||||
value: string;
|
||||
expiration: number;
|
||||
}> {
|
||||
let systemApp = (await AppModel.findOne({
|
||||
where: { name: 'system' },
|
||||
})) as App;
|
||||
if (!systemApp) {
|
||||
systemApp = await this.create({
|
||||
name: 'system',
|
||||
scopes: ['crons', 'system'],
|
||||
} as App);
|
||||
}
|
||||
const nowTime = Math.round(Date.now() / 1000);
|
||||
let token;
|
||||
if (
|
||||
!systemApp.tokens ||
|
||||
!systemApp.tokens.length ||
|
||||
nowTime > [...systemApp.tokens].pop()!.expiration
|
||||
) {
|
||||
const authToken = await this.authToken({
|
||||
client_id: systemApp.client_id,
|
||||
client_secret: systemApp.client_secret,
|
||||
});
|
||||
token = authToken.data;
|
||||
} else {
|
||||
token = [...systemApp.tokens].pop();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import ScheduleService, { TaskCallbacks } from './schedule';
|
||||
import { SimpleIntervalSchedule } from 'toad-scheduler';
|
||||
import SockService from './sock';
|
||||
import SshKeyService from './sshKey';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@Service()
|
||||
export default class SubscriptionService {
|
||||
@@ -371,11 +372,10 @@ export default class SubscriptionService {
|
||||
const err = await this.killTask(command);
|
||||
const absolutePath = await this.handleLogPath(doc.log_path as string);
|
||||
const str = err ? `\n${err}` : '';
|
||||
|
||||
fs.appendFileSync(
|
||||
`${absolutePath}`,
|
||||
`${str}\n## 执行结束... ${new Date()
|
||||
.toLocaleString('zh', { hour12: false })
|
||||
.replace(' 24:', ' 00:')} `,
|
||||
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Request } from 'express';
|
||||
import ScheduleService from './schedule';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import got from 'got';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@Service()
|
||||
export default class UserService {
|
||||
@@ -111,7 +111,7 @@ export default class UserService {
|
||||
});
|
||||
await this.notificationService.notify(
|
||||
'登录通知',
|
||||
`你于${new Date(timestamp).toLocaleString()}在 ${address} ${
|
||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||
req.platform
|
||||
}端 登录成功,ip地址 ${ip}`,
|
||||
);
|
||||
@@ -140,7 +140,7 @@ export default class UserService {
|
||||
});
|
||||
await this.notificationService.notify(
|
||||
'登录通知',
|
||||
`你于${new Date(timestamp).toLocaleString()}在 ${address} ${
|
||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||
req.platform
|
||||
}端 登录失败,ip地址 ${ip}`,
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ AutoStartBot=""
|
||||
BotRepoUrl=""
|
||||
|
||||
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
|
||||
PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
PipMirror="https://pypi.doubanio.com/simple/"
|
||||
|
||||
## 通知环境变量
|
||||
## 1. Server酱
|
||||
|
||||
+8
-26
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
get_token() {
|
||||
token=$(cat $file_auth_user | jq -r .token)
|
||||
token=$(ts-node-transpile-only "$dir_shell/token.ts")
|
||||
}
|
||||
|
||||
add_cron_api() {
|
||||
@@ -17,7 +17,7 @@ add_cron_api() {
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
@@ -52,7 +52,7 @@ update_cron_api() {
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
@@ -84,7 +84,7 @@ update_cron_command_api() {
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
@@ -109,7 +109,7 @@ del_cron_api() {
|
||||
local ids=$1
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'DELETE' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
@@ -130,24 +130,6 @@ del_cron_api() {
|
||||
fi
|
||||
}
|
||||
|
||||
get_user_info() {
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/user?t=$currentTimeStamp" \
|
||||
-H 'Accept: */*' \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \
|
||||
-H 'Referer: http://0.0.0.0:5700/crontab' \
|
||||
-H 'Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7' \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
if [[ $code != 200 ]]; then
|
||||
echo -e "请先登录!"
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
update_cron() {
|
||||
local ids="$1"
|
||||
local status="$2"
|
||||
@@ -157,7 +139,7 @@ update_cron() {
|
||||
local runningTime="${6:-0}"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons/status?t=$currentTimeStamp" \
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
@@ -172,7 +154,7 @@ update_cron() {
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code != 200 ]]; then
|
||||
echo -e "\n## 更新任务状态失败(${message})\n" >> $log_path
|
||||
echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -181,7 +163,7 @@ notify_api() {
|
||||
local content=$2
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/api/system/notify?t=$currentTimeStamp" \
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
|
||||
+50
-7
@@ -14,7 +14,6 @@ dir_log=$dir_data/log
|
||||
dir_db=$dir_data/db
|
||||
dir_dep=$dir_data/deps
|
||||
dir_list_tmp=$dir_log/.tmp
|
||||
dir_code=$dir_log/code
|
||||
dir_update_log=$dir_log/update
|
||||
ql_static_repo=$dir_repo/static
|
||||
|
||||
@@ -52,12 +51,10 @@ list_own_drop=$dir_list_tmp/own_drop.list
|
||||
link_name=(
|
||||
task
|
||||
ql
|
||||
notify
|
||||
)
|
||||
original_name=(
|
||||
task.sh
|
||||
update.sh
|
||||
notify.sh
|
||||
)
|
||||
|
||||
init_env() {
|
||||
@@ -101,7 +98,7 @@ make_dir() {
|
||||
}
|
||||
|
||||
detect_termux() {
|
||||
if [[ ${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT} ]] || [[ $PATH == *com.termux* ]]; then
|
||||
if [[ $PATH == *com.termux* ]]; then
|
||||
is_termux=1
|
||||
else
|
||||
is_termux=0
|
||||
@@ -248,11 +245,11 @@ fix_config() {
|
||||
npm_install_sub() {
|
||||
set_proxy
|
||||
if [ $is_termux -eq 1 ]; then
|
||||
npm install --production --no-bin-links --registry=https://registry.npm.taobao.org || npm install --production --no-bin-links
|
||||
npm install --production --no-bin-links --registry=https://registry.npmmirror.com || npm install --production --no-bin-links
|
||||
elif ! type pnpm &>/dev/null; then
|
||||
npm install --production --registry=https://registry.npm.taobao.org || npm install --production
|
||||
npm install --production --registry=https://registry.npmmirror.com || npm install --production
|
||||
else
|
||||
pnpm install --loglevel error --production --registry=https://registry.npm.taobao.org || pnpm install --production --loglevel error
|
||||
pnpm install --loglevel error --production --registry=https://registry.npmmirror.com || pnpm install --production --loglevel error
|
||||
fi
|
||||
unset_proxy
|
||||
}
|
||||
@@ -380,6 +377,52 @@ reload_pm2() {
|
||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
||||
}
|
||||
|
||||
diff_time() {
|
||||
local format="$1"
|
||||
local begin_time="$2"
|
||||
local end_time="$3"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
|
||||
else
|
||||
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
||||
fi
|
||||
echo "$diff_time"
|
||||
}
|
||||
|
||||
format_time() {
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
else
|
||||
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
fi
|
||||
}
|
||||
|
||||
format_log_time() {
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
else
|
||||
echo $(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
fi
|
||||
}
|
||||
|
||||
format_timestamp() {
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%s")
|
||||
else
|
||||
echo $(date -d "$time" "+%s")
|
||||
fi
|
||||
}
|
||||
|
||||
init_env
|
||||
detect_termux
|
||||
detect_macos
|
||||
|
||||
+33
-25
@@ -84,8 +84,8 @@ run_normal() {
|
||||
fi
|
||||
fi
|
||||
|
||||
local time=$(date)
|
||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -99,12 +99,13 @@ run_normal() {
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd="&>> $dir_log/$log_path"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
local begin_timestamp=$(date -d "$time" "+%s")
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
|
||||
@@ -118,12 +119,13 @@ run_normal() {
|
||||
cd ${relative_path}
|
||||
file_param=${file_param/$relative_path\//}
|
||||
fi
|
||||
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
|
||||
|
||||
eval $timeoutCmd $which_program $file_param $cmd
|
||||
|
||||
eval . $file_task_after "$@" $cmd
|
||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local end_timestamp=$(date "+%s")
|
||||
local diff_time=$(( $end_timestamp - $begin_timestamp ))
|
||||
local diff_time=$(expr $end_timestamp - $begin_timestamp)
|
||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $cmd
|
||||
}
|
||||
@@ -154,8 +156,8 @@ run_concurrent() {
|
||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||
|
||||
define_program "$file_param"
|
||||
local time=$(date)
|
||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -169,12 +171,12 @@ run_concurrent() {
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd="&>> $dir_log/$log_path"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
local begin_timestamp=$(date -d "$time" "+%s")
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
@@ -196,7 +198,7 @@ run_concurrent() {
|
||||
for i in "${!array[@]}"; do
|
||||
export ${env_param}=${array[i]}
|
||||
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
|
||||
timeout -k 10s $command_timeout_time $which_program $file_param &>$single_log_path &
|
||||
eval $timeoutCmd $which_program $file_param &>$single_log_path &
|
||||
done
|
||||
|
||||
wait
|
||||
@@ -224,8 +226,8 @@ run_designated() {
|
||||
fi
|
||||
|
||||
define_program "$file_param"
|
||||
local time=$(date)
|
||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -239,12 +241,12 @@ run_designated() {
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd="&>> $dir_log/$log_path"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
local begin_timestamp=$(date -d "$time" "+%s")
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
|
||||
local envs=$(eval echo "\$${env_param}")
|
||||
local array=($(echo $envs | sed 's/&/ /g'))
|
||||
@@ -274,7 +276,7 @@ run_designated() {
|
||||
cd ${relative_path}
|
||||
file_param=${file_param/$relative_path\//}
|
||||
fi
|
||||
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
|
||||
eval $timeoutCmd $which_program $file_param $cmd
|
||||
|
||||
eval . $file_task_after "$@" $cmd
|
||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
@@ -288,8 +290,8 @@ run_designated() {
|
||||
run_else() {
|
||||
local file_param="$1"
|
||||
define_program "$file_param"
|
||||
local time=$(date)
|
||||
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
@@ -303,12 +305,12 @@ run_else() {
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd="&>> $dir_log/$log_path"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
|
||||
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
local begin_timestamp=$(date -d "$time" "+%s")
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
@@ -325,7 +327,7 @@ run_else() {
|
||||
fi
|
||||
|
||||
shift
|
||||
eval timeout -k 10s $command_timeout_time $which_program "$file_param" "$@" $cmd
|
||||
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
|
||||
|
||||
eval . $file_task_after "$file_param" "$@" $cmd
|
||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
@@ -348,6 +350,12 @@ main() {
|
||||
done
|
||||
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
||||
|
||||
timeoutCmd=""
|
||||
if type timeout &>/dev/null; then
|
||||
timeoutCmd="timeout -k 10s $command_timeout_time "
|
||||
fi
|
||||
|
||||
time_format="%Y-%m-%d %H:%M:%S"
|
||||
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
|
||||
case $# in
|
||||
1)
|
||||
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
import 'reflect-metadata';
|
||||
import OpenService from '../back/services/open';
|
||||
import { Container } from 'typedi';
|
||||
import LoggerInstance from '../back/loaders/logger';
|
||||
import fs from 'fs';
|
||||
import config from '../back/config';
|
||||
import path from 'path';
|
||||
|
||||
const tokenFile = path.join(config.configPath, 'token.json');
|
||||
|
||||
async function getToken() {
|
||||
try {
|
||||
const data = await readFile();
|
||||
const nowTime = Math.round(Date.now() / 1000);
|
||||
if (data.value && data.expiration > nowTime) {
|
||||
console.log(data.value);
|
||||
} else {
|
||||
Container.set('logger', LoggerInstance);
|
||||
const openService = Container.get(OpenService);
|
||||
const appToken = await openService.findSystemToken();
|
||||
console.log(appToken.value);
|
||||
await writeFile({
|
||||
value: appToken.value,
|
||||
expiration: appToken.expiration,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function readFile() {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
fs.readFile(
|
||||
path.join(config.configPath, 'token.json'),
|
||||
{ encoding: 'utf8' },
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
resolve({});
|
||||
} else {
|
||||
resolve(JSON.parse(data));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function writeFile(data: any) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(tokenFile, JSON.stringify(data), { encoding: 'utf8' }, () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getToken();
|
||||
+8
-8
@@ -12,14 +12,16 @@ diff_cron() {
|
||||
local list_task="$2"
|
||||
local list_add="$3"
|
||||
local list_drop="$4"
|
||||
if [[ -s $list_task ]]; then
|
||||
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||
grep -vwf $list_task $list_scripts >$list_add
|
||||
elif [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||
grep -vwf $list_scripts $list_task >$list_drop
|
||||
fi
|
||||
|
||||
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||
cp -f $list_scripts $list_add
|
||||
fi
|
||||
if [[ -s $list_scripts ]]; then
|
||||
grep -vwf $list_scripts $list_task >$list_drop
|
||||
else
|
||||
|
||||
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
|
||||
cp -f $list_task $list_drop
|
||||
fi
|
||||
}
|
||||
@@ -486,7 +488,6 @@ main() {
|
||||
run_extra_shell >>$log_path
|
||||
;;
|
||||
repo)
|
||||
get_user_info
|
||||
get_uniq_path "$p2" "$p6"
|
||||
if [[ -n $p2 ]]; then
|
||||
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
|
||||
@@ -496,7 +497,6 @@ main() {
|
||||
fi
|
||||
;;
|
||||
raw)
|
||||
get_user_info
|
||||
get_uniq_path "$p2"
|
||||
if [[ -n $p2 ]]; then
|
||||
update_raw "$p2"
|
||||
@@ -538,7 +538,7 @@ main() {
|
||||
;;
|
||||
esac
|
||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
||||
local diff_time=$(diff_time "%Y-%m-%d %H:%M:%S" "$begin_time" "$end_time")
|
||||
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
|
||||
echo -e "\n## 执行结束... $end_time 耗时 $diff_time 秒" >>$log_path
|
||||
cat $log_path
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
FileOutlined,
|
||||
PlayCircleOutlined,
|
||||
PauseCircleOutlined,
|
||||
FullscreenOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { CrontabStatus } from './index';
|
||||
import { diffTime } from '@/utils/date';
|
||||
@@ -106,7 +107,8 @@ const CronDetailModal = ({
|
||||
glyphMargin: false,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
onMount={(editor, monaco) => {
|
||||
console.log(monaco);
|
||||
editorRef.current = editor;
|
||||
}}
|
||||
/>
|
||||
@@ -352,6 +354,11 @@ const CronDetailModal = ({
|
||||
});
|
||||
};
|
||||
|
||||
const fullscreen = () => {
|
||||
const editorElement = editorRef.current._domElement as HTMLElement;
|
||||
editorElement.parentElement?.requestFullscreen();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (cron && cron.id) {
|
||||
setCurrentCron(cron);
|
||||
@@ -529,13 +536,20 @@ const CronDetailModal = ({
|
||||
}}
|
||||
tabBarExtraContent={
|
||||
activeTabKey === 'script' && (
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={saveFile}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={saveFile}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<FullscreenOutlined />}
|
||||
onClick={fullscreen}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
Vendored
+15
-1
@@ -25,7 +25,7 @@ import EditNameModal from './editNameModal';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import './index.less';
|
||||
import { getTableScroll } from '@/utils/index';
|
||||
import { exportJson, getTableScroll } from '@/utils/index';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { Search } = Input;
|
||||
@@ -462,6 +462,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const exportEnvs = () => {
|
||||
const envs = value
|
||||
.filter((x) => selectedRowIds.includes(x.id))
|
||||
.map((x) => ({ value: x.value, name: x.name, remarks: x.remarks }));
|
||||
exportJson('env.json', JSON.stringify(envs));
|
||||
};
|
||||
|
||||
const modifyName = () => {
|
||||
setIsEditNameModalVisible(true);
|
||||
};
|
||||
@@ -516,6 +523,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
>
|
||||
批量删除
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => exportEnvs()}
|
||||
style={{ marginLeft: 8, marginRight: 8 }}
|
||||
>
|
||||
批量导出
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => operateEnvs(0)}
|
||||
|
||||
@@ -48,7 +48,7 @@ const EditScriptNameModal = ({
|
||||
})
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('保存文件成功');
|
||||
message.success('新建文件成功');
|
||||
const key = values.path ? `${values.path}/` : '';
|
||||
const filename = file ? file.name : values.filename;
|
||||
handleCancel({
|
||||
|
||||
@@ -205,3 +205,39 @@ export function getTableScroll({
|
||||
let height = document.body.clientHeight - mainTop - extraHeight;
|
||||
return height;
|
||||
}
|
||||
|
||||
// 自动触发点击事件
|
||||
function automaticClick(elment: HTMLElement) {
|
||||
const ev = document.createEvent('MouseEvents');
|
||||
ev.initMouseEvent(
|
||||
'click',
|
||||
true,
|
||||
false,
|
||||
window,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
null,
|
||||
);
|
||||
elment.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
// 导出文件
|
||||
export function exportJson(name: string, data: string) {
|
||||
const urlObject = window.URL || window.webkitURL || window;
|
||||
const export_blob = new Blob([data]);
|
||||
const createA = document.createElementNS(
|
||||
'http://www.w3.org/1999/xhtml',
|
||||
'a',
|
||||
) as any;
|
||||
createA.href = urlObject.createObjectURL(export_blob);
|
||||
createA.download = name;
|
||||
automaticClick(createA);
|
||||
}
|
||||
|
||||
+4
-8
@@ -1,9 +1,5 @@
|
||||
export const version = '2.13.3';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/303';
|
||||
export const changeLog = `2.13.3 版本说明
|
||||
1. 新建脚本支持文件上传
|
||||
2. 修复调试脚本运行,不需要每次保存
|
||||
3. 修复新建脚本选择嵌套目录
|
||||
4. 修复粘贴订阅,私有仓库字段不更新
|
||||
5. 修复脚本管理、日志管理树
|
||||
export const version = '2.13.5';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/320';
|
||||
export const changeLog = `2.13.5 版本说明
|
||||
1. 修复task命令获取时间异常
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user