mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 10:40:52 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e07d2b6639 | ||
|
|
220a87627d | ||
|
|
87bca2ac4e | ||
|
|
c320906149 | ||
|
|
27958a1a90 | ||
|
|
82c7011522 | ||
|
|
fa6d57d809 | ||
|
|
1060fc7476 | ||
|
|
56bc38e9e7 | ||
|
|
18be65d6fe | ||
|
|
3409085195 | ||
|
|
17e17ac077 | ||
|
|
c655e1da38 | ||
|
|
0ea158724f | ||
|
|
fb521498ff | ||
|
|
399550ccad | ||
|
|
c7c30c86f2 | ||
|
|
5f6c0882d6 | ||
|
|
6e05a4f3a5 | ||
|
|
ea8aa6a231 | ||
|
|
ae2570c677 | ||
|
|
c76d952d41 | ||
|
|
cdf9fb24be | ||
|
|
2131e16781 | ||
|
|
b0c8425639 | ||
|
|
f0869567de | ||
|
|
be2a19949b | ||
|
|
8627cccb58 | ||
|
|
4a8eaebf17 | ||
|
|
e7edd0a5a4 | ||
|
|
9ccbea0a04 | ||
|
|
ab715ef840 | ||
|
|
117822fe7c | ||
|
|
da922678a1 |
@@ -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:
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||
</div>
|
||||
|
||||
[](https://whyour.cn)
|
||||
[](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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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';
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ export class SockMessage {
|
||||
export type SockMessageType =
|
||||
| 'ping'
|
||||
| 'installDependence'
|
||||
| 'uninstallDependence'
|
||||
| 'updateSystemVersion'
|
||||
| 'manuallyRunScript';
|
||||
|
||||
+52
-19
@@ -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,14 +401,26 @@ 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;
|
||||
return command.substring(start, end);
|
||||
|
||||
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) {
|
||||
@@ -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 },
|
||||
|
||||
+18
-16
@@ -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(() => {
|
||||
this.removeDb(depIds);
|
||||
}, 5000);
|
||||
// 如果删除依赖成功或者强制删除
|
||||
if ((isSucceed || force) && !isInstall) {
|
||||
this.removeDb(depIds);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
+61
-43
@@ -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,25 +34,29 @@ export default class ScheduleService {
|
||||
_id,
|
||||
nodeSchedule.scheduleJob(id + '', schedule, async () => {
|
||||
try {
|
||||
exec(command, async (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
exec(
|
||||
command,
|
||||
{ maxBuffer: this.maxBuffer },
|
||||
async (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
});
|
||||
if (stderr) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
@@ -81,39 +87,51 @@ export default class ScheduleService {
|
||||
name,
|
||||
command,
|
||||
);
|
||||
const task = new Task(name, async () => {
|
||||
try {
|
||||
exec(command, async (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
const task = new AsyncTask(
|
||||
name,
|
||||
async () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
exec(
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
{ maxBuffer: this.maxBuffer },
|
||||
async (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
stderr,
|
||||
if (stderr) {
|
||||
await this.logger.info(
|
||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||
command,
|
||||
new Date().toLocaleString(),
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
await this.logger.info(
|
||||
},
|
||||
(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);
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
+238
-30
@@ -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] !== '' && (
|
||||
<Divider type="vertical"></Divider>
|
||||
)}
|
||||
{cron.labels?.length > 0 &&
|
||||
cron.labels[0] !== '' &&
|
||||
cron.labels?.map((label: string, i: number) => (
|
||||
<Tag color="blue" style={{ marginRight: 5 }}>
|
||||
{label}
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
<div className="crontab-title-wrapper">
|
||||
<div>
|
||||
<span>{currentCron.name}</span>
|
||||
{currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && (
|
||||
<Divider type="vertical"></Divider>
|
||||
)}
|
||||
{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,29 +459,30 @@ const CronDetailModal = ({
|
||||
运行中
|
||||
</Tag>
|
||||
)}
|
||||
{cron.status === CrontabStatus.queued && (
|
||||
{currentCron.status === CrontabStatus.queued && (
|
||||
<Tag icon={<FieldTimeOutlined />} color="default">
|
||||
队列中
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{cron.isDisabled === 1 && cron.status === CrontabStatus.idle && (
|
||||
<Tag icon={<CloseCircleOutlined />} color="error">
|
||||
已禁用
|
||||
</Tag>
|
||||
)}
|
||||
{currentCron.isDisabled === 1 &&
|
||||
currentCron.status === CrontabStatus.idle && (
|
||||
<Tag icon={<CloseCircleOutlined />} color="error">
|
||||
已禁用
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
</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,
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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={() => deleteDependence(record, index, true)}>
|
||||
<DeleteFilled />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<Tooltip title={isPc ? '日志' : ''}>
|
||||
<a
|
||||
onClick={() => {
|
||||
setLogDependence({ ...record, timestamp: Date.now() });
|
||||
}}
|
||||
>
|
||||
<FileTextOutlined />
|
||||
</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>项
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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的Key,https://github.com/easychen/pushdeer',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
bark: [
|
||||
{
|
||||
label: 'barkPush',
|
||||
|
||||
+9
-14
@@ -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获取日志目录
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user