Compare commits

..

29 Commits

Author SHA1 Message Date
whyour 4af9b22442 更新版本 v2.14.9 2022-10-29 18:50:16 +08:00
whyour 8a392170cf webhook通知支持嵌套body 2022-10-29 18:43:16 +08:00
whyour 6385e053ae 增加飞书机器人通知 2022-10-29 16:41:37 +08:00
Leo_chen 2789119882 添加智能微秘书通知 (#1680) 2022-10-29 15:45:16 +08:00
whyour 3e0553c3e8 修改登陆逻辑 2022-10-29 10:05:54 +08:00
whyour 5e16ace1e7 修改检查更新日志 2022-10-27 10:34:55 +08:00
whyour 0ca23cc4ca 修改环境变量默认排序 2022-10-27 00:02:50 +08:00
whyour 4949457001 修复ql命令日志打印 2022-10-26 23:50:07 +08:00
whyour 8ae13fe823 增加 -max 指定环境变量 2022-10-26 14:27:14 +08:00
whyour fdf672092a 修改获取version文件超时时间 2022-10-25 23:52:30 +08:00
whyour 48dd6ea826 修复表格滚动高度 2022-10-25 23:12:38 +08:00
whyour 68bb4f15e2 修复shell获取磁盘使用量 2022-10-24 22:45:06 +08:00
whyour 9c6d9ec22d 更新版本 v2.14.8 2022-10-22 18:36:38 +08:00
whyour ac075ace73 修复调试脚本语言判断 2022-10-22 00:31:34 +08:00
whyour eb58774ee9 优化task.sh 2022-10-22 00:25:54 +08:00
whyour 04613bf9a9 修复tsconfig 2022-10-20 15:30:22 +08:00
whyour 4194f1964d 修改日志结束符判定 2022-10-20 15:16:17 +08:00
whyour 42c64c82d7 修复 ql 命令日志目录 2022-10-16 21:59:19 +08:00
whyour d44a8966f2 修复环境变量名称验证 2022-10-16 09:22:37 +08:00
whyour ff44330aa1 修复 ql 命令日志目录 2022-10-15 18:40:15 +08:00
whyour ed259e8168 修复 task 命令执行时参数引号丢失 2022-10-15 18:29:34 +08:00
whyour 444836975d 修改 ql 命令打印日志逻辑 2022-10-14 23:02:20 +08:00
whyour 3756fa93e4 修复 ql 命令 2022-10-14 21:17:04 +08:00
whyour 064b52b306 修改 timezone action 2022-10-14 20:55:58 +08:00
whyour 648dc51474 更新ql指令 2022-10-14 15:17:37 +08:00
whyour 7c0bc32759 修改环境变量导入 2022-10-13 23:41:54 +08:00
whyour d4b6cc5e4d 修复调试脚本选择脚本 2022-10-13 23:39:28 +08:00
whyour 75fca715e3 增加环境变量导入 2022-10-11 22:16:36 +08:00
whyour 6e9a4a29ef 修复shell语法 2022-10-09 13:49:19 +08:00
38 changed files with 961 additions and 600 deletions
+3 -5
View File
@@ -57,12 +57,10 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Set time zone
uses: szenius/set-timezone@v1.0
- name: Setup timezone
uses: zcong1993/setup-timezone@master
with:
timezoneLinux: "Asia/Shanghai"
timezoneMacos: "Asia/Shanghai"
timezoneWindows: "China Standard Time"
timezone: Asia/Shanghai
- name: Login to DockerHub
uses: docker/login-action@v2
-1
View File
@@ -61,7 +61,6 @@ podman run -dit \
-p 5700:5700 \
--name qinglong \
--hostname qinglong \
--restart unless-stopped \
docker.io/whyour/qinglong:latest
```
-1
View File
@@ -61,7 +61,6 @@ podman run -dit \
-p 5700:5700 \
--name qinglong \
--hostname qinglong \
--restart unless-stopped \
docker.io/whyour/qinglong:latest
```
+49 -1
View File
@@ -3,8 +3,21 @@ import { Container } from 'typedi';
import EnvService from '../services/env';
import { Logger } from 'winston';
import { celebrate, Joi } from 'celebrate';
import multer from 'multer';
import config from '../config';
import fs from 'fs';
const route = Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, config.scriptPath);
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({ storage: storage });
export default (app: Router) => {
app.use('/envs', route);
@@ -26,7 +39,9 @@ export default (app: Router) => {
body: Joi.array().items(
Joi.object({
value: Joi.string().required(),
name: Joi.string().required(),
name: Joi.string()
.required()
.pattern(/^[a-zA-Z_][0-9a-zA-Z_]*$/),
remarks: Joi.string().optional().allow(''),
}),
),
@@ -177,4 +192,37 @@ export default (app: Router) => {
}
},
);
route.post(
'/upload',
upload.single('env'),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const envService = Container.get(EnvService);
const fileContent = await fs.promises.readFile(req!.file!.path, 'utf8');
const parseContent = JSON.parse(fileContent);
const data = Array.isArray(parseContent)
? parseContent
: [parseContent];
if (data.every((x) => x.name && x.value)) {
const result = await envService.create(
data.map((x) => ({
name: x.name,
value: x.value,
remarks: x.remarks,
})),
);
return res.send({ code: 200, data: result });
} else {
return res.send({
code: 400,
message: '文件缺少name或者value字段,参考导出文件格式',
});
}
} catch (e) {
return next(e);
}
},
);
};
+1
View File
@@ -0,0 +1 @@
export const LOG_END_SYMBOL = '\n          ';
+6 -1
View File
@@ -419,7 +419,12 @@ export function parseBody(
return;
}
parsed[key] = val;
try {
const jsonValue = JSON.parse(val);
parsed[key] = jsonValue;
} catch (error) {
parsed[key] = val;
}
});
switch (contentType) {
+19 -2
View File
@@ -11,9 +11,11 @@ export enum NotificationMode {
'dingtalkBot' = 'dingtalkBot',
'weWorkBot' = 'weWorkBot',
'weWorkApp' = 'weWorkApp',
'aibotk' = 'aibotk',
'iGot' = 'iGot',
'pushPlus' = 'pushPlus',
'email' = 'email',
'feishu' = 'feishu',
'webhook' = 'webhook',
}
@@ -75,6 +77,12 @@ export class WeWorkAppNotification extends NotificationBaseInfo {
public weWorkAppKey = '';
}
export class AibotkNotification extends NotificationBaseInfo {
public aibotkKey: string = '';
public aibotkType: 'room' | 'contact' = 'room';
public aibotkName: string = '';
}
export class IGotNotification extends NotificationBaseInfo {
public iGotPushKey = '';
}
@@ -95,7 +103,14 @@ export class WebhookNotification extends NotificationBaseInfo {
public webhookBody: string = '';
public webhookUrl: string = '';
public webhookMethod: 'GET' | 'POST' | 'PUT' = 'GET';
public webhookContentType: 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded' = 'application/json';
public webhookContentType:
| 'application/json'
| 'multipart/form-data'
| 'application/x-www-form-urlencoded' = 'application/json';
}
export class LarkNotification extends NotificationBaseInfo {
public larkKey = '';
}
export interface NotificationInfo
@@ -109,7 +124,9 @@ export interface NotificationInfo
DingtalkBotNotification,
WeWorkBotNotification,
WeWorkAppNotification,
AibotkNotification,
IGotNotification,
PushPlusNotification,
EmailNotification,
WebhookNotification {}
WebhookNotification,
LarkNotification {}
+3 -2
View File
@@ -11,6 +11,7 @@ import { promisify } from 'util';
import { Op } from 'sequelize';
import path from 'path';
import dayjs from 'dayjs';
import { LOG_END_SYMBOL } from '../config/const';
@Service()
export default class CronService {
@@ -335,7 +336,7 @@ export default class CronService {
`${absolutePath}`,
`${str}\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)}${diffTimeStr}`,
)}${diffTimeStr}${LOG_END_SYMBOL}`,
);
}
}
@@ -420,7 +421,7 @@ export default class CronService {
);
cp.stderr.on('data', (data) => {
if (logFileExist) {
fs.appendFileSync(`${absolutePath}`, `${data}`);
fs.appendFileSync(`${absolutePath}`, `${data.toString()}`);
}
});
cp.on('error', (err) => {
+4 -4
View File
@@ -76,12 +76,12 @@ export default class CronViewService {
const views = await this.list();
if (toIndex === 0 || toIndex === views.length - 1) {
targetPosition = isUpward
? views[0].position * 2
: views[toIndex].position / 2;
? views[0].position! * 2
: views[toIndex].position! / 2;
} else {
targetPosition = isUpward
? (views[toIndex].position + views[toIndex - 1].position) / 2
: (views[toIndex].position + views[toIndex + 1].position) / 2;
? (views[toIndex].position! + views[toIndex - 1].position!) / 2
: (views[toIndex].position! + views[toIndex + 1].position!) / 2;
}
const newDoc = await this.update({
id,
+7 -8
View File
@@ -67,12 +67,12 @@ export default class EnvService {
const envs = await this.envs();
if (toIndex === 0 || toIndex === envs.length - 1) {
targetPosition = isUpward
? envs[0].position * 2
: envs[toIndex].position / 2;
? envs[0].position! * 2
: envs[toIndex].position! / 2;
} else {
targetPosition = isUpward
? (envs[toIndex].position + envs[toIndex - 1].position) / 2
: (envs[toIndex].position + envs[toIndex + 1].position) / 2;
? (envs[toIndex].position! + envs[toIndex - 1].position!) / 2
: (envs[toIndex].position! + envs[toIndex + 1].position!) / 2;
}
const newDoc = await this.update({
id,
@@ -115,7 +115,7 @@ export default class EnvService {
const result = await this.find(condition, [
['status', 'ASC'],
['position', 'DESC'],
['createdAt', 'DESC'],
['createdAt', 'ASC'],
]);
return result as any;
} catch (error) {
@@ -158,7 +158,7 @@ export default class EnvService {
const envs = await this.envs(
'',
{ position: -1 },
{ name: { [Op.not]: null } },
{ name: { [Op.not]: null }, status: EnvStatus.normal },
);
const groups = _.groupBy(envs, 'name');
let env_string = '';
@@ -167,9 +167,8 @@ export default class EnvService {
const group = groups[key];
// 忽略不符合bash要求的环境变量名称
if (/^[a-zA-Z_][0-9a-zA-Z_]+$/.test(key)) {
if (/^[a-zA-Z_][0-9a-zA-Z_]*$/.test(key)) {
let value = _(group)
.filter((x) => x.status !== EnvStatus.disabled)
.map('value')
.join('&')
.replace(/(\\)[^n]/g, '\\\\')
+101 -41
View File
@@ -24,18 +24,23 @@ export default class NotificationService {
['dingtalkBot', this.dingtalkBot],
['weWorkBot', this.weWorkBot],
['weWorkApp', this.weWorkApp],
['aibotk', this.aibotk],
['iGot', this.iGot],
['pushPlus', this.pushPlus],
['email', this.email],
['webhook', this.webhook],
['lark', this.lark],
]);
private timeout = 10000;
private title = '';
private content = '';
private params!: Omit<NotificationInfo, 'type'>;
private gotOption = {
timeout: 30000,
retry: 1,
};
constructor(@Inject('logger') private logger: winston.Logger) { }
constructor(@Inject('logger') private logger: winston.Logger) {}
public async notify(
title: string,
@@ -76,8 +81,7 @@ export default class NotificationService {
const { gotifyUrl, gotifyToken, gotifyPriority } = this.params;
const res: any = await got
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `title=${encodeURIComponent(
this.title,
)}&message=${encodeURIComponent(
@@ -95,8 +99,7 @@ export default class NotificationService {
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
const res: any = await got
.post(`${goCqHttpBotUrl}?${goCqHttpBotQq}`, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: { message: `${this.title}\n${this.content}` },
headers: { Authorization: 'Bearer ' + goCqHttpBotToken },
})
@@ -111,8 +114,7 @@ export default class NotificationService {
: `https://sc.ftqq.com/${serverChanKey}.send`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `title=${this.title}&desp=${this.content}`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
@@ -125,8 +127,7 @@ export default class NotificationService {
const url = `https://api2.pushdeer.com/message/push`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `pushkey=${pushDeerKey}&text=${encodeURIComponent(
this.title,
)}&desp=${encodeURIComponent(this.content)}&type=markdown`,
@@ -143,8 +144,7 @@ export default class NotificationService {
const url = `${chatUrl}${chatToken}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `payload={"text":"${this.title}\n${this.content}"}`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
@@ -164,8 +164,7 @@ export default class NotificationService {
)}?icon=${barkIcon}?sound=${barkSound}&group=${barkGroup}`;
const res: any = await got
.get(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
.json();
@@ -182,8 +181,9 @@ export default class NotificationService {
telegramBotUserId,
} = this.params;
const authStr = telegramBotProxyAuth ? `${telegramBotProxyAuth}@` : '';
const url = `https://${telegramBotApiHost ? telegramBotApiHost : 'api.telegram.org'
}/bot${telegramBotToken}/sendMessage`;
const url = `https://${
telegramBotApiHost ? telegramBotApiHost : 'api.telegram.org'
}/bot${telegramBotToken}/sendMessage`;
let agent;
if (telegramBotProxyHost && telegramBotProxyPort) {
const options: any = {
@@ -202,8 +202,7 @@ export default class NotificationService {
}
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `chat_id=${telegramBotUserId}&text=${this.title}\n\n${this.content}&disable_web_page_preview=true`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
agent,
@@ -225,8 +224,7 @@ export default class NotificationService {
const url = `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkBotToken}${secretParam}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
msgtype: 'text',
text: {
@@ -243,8 +241,7 @@ export default class NotificationService {
const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weWorkBotKey}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
msgtype: 'text',
text: {
@@ -263,8 +260,7 @@ export default class NotificationService {
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
const tokenRes: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
corpid,
corpsecret,
@@ -314,8 +310,7 @@ export default class NotificationService {
.post(
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
{
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
touser,
agentid,
@@ -329,13 +324,53 @@ export default class NotificationService {
return res.errcode === 0;
}
private async aibotk() {
const { aibotkKey, aibotkType, aibotkName } = this.params;
let url = '';
let json = {};
switch (aibotkType) {
case 'room':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/room';
json = {
apiKey: `${aibotkKey}`,
roomName: `${aibotkName}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${this.title}\n${this.content}`,
},
};
break;
case 'contact':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/contact';
json = {
apiKey: `${aibotkKey}`,
name: `${aibotkName}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${this.title}\n${this.content}`,
},
};
break;
}
const res: any = await got
.post(url, {
...this.gotOption,
json: {
...json,
},
})
.json();
return res.code === 0;
}
private async iGot() {
const { iGotPushKey } = this.params;
const url = `https://push.hellyw.com/${iGotPushKey.toLowerCase()}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `title=${this.title}&content=${this.content}`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
@@ -349,8 +384,7 @@ export default class NotificationService {
const url = `https://www.pushplus.plus/send`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
token: `${pushPlusToken}`,
title: `${this.title}`,
@@ -363,6 +397,21 @@ export default class NotificationService {
return res.code === 200;
}
private async lark() {
const { larkKey } = this.params;
const res: any = await got
.post(`https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`, {
...this.gotOption,
json: {
msg_type: 'text',
content: { text: `${this.title}\n\n${this.content}` },
},
headers: { 'Content-Type': 'application/json' },
})
.json();
return res.StatusCode === 0;
}
private async email() {
const { emailPass, emailService, emailUser } = this.params;
const transporter = nodemailer.createTransport({
@@ -386,11 +435,19 @@ export default class NotificationService {
}
private async webhook() {
const { webhookUrl, webhookBody, webhookHeaders, webhookMethod, webhookContentType } =
this.params;
const {
webhookUrl,
webhookBody,
webhookHeaders,
webhookMethod,
webhookContentType,
} = this.params;
const { formatBody, formatUrl } = this.formatNotifyContent(webhookUrl, webhookBody);
if (!formatUrl && !formatBody) {
const { formatBody, formatUrl } = this.formatNotifyContent(
webhookUrl,
webhookBody,
);
if (!formatUrl && !formatBody) {
return false;
}
const headers = parseHeaders(webhookHeaders);
@@ -399,11 +456,10 @@ export default class NotificationService {
const options = {
method: webhookMethod,
headers,
timeout: this.timeout,
retry: 0,
...this.gotOption,
allowGetBody: true,
...bodyParam
}
...bodyParam,
};
const res = await got(formatUrl, options);
return String(res.statusCode).startsWith('20');
}
@@ -427,8 +483,12 @@ export default class NotificationService {
}
return {
formatUrl: url.replaceAll('$title', encodeURIComponent(this.title)).replaceAll('$content', encodeURIComponent(this.content)),
formatBody: body.replaceAll('$title', this.title).replaceAll('$content', this.content),
}
formatUrl: url
.replaceAll('$title', encodeURIComponent(this.title))
.replaceAll('$content', encodeURIComponent(this.content)),
formatBody: body
.replaceAll('$title', this.title)
.replaceAll('$content', this.content),
};
}
}
+2 -1
View File
@@ -6,6 +6,7 @@ import SockService from './sock';
import CronService from './cron';
import ScheduleService, { TaskCallbacks } from './schedule';
import config from '../config';
import { LOG_END_SYMBOL } from '../config/const';
@Service()
export default class ScriptService {
@@ -55,7 +56,7 @@ export default class ScriptService {
type: 'manuallyRunScript',
message: `${str}\n## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `,
.replace(' 24:', ' 00:')}${LOG_END_SYMBOL}`,
});
return { code: 200 };
+3 -2
View File
@@ -29,6 +29,7 @@ import { SimpleIntervalSchedule } from 'toad-scheduler';
import SockService from './sock';
import SshKeyService from './sshKey';
import dayjs from 'dayjs';
import { LOG_END_SYMBOL } from '../config/const';
@Service()
export default class SubscriptionService {
@@ -232,7 +233,7 @@ export default class SubscriptionService {
absolutePath,
`\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)} 耗时 ${diff}`,
)} 耗时 ${diff}${LOG_END_SYMBOL}`,
);
await SubscriptionModel.update(
@@ -353,7 +354,7 @@ export default class SubscriptionService {
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `,
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')}${LOG_END_SYMBOL}`,
);
}
+1 -6
View File
@@ -89,8 +89,7 @@ export default class SystemService {
let lastLog = '';
try {
const result = await got.get(config.lastVersionFile, {
timeout: 6000,
retry: 0,
timeout: 30000,
});
const lastVersionFileContent = result.body;
lastVersion = lastVersionFileContent.match(versionRegx)![1];
@@ -137,10 +136,6 @@ export default class SystemService {
public async updateSystem() {
const cp = spawn('ql -l update', { shell: '/bin/bash' });
this.sockService.sendMessage({
type: 'updateSystemVersion',
message: `开始更新系统`,
});
cp.stdout.on('data', (data) => {
this.sockService.sendMessage({
type: 'updateSystemVersion',
+23 -14
View File
@@ -24,7 +24,7 @@ export default class UserService {
@Inject('logger') private logger: winston.Logger,
private scheduleService: ScheduleService,
private sockService: SockService,
) { }
) {}
public async login(
payloads: {
@@ -67,7 +67,9 @@ export default class UserService {
const retriesTime = Math.pow(3, retries) * 1000;
if (retries > 2 && timestamp - lastlogon < retriesTime) {
const waitTime = Math.ceil((retriesTime - (timestamp - lastlogon)) / 1000);
const waitTime = Math.ceil(
(retriesTime - (timestamp - lastlogon)) / 1000,
);
return {
code: 410,
message: `失败次数过多,请${waitTime}秒后重试`,
@@ -75,18 +77,23 @@ export default class UserService {
};
}
if (
username === cUsername &&
password === cPassword &&
twoFactorActivated &&
needTwoFactor
) {
this.updateAuthInfo(content, {
isTwoFactorChecking: true,
});
return {
code: 420,
message: '',
};
}
const { ip, address } = await getNetIp(req);
if (username === cUsername && password === cPassword) {
if (twoFactorActivated && needTwoFactor) {
this.updateAuthInfo(content, {
isTwoFactorChecking: true,
});
return {
code: 420,
message: '',
};
}
const data = createRandomString(50, 100);
const expiration = twoFactorActivated ? 60 : 20;
let token = jwt.sign({ data }, config.secret as any, {
@@ -109,7 +116,8 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${req.platform
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录成功,ip地址 ${ip}`,
);
await this.getLoginLog();
@@ -137,7 +145,8 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${req.platform
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录失败,ip地址 ${ip}`,
);
await this.getLoginLog();
+9
View File
@@ -151,4 +151,13 @@ export DEER_KEY=""
export CHAT_URL=""
export CHAT_TOKEN=""
## 13. aibotk
## 官方说明文档:http://wechat.aibotk.com/oapi/oapi?from=ql
## aibotk_key (必填)填写智能微秘书个人中心的apikey
export AIBOTK_KEY=""
## aibotk_type (必填)填写发送的目标 room 或 contact, 填其他的不生效
export AIBOTK_TYPE=""
## aibotk_name (必填)填写群名或用户昵称,和上面的type类型要对应
export AIBOTK_NAME=""
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
+188 -153
View File
@@ -110,6 +110,20 @@ let PUSH_PLUS_USER = '';
let QQ_SKEY = '';
let QQ_MODE = '';
// =======================================智能微秘书设置区域=======================================
//官方文档:http://wechat.aibotk.com/docs/about
//AIBOTK_KEY 填写智能微秘书个人中心的apikey
//AIBOTK_TYPE:填写发送的目标 room 或 contact, 填其他的不生效
//AIBOTK_NAME: 填写群名或用户昵称,和上面的type类型要对应
let AIBOTK_KEY = '';
let AIBOTK_TYPE = '';
let AIBOTK_NAME = '';
// =======================================飞书机器人设置区域=======================================
//官方文档:https://www.feishu.cn/hc/zh-CN/articles/360024984973
//FSKEY 飞书机器人的 FSKEY
let FSKEY = '';
//==========================云端环境变量的判断与接收=========================
if (process.env.GOTIFY_URL) {
GOTIFY_URL = process.env.GOTIFY_URL;
@@ -220,6 +234,20 @@ if (process.env.PUSH_PLUS_TOKEN) {
if (process.env.PUSH_PLUS_USER) {
PUSH_PLUS_USER = process.env.PUSH_PLUS_USER;
}
if (process.env.AIBOTK_KEY) {
AIBOTK_KEY = process.env.AIBOTK_KEY;
}
if (process.env.AIBOTK_TYPE) {
AIBOTK_TYPE = process.env.AIBOTK_TYPE;
}
if (process.env.AIBOTK_NAME) {
AIBOTK_NAME = process.env.AIBOTK_NAME;
}
if (process.env.FSKEY) {
FSKEY = process.env.FSKEY;
}
//==========================云端环境变量的判断与接收=========================
/**
@@ -255,6 +283,8 @@ async function sendNotify(
gotifyNotify(text, desp), //gotify
ChatNotify(text, desp), //synolog chat
PushDeerNotify(text, desp), //PushDeer
aibotkNotify(text, desp), //智能微秘书
fsBotNotify(text, desp), //飞书机器人
]);
}
@@ -295,7 +325,7 @@ function gotifyNotify(text, desp) {
});
}
function gobotNotify(text, desp, time = 2100) {
function gobotNotify(text, desp) {
return new Promise((resolve) => {
if (GOBOT_URL) {
const options = {
@@ -306,38 +336,34 @@ function gobotNotify(text, desp, time = 2100) {
},
timeout,
};
setTimeout(() => {
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送go-cqhttp通知调用API失败!!\n');
console.log(err);
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送go-cqhttp通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
if (data.retcode === 0) {
console.log('go-cqhttp发送通知消息成功🎉\n');
} else if (data.retcode === 100) {
console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`);
} else {
data = JSON.parse(data);
if (data.retcode === 0) {
console.log('go-cqhttp发送通知消息成功🎉\n');
} else if (data.retcode === 100) {
console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`);
} else {
console.log(
`go-cqhttp发送通知消息异常\n${JSON.stringify(data)}`,
);
}
console.log(`go-cqhttp发送通知消息异常\n${JSON.stringify(data)}`);
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}, time);
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function serverNotify(text, desp, time = 2100) {
function serverNotify(text, desp) {
return new Promise((resolve) => {
if (SCKEY) {
//微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换
@@ -352,33 +378,29 @@ function serverNotify(text, desp, time = 2100) {
},
timeout,
};
setTimeout(() => {
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
//server酱和Server酱·Turbo版的返回json格式不太一样
if (data.errno === 0 || data.data.errno === 0) {
console.log('server酱发送通知消息成功🎉\n');
} else if (data.errno === 1024) {
// 一分钟内发送相同的内容会触发
console.log(`server酱发送通知消息异常: ${data.errmsg}\n`);
} else {
data = JSON.parse(data);
//server酱和Server酱·Turbo版的返回json格式不太一样
if (data.errno === 0 || data.data.errno === 0) {
console.log('server酱发送通知消息成功🎉\n');
} else if (data.errno === 1024) {
// 一分钟内发送相同的内容会触发
console.log(`server酱发送通知消息异常: ${data.errmsg}\n`);
} else {
console.log(
`server酱发送通知消息异常\n${JSON.stringify(data)}`,
);
}
console.log(`server酱发送通知消息异常\n${JSON.stringify(data)}`);
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}, time);
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
@@ -398,35 +420,29 @@ function PushDeerNotify(text, desp) {
},
timeout,
};
$.post(
options,
(err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
// 通过返回的result的长度来判断是否成功
if (
data.content.result.length !== undefined &&
data.content.result.length > 0
) {
console.log('PushDeer发送通知消息成功🎉\n');
} else {
data = JSON.parse(data);
// 通过返回的result的长度来判断是否成功
if (
data.content.result.length !== undefined &&
data.content.result.length > 0
) {
console.log('PushDeer发送通知消息成功🎉\n');
} else {
console.log(
`PushDeer发送通知消息异常\n${JSON.stringify(data)}`,
);
}
console.log(`PushDeer发送通知消息异常\n${JSON.stringify(data)}`);
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
},
time,
);
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
@@ -470,83 +486,6 @@ function ChatNotify(text, desp) {
});
}
function CoolPush(text, desp) {
return new Promise((resolve) => {
if (QQ_SKEY) {
let options = {
url: `https://push.xuthus.cc/${QQ_MODE}/${QQ_SKEY}`,
headers: {
'Content-Type': 'application/json',
},
};
// 已知敏感词
text = text.replace(/京豆/g, '豆豆');
desp = desp.replace(/京豆/g, '');
desp = desp.replace(/🐶/g, '');
desp = desp.replace(/红包/g, 'H包');
switch (QQ_MODE) {
case 'email':
options.json = {
t: text,
c: desp,
};
break;
default:
options.body = `${text}\n\n${desp}`;
}
let pushMode = function (t) {
switch (t) {
case 'send':
return '个人';
case 'group':
return 'QQ群';
case 'wx':
return '微信';
case 'ww':
return '企业微信';
case 'email':
return '邮件';
default:
return '未知方式';
}
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log(`发送${pushMode(QQ_MODE)}通知调用API失败!!\n`);
console.log(err);
} else {
data = JSON.parse(data);
if (data.code === 200) {
console.log(`酷推发送${pushMode(QQ_MODE)}通知消息成功🎉\n`);
} else if (data.code === 400) {
console.log(
`QQ酷推(Cool Push)发送${pushMode(QQ_MODE)}推送失败:${
data.msg
}\n`,
);
} else if (data.code === 503) {
console.log(`QQ酷推出错,${data.message}${data.data}\n`);
} else {
console.log(`酷推推送异常: ${JSON.stringify(data)}`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function BarkNotify(text, desp, params = {}) {
return new Promise((resolve) => {
if (BARK_PUSH) {
@@ -649,7 +588,7 @@ function ddBotNotify(text, desp) {
json: {
msgtype: 'text',
text: {
content: ` ${text}\n\n${desp}`,
content: `${text}\n\n${desp}`,
},
},
headers: {
@@ -716,7 +655,7 @@ function qywxBotNotify(text, desp) {
json: {
msgtype: 'text',
text: {
content: ` ${text}\n\n${desp}`,
content: `${text}\n\n${desp}`,
},
},
headers: {
@@ -983,6 +922,102 @@ function pushPlusNotify(text, desp) {
});
}
function aibotkNotify(text, desp) {
return new Promise((resolve) => {
if (AIBOTK_KEY && AIBOTK_TYPE && AIBOTK_NAME) {
let json = {};
let url = '';
switch (AIBOTK_TYPE) {
case 'room':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/room';
json = {
apiKey: `${AIBOTK_KEY}`,
roomName: `${AIBOTK_NAME}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${text}\n${desp}`,
},
};
break;
case 'contact':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/contact';
json = {
apiKey: `${AIBOTK_KEY}`,
name: `${AIBOTK_NAME}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${text}\n${desp}`,
},
};
break;
}
const options = {
url: url,
json,
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('智能微秘书发送通知消息失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
if (data.code === 0) {
console.log('智能微秘书发送通知消息成功🎉。\n');
} else {
console.log(`${data.error}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}
});
}
function fsBotNotify(text, desp) {
return new Promise((resolve) => {
if (FSKEY) {
const options = {
url: `https://open.feishu.cn/open-apis/bot/v2/hook/${FSKEY}`,
json: { msg_type: 'text', content: { text: `${title}\n\n${content}` } },
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
if (data.StatusCode === 0) {
console.log('飞书发送通知消息成功🎉\n');
} else {
console.log(`${data.msg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
module.exports = {
sendNotify,
BARK_PUSH,
+39
View File
@@ -81,6 +81,10 @@ push_config = {
'TG_PROXY_AUTH': '', # tg 代理认证参数
'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST
'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT
'AIBOTK_KEY': '', # 智能微秘书 个人中心的apikey 文档地址:http://wechat.aibotk.com/docs/about
'AIBOTK_TYPE': '', # 智能微秘书 发送目标 room 或 contact
'AIBOTK_NAME': '', # 智能微秘书 发送群名 或者好友昵称和type要对应好
}
notify_function = []
# fmt: on
@@ -521,6 +525,39 @@ def telegram_bot(title: str, content: str) -> None:
print("tg 推送失败!")
def aibotk(title: str, content: str) -> None:
"""
使用 智能微秘书 推送消息。
"""
if not push_config.get("AIBOTK_KEY") or not push_config.get("AIBOTK_TYPE") or not push_config.get("AIBOTK_NAME"):
print("智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送")
return
print("智能微秘书 服务启动")
if push_config.get("AIBOTK_TYPE") == 'room':
url = "https://api-bot.aibotk.com/openapi/v1/chat/room"
data = {
"apiKey": push_config.get("AIBOTK_KEY"),
"roomName": push_config.get("AIBOTK_NAME"),
"message": {"type": 1, "content": f'【青龙快讯】\n\n${title}\n${content}' }
}
else:
url = "https://api-bot.aibotk.com/openapi/v1/chat/contact"
data = {
"apiKey": push_config.get("AIBOTK_KEY"),
"name": push_config.get("AIBOTK_NAME"),
"message": {"type": 1, "content": f'【青龙快讯】\n\n${title}\n${content}' }
}
body = json.dumps(data).encode(encoding="utf-8")
headers = {"Content-Type": "application/json"}
response = requests.post(url=url, data=body, headers=headers).json()
print(response)
if response["code"] == 0:
print("智能微秘书 推送成功!")
else:
print(f'智能微秘书 推送失败!{response["error"]}')
def one() -> str:
"""
获取一条一言。
@@ -561,6 +598,8 @@ if push_config.get("QYWX_KEY"):
notify_function.append(wecom_bot)
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
notify_function.append(telegram_bot)
if push_config.get("AIBOTK_KEY") and push_config.get("AIBOTK_TYPE") and push_config.get("AIBOTK_NAME"):
notify_function.append(aibotk)
def send(title: str, content: str) -> None:
+273
View File
@@ -0,0 +1,273 @@
#!/usr/bin/env bash
## 导入通用变量与函数
dir_shell=$QL_DIR/shell
. $dir_shell/share.sh
. $dir_shell/api.sh
random_delay() {
local random_delay_max=$RandomDelay
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
local file_param=$1
local file_extensions=${RandomDelayFileExtensions-"js"}
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
if [[ -n $file_extensions ]]; then
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
return
fi
fi
local current_min
current_min=$(date "+%-M")
for minute in $ignored_minutes; do
if [[ $current_min -eq $minute ]]; then
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
return
fi
done
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
sleep $delay_second
fi
}
## scripts目录下所有可运行脚本数组
gen_array_scripts() {
local dir_current=$(pwd)
local i="-1"
cd $dir_scripts
for file in $(ls); do
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
let i++
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1)
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
fi
done
cd $dir_current
}
## 使用说明
usage() {
gen_array_scripts
echo -e "task命令运行本程序自动添加进crontab的脚本,需要输入脚本的绝对路径或去掉 “$dir_scripts/” 目录后的相对路径(定时任务中请写作相对路径),用法为:"
echo -e "1.$cmd_task <file_name> # 依次执行,如果设置了随机延迟,将随机延迟一定秒数"
echo -e "2.$cmd_task <file_name> now # 依次执行,无论是否设置了随机延迟,均立即运行,前台会输出日志,同时记录在日志文件中"
echo -e "3.$cmd_task <file_name> conc <环境变量名称> <账号编号,空格分隔>(可选的) # 并发执行,无论是否设置了随机延迟,均立即运行,前台不产生日志,直接记录在日志文件中,且可指定账号执行"
echo -e "4.$cmd_task <file_name> desi <环境变量名称> <账号编号,空格分隔> # 指定账号执行,无论是否设置了随机延迟,均立即运行"
if [[ ${#array_scripts[*]} -gt 0 ]]; then
echo -e "\n当前有以下脚本可以运行:"
for ((i = 0; i < ${#array_scripts[*]}; i++)); do
echo -e "$(($i + 1)). ${array_scripts_name[i]}${array_scripts[i]}"
done
else
echo -e "\n暂无脚本可以执行"
fi
}
## run nohup,$1:文件名,不含路径,带后缀
run_nohup() {
local file_name=$1
nohup node $file_name &>$log_path &
}
check_server() {
cpu_use=$(top -b -n 1 | grep CPU | grep -v -E 'grep|PID' | awk '{print $2}' | cut -f 1 -d "%" | head -n 1)
mem_free=$(free -m | grep "Mem" | awk '{print $3}' | head -n 1)
mem_total=$(free -m | grep "Mem" | awk '{print $2}' | head -n 1)
mem_use=$(printf "%d%%" $((mem_free * 100 / mem_total)) | cut -f 1 -d "%" | head -n 1)
disk_use=$(df -P | grep /dev | grep -v -E '(tmp|boot|shm)' | awk '{print $5}' | cut -f 1 -d "%" | head -n 1)
if [[ $cpu_use -gt $cpu_warn ]] || [[ $mem_free -lt $mem_warn ]] || [[ $disk_use -gt $disk_warn ]]; then
local resource=$(top -b -n 1 | grep -v -E 'grep|Mem|idle|Load' | awk '{$2="";$3="";$4="";$5="";$7="";print $0}' | head -n 10)
notify_api "服务器资源异常警告" "当前CPU占用 $cpu_use% 内存占用 $mem_use% 磁盘占用 $disk_use% \n资源占用详情 \n\n $resource"
fi
}
handle_task_before() {
begin_time=$(format_time "$time_format" "$time")
begin_timestamp=$(format_timestamp "$time_format" "$time")
echo -e "## 开始执行... $begin_time\n"
[[ $is_macos -eq 0 ]] && check_server
if [[ -s $task_error_log_path ]]; then
eval cat $task_error_log_path $cmd
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
eval echo $cmd
fi
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
. $file_task_before "$@"
}
handle_task_after() {
. $file_task_after "$@"
local etime=$(date "+$time_format")
local end_time=$(format_time "$time_format" "$etime")
local end_timestamp=$(format_timestamp "$time_format" "$etime")
local diff_time=$(($end_timestamp - $begin_timestamp))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time"
echo -e "\n     "
}
## 正常运行单个脚本,$1:传入参数
run_normal() {
local file_param=$1
if [[ $# -eq 1 ]]; then
random_delay "$file_param"
fi
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ${file_param} != /* ]] && [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
$timeoutCmd $which_program $file_param
}
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
run_concurrent() {
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]]; then
echo -e "\n 缺少并发运行的环境变量参数"
exit 1
fi
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval $timeoutCmd $which_program $file_param &>$single_log_path &
done
wait
for i in "${!array[@]}"; do
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
cat $single_log_path
[[ -f $single_log_path ]] && rm -f $single_log_path
done
}
run_designated() {
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
echo -e "\n 缺少单独运行的参数 task xxx.js desi Test 1 3"
exit 1
fi
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
$timeoutCmd $which_program $file_param
}
## 运行其他命令
run_else() {
local file_param="$1"
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
shift
local params=$(echo "$@" | sed 's/ /\" \"/g')
$timeoutCmd $which_program $file_param \"$params\"
}
## 命令检测
main() {
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
case $# in
1)
run_normal "$1"
;;
*)
case $2 in
now)
run_normal "$1" "$2"
;;
conc)
run_concurrent "$1" "$3" "$*"
;;
desi)
run_designated "$1" "$3" "$*"
;;
*)
run_else "$@"
;;
esac
;;
esac
elif [[ $# -eq 0 ]]; then
echo
usage
else
run_else "$@"
fi
}
handle_task_before "$@"
main "$@"
handle_task_after "$@"
+1 -1
View File
@@ -486,4 +486,4 @@ detect_macos
define_cmd
fix_config
import_config $1 >$task_error_log_path 2>&1
import_config $1 2>$task_error_log_path
+22 -286
View File
@@ -21,78 +21,12 @@ define_program() {
fi
}
random_delay() {
local random_delay_max=$RandomDelay
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
local file_param=$1
local file_extensions=${RandomDelayFileExtensions-"js"}
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
if [[ -n $file_extensions ]]; then
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
return
fi
fi
local current_min
current_min=$(date "+%-M")
for minute in $ignored_minutes; do
if [[ $current_min -eq $minute ]]; then
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
return
fi
done
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
sleep $delay_second
fi
}
## scripts目录下所有可运行脚本数组
gen_array_scripts() {
local dir_current=$(pwd)
local i="-1"
cd $dir_scripts
for file in $(ls); do
if [[ -f $file ]] && [[ $file == *.js && $file != sendNotify.js ]]; then
let i++
array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g")
array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1)
[[ -z ${array_scripts_name[i]} ]] && array_scripts_name[i]="<未识别出活动名称>"
fi
done
cd $dir_current
}
## 使用说明
usage() {
define_cmd
gen_array_scripts
echo -e "task命令运行本程序自动添加进crontab的脚本,需要输入脚本的绝对路径或去掉 “$dir_scripts/” 目录后的相对路径(定时任务中请写作相对路径),用法为:"
echo -e "1.$cmd_task <file_name> # 依次执行,如果设置了随机延迟,将随机延迟一定秒数"
echo -e "2.$cmd_task <file_name> now # 依次执行,无论是否设置了随机延迟,均立即运行,前台会输出日志,同时记录在日志文件中"
echo -e "3.$cmd_task <file_name> conc <环境变量名称> <账号编号,空格分隔>(可选的) # 并发执行,无论是否设置了随机延迟,均立即运行,前台不产生日志,直接记录在日志文件中,且可指定账号执行"
echo -e "4.$cmd_task <file_name> desi <环境变量名称> <账号编号,空格分隔> # 指定账号执行,无论是否设置了随机延迟,均立即运行"
if [[ ${#array_scripts[*]} -gt 0 ]]; then
echo -e "\n当前有以下脚本可以运行:"
for ((i = 0; i < ${#array_scripts[*]}; i++)); do
echo -e "$(($i + 1)). ${array_scripts_name[i]}${array_scripts[i]}"
done
else
echo -e "\n暂无脚本可以执行"
fi
}
## run nohup,$1:文件名,不含路径,带后缀
run_nohup() {
local file_name=$1
nohup node $file_name &>$log_path &
}
handle_log_path() {
define_program "$file_param"
local file_param=$1
if [[ -z $file_param ]];then
file_param="task"
fi
local suffix=""
if [[ ! -z $ID ]]; then
@@ -118,226 +52,28 @@ handle_log_path() {
make_dir "$dir_log/$log_dir"
}
check_server() {
cpu_use=$(top -b -n 1 | grep CPU | grep -v -E 'grep|PID' | awk '{print $2}' | cut -f 1 -d "%")
mem_free=$(free -m | grep "Mem" | awk '{print $3}')
mem_total=$(free -m | grep "Mem" | awk '{print $2}')
mem_use=$(printf "%d%%" $((mem_free * 100 / mem_total)) | cut -f 1 -d "%")
disk_use=$(df -P | grep /dev | grep -v -E '(tmp|boot|shm)' | awk '{print $5}' | cut -f 1 -d "%")
# eval echo -e "\#\# 当前CPU占用 $cpu_use% 内存占用 $mem_use% 磁盘占用 $disk_use% \\\n" $cmd
if [[ $cpu_use -gt $cpu_warn ]] || [[ $mem_free -lt $mem_warn ]] || [[ $disk_use -gt $disk_warn ]]; then
local resource = $(top -b -n 1 | grep -v -E 'grep|Mem|idle|Load' | awk '{$2="";$3="";$4="";$5="";$7="";print $0}' | head -n 10)
notify_api "服务器资源异常警告" "当前CPU占用 $cpu_use% 内存占用 $mem_use% 磁盘占用 $disk_use% \n资源占用详情 \n\n $resource"
fi
}
handle_task_before() {
handle_log_path
begin_time=$(format_time "$time_format" "$time")
begin_timestamp=$(format_timestamp "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ $is_macos -eq 0 ]] && check_server
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
eval . $file_task_before "$@" $cmd
}
handle_task_after() {
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local diff_time=$(($end_timestamp - $begin_timestamp))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
}
## 正常运行单个脚本,$1:传入参数
run_normal() {
local file_param=$1
if [[ $# -eq 1 ]]; then
random_delay "$file_param"
fi
handle_task_before "$@"
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval $timeoutCmd $which_program $file_param $cmd
handle_task_after "$@"
}
## 并发执行时,设定的 RandomDelay 不会生效,即所有任务立即执行
run_concurrent() {
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]]; then
echo -e "\n 缺少并发运行的环境变量参数"
exit 1
fi
handle_task_before "$@"
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
eval $timeoutCmd $which_program $file_param &>$single_log_path &
done
wait
for i in "${!array[@]}"; do
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
handle_task_after "$@"
}
run_designated() {
local file_param="$1"
local env_param="$2"
local num_param=$(echo "$3" | perl -pe "s|.*$2(.*)|\1|")
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
echo -e "\n 缺少单独运行的参数 task xxx.js desi Test 1 3"
exit 1
fi
handle_task_before "$@"
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
local n=0
for i in ${runArr[@]}; do
array_run[n]=${array[$i - 1]}
let n++
done
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval $timeoutCmd $which_program $file_param $cmd
handle_task_after "$@"
}
## 运行其他命令
run_else() {
local file_param="$1"
handle_task_before "$@"
cd $dir_scripts
local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
shift
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
handle_task_after "$@"
}
## 命令检测
main() {
show_log="false"
while getopts ":l" opt; do
case $opt in
l)
show_log="true"
;;
esac
done
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
format_params() {
time_format="%Y-%m-%d %H:%M:%S"
timeoutCmd=""
if type timeout &>/dev/null; then
timeoutCmd="timeout -k 10s $command_timeout_time "
fi
time_format="%Y-%m-%d %H:%M:%S"
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
case $# in
1)
run_normal "$1"
;;
*)
case $2 in
now)
run_normal "$1" "$2"
;;
conc)
run_concurrent "$1" "$3" "$*"
;;
desi)
run_designated "$1" "$3" "$*"
;;
*)
run_else "$@"
;;
esac
;;
esac
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
elif [[ $# -eq 0 ]]; then
echo
usage
else
run_else "$@"
fi
}
main "$@"
show_log="false"
while getopts ":l" opt; do
case $opt in
l)
show_log="true"
;;
esac
done
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
format_params
define_program "$@"
handle_log_path "$@"
eval . $dir_shell/otask.sh "$@" "$cmd"
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
exit 0
+52 -35
View File
@@ -230,10 +230,10 @@ run_extra_shell() {
## 脚本用法
usage() {
echo -e "本脚本用法:"
echo -e "ql命令使用方法:"
echo -e "1. $cmd_update update # 更新并重启青龙"
echo -e "2. $cmd_update extra # 运行自定义脚本"
echo -e "3. $cmd_update raw <fileurl> # 更新单个脚本文件"
echo -e "3. $cmd_update raw <fileurl> # 更新单个脚本文件"
echo -e "4. $cmd_update repo <repourl> <path> <blacklist> <dependence> <branch> <extensions> # 更新单个仓库的脚本"
echo -e "5. $cmd_update rmlog <days> # 删除旧日志"
echo -e "6. $cmd_update bot # 启动tg-bot"
@@ -432,30 +432,48 @@ main() {
local p5=$5
local p6=$6
local p7=$7
local log_dir="${p1}"
make_dir "$dir_log/$log_dir"
local log_time=$(date "+%Y-%m-%d-%H-%M-%S")
local log_path="$dir_log/update/${log_time}_$p1.log"
local begin_time=$(date '+%Y-%m-%d %H:%M:%S')
local log_path="${log_dir}/${log_time}.log"
local file_path="$dir_log/$log_path"
cmd=">> $file_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
local time_format="%Y-%m-%d %H:%M:%S"
local time=$(date "+$time_format")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
local begin_time=$(format_time "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
if [[ -s $task_error_log_path ]]; then
eval cat $task_error_log_path $cmd
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
eval echo $cmd
fi
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
eval echo -e "请移除 -l 参数" $cmd
exit 1
fi
case $p1 in
update)
cmd=">> $log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
eval echo -e "## 开始执行... $begin_time\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
eval update_qinglong "$2" $cmd
;;
extra)
echo -e "## 开始执行... $begin_time\n" >>$log_path
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
run_extra_shell >>$log_path
eval run_extra_shell $cmd
;;
repo)
get_uniq_path "$p2" "$p6"
if [[ -n $p2 ]]; then
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
else
echo -e "命令输入错误...\n"
usage
eval echo -e "命令输入错误...\\\n" $cmd
eval usage $cmd
fi
;;
raw)
@@ -463,47 +481,46 @@ main() {
if [[ -n $p2 ]]; then
update_raw "$p2"
else
echo -e "命令输入错误...\n"
usage
eval echo -e "命令输入错误...\\\n" $cmd
eval usage $cmd
fi
;;
rmlog)
echo -e "## 开始执行... $begin_time\n" >>$log_path
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
. $dir_shell/rmlog.sh "$p2" >>$log_path
eval . $dir_shell/rmlog.sh "$p2" $cmd
;;
bot)
echo -e "## 开始执行... $begin_time\n" >>$log_path
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
. $dir_shell/bot.sh >>$log_path
eval . $dir_shell/bot.sh $cmd
;;
check)
echo -e "## 开始执行... $begin_time\n" >>$log_path
[[ -f $task_error_log_path ]] && cat $task_error_log_path >>$log_path
. $dir_shell/check.sh >>$log_path
eval . $dir_shell/check.sh $cmd
;;
resetlet)
echo -e "## 开始执行... $begin_time\n" >>$log_path
auth_value=$(cat $file_auth_user | jq '.retries =0' -c)
echo -e "重置登录错误次数成功 \n $auth_value" >>$log_path
echo "$auth_value" >$file_auth_user
echo -e "重置登录错误次数成功" $cmd
;;
resettfa)
echo -e "## 开始执行... $begin_time\n" >>$log_path
auth_value=$(cat $file_auth_user | jq '.twoFactorActivated =false' | jq '.twoFactorActived =false' -c)
echo -e "禁用两步验证成功 \n $auth_value" >>$log_path
echo "$auth_value" >$file_auth_user
eval echo -e "禁用两步验证成功" $cmd
;;
*)
echo -e "命令输入错误...\n"
usage
eval echo -e "命令输入错误...\\\n" $cmd
eval usage $cmd
;;
esac
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local diff_time=$(diff_time "%Y-%m-%d %H:%M:%S" "$begin_time" "$end_time")
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
echo -e "\n## 执行结束... $end_time 耗时 $diff_time" >>$log_path
cat $log_path
local etime=$(date "+$time_format")
local end_time=$(format_time "$time_format" "$etime")
local end_timestamp=$(format_timestamp "$time_format" "$etime")
local diff_time=$(($end_timestamp - $begin_timestamp))
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n          " $cmd
if [[ -f $file_path ]]; then
cat $file_path
fi
}
+1
View File
@@ -80,6 +80,7 @@ const Config = () => {
loading={loading}
extra={[
<TreeSelect
treeExpandAction="click"
className="config-select"
value={select}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+9 -4
View File
@@ -1,4 +1,4 @@
import React, { PureComponent, Fragment, useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
message,
@@ -388,6 +388,7 @@ const Crontab = () => {
const [cronViews, setCronViews] = useState<any[]>([]);
const [enabledCronViews, setEnabledCronViews] = useState<any[]>([]);
const [moreMenuActive, setMoreMenuActive] = useState(false);
const tableRef = useRef<any>();
const goToScriptManager = (record: any) => {
const cmd = record.command.split(' ') as string[];
@@ -874,12 +875,15 @@ const Crontab = () => {
sorter: {},
filters: {},
});
setTimeout(() => {
setTableScrollHeight(getTableScroll());
});
getCronViews();
}, []);
useEffect(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll());
}
}, [tableRef.current]);
const panelContent = (
<>
{selectedRowIds.length > 0 && (
@@ -939,6 +943,7 @@ const Crontab = () => {
</div>
)}
<Table
ref={tableRef}
columns={columns}
pagination={{
current: pageConf.page,
+3 -2
View File
@@ -7,6 +7,7 @@ import {
CheckCircleOutlined,
} from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-layout';
import { logEnded } from '@/utils';
enum CrontabStatus {
'running',
@@ -49,9 +50,9 @@ const CronLogModal = ({
const log = data as string;
setValue(log || '暂无日志');
setExecuting(
log && !log.includes('执行结束') && !log.includes('重启面板'),
log && !logEnded(log) && !log.includes('重启面板'),
);
if (log && !log.includes('执行结束') && !log.includes('重启面板')) {
if (log && !logEnded(log) && !log.includes('重启面板')) {
setTimeout(() => {
getCronLog();
}, 2000);
+5 -3
View File
@@ -168,6 +168,7 @@ const Dependence = () => {
const [logDependence, setLogDependence] = useState<any>();
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [type, setType] = useState('nodejs');
const tableRef = useRef<any>();
const getDependencies = () => {
setLoading(true);
@@ -368,10 +369,10 @@ const Dependence = () => {
}, [searchText, type]);
useEffect(() => {
setTimeout(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
});
}, []);
}
}, [tableRef.current]);
useEffect(() => {
if (logDependence) {
@@ -454,6 +455,7 @@ const Dependence = () => {
)}
<DndProvider backend={HTML5Backend}>
<Table
ref={tableRef}
columns={columns}
rowSelection={rowSelection}
pagination={false}
+45 -3
View File
@@ -9,6 +9,8 @@ import {
Typography,
Tooltip,
Input,
UploadProps,
Upload,
} from 'antd';
import {
EditOutlined,
@@ -16,6 +18,7 @@ import {
SyncOutlined,
CheckCircleOutlined,
StopOutlined,
UploadOutlined,
} from '@ant-design/icons';
import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
@@ -250,6 +253,8 @@ const Env = () => {
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
const [searchText, setSearchText] = useState('');
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
const [importLoading, setImportLoading] = useState(false);
const tableRef = useRef<any>();
const getEnvs = () => {
setLoading(true);
@@ -472,15 +477,42 @@ const Env = () => {
setSearchText(value.trim());
};
const uploadProps: UploadProps = {
accept: 'application/json',
beforeUpload: async (file) => {
const formData = new FormData();
formData.append('env', file);
setImportLoading(true);
try {
const { code, data } = await request.post(
`${config.apiPrefix}envs/upload`,
{
data: formData,
},
);
if (code === 200) {
message.success(`成功上传${data.length}个环境变量`);
getEnvs();
}
setImportLoading(false);
} catch (error: any) {
setImportLoading(false);
}
return false;
},
fileList: [],
};
useEffect(() => {
getEnvs();
}, [searchText]);
useEffect(() => {
setTimeout(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
});
}, []);
}
}, [tableRef.current]);
return (
<PageContainer
@@ -494,6 +526,15 @@ const Env = () => {
loading={loading}
onSearch={onSearch}
/>,
<Upload {...uploadProps}>
<Button
type="primary"
icon={<UploadOutlined />}
loading={importLoading}
>
</Button>
</Upload>,
<Button key="2" type="primary" onClick={() => addEnv()}>
</Button>,
@@ -547,6 +588,7 @@ const Env = () => {
)}
<DndProvider backend={HTML5Backend}>
<Table
ref={tableRef}
columns={columns}
rowSelection={rowSelection}
pagination={false}
+1
View File
@@ -185,6 +185,7 @@ const Log = () => {
isPhone
? [
<TreeSelect
treeExpandAction="click"
className="log-select"
value={select}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+11 -1
View File
@@ -7,6 +7,7 @@ import Editor from '@monaco-editor/react';
import SaveModal from './saveModal';
import SettingModal from './setting';
import { useTheme } from '@/utils/hooks';
import { logEnded } from '@/utils';
const { Option } = Select;
const LangMap: any = {
@@ -57,6 +58,11 @@ const EditModal = ({
if (node.key === selectedKey || !value) {
return;
}
if (node.type === 'directory') {
return;
}
const newMode = LangMap[value.slice(-3)] || '';
setCNode(node);
setLanguage(newMode);
@@ -123,7 +129,7 @@ const EditModal = ({
return;
}
if (_message.includes('执行结束')) {
if (logEnded(_message)) {
setTimeout(() => {
setIsRunning(false);
}, 300);
@@ -136,10 +142,13 @@ const EditModal = ({
}, [socketMessage]);
useEffect(() => {
setLog('');
if (currentNode) {
setCNode(currentNode);
setValue(content as string);
setSelectedKey(currentNode.key);
const newMode = LangMap[currentNode.title.slice(-3)] || '';
setLanguage(newMode);
}
}, [content, currentNode]);
@@ -150,6 +159,7 @@ const EditModal = ({
title={
<>
<TreeSelect
treeExpandAction="click"
style={{ marginRight: 8, width: 150 }}
value={selectedKey}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+5 -1
View File
@@ -82,7 +82,11 @@ const Script = () => {
const getDetail = (node: any) => {
request
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
.get(
`${config.apiPrefix}scripts/${encodeURIComponent(node.title)}?path=${
node.parent || ''
}`,
)
.then(({ code, data }) => {
if (code === 200) {
setValue(data);
+3 -5
View File
@@ -108,10 +108,9 @@ const CheckUpdate = ({ socketMessage }: any) => {
maskClosable: false,
closable: false,
okButtonProps: { disabled: true },
title: '更新日志',
title: '更新中...',
centered: true,
content: (
<div style={{ height: '60vh', overflowY: 'auto' }}>
<pre
style={{
wordBreak: 'break-all',
@@ -122,7 +121,6 @@ const CheckUpdate = ({ socketMessage }: any) => {
>
{value}
</pre>
</div>
),
});
};
@@ -145,7 +143,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
closable: updateFailed,
okButtonProps: { disabled: !updateFailed },
content: (
<div style={{ height: '60vh', overflowY: 'auto' }}>
<>
<pre
style={{
wordBreak: 'break-all',
@@ -157,7 +155,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
{newMessage}
</pre>
<div id="log-identifier" style={{ paddingBottom: 5 }}></div>
</div>
</>
),
});
+9 -4
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
message,
@@ -247,6 +247,7 @@ const Subscription = () => {
const [searchValue, setSearchValue] = useState('');
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [logSubscription, setLogSubscription] = useState<any>();
const tableRef = useRef<any>();
const runSubscription = (record: any, index: number) => {
Modal.confirm({
@@ -539,11 +540,14 @@ const Subscription = () => {
useEffect(() => {
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
setTimeout(() => {
setTableScrollHeight(getTableScroll());
});
}, []);
useEffect(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll());
}
}, [tableRef.current]);
return (
<PageContainer
className="ql-container-wrapper subscriptiontab-wrapper"
@@ -568,6 +572,7 @@ const Subscription = () => {
}}
>
<Table
ref={tableRef}
columns={columns}
pagination={{
current: currentPage,
+3 -2
View File
@@ -7,6 +7,7 @@ import {
CheckCircleOutlined,
} from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-layout';
import { logEnded } from '@/utils';
const SubscriptionLogModal = ({
subscription,
@@ -43,8 +44,8 @@ const SubscriptionLogModal = ({
) {
const log = data as string;
setValue(log || '暂无日志');
setExecuting(log && !log.includes('执行结束'));
if (log && !log.includes('执行结束')) {
setExecuting(log && !logEnded(log));
if (log && !logEnded(log)) {
setTimeout(() => {
getCronLog();
}, 2000);
+36 -1
View File
@@ -88,10 +88,12 @@ export default {
{ value: 'dingtalkBot', label: '钉钉机器人' },
{ value: 'weWorkBot', label: '企业微信机器人' },
{ value: 'weWorkApp', label: '企业微信应用' },
{ value: 'aibotk', label: '智能微秘书' },
{ value: 'iGot', label: 'IGot' },
{ value: 'pushPlus', label: 'PushPlus' },
{ value: 'chat', label: '群辉chat' },
{ value: 'email', label: '邮箱' },
{ value: 'lark', label: '飞书机器人' },
{ value: 'webhook', label: '自定义通知' },
{ value: 'closed', label: '已关闭' },
],
@@ -196,6 +198,28 @@ export default {
required: true,
},
],
aibotk: [
{
label: 'aibotkKey',
tip: '密钥key,智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql',
required: true,
},
{
label: 'aibotkType',
tip: '发送的目标,群组或者好友',
required: true,
placeholder: '请输入要发送的目标',
items: [
{ value: 'room', label: '群聊' },
{ value: 'contact', label: '好友' },
],
},
{
label: 'aibotkName',
tip: '要发送的用户昵称或群名,如果目标是群,需要填群名,如果目标是好友,需要填好友昵称',
required: true,
},
],
iGot: [
{
label: 'iGotPushKey',
@@ -214,6 +238,13 @@ export default {
tip: '一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)',
},
],
lark: [
{
label: 'larkKey',
tip: '飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973',
required: true,
},
],
email: [
{
label: 'emailService',
@@ -234,7 +265,11 @@ export default {
label: 'webhookContentType',
tip: '请求头Content-Type',
required: true,
items: [{ value: 'application/json' }, { value: 'multipart/form-data' }, { value: 'application/x-www-form-urlencoded' }],
items: [
{ value: 'application/json' },
{ value: 'multipart/form-data' },
{ value: 'application/x-www-form-urlencoded' },
],
},
{
label: 'webhookUrl',
+1
View File
@@ -0,0 +1 @@
export const LOG_END_SYMBOL = '\n          ';
+12 -5
View File
@@ -1,3 +1,5 @@
import { LOG_END_SYMBOL } from './const';
export default function browserType() {
// 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
const ua = navigator.userAgent.toLowerCase();
@@ -150,9 +152,9 @@ export default function browserType() {
shell === 'none'
? {}
: {
shell, // wechat qq uc 360 2345 sougou liebao maxthon
shellVs,
},
shell, // wechat qq uc 360 2345 sougou liebao maxthon
shellVs,
},
);
console.log(
@@ -188,8 +190,8 @@ export function getTableScroll({
if (id) {
tHeader = document.getElementById(id)
? document
.getElementById(id)!
.getElementsByClassName('ant-table-thead')[0]
.getElementById(id)!
.getElementsByClassName('ant-table-thead')[0]
: null;
} else {
tHeader = document.querySelector('.ant-table-wrapper');
@@ -273,3 +275,8 @@ export function depthFirstSearch<
return c;
}
export function logEnded(log: string): boolean {
const endTips = [LOG_END_SYMBOL, '执行结束'];
return endTips.some((x) => log.includes(x));
}
+8 -5
View File
@@ -1,6 +1,9 @@
export const version = '2.14.7';
export const changeLogLink = 'https://t.me/jiao_long/334';
export const changeLog = `2.14.7 版本说明
1. 修复task_before无法获取任务执行参数
2. 修改资源判定逻辑
export const version = '2.14.9';
export const changeLogLink = 'https://t.me/jiao_long/336';
export const changeLog = `2.14.9 版本说明
1. 通知支持飞书和智能微秘书,感谢 https://github.com/leochen-g
2. webhook通知body支持嵌套json
3. 修复shell获取磁盘占用
4. 修复定时任务移动端滚动
5. 其他bug修复
`;
+3
View File
@@ -7,6 +7,9 @@
"./src/types",
"./node_modules/celebrate/lib/index.d.ts"
],
"paths": {
"@/*": ["./back/*"],
},
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,