mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 03:10:48 +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
|
- name: build front and back
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
npm i -g pnpm
|
||||||
yarn build:front
|
pnpm install
|
||||||
yarn build:back
|
pnpm build:front
|
||||||
|
pnpm build:back
|
||||||
|
|
||||||
- name: copy to static repo
|
- name: copy to static repo
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[](https://whyour.cn)
|
[](https://whyour.cn)
|
||||||
|
|
||||||
简体中文 | [English](./README-en.md)
|
简体中文 | [English](./README-en.md)
|
||||||
|
|
||||||
@@ -168,8 +168,10 @@ task <file_path> desi <env_name> <account_number>
|
|||||||
$ git clone git@github.com:whyour/qinglong.git
|
$ git clone git@github.com:whyour/qinglong.git
|
||||||
$ cd qinglong
|
$ cd qinglong
|
||||||
$ cp .env.example .env
|
$ cp .env.example .env
|
||||||
$ yarn install
|
# 推荐使用 pnpm https://pnpm.io/zh/installation
|
||||||
$ yarn start
|
$ npm install -g pnpm
|
||||||
|
$ pnpm install
|
||||||
|
$ pnpm start
|
||||||
```
|
```
|
||||||
|
|
||||||
打开你的浏览器,访问 http://127.0.0.1:5700
|
打开你的浏览器,访问 http://127.0.0.1:5700
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default (app: Router) => {
|
|||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const dependenceService = Container.get(DependenceService);
|
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 });
|
return res.send({ code: 200, data });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('🔥 error: %o', e);
|
logger.error('🔥 error: %o', e);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { createRandomString } from './util';
|
|||||||
|
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
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 envFound = dotenv.config();
|
||||||
const rootPath = process.cwd();
|
const rootPath = process.cwd();
|
||||||
|
|||||||
@@ -37,13 +37,13 @@ export enum DependenceTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum InstallDependenceCommandTypes {
|
export enum InstallDependenceCommandTypes {
|
||||||
'npm i -g --force',
|
'npm i -g -f --loglevel warn',
|
||||||
'pip3 install',
|
'pip3 install',
|
||||||
'apk add --no-cache -f',
|
'apk add --no-cache -f',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum unInstallDependenceCommandTypes {
|
export enum unInstallDependenceCommandTypes {
|
||||||
'npm uninstall -g --force',
|
'npm uninstall -g -f --loglevel warn',
|
||||||
'pip3 uninstall -y',
|
'pip3 uninstall -y',
|
||||||
'apk del -f',
|
'apk del -f',
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ export class PushDeerNotification extends NotificationBaseInfo {
|
|||||||
|
|
||||||
export class BarkNotification extends NotificationBaseInfo {
|
export class BarkNotification extends NotificationBaseInfo {
|
||||||
public barkPush = '';
|
public barkPush = '';
|
||||||
public barkIcon = 'https://qn.whyour.cn/logo.png';
|
public barkIcon = 'http://qn.whyour.cn/logo.png';
|
||||||
public barkSound = '';
|
public barkSound = '';
|
||||||
public barkGroup = 'qinglong';
|
public barkGroup = 'qinglong';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ export class SockMessage {
|
|||||||
export type SockMessageType =
|
export type SockMessageType =
|
||||||
| 'ping'
|
| 'ping'
|
||||||
| 'installDependence'
|
| 'installDependence'
|
||||||
|
| 'uninstallDependence'
|
||||||
| 'updateSystemVersion'
|
| 'updateSystemVersion'
|
||||||
| 'manuallyRunScript';
|
| 'manuallyRunScript';
|
||||||
|
|||||||
+51
-18
@@ -9,6 +9,7 @@ import { getFileContentByName, concurrentRun, fileExist } from '../config/util';
|
|||||||
import { promises, existsSync } from 'fs';
|
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';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronService {
|
export default class CronService {
|
||||||
@@ -16,7 +17,7 @@ export default class CronService {
|
|||||||
|
|
||||||
private isSixCron(cron: Crontab) {
|
private isSixCron(cron: Crontab) {
|
||||||
const { schedule } = cron;
|
const { schedule } = cron;
|
||||||
if (schedule.split(/ +/).length === 6) {
|
if (schedule?.split(/ +/).length === 6) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -194,11 +195,12 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const err = await this.killTask(doc.command);
|
const err = await this.killTask(doc.command);
|
||||||
const logFileExist = await fileExist(doc.log_path);
|
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
|
||||||
if (doc.log_path && logFileExist) {
|
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
||||||
|
if (logFileExist) {
|
||||||
const str = err ? `\n${err}` : '';
|
const str = err ? `\n${err}` : '';
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(
|
||||||
`${doc.log_path}`,
|
`${absolutePath}`,
|
||||||
`${str}\n## 执行结束... ${new Date()
|
`${str}\n## 执行结束... ${new Date()
|
||||||
.toLocaleString('zh', { hour12: false })
|
.toLocaleString('zh', { hour12: false })
|
||||||
.replace(' 24:', ' 00:')} `,
|
.replace(' 24:', ' 00:')} `,
|
||||||
@@ -256,6 +258,8 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let { id, command, log_path } = cron;
|
let { id, command, log_path } = cron;
|
||||||
|
const absolutePath = path.resolve(config.logPath, `${log_path}`);
|
||||||
|
const logFileExist = log_path && (await fileExist(absolutePath));
|
||||||
|
|
||||||
this.logger.silly('Running job');
|
this.logger.silly('Running job');
|
||||||
this.logger.silly('ID: ' + id);
|
this.logger.silly('ID: ' + id);
|
||||||
@@ -276,13 +280,13 @@ export default class CronService {
|
|||||||
{ where: { id } },
|
{ where: { id } },
|
||||||
);
|
);
|
||||||
cp.stderr.on('data', (data) => {
|
cp.stderr.on('data', (data) => {
|
||||||
if (log_path) {
|
if (logFileExist) {
|
||||||
fs.appendFileSync(`${log_path}`, `${data}`);
|
fs.appendFileSync(`${absolutePath}`, `${data}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cp.on('error', (err) => {
|
cp.on('error', (err) => {
|
||||||
if (log_path) {
|
if (logFileExist) {
|
||||||
fs.appendFileSync(`${log_path}`, `${JSON.stringify(err)}`);
|
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -323,8 +327,10 @@ export default class CronService {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.log_path) {
|
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
|
||||||
return getFileContentByName(`${doc.log_path}`);
|
const logFileExist = doc.log_path && (await fileExist(absolutePath));
|
||||||
|
if (logFileExist) {
|
||||||
|
return getFileContentByName(`${absolutePath}`);
|
||||||
}
|
}
|
||||||
const [, commandStr, url] = doc.command.split(/ +/);
|
const [, commandStr, url] = doc.command.split(/ +/);
|
||||||
let logPath = this.getKey(commandStr);
|
let logPath = this.getKey(commandStr);
|
||||||
@@ -353,6 +359,21 @@ export default class CronService {
|
|||||||
return [];
|
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(/ +/);
|
const [, commandStr, url] = doc.command.split(/ +/);
|
||||||
let logPath = this.getKey(commandStr);
|
let logPath = this.getKey(commandStr);
|
||||||
const isQlCommand = doc.command.startsWith('ql ');
|
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 =
|
const start =
|
||||||
command.lastIndexOf('/') !== -1 ? command.lastIndexOf('/') + 1 : 0;
|
command.lastIndexOf('/') !== -1 ? command.lastIndexOf('/') + 1 : 0;
|
||||||
const end =
|
const end =
|
||||||
command.lastIndexOf('.') !== -1
|
command.lastIndexOf('.') !== -1
|
||||||
? command.lastIndexOf('.')
|
? command.lastIndexOf('.')
|
||||||
: command.length;
|
: 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);
|
return command.substring(start, end);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private make_command(tab: Crontab) {
|
private make_command(tab: Crontab) {
|
||||||
const crontab_job_string = `ID=${tab.id} ${tab.command}`;
|
const crontab_job_string = `ID=${tab.id} ${tab.command}`;
|
||||||
@@ -400,7 +433,7 @@ export default class CronService {
|
|||||||
var crontab_string = '';
|
var crontab_string = '';
|
||||||
tabs.forEach((tab) => {
|
tabs.forEach((tab) => {
|
||||||
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
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 += '# ';
|
||||||
crontab_string += tab.schedule;
|
crontab_string += tab.schedule;
|
||||||
crontab_string += ' ';
|
crontab_string += ' ';
|
||||||
@@ -426,22 +459,22 @@ export default class CronService {
|
|||||||
|
|
||||||
public import_crontab() {
|
public import_crontab() {
|
||||||
exec('crontab -l', (error, stdout, stderr) => {
|
exec('crontab -l', (error, stdout, stderr) => {
|
||||||
var lines = stdout.split('\n');
|
const lines = stdout.split('\n');
|
||||||
var namePrefix = new Date().getTime();
|
const namePrefix = new Date().getTime();
|
||||||
|
|
||||||
lines.reverse().forEach(async (line, index) => {
|
lines.reverse().forEach(async (line, index) => {
|
||||||
line = line.replace(/\t+/g, ' ');
|
line = line.replace(/\t+/g, ' ');
|
||||||
var regex =
|
const regex =
|
||||||
/^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/;
|
/^((\@[a-zA-Z]+\s+)|(([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+))/;
|
||||||
var command = line.replace(regex, '').trim();
|
const command = line.replace(regex, '').trim();
|
||||||
var schedule = line.replace(command, '').trim();
|
const schedule = line.replace(command, '').trim();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
command &&
|
command &&
|
||||||
schedule &&
|
schedule &&
|
||||||
cron_parser.parseExpression(schedule).hasNext()
|
cron_parser.parseExpression(schedule).hasNext()
|
||||||
) {
|
) {
|
||||||
var name = namePrefix + '_' + index;
|
const name = namePrefix + '_' + index;
|
||||||
|
|
||||||
const _crontab = await CrontabModel.findOne({
|
const _crontab = await CrontabModel.findOne({
|
||||||
where: { command, schedule },
|
where: { command, schedule },
|
||||||
|
|||||||
+17
-15
@@ -56,13 +56,13 @@ export default class DependenceService {
|
|||||||
return await this.getDb({ id: payload.id });
|
return await this.getDb({ id: payload.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async remove(ids: number[]) {
|
public async remove(ids: number[], force = false): Promise<Dependence[]> {
|
||||||
await DependenceModel.update(
|
await DependenceModel.update(
|
||||||
{ status: DependenceStatus.removing, log: [] },
|
{ status: DependenceStatus.removing, log: [] },
|
||||||
{ where: { id: ids } },
|
{ where: { id: ids } },
|
||||||
);
|
);
|
||||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||||
this.installOrUninstallDependencies(docs, false);
|
this.installOrUninstallDependencies(docs, false, force);
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,12 +128,16 @@ export default class DependenceService {
|
|||||||
public installOrUninstallDependencies(
|
public installOrUninstallDependencies(
|
||||||
dependencies: Dependence[],
|
dependencies: Dependence[],
|
||||||
isInstall: boolean = true,
|
isInstall: boolean = true,
|
||||||
|
force: boolean = false,
|
||||||
) {
|
) {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
if (dependencies.length === 0) {
|
if (dependencies.length === 0) {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const socketMessageType = !force
|
||||||
|
? 'installDependence'
|
||||||
|
: 'uninstallDependence';
|
||||||
const depNames = dependencies.map((x) => x.name).join(' ');
|
const depNames = dependencies.map((x) => x.name).join(' ');
|
||||||
const depRunCommand = (
|
const depRunCommand = (
|
||||||
isInstall
|
isInstall
|
||||||
@@ -145,21 +149,21 @@ export default class DependenceService {
|
|||||||
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'installDependence',
|
type: socketMessageType,
|
||||||
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||||
startTime,
|
startTime,
|
||||||
).toLocaleString()}`,
|
).toLocaleString()}\n\n`,
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
await this.updateLog(
|
await this.updateLog(
|
||||||
depIds,
|
depIds,
|
||||||
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
|
||||||
startTime,
|
startTime,
|
||||||
).toLocaleString()}\n`,
|
).toLocaleString()}\n\n`,
|
||||||
);
|
);
|
||||||
cp.stdout.on('data', async (data) => {
|
cp.stdout.on('data', async (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'installDependence',
|
type: socketMessageType,
|
||||||
message: data.toString(),
|
message: data.toString(),
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
@@ -168,7 +172,7 @@ export default class DependenceService {
|
|||||||
|
|
||||||
cp.stderr.on('data', async (data) => {
|
cp.stderr.on('data', async (data) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'installDependence',
|
type: socketMessageType,
|
||||||
message: data.toString(),
|
message: data.toString(),
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
@@ -177,7 +181,7 @@ export default class DependenceService {
|
|||||||
|
|
||||||
cp.on('error', async (err) => {
|
cp.on('error', async (err) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'installDependence',
|
type: socketMessageType,
|
||||||
message: JSON.stringify(err),
|
message: JSON.stringify(err),
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
@@ -191,15 +195,15 @@ export default class DependenceService {
|
|||||||
const resultText = isSucceed ? '成功' : '失败';
|
const resultText = isSucceed ? '成功' : '失败';
|
||||||
|
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
type: 'installDependence',
|
type: socketMessageType,
|
||||||
message: `依赖${actionText}${resultText},结束时间 ${new Date(
|
message: `\n依赖${actionText}${resultText},结束时间 ${new Date(
|
||||||
endTime,
|
endTime,
|
||||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||||
references: depIds,
|
references: depIds,
|
||||||
});
|
});
|
||||||
await this.updateLog(
|
await this.updateLog(
|
||||||
depIds,
|
depIds,
|
||||||
`依赖${actionText}${resultText},结束时间 ${new Date(
|
`\n依赖${actionText}${resultText},结束时间 ${new Date(
|
||||||
endTime,
|
endTime,
|
||||||
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
).toLocaleString()},耗时 ${(endTime - startTime) / 1000} 秒`,
|
||||||
);
|
);
|
||||||
@@ -216,11 +220,9 @@ export default class DependenceService {
|
|||||||
}
|
}
|
||||||
await DependenceModel.update({ status }, { where: { id: depIds } });
|
await DependenceModel.update({ status }, { where: { id: depIds } });
|
||||||
|
|
||||||
// 如果删除依赖成功,3秒后删除数据库记录
|
// 如果删除依赖成功或者强制删除
|
||||||
if (isSucceed && !isInstall) {
|
if ((isSucceed || force) && !isInstall) {
|
||||||
setTimeout(() => {
|
|
||||||
this.removeDb(depIds);
|
this.removeDb(depIds);
|
||||||
}, 5000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(null);
|
resolve(null);
|
||||||
|
|||||||
@@ -168,13 +168,9 @@ export default class EnvService {
|
|||||||
.filter((x) => x.status !== EnvStatus.disabled)
|
.filter((x) => x.status !== EnvStatus.disabled)
|
||||||
.map('value')
|
.map('value')
|
||||||
.join('&')
|
.join('&')
|
||||||
.replace(/ /g, '');
|
.replace(/"/g, '\"')
|
||||||
if (/"/.test(value)) {
|
.trim();
|
||||||
value = `'${value}'`;
|
env_string += `export ${key}="${value}"\n`;
|
||||||
} else {
|
|
||||||
value = `"${value}"`;
|
|
||||||
}
|
|
||||||
env_string += `export ${key}=${value}\n`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,15 +119,14 @@ export default class NotificationService {
|
|||||||
|
|
||||||
private async pushDeer() {
|
private async pushDeer() {
|
||||||
const { pushDeerKey } = this.params;
|
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 url = `https://api2.pushdeer.com/message/push`;
|
||||||
const res: any = await got
|
const res: any = await got
|
||||||
.post(url, {
|
.post(url, {
|
||||||
timeout: this.timeout,
|
timeout: this.timeout,
|
||||||
retry: 0,
|
retry: 0,
|
||||||
body: `pushkey=${pushDeerKey}&text=${
|
body: `pushkey=${pushDeerKey}&text=${encodeURIComponent(
|
||||||
this.title
|
this.title,
|
||||||
}&desp=${encodeURIComponent(this.content)}&type=markdown`,
|
)}&desp=${encodeURIComponent(this.content)}&type=markdown`,
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
|
|||||||
+31
-13
@@ -5,8 +5,8 @@ import { Crontab } from '../data/cron';
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import {
|
import {
|
||||||
ToadScheduler,
|
ToadScheduler,
|
||||||
SimpleIntervalJob,
|
LongIntervalJob,
|
||||||
Task,
|
AsyncTask,
|
||||||
SimpleIntervalSchedule,
|
SimpleIntervalSchedule,
|
||||||
} from 'toad-scheduler';
|
} from 'toad-scheduler';
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ export default class ScheduleService {
|
|||||||
|
|
||||||
private intervalSchedule = new ToadScheduler();
|
private intervalSchedule = new ToadScheduler();
|
||||||
|
|
||||||
|
private maxBuffer = 200 * 1024 * 1024;
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
async createCronTask({ id = 0, command, name, schedule = '' }: Crontab) {
|
async createCronTask({ id = 0, command, name, schedule = '' }: Crontab) {
|
||||||
@@ -32,7 +34,10 @@ export default class ScheduleService {
|
|||||||
_id,
|
_id,
|
||||||
nodeSchedule.scheduleJob(id + '', schedule, async () => {
|
nodeSchedule.scheduleJob(id + '', schedule, async () => {
|
||||||
try {
|
try {
|
||||||
exec(command, async (error, stdout, stderr) => {
|
exec(
|
||||||
|
command,
|
||||||
|
{ maxBuffer: this.maxBuffer },
|
||||||
|
async (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
await this.logger.info(
|
await this.logger.info(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
@@ -50,7 +55,8 @@ export default class ScheduleService {
|
|||||||
stderr,
|
stderr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.logger.info(
|
await this.logger.info(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
@@ -81,9 +87,15 @@ export default class ScheduleService {
|
|||||||
name,
|
name,
|
||||||
command,
|
command,
|
||||||
);
|
);
|
||||||
const task = new Task(name, async () => {
|
const task = new AsyncTask(
|
||||||
|
name,
|
||||||
|
async () => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
exec(command, async (error, stdout, stderr) => {
|
exec(
|
||||||
|
command,
|
||||||
|
{ maxBuffer: this.maxBuffer },
|
||||||
|
async (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
await this.logger.info(
|
await this.logger.info(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
@@ -101,19 +113,25 @@ export default class ScheduleService {
|
|||||||
stderr,
|
stderr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.logger.info(
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.logger.info(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
command,
|
command,
|
||||||
new Date().toLocaleString(),
|
new Date().toLocaleString(),
|
||||||
error,
|
err,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const job = new SimpleIntervalJob({ ...schedule }, task, _id);
|
const job = new LongIntervalJob({ ...schedule }, task, _id);
|
||||||
|
|
||||||
this.intervalSchedule.addIntervalJob(job);
|
this.intervalSchedule.addIntervalJob(job);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ export default class SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateLogRemoveFrequency(frequency: number) {
|
public async updateLogRemoveFrequency(frequency: number) {
|
||||||
|
const oDoc = await this.getLogRemoveFrequency();
|
||||||
const result = await this.updateAuthDb({
|
const result = await this.updateAuthDb({
|
||||||
|
...oDoc,
|
||||||
type: AuthDataType.removeLogFrequency,
|
type: AuthDataType.removeLogFrequency,
|
||||||
info: { frequency },
|
info: { frequency },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export PUSH_KEY=""
|
|||||||
## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123
|
## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123
|
||||||
export BARK_PUSH=""
|
export BARK_PUSH=""
|
||||||
## 下方填写推送图标设置,自定义推送图标(需iOS15或以上)
|
## 下方填写推送图标设置,自定义推送图标(需iOS15或以上)
|
||||||
export BARK_ICON="https://qn.whyour.cn/logo.png"
|
export BARK_ICON="http://qn.whyour.cn/logo.png"
|
||||||
## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声
|
## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声
|
||||||
export BARK_SOUND=""
|
export BARK_SOUND=""
|
||||||
## 下方填写推送消息分组,默认为"QingLong"
|
## 下方填写推送消息分组,默认为"QingLong"
|
||||||
|
|||||||
+8
-4
@@ -43,7 +43,7 @@ let PUSHDEER_KEY = '';
|
|||||||
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
||||||
let BARK_PUSH = '';
|
let BARK_PUSH = '';
|
||||||
//BARK app推送图标,自定义推送图标(需iOS15或以上)
|
//BARK app推送图标,自定义推送图标(需iOS15或以上)
|
||||||
let BARK_ICON = 'https://qn.whyour.cn/logo.png';
|
let BARK_ICON = 'http://qn.whyour.cn/logo.png';
|
||||||
//BARK app推送铃声,铃声列表去APP查看复制填写
|
//BARK app推送铃声,铃声列表去APP查看复制填写
|
||||||
let BARK_SOUND = '';
|
let BARK_SOUND = '';
|
||||||
//BARK app推送消息的分组, 默认为"QingLong"
|
//BARK app推送消息的分组, 默认为"QingLong"
|
||||||
@@ -383,7 +383,7 @@ function PushDeerNotify(text, desp, time = 2100) {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
} else {
|
} else {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
// 通过反悔的result的长度来判断是否成功
|
// 通过返回的result的长度来判断是否成功
|
||||||
if (
|
if (
|
||||||
data.content.result.length !== undefined &&
|
data.content.result.length !== undefined &&
|
||||||
data.content.result.length > 0
|
data.content.result.length > 0
|
||||||
@@ -529,9 +529,13 @@ function tgBotNotify(text, desp) {
|
|||||||
if (TG_BOT_TOKEN && TG_USER_ID) {
|
if (TG_BOT_TOKEN && TG_USER_ID) {
|
||||||
const options = {
|
const options = {
|
||||||
url: `https://${TG_API_HOST}/bot${TG_BOT_TOKEN}/sendMessage`,
|
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: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -271,7 +271,7 @@ def pushdeer(title: str, content: str) -> None:
|
|||||||
print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送")
|
print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送")
|
||||||
return
|
return
|
||||||
print("PushDeer 服务启动")
|
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'
|
url = 'https://api2.pushdeer.com/message/push'
|
||||||
response = requests.post(url, data=data).json()
|
response = requests.post(url, data=data).json()
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -5,8 +5,9 @@ const envFound = dotenv.config();
|
|||||||
const accessKey = process.env.QINIU_AK;
|
const accessKey = process.env.QINIU_AK;
|
||||||
const secretKey = process.env.QINIU_SK;
|
const secretKey = process.env.QINIU_SK;
|
||||||
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
|
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
|
||||||
|
const key = 'version.ts';
|
||||||
const options = {
|
const options = {
|
||||||
scope: process.env.QINIU_SCOPE,
|
scope: `${process.env.QINIU_SCOPE}:${key}`,
|
||||||
};
|
};
|
||||||
const putPolicy = new qiniu.rs.PutPolicy(options);
|
const putPolicy = new qiniu.rs.PutPolicy(options);
|
||||||
const uploadToken = putPolicy.uploadToken(mac);
|
const uploadToken = putPolicy.uploadToken(mac);
|
||||||
@@ -14,8 +15,11 @@ const uploadToken = putPolicy.uploadToken(mac);
|
|||||||
const localFile = 'src/version.ts';
|
const localFile = 'src/version.ts';
|
||||||
const config = new qiniu.conf.Config({ zone: qiniu.zone.Zone_z1 });
|
const config = new qiniu.conf.Config({ zone: qiniu.zone.Zone_z1 });
|
||||||
const formUploader = new qiniu.form_up.FormUploader(config);
|
const formUploader = new qiniu.form_up.FormUploader(config);
|
||||||
const putExtra = new qiniu.form_up.PutExtra('', '', 'text/plain');
|
const putExtra = new qiniu.form_up.PutExtra(
|
||||||
const key = 'version.ts';
|
'',
|
||||||
|
'',
|
||||||
|
'text/plain; charset=utf-8',
|
||||||
|
);
|
||||||
// 文件上传
|
// 文件上传
|
||||||
formUploader.putFile(
|
formUploader.putFile(
|
||||||
uploadToken,
|
uploadToken,
|
||||||
|
|||||||
+3
-3
@@ -178,7 +178,7 @@ update_cron() {
|
|||||||
|
|
||||||
notify_api() {
|
notify_api() {
|
||||||
local title=$1
|
local title=$1
|
||||||
local content=$1
|
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/api/system/notify?t=$currentTimeStamp" \
|
||||||
@@ -196,9 +196,9 @@ notify_api() {
|
|||||||
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 "成功"
|
echo -e "通知发送成功"
|
||||||
else
|
else
|
||||||
echo -e "失败(${message})"
|
echo -e "通知失败(${message})"
|
||||||
fi
|
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
|
## 选择python3还是node
|
||||||
define_program() {
|
define_program() {
|
||||||
local first_param=$1
|
local file_param=$1
|
||||||
if [[ $first_param == *.js ]]; then
|
if [[ $file_param == *.js ]]; then
|
||||||
which_program="node"
|
which_program="node"
|
||||||
elif [[ $first_param == *.py ]] || [[ $first_param == *.pyc ]]; then
|
elif [[ $file_param == *.py ]] || [[ $file_param == *.pyc ]]; then
|
||||||
which_program="python3"
|
which_program="python3"
|
||||||
elif [[ $first_param == *.sh ]]; then
|
elif [[ $file_param == *.sh ]]; then
|
||||||
which_program="bash"
|
which_program="bash"
|
||||||
elif [[ $first_param == *.ts ]]; then
|
elif [[ $file_param == *.ts ]]; then
|
||||||
which_program="ts-node-transpile-only"
|
which_program="ts-node-transpile-only"
|
||||||
else
|
else
|
||||||
which_program=""
|
which_program=""
|
||||||
@@ -76,41 +76,48 @@ run_nohup() {
|
|||||||
|
|
||||||
## 正常运行单个脚本,$1:传入参数
|
## 正常运行单个脚本,$1:传入参数
|
||||||
run_normal() {
|
run_normal() {
|
||||||
local first_param=$1
|
local file_param=$1
|
||||||
define_program "$first_param"
|
define_program "$file_param"
|
||||||
if [[ $first_param == *.js ]]; then
|
if [[ $file_param == *.js ]]; then
|
||||||
if [[ $# -eq 1 ]]; then
|
if [[ $# -eq 1 ]]; then
|
||||||
random_delay
|
random_delay
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||||
log_dir_tmp="${first_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
log_dir_tmp_path="${first_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_path##*/}"
|
||||||
[[ $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="$dir_log/${log_dir_tmp%%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $log_path"
|
cmd="&>> $dir_log/$log_path"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$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_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s")
|
local begin_timestamp=$(date "+%s")
|
||||||
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
|
||||||
|
|
||||||
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"
|
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||||
eval . $file_task_before "$@" $cmd
|
eval . $file_task_before "$@" $cmd
|
||||||
|
|
||||||
cd $dir_scripts
|
cd $dir_scripts
|
||||||
local relative_path="${first_param%/*}"
|
local relative_path="${file_param%/*}"
|
||||||
if [[ ! -z ${relative_path} ]] && [[ ${first_param} =~ "/" ]]; then
|
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
first_param=${first_param/$relative_path\//}
|
file_param=${file_param/$relative_path\//}
|
||||||
fi
|
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
|
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')
|
||||||
@@ -122,7 +129,7 @@ run_normal() {
|
|||||||
|
|
||||||
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
|
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
|
||||||
run_concurrent() {
|
run_concurrent() {
|
||||||
local first_param="$1"
|
local file_param="$1"
|
||||||
local env_param="$2"
|
local env_param="$2"
|
||||||
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
|
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
|
||||||
if [[ ! $env_param ]]; then
|
if [[ ! $env_param ]]; then
|
||||||
@@ -145,17 +152,24 @@ run_concurrent() {
|
|||||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||||
|
|
||||||
define_program "$first_param"
|
define_program "$file_param"
|
||||||
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||||
log_dir_tmp="${first_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
log_dir_tmp_path="${first_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_path##*/}"
|
||||||
[[ $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="$dir_log/${log_dir_tmp%%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $log_path"
|
cmd="&>> $dir_log/$log_path"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$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_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s")
|
local begin_timestamp=$(date "+%s")
|
||||||
@@ -163,7 +177,7 @@ run_concurrent() {
|
|||||||
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
|
||||||
|
|
||||||
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"
|
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||||
eval . $file_task_before "$@" $cmd
|
eval . $file_task_before "$@" $cmd
|
||||||
|
|
||||||
@@ -172,20 +186,20 @@ run_concurrent() {
|
|||||||
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
|
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
|
||||||
|
|
||||||
cd $dir_scripts
|
cd $dir_scripts
|
||||||
local relative_path="${first_param%/*}"
|
local relative_path="${file_param%/*}"
|
||||||
if [[ ! -z ${relative_path} ]] && [[ ${first_param} =~ "/" ]]; then
|
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
first_param=${first_param/$relative_path\//}
|
file_param=${file_param/$relative_path\//}
|
||||||
fi
|
fi
|
||||||
for i in "${!array[@]}"; do
|
for i in "${!array[@]}"; do
|
||||||
export ${env_param}=${array[i]}
|
export ${env_param}=${array[i]}
|
||||||
single_log_path="$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 $first_param &>$single_log_path &
|
timeout -k 10s $command_timeout_time $which_program $file_param &>$single_log_path &
|
||||||
done
|
done
|
||||||
|
|
||||||
wait
|
wait
|
||||||
for i in "${!array[@]}"; do
|
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
|
eval cat $single_log_path $cmd
|
||||||
[[ -f $single_log_path ]] && rm -f $single_log_path
|
[[ -f $single_log_path ]] && rm -f $single_log_path
|
||||||
done
|
done
|
||||||
@@ -210,14 +224,21 @@ run_designated() {
|
|||||||
define_program "$file_param"
|
define_program "$file_param"
|
||||||
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||||
log_dir_tmp="${file_param##*/}"
|
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_path##*/}"
|
||||||
[[ $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="$dir_log/${log_dir_tmp%%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $log_path"
|
cmd="&>> $dir_log/$log_path"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$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_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s")
|
local begin_timestamp=$(date "+%s")
|
||||||
@@ -266,14 +287,21 @@ run_else() {
|
|||||||
define_program "$file_param"
|
define_program "$file_param"
|
||||||
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
log_time=$(date "+%Y-%m-%d-%H-%M-%S")
|
||||||
log_dir_tmp="${file_param##*/}"
|
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_path##*/}"
|
||||||
[[ $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="$dir_log/${log_dir_tmp%%.*}"
|
log_dir="${log_dir_tmp%.*}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd="&>> $log_path"
|
cmd="&>> $dir_log/$log_path"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$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_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
local begin_timestamp=$(date "+%s")
|
local begin_timestamp=$(date "+%s")
|
||||||
@@ -338,7 +366,7 @@ main() {
|
|||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
[[ -f $log_path ]] && cat $log_path
|
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
|
||||||
elif [[ $# -eq 0 ]]; then
|
elif [[ $# -eq 0 ]]; then
|
||||||
echo
|
echo
|
||||||
usage
|
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]}
|
selectedKeys={[props.location.pathname]}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
ErrorBoundary={Sentry.ErrorBoundary}
|
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={
|
title={
|
||||||
<>
|
<>
|
||||||
<span style={{ fontSize: 16 }}>控制面板</span>
|
<span style={{ fontSize: 16 }}>控制面板</span>
|
||||||
|
|||||||
+228
-20
@@ -10,6 +10,7 @@ import {
|
|||||||
List,
|
List,
|
||||||
Divider,
|
Divider,
|
||||||
Typography,
|
Typography,
|
||||||
|
Tooltip,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
@@ -17,6 +18,8 @@ import {
|
|||||||
FieldTimeOutlined,
|
FieldTimeOutlined,
|
||||||
Loading3QuartersOutlined,
|
Loading3QuartersOutlined,
|
||||||
FileOutlined,
|
FileOutlined,
|
||||||
|
PlayCircleOutlined,
|
||||||
|
PauseCircleOutlined,
|
||||||
} 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';
|
||||||
@@ -24,6 +27,7 @@ import { request } from '@/utils/http';
|
|||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import CronLogModal from './logModal';
|
import CronLogModal from './logModal';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
|
import IconFont from '@/components/iconfont';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -68,6 +72,7 @@ const CronDetailModal = ({
|
|||||||
const [scriptInfo, setScriptInfo] = useState<any>({});
|
const [scriptInfo, setScriptInfo] = useState<any>({});
|
||||||
const [logUrl, setLogUrl] = useState('');
|
const [logUrl, setLogUrl] = useState('');
|
||||||
const [validTabs, setValidTabs] = useState(tabList);
|
const [validTabs, setValidTabs] = useState(tabList);
|
||||||
|
const [currentCron, setCurrentCron] = useState<any>({});
|
||||||
|
|
||||||
const contentList: any = {
|
const contentList: any = {
|
||||||
log: (
|
log: (
|
||||||
@@ -103,7 +108,7 @@ const CronDetailModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onClickItem = (item: LogItem) => {
|
const onClickItem = (item: LogItem) => {
|
||||||
localStorage.setItem('logCron', cron.id);
|
localStorage.setItem('logCron', currentCron.id);
|
||||||
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
|
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
|
.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(() => {
|
useEffect(() => {
|
||||||
if (cron && cron.id) {
|
if (cron && cron.id) {
|
||||||
|
setCurrentCron(cron);
|
||||||
getLogs();
|
getLogs();
|
||||||
getScript();
|
getScript();
|
||||||
}
|
}
|
||||||
@@ -206,19 +353,76 @@ const CronDetailModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<>
|
<div className="crontab-title-wrapper">
|
||||||
<span>{cron.name}</span>
|
<div>
|
||||||
{cron.labels?.length > 0 && cron.labels[0] !== '' && (
|
<span>{currentCron.name}</span>
|
||||||
|
{currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && (
|
||||||
<Divider type="vertical"></Divider>
|
<Divider type="vertical"></Divider>
|
||||||
)}
|
)}
|
||||||
{cron.labels?.length > 0 &&
|
{currentCron.labels?.length > 0 &&
|
||||||
cron.labels[0] !== '' &&
|
currentCron.labels[0] !== '' &&
|
||||||
cron.labels?.map((label: string, i: number) => (
|
currentCron.labels?.map((label: string, i: number) => (
|
||||||
<Tag color="blue" style={{ marginRight: 5 }}>
|
<Tag color="blue" style={{ marginRight: 5 }}>
|
||||||
{label}
|
{label}
|
||||||
</Tag>
|
</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
|
centered
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@@ -232,21 +436,22 @@ const CronDetailModal = ({
|
|||||||
<Card>
|
<Card>
|
||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">任务</div>
|
<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>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card style={{ marginTop: 10 }}>
|
<Card style={{ marginTop: 10 }}>
|
||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">状态</div>
|
<div className="cron-detail-info-title">状态</div>
|
||||||
<div className="cron-detail-info-value">
|
<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 icon={<ClockCircleOutlined />} color="default">
|
||||||
空闲中
|
空闲中
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
{cron.status === CrontabStatus.running && (
|
{currentCron.status === CrontabStatus.running && (
|
||||||
<Tag
|
<Tag
|
||||||
icon={<Loading3QuartersOutlined spin />}
|
icon={<Loading3QuartersOutlined spin />}
|
||||||
color="processing"
|
color="processing"
|
||||||
@@ -254,14 +459,15 @@ const CronDetailModal = ({
|
|||||||
运行中
|
运行中
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
{cron.status === CrontabStatus.queued && (
|
{currentCron.status === CrontabStatus.queued && (
|
||||||
<Tag icon={<FieldTimeOutlined />} color="default">
|
<Tag icon={<FieldTimeOutlined />} color="default">
|
||||||
队列中
|
队列中
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{cron.isDisabled === 1 && cron.status === CrontabStatus.idle && (
|
{currentCron.isDisabled === 1 &&
|
||||||
|
currentCron.status === CrontabStatus.idle && (
|
||||||
<Tag icon={<CloseCircleOutlined />} color="error">
|
<Tag icon={<CloseCircleOutlined />} color="error">
|
||||||
已禁用
|
已禁用
|
||||||
</Tag>
|
</Tag>
|
||||||
@@ -270,13 +476,13 @@ const CronDetailModal = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">定时</div>
|
<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>
|
||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">最后运行时间</div>
|
<div className="cron-detail-info-title">最后运行时间</div>
|
||||||
<div className="cron-detail-info-value">
|
<div className="cron-detail-info-value">
|
||||||
{cron.last_execution_time
|
{currentCron.last_execution_time
|
||||||
? new Date(cron.last_execution_time * 1000)
|
? new Date(currentCron.last_execution_time * 1000)
|
||||||
.toLocaleString(language, {
|
.toLocaleString(language, {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
})
|
})
|
||||||
@@ -287,14 +493,16 @@ const CronDetailModal = ({
|
|||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">最后运行时长</div>
|
<div className="cron-detail-info-title">最后运行时长</div>
|
||||||
<div className="cron-detail-info-value">
|
<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>
|
</div>
|
||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">下次运行时间</div>
|
<div className="cron-detail-info-title">下次运行时间</div>
|
||||||
<div className="cron-detail-info-value">
|
<div className="cron-detail-info-value">
|
||||||
{cron.nextRunTime &&
|
{currentCron.nextRunTime &&
|
||||||
cron.nextRunTime
|
currentCron.nextRunTime
|
||||||
.toLocaleString(language, {
|
.toLocaleString(language, {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -77,6 +77,22 @@
|
|||||||
margin-top: 12px;
|
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 {
|
.log-item {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
StopOutlined,
|
DeleteFilled,
|
||||||
BugOutlined,
|
BugOutlined,
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -120,6 +120,15 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
const isPc = !isPhone;
|
const isPc = !isPhone;
|
||||||
return (
|
return (
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
|
<Tooltip title={isPc ? '日志' : ''}>
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
setLogDependence({ ...record, timestamp: Date.now() });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileTextOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
{record.status !== Status.安装中 &&
|
{record.status !== Status.安装中 &&
|
||||||
record.status !== Status.删除中 && (
|
record.status !== Status.删除中 && (
|
||||||
<>
|
<>
|
||||||
@@ -133,17 +142,13 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
<DeleteOutlined />
|
<DeleteOutlined />
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
<Tooltip title={isPc ? '强制删除' : ''}>
|
||||||
)}
|
<a onClick={() => deleteDependence(record, index, true)}>
|
||||||
<Tooltip title={isPc ? '日志' : ''}>
|
<DeleteFilled />
|
||||||
<a
|
|
||||||
onClick={() => {
|
|
||||||
setLogDependence({ ...record, timestamp: Date.now() });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileTextOutlined />
|
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -182,7 +187,11 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
setIsModalVisible(true);
|
setIsModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteDependence = (record: any, index: number) => {
|
const deleteDependence = (
|
||||||
|
record: any,
|
||||||
|
index: number,
|
||||||
|
force: boolean = false,
|
||||||
|
) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: (
|
content: (
|
||||||
@@ -196,10 +205,19 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
),
|
),
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}dependencies`, { data: [record.id] })
|
.delete(`${config.apiPrefix}dependencies${force ? '/force' : ''}`, {
|
||||||
|
data: [record.id],
|
||||||
|
})
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
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 {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
@@ -275,13 +293,16 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
onChange: onSelectChange,
|
onChange: onSelectChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
const delDependencies = () => {
|
const delDependencies = (force: boolean) => {
|
||||||
|
const forceUrl = force ? '/force' : '';
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: <>确认删除选中的依赖吗</>,
|
content: <>确认删除选中的依赖吗</>,
|
||||||
onOk() {
|
onOk() {
|
||||||
request
|
request
|
||||||
.delete(`${config.apiPrefix}dependencies`, { data: selectedRowIds })
|
.delete(`${config.apiPrefix}dependencies${forceUrl}`, {
|
||||||
|
data: selectedRowIds,
|
||||||
|
})
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.code === 200) {
|
if (data.code === 200) {
|
||||||
setSelectedRowIds([]);
|
setSelectedRowIds([]);
|
||||||
@@ -377,10 +398,17 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
onClick={delDependencies}
|
onClick={() => delDependencies(false)}
|
||||||
>
|
>
|
||||||
批量删除
|
批量删除
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
|
onClick={() => delDependencies(true)}
|
||||||
|
>
|
||||||
|
批量强制删除
|
||||||
|
</Button>
|
||||||
<span style={{ marginLeft: 8 }}>
|
<span style={{ marginLeft: 8 }}>
|
||||||
已选择
|
已选择
|
||||||
<a>{selectedRowIds?.length}</a>项
|
<a>{selectedRowIds?.length}</a>项
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ const Initialization = () => {
|
|||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src="https://qn.whyour.cn/logo.png"
|
src="http://qn.whyour.cn/logo.png"
|
||||||
/>
|
/>
|
||||||
<span className={styles.title}>初始化配置</span>
|
<span className={styles.title}>初始化配置</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ const Login = () => {
|
|||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src="https://qn.whyour.cn/logo.png"
|
src="http://qn.whyour.cn/logo.png"
|
||||||
/>
|
/>
|
||||||
<span className={styles.title}>
|
<span className={styles.title}>
|
||||||
{twoFactor ? '两步验证' : config.siteName}
|
{twoFactor ? '两步验证' : config.siteName}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const About = () => {
|
|||||||
<img
|
<img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
style={{ width: 140, marginRight: 20 }}
|
style={{ width: 140, marginRight: 20 }}
|
||||||
src="https://qn.whyour.cn/logo.png"
|
src="http://qn.whyour.cn/logo.png"
|
||||||
/>
|
/>
|
||||||
<div className={styles.right}>
|
<div className={styles.right}>
|
||||||
<span className={styles.title}>青龙</span>
|
<span className={styles.title}>青龙</span>
|
||||||
|
|||||||
+8
-2
@@ -77,7 +77,7 @@ export default {
|
|||||||
{ value: 'gotify', label: 'Gotify' },
|
{ value: 'gotify', label: 'Gotify' },
|
||||||
{ value: 'goCqHttpBot', label: 'GoCqHttpBot' },
|
{ value: 'goCqHttpBot', label: 'GoCqHttpBot' },
|
||||||
{ value: 'serverChan', label: 'Server酱' },
|
{ value: 'serverChan', label: 'Server酱' },
|
||||||
{ value: 'PushDeer', label: 'PushDeer' },
|
{ value: 'pushDeer', label: 'PushDeer' },
|
||||||
{ value: 'bark', label: 'Bark' },
|
{ value: 'bark', label: 'Bark' },
|
||||||
{ value: 'telegramBot', label: 'Telegram机器人' },
|
{ value: 'telegramBot', label: 'Telegram机器人' },
|
||||||
{ value: 'dingtalkBot', label: '钉钉机器人' },
|
{ value: 'dingtalkBot', label: '钉钉机器人' },
|
||||||
@@ -114,7 +114,13 @@ export default {
|
|||||||
serverChan: [
|
serverChan: [
|
||||||
{ label: 'serverChanKey', tip: 'Server酱SENDKEY', required: true },
|
{ 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: [
|
bark: [
|
||||||
{
|
{
|
||||||
label: 'barkPush',
|
label: 'barkPush',
|
||||||
|
|||||||
+9
-14
@@ -1,15 +1,10 @@
|
|||||||
export const version = '2.12.0';
|
export const version = '2.12.2';
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/288';
|
export const changeLogLink = 'https://t.me/jiao_long/290';
|
||||||
export const changeLog = `2.12.0 版本说明
|
export const changeLog = `2.12.2 版本说明
|
||||||
1. 全新定时任务详情,支持日志查看、脚本编辑
|
1. 任务详情支持运行、禁用、置顶操作
|
||||||
2. openapi增加发送通知接口,可用于脚本直接调用
|
2. 依赖管理增加直接强制删除
|
||||||
3. 增加pushDeer推送,感谢 https://github.com/NekoMio PR
|
3. 修复环境变量引号转义逻辑,感谢 https://github.com/JerryWn12 PR
|
||||||
4. 增加public服务,当服务异常时,查询服务状态及日志,供页面使用。
|
4. 修复定时删除日志设置,支持设置为24天以上
|
||||||
5. 增加ql check可视化错误提示
|
5. 修复拉取脚本,shell发送通知
|
||||||
6. 修改openapi获取token逻辑,最多存储5个可用的token。
|
6. 修复shell获取日志目录
|
||||||
7. 调整数据目录,log、db、scripts、config等目录迁移到 /ql/data 目录,docker映射只需映射data目录
|
|
||||||
8. 版本文件存储到七牛云,方便检查更新
|
|
||||||
9. 修复编辑应用初始值
|
|
||||||
10. 修复退出登录和定时任务搜索
|
|
||||||
11. 其他bug修复
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user