Compare commits

..
27 Commits
Author SHA1 Message Date
whyour c0977f9e37 更新版本 v2.13.5 2022-07-17 22:29:25 +08:00
whyour e9b85347f4 修复shell获取时间 2022-07-17 13:24:59 +08:00
whyour 325482cbb1 更新版本 v2.13.4 2022-07-16 12:31:50 +08:00
whyour 45e02a55c3 修改npm镜像源 2022-07-16 12:25:14 +08:00
whyour 6b8ef75b51 修复获取环境变量详情 2022-07-16 12:16:33 +08:00
whyour f2535ece76 移除内置token判断 2022-07-11 21:21:35 +08:00
whyour b2afa65cb1 任务详情编辑脚本支持全屏 2022-07-04 22:44:51 +08:00
whyour 3a62cc37ff 环境变量增加导出功能 2022-07-04 22:24:50 +08:00
whyour 93c69826ad 脚本管理按文件类型排序 2022-06-26 22:25:21 +08:00
whyour 70727d0e26 修复mac和linux兼容性 2022-06-26 18:59:39 +08:00
whyour 2578aae0ba 修复手动停止任务耗时日志 2022-06-19 14:32:09 +08:00
whyour 616e56ea95 修复系统token验证 2022-06-18 19:24:56 +08:00
whyour 96ae20de68 修复系统token权限 2022-06-18 19:18:14 +08:00
whyour 6975ff4aa1 修复repo命令 2022-06-18 19:03:50 +08:00
whyour 1d8a1e9c4a 修复repo命令 2022-06-18 19:00:22 +08:00
whyour 2719280cdc 修改结束message 2022-06-18 12:45:38 +08:00
whyour 575e41172f 修改日期打印 2022-06-18 12:43:23 +08:00
whyour bd50b1e976 Merge branch 'develop' of github.com:whyour/qinglong into develop 2022-06-18 12:30:11 +08:00
whyour dfb2572b4f 修复node kill进程逻辑 2022-06-18 12:30:02 +08:00
二毛andGitHub 7746b18023 区分原生termux和termux中安装的Linux发行版 (#1498)
检测termux时只看环境变量,以便于区分原生termux和termux中安装的Linux发行版
测试发现${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT}在termux中安装的Linux发行版中仍旧存在,但是发行版中应该不用设置软连接吧
2022-06-14 22:55:49 +08:00
whyour 05c738c7c6 修改系统内部获取token方式 2022-06-14 22:43:18 +08:00
whyour e561e12356 修改更新定时任务参数验证 2022-06-12 21:12:01 +08:00
whyour 595b28ba62 移除notify软链命令 2022-06-10 21:23:43 +08:00
whyour 366caed74e 修复根目录环境变量 2022-06-09 16:44:58 +08:00
whyour 2e26a8c24e 修复shell本地开发兼容性 2022-06-09 12:10:41 +08:00
whyour c09c375066 修复无法更新脚本内容为空 2022-06-08 23:57:22 +08:00
whyour 4f93905507 修改默认pip源 2022-06-08 22:54:44 +08:00
28 changed files with 380 additions and 139 deletions
+3 -3
View File
@@ -7,6 +7,6 @@ LOG_LEVEL='debug'
SECRET='whyour' SECRET='whyour'
QINIU_AK = '' QINIU_AK=''
QINIU_SK = '' QINIU_SK=''
QINIU_SCOPE = '' QINIU_SCOPE=''
+1 -1
View File
@@ -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 strict-peer-dependencies=false
+7 -7
View File
@@ -182,9 +182,9 @@ export default (app: Router) => {
celebrate({ celebrate({
body: Joi.object({ body: Joi.object({
labels: Joi.array().optional().allow(null), labels: Joi.array().optional().allow(null),
command: Joi.string().optional(), command: Joi.string().required(),
schedule: Joi.string().optional(), schedule: Joi.string().required(),
name: Joi.string().optional(), name: Joi.string().optional().allow(null),
id: Joi.number().required(), id: Joi.number().required(),
}), }),
}), }),
@@ -297,10 +297,10 @@ export default (app: Router) => {
body: Joi.object({ body: Joi.object({
ids: Joi.array().items(Joi.number().required()), ids: Joi.array().items(Joi.number().required()),
status: Joi.string().required(), status: Joi.string().required(),
pid: Joi.string().optional(), pid: Joi.string().optional().allow(null),
log_path: Joi.string().optional(), log_path: Joi.string().optional().allow(null),
last_running_time: Joi.number().optional(), last_running_time: Joi.number().optional().allow(null),
last_execution_time: Joi.number().optional(), last_execution_time: Joi.number().optional().allow(null),
}), }),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
+1 -1
View File
@@ -170,7 +170,7 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');
try { try {
const envService = Container.get(EnvService); 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 }); return res.send({ code: 200, data });
} catch (e) { } catch (e) {
return next(e); return next(e);
+5 -1
View File
@@ -6,6 +6,7 @@ import config from '../config';
import { getFileContentByName, readDirs } from '../config/util'; import { getFileContentByName, readDirs } from '../config/util';
import { join } from 'path'; import { join } from 'path';
const route = Router(); const route = Router();
const blacklist = ['.tmp'];
export default (app: Router) => { export default (app: Router) => {
app.use('/logs', route); app.use('/logs', route);
@@ -13,7 +14,7 @@ export default (app: Router) => {
route.get('/', async (req: Request, res: Response, next: NextFunction) => { route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');
try { try {
const result = readDirs(config.logPath, config.logPath); const result = readDirs(config.logPath, config.logPath, blacklist);
res.send({ res.send({
code: 200, code: 200,
data: result, data: result,
@@ -29,6 +30,9 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');
try { try {
if (blacklist.includes(req.path)) {
return res.send({ code: 403, message: '暂无权限' });
}
const filePath = join( const filePath = join(
config.logPath, config.logPath,
(req.query.path || '') as string, (req.query.path || '') as string,
+1 -1
View File
@@ -25,7 +25,7 @@ export default (app: Router) => {
'/apps', '/apps',
celebrate({ celebrate({
body: Joi.object({ body: Joi.object({
name: Joi.string().optional().allow(''), name: Joi.string().optional().allow('').disallow('system'),
scopes: Joi.array().items(Joi.string().required()), scopes: Joi.array().items(Joi.string().required()),
}), }),
}), }),
+1 -1
View File
@@ -133,7 +133,7 @@ export default (app: Router) => {
body: Joi.object({ body: Joi.object({
filename: Joi.string().required(), filename: Joi.string().required(),
path: Joi.string().optional().allow(''), path: Joi.string().optional().allow(''),
content: Joi.string().required(), content: Joi.string().required().allow(''),
}), }),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
+1
View File
@@ -69,6 +69,7 @@ export default {
'cookie.sh', 'cookie.sh',
'crontab.list', 'crontab.list',
'env.sh', 'env.sh',
'token.json',
], ],
writePathList: [configPath, scriptPath], writePathList: [configPath, scriptPath],
bakPath, bakPath,
+25 -2
View File
@@ -2,6 +2,7 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import got from 'got'; import got from 'got';
import iconv from 'iconv-lite'; import iconv from 'iconv-lite';
import { exec } from 'child_process';
export function getFileContentByName(fileName: string) { export function getFileContentByName(fileName: string) {
if (fs.existsSync(fileName)) { if (fs.existsSync(fileName)) {
@@ -277,6 +278,11 @@ export async function concurrentRun(
return replyList; return replyList;
} }
enum FileType {
'directory',
'file',
}
export function readDirs( export function readDirs(
dir: string, dir: string,
baseDir: string = '', baseDir: string = '',
@@ -297,7 +303,10 @@ export function readDirs(
type: 'directory', type: 'directory',
disabled: true, disabled: true,
parent: relativePath, 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 { return {
@@ -307,7 +316,9 @@ export function readDirs(
parent: relativePath, parent: relativePath,
}; };
}); });
return result; return result.sort(
(a: any, b: any) => (FileType as any)[a.type] - (FileType as any)[b.type],
);
} }
export function readDir( export function readDir(
@@ -332,3 +343,15 @@ export function readDir(
}); });
return result; 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));
},
);
});
}
+26
View File
@@ -2,6 +2,7 @@ import path from 'path';
import fs from 'fs'; import fs from 'fs';
import chokidar from 'chokidar'; import chokidar from 'chokidar';
import config from '../config/index'; import config from '../config/index';
import { promiseExec } from '../config/util';
function linkToNodeModule(src: string, dst?: string) { function linkToNodeModule(src: string, dst?: string) {
const target = path.join(config.rootPath, 'node_modules', dst || src); 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') => { export default async (src: string = 'deps') => {
await linkCommand();
linkToNodeModule(src); linkToNodeModule(src);
const source = path.join(config.rootPath, src); const source = path.join(config.rootPath, src);
-7
View File
@@ -80,13 +80,6 @@ export default ({ app }: { app: Application }) => {
) { ) {
return next(); 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'); const data = fs.readFileSync(config.authConfigFile, 'utf8');
if (data) { if (data) {
+8
View File
@@ -64,5 +64,13 @@ export default async () => {
dotenv.config({ path: confFile }); 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'); Logger.info('✌️ Init file down');
}; };
+13 -3
View File
@@ -10,6 +10,7 @@ import { promises, existsSync } from 'fs';
import { promisify } from 'util'; import { promisify } from 'util';
import { Op } from 'sequelize'; import { Op } from 'sequelize';
import path from 'path'; import path from 'path';
import dayjs from 'dayjs';
@Service() @Service()
export default class CronService { export default class CronService {
@@ -197,13 +198,21 @@ export default class CronService {
const err = await this.killTask(doc.command); const err = await this.killTask(doc.command);
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`); const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
const logFileExist = doc.log_path && (await fileExist(absolutePath)); 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) { if (logFileExist) {
const str = err ? `\n${err}` : ''; const str = err ? `\n${err}` : '';
fs.appendFileSync( fs.appendFileSync(
`${absolutePath}`, `${absolutePath}`,
`${str}\n## 执行结束... ${new Date() `${str}\n## 执行结束... ${endTime.format(
.toLocaleString('zh', { hour12: false }) 'YYYY-MM-DD HH:mm:ss',
.replace(' 24:', ' 00:')} `, )}${diffTimeStr}`,
); );
} }
} }
@@ -228,6 +237,7 @@ export default class CronService {
const killLogs = []; const killLogs = [];
if (pids && pids.length > 0) { if (pids && pids.length > 0) {
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个 // node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
pids = pids.slice(0, 3);
for (const id of pids) { for (const id of pids) {
const c = `kill -9 ${id.slice(1)}`; const c = `kill -9 ${id.slice(1)}`;
try { try {
+14 -20
View File
@@ -14,6 +14,7 @@ import { spawn } from 'child_process';
import SockService from './sock'; import SockService from './sock';
import { Op } from 'sequelize'; import { Op } from 'sequelize';
import { concurrentRun } from '../config/util'; import { concurrentRun } from '../config/util';
import dayjs from 'dayjs';
@Service() @Service()
export default class DependenceService { export default class DependenceService {
@@ -161,20 +162,17 @@ export default class DependenceService {
)[dependencies[0].type as any]; )[dependencies[0].type as any];
const actionText = isInstall ? '安装' : '删除'; const actionText = isInstall ? '安装' : '删除';
const depIds = dependencies.map((x) => x.id) as number[]; 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({ this.sockService.sendMessage({
type: socketMessageType, type: socketMessageType,
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date( message,
startTime,
).toLocaleString()}\n\n`,
references: depIds, references: depIds,
}); });
await this.updateLog( await this.updateLog(depIds, message);
depIds,
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
startTime,
).toLocaleString()}\n\n`,
);
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' }); const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
@@ -207,23 +205,19 @@ export default class DependenceService {
}); });
cp.on('close', async (code) => { cp.on('close', async (code) => {
const endTime = Date.now(); const endTime = dayjs();
const isSucceed = code === 0; const isSucceed = code === 0;
const resultText = isSucceed ? '成功' : '失败'; const resultText = isSucceed ? '成功' : '失败';
const message = `\n依赖${actionText}${resultText},结束时间 ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)},耗时 ${endTime.diff(startTime, 'second')}`;
this.sockService.sendMessage({ this.sockService.sendMessage({
type: socketMessageType, type: socketMessageType,
message: `\n依赖${actionText}${resultText},结束时间 ${new Date( message,
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
references: depIds, references: depIds,
}); });
await this.updateLog( await this.updateLog(depIds, message);
depIds,
`\n依赖${actionText}${resultText},结束时间 ${new Date(
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
);
let status = null; let status = null;
if (isSucceed) { if (isSucceed) {
+34 -1
View File
@@ -90,7 +90,9 @@ export default class OpenService {
} }
try { try {
const result = await this.find(condition); 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) { } catch (error) {
throw error; throw error;
} }
@@ -142,4 +144,35 @@ export default class OpenService {
return { code: 400, message: 'client_id或client_seret有误' }; 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;
}
} }
+3 -3
View File
@@ -28,6 +28,7 @@ import ScheduleService, { TaskCallbacks } from './schedule';
import { SimpleIntervalSchedule } from 'toad-scheduler'; import { SimpleIntervalSchedule } from 'toad-scheduler';
import SockService from './sock'; import SockService from './sock';
import SshKeyService from './sshKey'; import SshKeyService from './sshKey';
import dayjs from 'dayjs';
@Service() @Service()
export default class SubscriptionService { export default class SubscriptionService {
@@ -371,11 +372,10 @@ export default class SubscriptionService {
const err = await this.killTask(command); const err = await this.killTask(command);
const absolutePath = await this.handleLogPath(doc.log_path as string); const absolutePath = await this.handleLogPath(doc.log_path as string);
const str = err ? `\n${err}` : ''; const str = err ? `\n${err}` : '';
fs.appendFileSync( fs.appendFileSync(
`${absolutePath}`, `${absolutePath}`,
`${str}\n## 执行结束... ${new Date() `${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `,
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `,
); );
} }
+3 -3
View File
@@ -13,7 +13,7 @@ import { Request } from 'express';
import ScheduleService from './schedule'; import ScheduleService from './schedule';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import SockService from './sock'; import SockService from './sock';
import got from 'got'; import dayjs from 'dayjs';
@Service() @Service()
export default class UserService { export default class UserService {
@@ -111,7 +111,7 @@ export default class UserService {
}); });
await this.notificationService.notify( await this.notificationService.notify(
'登录通知', '登录通知',
`你于${new Date(timestamp).toLocaleString()}${address} ${ `你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform req.platform
}端 登录成功,ip地址 ${ip}`, }端 登录成功,ip地址 ${ip}`,
); );
@@ -140,7 +140,7 @@ export default class UserService {
}); });
await this.notificationService.notify( await this.notificationService.notify(
'登录通知', '登录通知',
`你于${new Date(timestamp).toLocaleString()}${address} ${ `你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform req.platform
}端 登录失败,ip地址 ${ip}`, }端 登录失败,ip地址 ${ip}`,
); );
+1 -1
View File
@@ -39,7 +39,7 @@ AutoStartBot=""
BotRepoUrl="" BotRepoUrl=""
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空 ## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple" PipMirror="https://pypi.doubanio.com/simple/"
## 通知环境变量 ## 通知环境变量
## 1. Server酱 ## 1. Server酱
+8 -26
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
get_token() { get_token() {
token=$(cat $file_auth_user | jq -r .token) token=$(ts-node-transpile-only "$dir_shell/token.ts")
} }
add_cron_api() { add_cron_api() {
@@ -17,7 +17,7 @@ add_cron_api() {
fi fi
local api=$( 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 "Accept: application/json" \
-H "Authorization: Bearer $token" \ -H "Authorization: Bearer $token" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \ -H "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 fi
local api=$( 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' \ -X 'PUT' \
-H "Accept: application/json" \ -H "Accept: application/json" \
-H "Authorization: Bearer $token" \ -H "Authorization: Bearer $token" \
@@ -84,7 +84,7 @@ update_cron_command_api() {
fi fi
local api=$( 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' \ -X 'PUT' \
-H "Accept: application/json" \ -H "Accept: application/json" \
-H "Authorization: Bearer $token" \ -H "Authorization: Bearer $token" \
@@ -109,7 +109,7 @@ del_cron_api() {
local ids=$1 local ids=$1
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( 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' \ -X 'DELETE' \
-H "Accept: application/json" \ -H "Accept: application/json" \
-H "Authorization: Bearer $token" \ -H "Authorization: Bearer $token" \
@@ -130,24 +130,6 @@ del_cron_api() {
fi 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() { update_cron() {
local ids="$1" local ids="$1"
local status="$2" local status="$2"
@@ -157,7 +139,7 @@ update_cron() {
local runningTime="${6:-0}" local runningTime="${6:-0}"
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( 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' \ -X 'PUT' \
-H "Accept: application/json" \ -H "Accept: application/json" \
-H "Authorization: Bearer $token" \ -H "Authorization: Bearer $token" \
@@ -172,7 +154,7 @@ update_cron() {
code=$(echo $api | jq -r .code) code=$(echo $api | jq -r .code)
message=$(echo $api | jq -r .message) message=$(echo $api | jq -r .message)
if [[ $code != 200 ]]; then if [[ $code != 200 ]]; then
echo -e "\n## 更新任务状态失败(${message})\n" >> $log_path echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path
fi fi
} }
@@ -181,7 +163,7 @@ notify_api() {
local content=$2 local content=$2
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( 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' \ -X 'PUT' \
-H "Accept: application/json" \ -H "Accept: application/json" \
-H "Authorization: Bearer $token" \ -H "Authorization: Bearer $token" \
+50 -7
View File
@@ -14,7 +14,6 @@ dir_log=$dir_data/log
dir_db=$dir_data/db dir_db=$dir_data/db
dir_dep=$dir_data/deps dir_dep=$dir_data/deps
dir_list_tmp=$dir_log/.tmp dir_list_tmp=$dir_log/.tmp
dir_code=$dir_log/code
dir_update_log=$dir_log/update dir_update_log=$dir_log/update
ql_static_repo=$dir_repo/static ql_static_repo=$dir_repo/static
@@ -52,12 +51,10 @@ list_own_drop=$dir_list_tmp/own_drop.list
link_name=( link_name=(
task task
ql ql
notify
) )
original_name=( original_name=(
task.sh task.sh
update.sh update.sh
notify.sh
) )
init_env() { init_env() {
@@ -101,7 +98,7 @@ make_dir() {
} }
detect_termux() { detect_termux() {
if [[ ${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT} ]] || [[ $PATH == *com.termux* ]]; then if [[ $PATH == *com.termux* ]]; then
is_termux=1 is_termux=1
else else
is_termux=0 is_termux=0
@@ -248,11 +245,11 @@ fix_config() {
npm_install_sub() { npm_install_sub() {
set_proxy set_proxy
if [ $is_termux -eq 1 ]; then 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 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 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 fi
unset_proxy unset_proxy
} }
@@ -380,6 +377,52 @@ reload_pm2() {
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null 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 init_env
detect_termux detect_termux
detect_macos detect_macos
+33 -25
View File
@@ -84,8 +84,8 @@ run_normal() {
fi fi
fi fi
local time=$(date) local time=$(date "+$time_format")
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S") log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}" log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then if [[ $file_param =~ "/" ]]; then
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_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}" log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log" log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path" cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd="" [[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir" make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S") local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(date -d "$time" "+%s") local begin_timestamp=$(format_timestamp "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd [[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -118,12 +119,13 @@ run_normal() {
cd ${relative_path} cd ${relative_path}
file_param=${file_param/$relative_path\//} file_param=${file_param/$relative_path\//}
fi 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 eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S') local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%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" [[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
} }
@@ -154,8 +156,8 @@ run_concurrent() {
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr} [[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
define_program "$file_param" define_program "$file_param"
local time=$(date) local time=$(date "+$time_format")
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S") log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}" log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then if [[ $file_param =~ "/" ]]; then
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_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}" log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log" log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path" cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd="" [[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir" make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S") local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(date -d "$time" "+%s") local begin_timestamp=$(format_timestamp "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd [[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -196,7 +198,7 @@ run_concurrent() {
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"
timeout -k 10s $command_timeout_time $which_program $file_param &>$single_log_path & eval $timeoutCmd $which_program $file_param &>$single_log_path &
done done
wait wait
@@ -224,8 +226,8 @@ run_designated() {
fi fi
define_program "$file_param" define_program "$file_param"
local time=$(date) local time=$(date "+$time_format")
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S") log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}" log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then if [[ $file_param =~ "/" ]]; then
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_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}" log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log" log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path" cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd="" [[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir" make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S") local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(date -d "$time" "+%s") local begin_timestamp=$(format_timestamp "$time_format" "$time")
local envs=$(eval echo "\$${env_param}") local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g')) local array=($(echo $envs | sed 's/&/ /g'))
@@ -274,7 +276,7 @@ run_designated() {
cd ${relative_path} cd ${relative_path}
file_param=${file_param/$relative_path\//} file_param=${file_param/$relative_path\//}
fi 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 eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S') local end_time=$(date '+%Y-%m-%d %H:%M:%S')
@@ -288,8 +290,8 @@ run_designated() {
run_else() { run_else() {
local file_param="$1" local file_param="$1"
define_program "$file_param" define_program "$file_param"
local time=$(date) local time=$(date "+$time_format")
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S") log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}" log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then if [[ $file_param =~ "/" ]]; then
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_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}" log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log" log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path" cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd="" [[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir" make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S") local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(date -d "$time" "+%s") local begin_timestamp=$(format_timestamp "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd [[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -325,7 +327,7 @@ run_else() {
fi fi
shift 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 eval . $file_task_after "$file_param" "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S') local end_time=$(date '+%Y-%m-%d %H:%M:%S')
@@ -348,6 +350,12 @@ main() {
done done
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1)) [[ "$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 if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
case $# in case $# in
1) 1)
Executable
+56
View File
@@ -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
View File
@@ -12,14 +12,16 @@ diff_cron() {
local list_task="$2" local list_task="$2"
local list_add="$3" local list_add="$3"
local list_drop="$4" 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 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 cp -f $list_scripts $list_add
fi fi
if [[ -s $list_scripts ]]; then
grep -vwf $list_scripts $list_task >$list_drop if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
else
cp -f $list_task $list_drop cp -f $list_task $list_drop
fi fi
} }
@@ -486,7 +488,6 @@ main() {
run_extra_shell >>$log_path run_extra_shell >>$log_path
;; ;;
repo) repo)
get_user_info
get_uniq_path "$p2" "$p6" get_uniq_path "$p2" "$p6"
if [[ -n $p2 ]]; then if [[ -n $p2 ]]; then
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7" update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
@@ -496,7 +497,6 @@ main() {
fi fi
;; ;;
raw) raw)
get_user_info
get_uniq_path "$p2" get_uniq_path "$p2"
if [[ -n $p2 ]]; then if [[ -n $p2 ]]; then
update_raw "$p2" update_raw "$p2"
@@ -538,7 +538,7 @@ main() {
;; ;;
esac esac
local end_time=$(date '+%Y-%m-%d %H:%M:%S') 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 if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
echo -e "\n## 执行结束... $end_time 耗时 $diff_time" >>$log_path echo -e "\n## 执行结束... $end_time 耗时 $diff_time" >>$log_path
cat $log_path cat $log_path
+22 -8
View File
@@ -20,6 +20,7 @@ import {
FileOutlined, FileOutlined,
PlayCircleOutlined, PlayCircleOutlined,
PauseCircleOutlined, PauseCircleOutlined,
FullscreenOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { CrontabStatus } from './index'; import { CrontabStatus } from './index';
import { diffTime } from '@/utils/date'; import { diffTime } from '@/utils/date';
@@ -106,7 +107,8 @@ const CronDetailModal = ({
glyphMargin: false, glyphMargin: false,
wordWrap: 'on', wordWrap: 'on',
}} }}
onMount={(editor) => { onMount={(editor, monaco) => {
console.log(monaco);
editorRef.current = editor; editorRef.current = editor;
}} }}
/> />
@@ -352,6 +354,11 @@ const CronDetailModal = ({
}); });
}; };
const fullscreen = () => {
const editorElement = editorRef.current._domElement as HTMLElement;
editorElement.parentElement?.requestFullscreen();
};
useEffect(() => { useEffect(() => {
if (cron && cron.id) { if (cron && cron.id) {
setCurrentCron(cron); setCurrentCron(cron);
@@ -529,13 +536,20 @@ const CronDetailModal = ({
}} }}
tabBarExtraContent={ tabBarExtraContent={
activeTabKey === 'script' && ( activeTabKey === 'script' && (
<Button <>
type="primary" <Button
style={{ marginRight: 8 }} type="primary"
onClick={saveFile} style={{ marginRight: 8 }}
> onClick={saveFile}
>
</Button>
</Button>
<Button
type="primary"
icon={<FullscreenOutlined />}
onClick={fullscreen}
/>
</>
) )
} }
> >
+15 -1
View File
@@ -25,7 +25,7 @@ import EditNameModal from './editNameModal';
import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import './index.less'; import './index.less';
import { getTableScroll } from '@/utils/index'; import { exportJson, getTableScroll } from '@/utils/index';
const { Text, Paragraph } = Typography; const { Text, Paragraph } = Typography;
const { Search } = Input; 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 = () => { const modifyName = () => {
setIsEditNameModalVisible(true); setIsEditNameModalVisible(true);
}; };
@@ -516,6 +523,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
> >
</Button> </Button>
<Button
type="primary"
onClick={() => exportEnvs()}
style={{ marginLeft: 8, marginRight: 8 }}
>
</Button>
<Button <Button
type="primary" type="primary"
onClick={() => operateEnvs(0)} onClick={() => operateEnvs(0)}
+1 -1
View File
@@ -48,7 +48,7 @@ const EditScriptNameModal = ({
}) })
.then(({ code, data }) => { .then(({ code, data }) => {
if (code === 200) { if (code === 200) {
message.success('保存文件成功'); message.success('新建文件成功');
const key = values.path ? `${values.path}/` : ''; const key = values.path ? `${values.path}/` : '';
const filename = file ? file.name : values.filename; const filename = file ? file.name : values.filename;
handleCancel({ handleCancel({
+36
View File
@@ -205,3 +205,39 @@ export function getTableScroll({
let height = document.body.clientHeight - mainTop - extraHeight; let height = document.body.clientHeight - mainTop - extraHeight;
return height; 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
View File
@@ -1,9 +1,5 @@
export const version = '2.13.3'; export const version = '2.13.5';
export const changeLogLink = 'https://t.me/jiao_long/303'; export const changeLogLink = 'https://t.me/jiao_long/320';
export const changeLog = `2.13.3 版本说明 export const changeLog = `2.13.5 版本说明
1. 新建脚本支持文件上传 1. 修复task命令获取时间异常
2. 修复调试脚本运行,不需要每次保存
3. 修复新建脚本选择嵌套目录
4. 修复粘贴订阅,私有仓库字段不更新
5. 修复脚本管理、日志管理树
`; `;