Compare commits

...
34 Commits
Author SHA1 Message Date
whyour e07d2b6639 修复获取最新version文件 2022-04-24 18:43:09 +08:00
whyour 220a87627d 更新版本 v2.12.2 2022-04-24 17:50:03 +08:00
whyour 87bca2ac4e 任务详情增加运行、禁用、置顶操作 2022-04-24 17:45:50 +08:00
whyour c320906149 依赖管理增加直接强制删除 2022-04-23 15:02:54 +08:00
JerryWnandGitHub 27958a1a90 修复双引号转义逻辑 #1331 (#1337) 2022-04-18 22:55:37 +08:00
whyour 82c7011522 修复定时删除日志设置,主持设置大于24天 2022-04-18 22:29:14 +08:00
whyour fa6d57d809 修复shell发送通知 2022-04-09 11:13:08 +08:00
whyour 1060fc7476 修复并发日志目录 2022-04-06 12:18:16 +08:00
whyour 56bc38e9e7 更新readme 2022-04-05 23:24:27 +08:00
whyour 18be65d6fe 更新发布脚本 2022-04-05 23:17:03 +08:00
whyour 3409085195 更新发布脚本 2022-04-05 23:14:47 +08:00
whyour 17e17ac077 增加发布脚本 2022-04-05 23:11:48 +08:00
whyour c655e1da38 修复日志目录判断 2022-04-05 00:28:14 +08:00
whyour 0ea158724f 修复日志目录获取 2022-04-05 00:21:32 +08:00
whyour fb521498ff 修复task日志打印 2022-04-04 23:32:27 +08:00
whyour 399550ccad 修复日志创建目录 2022-04-04 22:40:02 +08:00
whyour c7c30c86f2 修复目录获取 2022-04-04 18:06:21 +08:00
whyour 5f6c0882d6 修改版本说明 2022-04-04 17:28:15 +08:00
whyour 6e05a4f3a5 更新版本号 v2.12.1 2022-04-04 17:24:42 +08:00
whyour ea8aa6a231 修改获取版本链接 2022-04-04 17:17:34 +08:00
whyour ae2570c677 修复shell日志路径截取 2022-04-03 22:31:11 +08:00
whyour c76d952d41 修改yarn命令 2022-04-03 21:49:20 +08:00
whyour cdf9fb24be 修复定时任务获取日志 2022-04-03 20:00:31 +08:00
whyour 2131e16781 删除console 2022-04-03 19:50:44 +08:00
whyour b0c8425639 修复日志路径拼接规则 2022-04-03 19:32:44 +08:00
whyour f0869567de 七牛云改为http访问 2022-04-01 08:29:16 +08:00
whyour be2a19949b 修复获取日志相对目录 2022-03-30 11:34:36 +08:00
whyour 8627cccb58 修改日志路径合并方式 2022-03-30 11:17:11 +08:00
whyour 4a8eaebf17 修复获取任务日志路径 2022-03-30 10:57:43 +08:00
kan3GitandGitHub e7edd0a5a4 tg发送消息改json (#1269)
发现有带&的消息都会漏掉&后面的内容,改为JSON发送
2022-03-26 17:23:54 +08:00
chen310andGitHub 9ccbea0a04 修复PushDeer推送Python版本错误 (#1270)
* 修复PushDeer推送Python版本错误
2022-03-26 17:20:02 +08:00
whyour ab715ef840 修复系统pushDeer通知 2022-03-26 17:17:13 +08:00
whyour 117822fe7c 修改任务最大内存占用200兆 2022-03-26 16:53:03 +08:00
whyour da922678a1 修复定时任务获取日志目录 2022-03-26 00:29:42 +08:00
30 changed files with 601 additions and 219 deletions
+4 -3
View File
@@ -27,9 +27,10 @@ jobs:
- name: build front and back
run: |
yarn install
yarn build:front
yarn build:back
npm i -g pnpm
pnpm install
pnpm build:front
pnpm build:back
- name: copy to static repo
env:
+5 -3
View File
@@ -22,7 +22,7 @@
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
</div>
[![](https://z3.ax1x.com/2021/11/18/I7KrTg.jpg)](https://whyour.cn)
[![](https://user-images.githubusercontent.com/22700758/161788855-c4e51cb8-d4e9-44fe-bb17-ee1a56c8549b.png)](https://whyour.cn)
简体中文 | [English](./README-en.md)
@@ -168,8 +168,10 @@ task <file_path> desi <env_name> <account_number>
$ git clone git@github.com:whyour/qinglong.git
$ cd qinglong
$ cp .env.example .env
$ yarn install
$ yarn start
# 推荐使用 pnpm https://pnpm.io/zh/installation
$ npm install -g pnpm
$ pnpm install
$ pnpm start
```
打开你的浏览器,访问 http://127.0.0.1:5700
+1 -1
View File
@@ -94,7 +94,7 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const dependenceService = Container.get(DependenceService);
const data = await dependenceService.removeDb(req.body);
const data = await dependenceService.remove(req.body, true);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
+1 -1
View File
@@ -4,7 +4,7 @@ import { createRandomString } from './util';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
const lastVersionFile = 'https://qn.whyour.cn/version.ts';
const lastVersionFile = `http://qn.whyour.cn/version.ts?v=${Date.now()}`;
const envFound = dotenv.config();
const rootPath = process.cwd();
+2 -2
View File
@@ -37,13 +37,13 @@ export enum DependenceTypes {
}
export enum InstallDependenceCommandTypes {
'npm i -g --force',
'npm i -g -f --loglevel warn',
'pip3 install',
'apk add --no-cache -f',
}
export enum unInstallDependenceCommandTypes {
'npm uninstall -g --force',
'npm uninstall -g -f --loglevel warn',
'pip3 uninstall -y',
'apk del -f',
}
+1 -1
View File
@@ -39,7 +39,7 @@ export class PushDeerNotification extends NotificationBaseInfo {
export class BarkNotification extends NotificationBaseInfo {
public barkPush = '';
public barkIcon = 'https://qn.whyour.cn/logo.png';
public barkIcon = 'http://qn.whyour.cn/logo.png';
public barkSound = '';
public barkGroup = 'qinglong';
}
+1
View File
@@ -13,5 +13,6 @@ export class SockMessage {
export type SockMessageType =
| 'ping'
| 'installDependence'
| 'uninstallDependence'
| 'updateSystemVersion'
| 'manuallyRunScript';
+51 -18
View File
@@ -9,6 +9,7 @@ import { getFileContentByName, concurrentRun, fileExist } from '../config/util';
import { promises, existsSync } from 'fs';
import { promisify } from 'util';
import { Op } from 'sequelize';
import path from 'path';
@Service()
export default class CronService {
@@ -16,7 +17,7 @@ export default class CronService {
private isSixCron(cron: Crontab) {
const { schedule } = cron;
if (schedule.split(/ +/).length === 6) {
if (schedule?.split(/ +/).length === 6) {
return true;
}
return false;
@@ -194,11 +195,12 @@ export default class CronService {
}
}
const err = await this.killTask(doc.command);
const logFileExist = await fileExist(doc.log_path);
if (doc.log_path && logFileExist) {
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
const logFileExist = doc.log_path && (await fileExist(absolutePath));
if (logFileExist) {
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${doc.log_path}`,
`${absolutePath}`,
`${str}\n## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `,
@@ -256,6 +258,8 @@ export default class CronService {
}
let { id, command, log_path } = cron;
const absolutePath = path.resolve(config.logPath, `${log_path}`);
const logFileExist = log_path && (await fileExist(absolutePath));
this.logger.silly('Running job');
this.logger.silly('ID: ' + id);
@@ -276,13 +280,13 @@ export default class CronService {
{ where: { id } },
);
cp.stderr.on('data', (data) => {
if (log_path) {
fs.appendFileSync(`${log_path}`, `${data}`);
if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${data}`);
}
});
cp.on('error', (err) => {
if (log_path) {
fs.appendFileSync(`${log_path}`, `${JSON.stringify(err)}`);
if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
}
});
@@ -323,8 +327,10 @@ export default class CronService {
return '';
}
if (doc.log_path) {
return getFileContentByName(`${doc.log_path}`);
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
const logFileExist = doc.log_path && (await fileExist(absolutePath));
if (logFileExist) {
return getFileContentByName(`${absolutePath}`);
}
const [, commandStr, url] = doc.command.split(/ +/);
let logPath = this.getKey(commandStr);
@@ -353,6 +359,21 @@ export default class CronService {
return [];
}
if (doc.log_path) {
const relativeDir = path.dirname(`${doc.log_path}`);
const dir = path.resolve(config.logPath, relativeDir);
if (existsSync(dir)) {
let files = await promises.readdir(dir);
return files
.map((x) => ({
filename: x,
directory: relativeDir.replace(config.logPath, ''),
time: fs.statSync(`${dir}/${x}`).mtime.getTime(),
}))
.sort((a, b) => b.time - a.time);
}
}
const [, commandStr, url] = doc.command.split(/ +/);
let logPath = this.getKey(commandStr);
const isQlCommand = doc.command.startsWith('ql ');
@@ -380,15 +401,27 @@ export default class CronService {
}
}
private getKey(command: string) {
private getKey(command: string): string {
const start =
command.lastIndexOf('/') !== -1 ? command.lastIndexOf('/') + 1 : 0;
const end =
command.lastIndexOf('.') !== -1
? command.lastIndexOf('.')
: command.length;
const tmpStr = command.substring(0, start - 1);
let index = 0;
if (tmpStr.lastIndexOf('/') !== -1 && tmpStr.startsWith('http')) {
index = tmpStr.lastIndexOf('/');
} else if (tmpStr.lastIndexOf(':') !== -1 && tmpStr.startsWith('git@')) {
index = tmpStr.lastIndexOf(':');
}
if (index) {
return `${tmpStr.substring(index + 1)}_${command.substring(start, end)}`;
} else {
return command.substring(start, end);
}
}
private make_command(tab: Crontab) {
const crontab_job_string = `ID=${tab.id} ${tab.command}`;
@@ -400,7 +433,7 @@ export default class CronService {
var crontab_string = '';
tabs.forEach((tab) => {
const _schedule = tab.schedule && tab.schedule.split(/ +/);
if (tab.isDisabled === 1 || _schedule.length !== 5) {
if (tab.isDisabled === 1 || _schedule!.length !== 5) {
crontab_string += '# ';
crontab_string += tab.schedule;
crontab_string += ' ';
@@ -426,22 +459,22 @@ export default class CronService {
public import_crontab() {
exec('crontab -l', (error, stdout, stderr) => {
var lines = stdout.split('\n');
var namePrefix = new Date().getTime();
const lines = stdout.split('\n');
const namePrefix = new Date().getTime();
lines.reverse().forEach(async (line, index) => {
line = line.replace(/\t+/g, ' ');
var regex =
const regex =
/^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/;
var command = line.replace(regex, '').trim();
var schedule = line.replace(command, '').trim();
const command = line.replace(regex, '').trim();
const schedule = line.replace(command, '').trim();
if (
command &&
schedule &&
cron_parser.parseExpression(schedule).hasNext()
) {
var name = namePrefix + '_' + index;
const name = namePrefix + '_' + index;
const _crontab = await CrontabModel.findOne({
where: { command, schedule },
+17 -15
View File
@@ -56,13 +56,13 @@ export default class DependenceService {
return await this.getDb({ id: payload.id });
}
public async remove(ids: number[]) {
public async remove(ids: number[], force = false): Promise<Dependence[]> {
await DependenceModel.update(
{ status: DependenceStatus.removing, log: [] },
{ where: { id: ids } },
);
const docs = await DependenceModel.findAll({ where: { id: ids } });
this.installOrUninstallDependencies(docs, false);
this.installOrUninstallDependencies(docs, false, force);
return docs;
}
@@ -128,12 +128,16 @@ export default class DependenceService {
public installOrUninstallDependencies(
dependencies: Dependence[],
isInstall: boolean = true,
force: boolean = false,
) {
return new Promise(async (resolve) => {
if (dependencies.length === 0) {
resolve(null);
return;
}
const socketMessageType = !force
? 'installDependence'
: 'uninstallDependence';
const depNames = dependencies.map((x) => x.name).join(' ');
const depRunCommand = (
isInstall
@@ -145,21 +149,21 @@ export default class DependenceService {
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
const startTime = Date.now();
this.sockService.sendMessage({
type: 'installDependence',
type: socketMessageType,
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
startTime,
).toLocaleString()}`,
).toLocaleString()}\n\n`,
references: depIds,
});
await this.updateLog(
depIds,
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
startTime,
).toLocaleString()}\n`,
).toLocaleString()}\n\n`,
);
cp.stdout.on('data', async (data) => {
this.sockService.sendMessage({
type: 'installDependence',
type: socketMessageType,
message: data.toString(),
references: depIds,
});
@@ -168,7 +172,7 @@ export default class DependenceService {
cp.stderr.on('data', async (data) => {
this.sockService.sendMessage({
type: 'installDependence',
type: socketMessageType,
message: data.toString(),
references: depIds,
});
@@ -177,7 +181,7 @@ export default class DependenceService {
cp.on('error', async (err) => {
this.sockService.sendMessage({
type: 'installDependence',
type: socketMessageType,
message: JSON.stringify(err),
references: depIds,
});
@@ -191,15 +195,15 @@ export default class DependenceService {
const resultText = isSucceed ? '成功' : '失败';
this.sockService.sendMessage({
type: 'installDependence',
message: `依赖${actionText}${resultText},结束时间 ${new Date(
type: socketMessageType,
message: `\n依赖${actionText}${resultText},结束时间 ${new Date(
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
references: depIds,
});
await this.updateLog(
depIds,
`依赖${actionText}${resultText},结束时间 ${new Date(
`\n依赖${actionText}${resultText},结束时间 ${new Date(
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
);
@@ -216,11 +220,9 @@ export default class DependenceService {
}
await DependenceModel.update({ status }, { where: { id: depIds } });
// 如果删除依赖成功3秒后删除数据库记录
if (isSucceed && !isInstall) {
setTimeout(() => {
// 如果删除依赖成功或者强制删除
if ((isSucceed || force) && !isInstall) {
this.removeDb(depIds);
}, 5000);
}
resolve(null);
+3 -7
View File
@@ -168,13 +168,9 @@ export default class EnvService {
.filter((x) => x.status !== EnvStatus.disabled)
.map('value')
.join('&')
.replace(/ /g, '');
if (/"/.test(value)) {
value = `'${value}'`;
} else {
value = `"${value}"`;
}
env_string += `export ${key}=${value}\n`;
.replace(/"/g, '\"')
.trim();
env_string += `export ${key}="${value}"\n`;
}
}
}
+3 -4
View File
@@ -119,15 +119,14 @@ export default class NotificationService {
private async pushDeer() {
const { pushDeerKey } = this.params;
// https://api2.pushdeer.com/message/push?pushkey=<key>&text=标题&desp=<markdown>&type=markdown
const url = `https://api2.pushdeer.com/message/push`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
body: `pushkey=${pushDeerKey}&text=${
this.title
}&desp=${encodeURIComponent(this.content)}&type=markdown`,
body: `pushkey=${pushDeerKey}&text=${encodeURIComponent(
this.title,
)}&desp=${encodeURIComponent(this.content)}&type=markdown`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
.json();
+31 -13
View File
@@ -5,8 +5,8 @@ import { Crontab } from '../data/cron';
import { exec } from 'child_process';
import {
ToadScheduler,
SimpleIntervalJob,
Task,
LongIntervalJob,
AsyncTask,
SimpleIntervalSchedule,
} from 'toad-scheduler';
@@ -16,6 +16,8 @@ export default class ScheduleService {
private intervalSchedule = new ToadScheduler();
private maxBuffer = 200 * 1024 * 1024;
constructor(@Inject('logger') private logger: winston.Logger) {}
async createCronTask({ id = 0, command, name, schedule = '' }: Crontab) {
@@ -32,7 +34,10 @@ export default class ScheduleService {
_id,
nodeSchedule.scheduleJob(id + '', schedule, async () => {
try {
exec(command, async (error, stdout, stderr) => {
exec(
command,
{ maxBuffer: this.maxBuffer },
async (error, stdout, stderr) => {
if (error) {
await this.logger.info(
'执行任务%s失败,时间:%s, 错误信息:%j',
@@ -50,7 +55,8 @@ export default class ScheduleService {
stderr,
);
}
});
},
);
} catch (error) {
await this.logger.info(
'执行任务%s失败,时间:%s, 错误信息:%j',
@@ -81,9 +87,15 @@ export default class ScheduleService {
name,
command,
);
const task = new Task(name, async () => {
const task = new AsyncTask(
name,
async () => {
return new Promise(async (resolve, reject) => {
try {
exec(command, async (error, stdout, stderr) => {
exec(
command,
{ maxBuffer: this.maxBuffer },
async (error, stdout, stderr) => {
if (error) {
await this.logger.info(
'执行任务%s失败,时间:%s, 错误信息:%j',
@@ -101,19 +113,25 @@ export default class ScheduleService {
stderr,
);
}
});
resolve();
},
);
} catch (error) {
await this.logger.info(
reject(error);
}
});
},
(err) => {
this.logger.info(
'执行任务%s失败,时间:%s, 错误信息:%j',
command,
new Date().toLocaleString(),
error,
err,
);
},
);
} finally {
}
});
const job = new SimpleIntervalJob({ ...schedule }, task, _id);
const job = new LongIntervalJob({ ...schedule }, task, _id);
this.intervalSchedule.addIntervalJob(job);
}
+2
View File
@@ -57,7 +57,9 @@ export default class SystemService {
}
public async updateLogRemoveFrequency(frequency: number) {
const oDoc = await this.getLogRemoveFrequency();
const result = await this.updateAuthDb({
...oDoc,
type: AuthDataType.removeLogFrequency,
info: { frequency },
});
+1 -1
View File
@@ -51,7 +51,7 @@ export PUSH_KEY=""
## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123
export BARK_PUSH=""
## 下方填写推送图标设置,自定义推送图标(需iOS15或以上)
export BARK_ICON="https://qn.whyour.cn/logo.png"
export BARK_ICON="http://qn.whyour.cn/logo.png"
## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声
export BARK_SOUND=""
## 下方填写推送消息分组,默认为"QingLong"
+8 -4
View File
@@ -43,7 +43,7 @@ let PUSHDEER_KEY = '';
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
let BARK_PUSH = '';
//BARK app推送图标,自定义推送图标(需iOS15或以上)
let BARK_ICON = 'https://qn.whyour.cn/logo.png';
let BARK_ICON = 'http://qn.whyour.cn/logo.png';
//BARK app推送铃声,铃声列表去APP查看复制填写
let BARK_SOUND = '';
//BARK app推送消息的分组, 默认为"QingLong"
@@ -383,7 +383,7 @@ function PushDeerNotify(text, desp, time = 2100) {
console.log(err);
} else {
data = JSON.parse(data);
// 通过反悔的result的长度来判断是否成功
// 通过返回的result的长度来判断是否成功
if (
data.content.result.length !== undefined &&
data.content.result.length > 0
@@ -529,9 +529,13 @@ function tgBotNotify(text, desp) {
if (TG_BOT_TOKEN && TG_USER_ID) {
const options = {
url: `https://${TG_API_HOST}/bot${TG_BOT_TOKEN}/sendMessage`,
body: `chat_id=${TG_USER_ID}&text=${text}\n\n${desp}&disable_web_page_preview=true`,
json: {
chat_id: `${TG_USER_ID}`,
text: `${text}\n\n${desp}`,
disable_web_page_preview: true,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Type': 'application/json',
},
timeout,
};
+1 -1
View File
@@ -271,7 +271,7 @@ def pushdeer(title: str, content: str) -> None:
print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送")
return
print("PushDeer 服务启动")
data = {"text": title, "desp": urllib.parse.urlencode({"text": content})}
data = {"text": title, "desp": content, "type": "markdown", "pushkey": push_config.get("DEER_KEY")}
url = 'https://api2.pushdeer.com/message/push'
response = requests.post(url, data=data).json()
+7 -3
View File
@@ -5,8 +5,9 @@ const envFound = dotenv.config();
const accessKey = process.env.QINIU_AK;
const secretKey = process.env.QINIU_SK;
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
const key = 'version.ts';
const options = {
scope: process.env.QINIU_SCOPE,
scope: `${process.env.QINIU_SCOPE}:${key}`,
};
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(mac);
@@ -14,8 +15,11 @@ const uploadToken = putPolicy.uploadToken(mac);
const localFile = 'src/version.ts';
const config = new qiniu.conf.Config({ zone: qiniu.zone.Zone_z1 });
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra('', '', 'text/plain');
const key = 'version.ts';
const putExtra = new qiniu.form_up.PutExtra(
'',
'',
'text/plain; charset=utf-8',
);
// 文件上传
formUploader.putFile(
uploadToken,
+3 -3
View File
@@ -178,7 +178,7 @@ update_cron() {
notify_api() {
local title=$1
local content=$1
local content=$2
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/system/notify?t=$currentTimeStamp" \
@@ -196,9 +196,9 @@ notify_api() {
code=$(echo $api | jq -r .code)
message=$(echo $api | jq -r .message)
if [[ $code == 200 ]]; then
echo -e "成功"
echo -e "通知发送成功"
else
echo -e "失败(${message})"
echo -e "通知失败(${message})"
fi
}
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
echo -e "开始发布"
echo -e "切换master分支"
git checkout master
echo -e "合并develop代码"
git merge origin/develop
echo -e "提交master代码"
git push
echo -e "更新cdn文件"
ts-node sample/tool.ts
string=$(cat src/version.ts | grep "version" | egrep "[^\']*" -o | egrep "\d\.*")
version="v$string"
echo -e "当前版本$version"
echo -e "删除已经存在的本地tag"
git tag -d "$version" &>/dev/null
echo -e "删除已经存在的远程tag"
git push origin :refs/tags/$version &>/dev/null
echo -e "创建新tag"
git tag -a "$version" -m "release $version"
echo -e "提交tag"
git push --tags
echo -e "完成发布"
+69 -41
View File
@@ -7,14 +7,14 @@ dir_shell=$QL_DIR/shell
## 选择python3还是node
define_program() {
local first_param=$1
if [[ $first_param == *.js ]]; then
local file_param=$1
if [[ $file_param == *.js ]]; then
which_program="node"
elif [[ $first_param == *.py ]] || [[ $first_param == *.pyc ]]; then
elif [[ $file_param == *.py ]] || [[ $file_param == *.pyc ]]; then
which_program="python3"
elif [[ $first_param == *.sh ]]; then
elif [[ $file_param == *.sh ]]; then
which_program="bash"
elif [[ $first_param == *.ts ]]; then
elif [[ $file_param == *.ts ]]; then
which_program="ts-node-transpile-only"
else
which_program=""
@@ -76,41 +76,48 @@ run_nohup() {
## 正常运行单个脚本,$1:传入参数
run_normal() {
local first_param=$1
define_program "$first_param"
if [[ $first_param == *.js ]]; then
local file_param=$1
define_program "$file_param"
if [[ $file_param == *.js ]]; then
if [[ $# -eq 1 ]]; then
random_delay
fi
fi
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
log_dir_tmp="${first_param##*/}"
log_dir_tmp_path="${first_param%%/*}"
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
log_dir_tmp_path="${file_param:1}"
else
log_dir_tmp_path="${file_param}"
fi
fi
log_dir_tmp_path="${log_dir_tmp_path%/*}"
log_dir_tmp_path="${log_dir_tmp_path##*/}"
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="$dir_log/${log_dir_tmp%%.*}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $log_path"
cmd="&>> $dir_log/$log_path"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$log_dir"
make_dir "$dir_log/$log_dir"
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
local begin_timestamp=$(date "+%s")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
local id=$(cat $list_crontab_user | grep -E "$cmd_task $first_param" | perl -pe "s|.*ID=(.*) $cmd_task $first_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
eval . $file_task_before "$@" $cmd
cd $dir_scripts
local relative_path="${first_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${first_param} =~ "/" ]]; then
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
first_param=${first_param/$relative_path\//}
file_param=${file_param/$relative_path\//}
fi
eval timeout -k 10s $command_timeout_time $which_program $first_param $cmd
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
@@ -122,7 +129,7 @@ run_normal() {
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
run_concurrent() {
local first_param="$1"
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]]; then
@@ -145,17 +152,24 @@ run_concurrent() {
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
define_program "$first_param"
define_program "$file_param"
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
log_dir_tmp="${first_param##*/}"
log_dir_tmp_path="${first_param%%/*}"
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
log_dir_tmp_path="${file_param:1}"
else
log_dir_tmp_path="${file_param}"
fi
fi
log_dir_tmp_path="${log_dir_tmp_path%/*}"
log_dir_tmp_path="${log_dir_tmp_path##*/}"
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="$dir_log/${log_dir_tmp%%.*}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $log_path"
cmd="&>> $dir_log/$log_path"
[[ "$show_log" == "true" ]] && cmd=""
make_dir $log_dir
make_dir "$dir_log/$log_dir"
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
local begin_timestamp=$(date "+%s")
@@ -163,7 +177,7 @@ run_concurrent() {
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
local id=$(cat $list_crontab_user | grep -E "$cmd_task $first_param" | perl -pe "s|.*ID=(.*) $cmd_task $first_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
eval . $file_task_before "$@" $cmd
@@ -172,20 +186,20 @@ run_concurrent() {
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
cd $dir_scripts
local relative_path="${first_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${first_param} =~ "/" ]]; then
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
first_param=${first_param/$relative_path\//}
file_param=${file_param/$relative_path\//}
fi
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
single_log_path="$log_dir/${single_log_time}_$((i + 1)).log"
timeout -k 10s $command_timeout_time $which_program $first_param &>$single_log_path &
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 &
done
wait
for i in "${!array[@]}"; do
single_log_path="$log_dir/${single_log_time}_$((i + 1)).log"
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval cat $single_log_path $cmd
[[ -f $single_log_path ]] && rm -f $single_log_path
done
@@ -210,14 +224,21 @@ run_designated() {
define_program "$file_param"
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
log_dir_tmp="${file_param##*/}"
log_dir_tmp_path="${file_param%%/*}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
log_dir_tmp_path="${file_param:1}"
else
log_dir_tmp_path="${file_param}"
fi
fi
log_dir_tmp_path="${log_dir_tmp_path%/*}"
log_dir_tmp_path="${log_dir_tmp_path##*/}"
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="$dir_log/${log_dir_tmp%%.*}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $log_path"
cmd="&>> $dir_log/$log_path"
[[ "$show_log" == "true" ]] && cmd=""
make_dir $log_dir
make_dir "$dir_log/$log_dir"
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
local begin_timestamp=$(date "+%s")
@@ -266,14 +287,21 @@ run_else() {
define_program "$file_param"
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
log_dir_tmp="${file_param##*/}"
log_dir_tmp_path="${file_param%%/*}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
log_dir_tmp_path="${file_param:1}"
else
log_dir_tmp_path="${file_param}"
fi
fi
log_dir_tmp_path="${log_dir_tmp_path%/*}"
log_dir_tmp_path="${log_dir_tmp_path##*/}"
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="$dir_log/${log_dir_tmp%%.*}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $log_path"
cmd="&>> $dir_log/$log_path"
[[ "$show_log" == "true" ]] && cmd=""
make_dir $log_dir
make_dir "$dir_log/$log_dir"
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
local begin_timestamp=$(date "+%s")
@@ -338,7 +366,7 @@ main() {
esac
;;
esac
[[ -f $log_path ]] && cat $log_path
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
elif [[ $# -eq 0 ]]; then
echo
usage
+7
View File
@@ -0,0 +1,7 @@
import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({
scriptUrl: ['//at.alicdn.com/t/font_3354854_pk18p04ny1a.js'],
});
export default IconFont;
+1 -1
View File
@@ -226,7 +226,7 @@ export default function (props: any) {
selectedKeys={[props.location.pathname]}
loading={loading}
ErrorBoundary={Sentry.ErrorBoundary}
logo={<Image preview={false} src="https://qn.whyour.cn/logo.png" />}
logo={<Image preview={false} src="http://qn.whyour.cn/logo.png" />}
title={
<>
<span style={{ fontSize: 16 }}></span>
+228 -20
View File
@@ -10,6 +10,7 @@ import {
List,
Divider,
Typography,
Tooltip,
} from 'antd';
import {
ClockCircleOutlined,
@@ -17,6 +18,8 @@ import {
FieldTimeOutlined,
Loading3QuartersOutlined,
FileOutlined,
PlayCircleOutlined,
PauseCircleOutlined,
} from '@ant-design/icons';
import { CrontabStatus } from './index';
import { diffTime } from '@/utils/date';
@@ -24,6 +27,7 @@ import { request } from '@/utils/http';
import config from '@/utils/config';
import CronLogModal from './logModal';
import Editor from '@monaco-editor/react';
import IconFont from '@/components/iconfont';
const { Text } = Typography;
@@ -68,6 +72,7 @@ const CronDetailModal = ({
const [scriptInfo, setScriptInfo] = useState<any>({});
const [logUrl, setLogUrl] = useState('');
const [validTabs, setValidTabs] = useState(tabList);
const [currentCron, setCurrentCron] = useState<any>({});
const contentList: any = {
log: (
@@ -103,7 +108,7 @@ const CronDetailModal = ({
};
const onClickItem = (item: LogItem) => {
localStorage.setItem('logCron', cron.id);
localStorage.setItem('logCron', currentCron.id);
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
request
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
@@ -196,8 +201,150 @@ const CronDetailModal = ({
});
};
const runCron = () => {
Modal.confirm({
title: '确认运行',
content: (
<>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
setTimeout(() => {
getLogs();
}, 1000);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const stopCron = () => {
Modal.confirm({
title: '确认停止',
content: (
<>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const enabledOrDisabledCron = () => {
Modal.confirm({
title: `确认${currentCron.isDisabled === 1 ? '启用' : '禁用'}`,
content: (
<>
{currentCron.isDisabled === 1 ? '启用' : '禁用'}
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(
`${config.apiPrefix}crons/${
currentCron.isDisabled === 1 ? 'enable' : 'disable'
}`,
{
data: [currentCron.id],
},
)
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({
...currentCron,
isDisabled: currentCron.isDisabled === 1 ? 0 : 1,
});
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const pinOrUnPinCron = () => {
Modal.confirm({
title: `确认${currentCron.isPinned === 1 ? '取消置顶' : '置顶'}`,
content: (
<>
{currentCron.isPinned === 1 ? '取消置顶' : '置顶'}
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(
`${config.apiPrefix}crons/${
currentCron.isPinned === 1 ? 'unpin' : 'pin'
}`,
{
data: [currentCron.id],
},
)
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({
...currentCron,
isPinned: currentCron.isPinned === 1 ? 0 : 1,
});
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
useEffect(() => {
if (cron && cron.id) {
setCurrentCron(cron);
getLogs();
getScript();
}
@@ -206,19 +353,76 @@ const CronDetailModal = ({
return (
<Modal
title={
<>
<span>{cron.name}</span>
{cron.labels?.length > 0 && cron.labels[0] !== '' && (
<div className="crontab-title-wrapper">
<div>
<span>{currentCron.name}</span>
{currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && (
<Divider type="vertical"></Divider>
)}
{cron.labels?.length > 0 &&
cron.labels[0] !== '' &&
cron.labels?.map((label: string, i: number) => (
{currentCron.labels?.length > 0 &&
currentCron.labels[0] !== '' &&
currentCron.labels?.map((label: string, i: number) => (
<Tag color="blue" style={{ marginRight: 5 }}>
{label}
</Tag>
))}
</>
</div>
<div className="operations">
<Tooltip
title={
currentCron.status === CrontabStatus.idle ? '运行' : '停止'
}
>
<Button
type="link"
icon={
currentCron.status === CrontabStatus.idle ? (
<PlayCircleOutlined />
) : (
<PauseCircleOutlined />
)
}
size="small"
onClick={
currentCron.status === CrontabStatus.idle ? runCron : stopCron
}
/>
</Tooltip>
<Tooltip title={currentCron.isDisabled === 1 ? '启用' : '禁用'}>
<Button
type="link"
icon={
<IconFont
type={
currentCron.isDisabled === 1
? 'ql-icon-qiyong'
: 'ql-icon-jinyong'
}
/>
}
size="small"
onClick={enabledOrDisabledCron}
/>
</Tooltip>
<Tooltip title={currentCron.isPinned === 1 ? '取消置顶' : '置顶'}>
<Button
type="link"
icon={
<IconFont
type={
currentCron.isPinned === 1
? 'ql-icon-quxiaozhiding'
: 'ql-icon-zhiding'
}
/>
}
size="small"
onClick={pinOrUnPinCron}
/>
</Tooltip>
</div>
</div>
}
centered
visible={visible}
@@ -232,21 +436,22 @@ const CronDetailModal = ({
<Card>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">{cron.command}</div>
<div className="cron-detail-info-value">{currentCron.command}</div>
</div>
</Card>
<Card style={{ marginTop: 10 }}>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{(!cron.isDisabled || cron.status !== CrontabStatus.idle) && (
{(!currentCron.isDisabled ||
currentCron.status !== CrontabStatus.idle) && (
<>
{cron.status === CrontabStatus.idle && (
{currentCron.status === CrontabStatus.idle && (
<Tag icon={<ClockCircleOutlined />} color="default">
</Tag>
)}
{cron.status === CrontabStatus.running && (
{currentCron.status === CrontabStatus.running && (
<Tag
icon={<Loading3QuartersOutlined spin />}
color="processing"
@@ -254,14 +459,15 @@ const CronDetailModal = ({
</Tag>
)}
{cron.status === CrontabStatus.queued && (
{currentCron.status === CrontabStatus.queued && (
<Tag icon={<FieldTimeOutlined />} color="default">
</Tag>
)}
</>
)}
{cron.isDisabled === 1 && cron.status === CrontabStatus.idle && (
{currentCron.isDisabled === 1 &&
currentCron.status === CrontabStatus.idle && (
<Tag icon={<CloseCircleOutlined />} color="error">
</Tag>
@@ -270,13 +476,13 @@ const CronDetailModal = ({
</div>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">{cron.schedule}</div>
<div className="cron-detail-info-value">{currentCron.schedule}</div>
</div>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{cron.last_execution_time
? new Date(cron.last_execution_time * 1000)
{currentCron.last_execution_time
? new Date(currentCron.last_execution_time * 1000)
.toLocaleString(language, {
hour12: false,
})
@@ -287,14 +493,16 @@ const CronDetailModal = ({
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{cron.last_running_time ? diffTime(cron.last_running_time) : '-'}
{currentCron.last_running_time
? diffTime(currentCron.last_running_time)
: '-'}
</div>
</div>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{cron.nextRunTime &&
cron.nextRunTime
{currentCron.nextRunTime &&
currentCron.nextRunTime
.toLocaleString(language, {
hour12: false,
})
+16
View File
@@ -77,6 +77,22 @@
margin-top: 12px;
}
}
.crontab-title-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
margin-right: 32px;
.operations {
display: flex;
align-items: center;
.ant-btn:not(:first-child) {
margin-left: 8px;
}
}
}
}
.log-item {
+44 -16
View File
@@ -16,7 +16,7 @@ import {
DeleteOutlined,
SyncOutlined,
CheckCircleOutlined,
StopOutlined,
DeleteFilled,
BugOutlined,
FileTextOutlined,
} from '@ant-design/icons';
@@ -120,6 +120,15 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
const isPc = !isPhone;
return (
<Space size="middle">
<Tooltip title={isPc ? '日志' : ''}>
<a
onClick={() => {
setLogDependence({ ...record, timestamp: Date.now() });
}}
>
<FileTextOutlined />
</a>
</Tooltip>
{record.status !== Status. &&
record.status !== Status. && (
<>
@@ -133,17 +142,13 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
<DeleteOutlined />
</a>
</Tooltip>
</>
)}
<Tooltip title={isPc ? '日志' : ''}>
<a
onClick={() => {
setLogDependence({ ...record, timestamp: Date.now() });
}}
>
<FileTextOutlined />
<Tooltip title={isPc ? '强制删除' : ''}>
<a onClick={() => deleteDependence(record, index, true)}>
<DeleteFilled />
</a>
</Tooltip>
</>
)}
</Space>
);
},
@@ -182,7 +187,11 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
setIsModalVisible(true);
};
const deleteDependence = (record: any, index: number) => {
const deleteDependence = (
record: any,
index: number,
force: boolean = false,
) => {
Modal.confirm({
title: '确认删除',
content: (
@@ -196,10 +205,19 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
),
onOk() {
request
.delete(`${config.apiPrefix}dependencies`, { data: [record.id] })
.delete(`${config.apiPrefix}dependencies${force ? '/force' : ''}`, {
data: [record.id],
})
.then((data: any) => {
if (data.code === 200) {
handleDependence(data.data[0]);
if (force) {
const i = value.findIndex((x) => x.id === data.data[0].id);
if (i !== -1) {
const result = [...value];
result.splice(i, 1);
setValue(result);
}
}
} else {
message.error(data);
}
@@ -275,13 +293,16 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
onChange: onSelectChange,
};
const delDependencies = () => {
const delDependencies = (force: boolean) => {
const forceUrl = force ? '/force' : '';
Modal.confirm({
title: '确认删除',
content: <></>,
onOk() {
request
.delete(`${config.apiPrefix}dependencies`, { data: selectedRowIds })
.delete(`${config.apiPrefix}dependencies${forceUrl}`, {
data: selectedRowIds,
})
.then((data: any) => {
if (data.code === 200) {
setSelectedRowIds([]);
@@ -377,10 +398,17 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
<Button
type="primary"
style={{ marginBottom: 5, marginLeft: 8 }}
onClick={delDependencies}
onClick={() => delDependencies(false)}
>
</Button>
<Button
type="primary"
style={{ marginBottom: 5, marginLeft: 8 }}
onClick={() => delDependencies(true)}
>
</Button>
<span style={{ marginLeft: 8 }}>
<a>{selectedRowIds?.length}</a>
+1 -1
View File
@@ -233,7 +233,7 @@ const Initialization = () => {
<img
alt="logo"
className={styles.logo}
src="https://qn.whyour.cn/logo.png"
src="http://qn.whyour.cn/logo.png"
/>
<span className={styles.title}></span>
</div>
+1 -1
View File
@@ -135,7 +135,7 @@ const Login = () => {
<img
alt="logo"
className={styles.logo}
src="https://qn.whyour.cn/logo.png"
src="http://qn.whyour.cn/logo.png"
/>
<span className={styles.title}>
{twoFactor ? '两步验证' : config.siteName}
+1 -1
View File
@@ -10,7 +10,7 @@ const About = () => {
<img
alt="logo"
style={{ width: 140, marginRight: 20 }}
src="https://qn.whyour.cn/logo.png"
src="http://qn.whyour.cn/logo.png"
/>
<div className={styles.right}>
<span className={styles.title}></span>
+8 -2
View File
@@ -77,7 +77,7 @@ export default {
{ value: 'gotify', label: 'Gotify' },
{ value: 'goCqHttpBot', label: 'GoCqHttpBot' },
{ value: 'serverChan', label: 'Server酱' },
{ value: 'PushDeer', label: 'PushDeer' },
{ value: 'pushDeer', label: 'PushDeer' },
{ value: 'bark', label: 'Bark' },
{ value: 'telegramBot', label: 'Telegram机器人' },
{ value: 'dingtalkBot', label: '钉钉机器人' },
@@ -114,7 +114,13 @@ export default {
serverChan: [
{ label: 'serverChanKey', tip: 'Server酱SENDKEY', required: true },
],
PushDeer: [{ label: 'PushDeerKey', tip: 'PushDeer的Key', required: true }],
pushDeer: [
{
label: 'pushDeerKey',
tip: 'PushDeer的Keyhttps://github.com/easychen/pushdeer',
required: true,
},
],
bark: [
{
label: 'barkPush',
+9 -14
View File
@@ -1,15 +1,10 @@
export const version = '2.12.0';
export const changeLogLink = 'https://t.me/jiao_long/288';
export const changeLog = `2.12.0 版本说明
1. 全新定时任务详情支持日志查看、脚本编辑
2. openapi增加发送通知接口,可用于脚本直接调用
3. 增加pushDeer推送,感谢 https://github.com/NekoMio PR
4. 增加public服务,当服务异常时,查询服务状态及日志,供页面使用。
5. 增加ql check可视化错误提示
6. 修改openapi获取token逻辑,最多存储5个可用的token。
7. 调整数据目录,log、db、scripts、config等目录迁移到 /ql/data 目录,docker映射只需映射data目录
8. 版本文件存储到七牛云,方便检查更新
9. 修复编辑应用初始值
10. 修复退出登录和定时任务搜索
11. 其他bug修复
export const version = '2.12.2';
export const changeLogLink = 'https://t.me/jiao_long/290';
export const changeLog = `2.12.2 版本说明
1. 任务详情支持运行、禁用、置顶操作
2. 依赖管理增加直接强制删除
3. 修复环境变量引号转义逻辑,感谢 https://github.com/JerryWn12 PR
4. 修复定时删除日志设置,支持设置为24天以上
5. 修复拉取脚本,shell发送通知
6. 修复shell获取日志目录
`;