mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 10:40:52 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78f643c50f | ||
|
|
2db4f1ba87 | ||
|
|
5741dbb78c | ||
|
|
88cf671465 | ||
|
|
29f082dea2 | ||
|
|
9a55968ed6 | ||
|
|
6ec6e25855 | ||
|
|
af48bc378b | ||
|
|
3f5ae4bcb9 | ||
|
|
c6624e9001 | ||
|
|
5530ce76e3 | ||
|
|
654b51e476 | ||
|
|
6428ac3624 | ||
|
|
c36450f436 | ||
|
|
dcea231249 | ||
|
|
f236119bd6 | ||
|
|
cb870fad85 | ||
|
|
44e2caedfc | ||
|
|
c0b5192296 | ||
|
|
ba43882c0b | ||
|
|
dfc706e16d | ||
|
|
25b03d4345 | ||
|
|
4e8f36d9a4 | ||
|
|
8f4d67ffa7 | ||
|
|
ad47277149 | ||
|
|
98c2a37ab6 | ||
|
|
f2fea47336 | ||
|
|
e274d3e2f9 | ||
|
|
36f4c3c02c | ||
|
|
ae891f8e55 | ||
|
|
c874dc9705 | ||
|
|
23bfbeb995 | ||
|
|
a75df03243 | ||
|
|
e44a2b5995 | ||
|
|
f23e7a9e49 | ||
|
|
e08f0cd308 | ||
|
|
1ebcfd8001 | ||
|
|
5168b044a3 | ||
|
|
931638b687 | ||
|
|
4d9bbcfd3f | ||
|
|
b0e3b26369 | ||
|
|
1e55f4065d | ||
|
|
518e8903a3 | ||
|
|
6e56a55f6f | ||
|
|
967071ad4e | ||
|
|
d23fcfaa5a | ||
|
|
a2f2306430 | ||
|
|
144b543091 | ||
|
|
e4ad8f512e | ||
|
|
591a5006da | ||
|
|
281ed6cfba | ||
|
|
7354a61184 | ||
|
|
04037f29e5 | ||
|
|
6b124bad89 | ||
|
|
54d99a4954 | ||
|
|
9947594366 | ||
|
|
d7ff934336 | ||
|
|
dc27856441 | ||
|
|
bd28682769 | ||
|
|
2060e62da4 | ||
|
|
70eac1e747 | ||
|
|
6f5a5fe066 | ||
|
|
8d24949202 | ||
|
|
f7b6237355 | ||
|
|
daf6f94c51 | ||
|
|
687d16e21b | ||
|
|
70571a9d34 | ||
|
|
4da8d8fe1e | ||
|
|
952cdc0e3f | ||
|
|
2de189d189 | ||
|
|
3edc6e83d7 |
@@ -0,0 +1,42 @@
|
||||
name: "\U0001F41E Bug report"
|
||||
description: Create a report to help us improve
|
||||
body:
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Qinglong version
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: |
|
||||
What do we need to do after opening your repro in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.
|
||||
placeholder: Steps to reproduce
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What is expected?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actually-happening
|
||||
attributes:
|
||||
label: What is actually happening?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info
|
||||
description: Output of `npx envinfo --system --binaries --browsers`
|
||||
render: shell
|
||||
placeholder: System, Binaries, Browsers
|
||||
- type: textarea
|
||||
id: additional-comments
|
||||
attributes:
|
||||
label: Any additional comments?
|
||||
description: e.g. some background/context of how you ran into this bug.
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Telegram Chat
|
||||
url: https://t.me/jiao_long
|
||||
about: Ask questions and discuss with other Qinglong users in real time.
|
||||
- name: Questions & Discussions
|
||||
url: https://github.com/whyour/qinglong/discussions/new?category=q-a
|
||||
about: Use GitHub discussions for message-board style questions and discussions.
|
||||
@@ -0,0 +1,40 @@
|
||||
name: "\U0001F680 New feature proposal"
|
||||
description: Suggest an idea for this project
|
||||
labels: [":sparkles: feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in the project and taking the time to fill out this feature report!
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Clear and concise description of the problem
|
||||
description: "Explain your use case, context, and rationale behind this feature request. More importantly, what is the **end user experience** you are trying to build that led to the need for this feature?"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: suggested-solution
|
||||
attributes:
|
||||
label: Suggested solution
|
||||
description: "In module [xy] we could provide following implementation..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternative
|
||||
attributes:
|
||||
label: Alternative
|
||||
description: Clear and concise description of any alternative solutions or features you've considered.
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Any other context or screenshots about the feature request here.
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Validations
|
||||
description: Before submitting the issue, please make sure you do the following
|
||||
options:
|
||||
- label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
|
||||
required: true
|
||||
@@ -0,0 +1,31 @@
|
||||
## PR Type
|
||||
What kind of change does this PR introduce?
|
||||
|
||||
<!-- Please check the one that applies to this PR using "x". -->
|
||||
|
||||
- [ ] Bugfix
|
||||
- [ ] Feature
|
||||
- [ ] Code style update (formatting, local variables)
|
||||
- [ ] Refactoring (no functional changes, no api changes)
|
||||
- [ ] Other... Please describe:
|
||||
|
||||
|
||||
## What is the current behavior?
|
||||
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
||||
|
||||
Issue Number: N/A
|
||||
|
||||
|
||||
## What is the new behavior?
|
||||
|
||||
|
||||
## Does this PR introduce a breaking change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
|
||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
|
||||
|
||||
## Other information
|
||||
@@ -0,0 +1,7 @@
|
||||
# Comment to be posted to on PRs from first time contributors in your repository
|
||||
newPRWelcomeComment: |
|
||||
💖 Thanks for opening this pull request! 💖
|
||||
Please be patient and we will get back to you as soon as we can.
|
||||
# Comment to be posted to on pull requests merged by a first time user
|
||||
firstPRMergeComment: >
|
||||
Congrats on merging your first pull request! 🎉🎉🎉
|
||||
@@ -1,20 +1,15 @@
|
||||
import { defineConfig } from 'umi';
|
||||
import { defineConfig } from '@umijs/max';
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
|
||||
export default defineConfig({
|
||||
hash: true,
|
||||
layout: false,
|
||||
antd: {},
|
||||
outputPath: 'static/dist',
|
||||
nodeModulesTransform: {
|
||||
type: 'none',
|
||||
fastRefresh: true,
|
||||
favicons: ['/images/favicon.svg'],
|
||||
mfsu: {
|
||||
strategy: 'eager',
|
||||
},
|
||||
fastRefresh: {},
|
||||
esbuild: {},
|
||||
webpack5: {},
|
||||
dynamicImport: {
|
||||
loading: '@/components/pageLoading',
|
||||
},
|
||||
favicon: '/images/favicon.svg',
|
||||
proxy: {
|
||||
'/api/public': {
|
||||
target: 'http://127.0.0.1:5400/',
|
||||
@@ -26,7 +21,7 @@ export default defineConfig({
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
chainWebpack: (config) => {
|
||||
chainWebpack: ((config: any) => {
|
||||
config.plugin('compression-webpack-plugin').use(
|
||||
new CompressionPlugin({
|
||||
algorithm: 'gzip',
|
||||
@@ -35,13 +30,13 @@ export default defineConfig({
|
||||
minRatio: 0.6,
|
||||
}),
|
||||
);
|
||||
},
|
||||
}) as any,
|
||||
externals: {
|
||||
react: 'window.React',
|
||||
'react-dom': 'window.ReactDOM',
|
||||
},
|
||||
scripts: [
|
||||
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
||||
'https://gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js',
|
||||
headScripts: [
|
||||
'https://gw.alipayobjects.com/os/lib/react/18.2.0/umd/react.production.min.js',
|
||||
'https://gw.alipayobjects.com/os/lib/react-dom/18.2.0/umd/react-dom.production.min.js',
|
||||
],
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/whyour/qinglong">
|
||||
<img width="150" src="https://z3.ax1x.com/2021/11/18/I7MpAe.png">
|
||||
<img width="150" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -183,7 +183,7 @@ task <file_path> desi <env_name> <account_number>
|
||||
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
||||
- [Ant Design](https://ant.design)
|
||||
- [Ant Design Pro](https://pro.ant.design/)
|
||||
- [Umijs3.0](https://umijs.org)
|
||||
- [Umijs](https://umijs.org)
|
||||
- [darkreader](https://github.com/darkreader/darkreader)
|
||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/whyour/qinglong">
|
||||
<img width="150" src="https://z3.ax1x.com/2021/11/18/I7MpAe.png">
|
||||
<img width="150" src="https://user-images.githubusercontent.com/22700758/191449379-f9f56204-0e31-4a16-be5a-331f52696a73.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -183,7 +183,7 @@ task <file_path> desi <env_name> <account_number>
|
||||
- [crontab-ui](https://github.com/alseambusher/crontab-ui)
|
||||
- [Ant Design](https://ant.design)
|
||||
- [Ant Design Pro](https://pro.ant.design/)
|
||||
- [Umijs3.0](https://umijs.org)
|
||||
- [Umijs](https://umijs.org)
|
||||
- [darkreader](https://github.com/darkreader/darkreader)
|
||||
- [admin-server](https://github.com/sunpu007/admin-server)
|
||||
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ export default (app: Router) => {
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
sorts: Joi.array().optional(),
|
||||
sorts: Joi.array().optional().allow(null),
|
||||
filters: Joi.array().optional(),
|
||||
}),
|
||||
}),
|
||||
@@ -49,7 +49,7 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
id: Joi.number().required(),
|
||||
sorts: Joi.array().optional(),
|
||||
sorts: Joi.array().optional().allow(null),
|
||||
filters: Joi.array().optional(),
|
||||
}),
|
||||
}),
|
||||
|
||||
+32
-1
@@ -3,8 +3,9 @@ import { Container } from 'typedi';
|
||||
import { Logger } from 'winston';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import { getFileContentByName, readDirs } from '../config/util';
|
||||
import { emptyDir, getFileContentByName, readDirs } from '../config/util';
|
||||
import { join } from 'path';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
const route = Router();
|
||||
const blacklist = ['.tmp'];
|
||||
|
||||
@@ -45,4 +46,34 @@ export default (app: Router) => {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().allow(''),
|
||||
type: Joi.string().optional()
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
let { filename, path, type } = req.body as {
|
||||
filename: string;
|
||||
path: string;
|
||||
type: string;
|
||||
};
|
||||
const filePath = join(config.logPath, path, filename);
|
||||
if (type === 'directory') {
|
||||
emptyDir(filePath);
|
||||
} else {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
+27
-10
@@ -4,6 +4,7 @@ import {
|
||||
readDirs,
|
||||
getLastModifyFilePath,
|
||||
readDir,
|
||||
emptyDir,
|
||||
} from '../config/util';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
@@ -77,12 +78,14 @@ export default (app: Router) => {
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
let { filename, path, content, originFilename } = req.body as {
|
||||
filename: string;
|
||||
path: string;
|
||||
content: string;
|
||||
originFilename: string;
|
||||
};
|
||||
let { filename, path, content, originFilename, directory } =
|
||||
req.body as {
|
||||
filename: string;
|
||||
path: string;
|
||||
content: string;
|
||||
originFilename: string;
|
||||
directory: string;
|
||||
};
|
||||
|
||||
if (!path) {
|
||||
path = config.scriptPath;
|
||||
@@ -105,11 +108,19 @@ export default (app: Router) => {
|
||||
return res.send({ code: 200 });
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
fs.mkdirSync(join(path, directory), { recursive: true });
|
||||
return res.send({ code: 200 });
|
||||
}
|
||||
|
||||
if (!originFilename) {
|
||||
originFilename = filename;
|
||||
}
|
||||
const originFilePath = `${path}${originFilename.replace(/\//g, '')}`;
|
||||
const filePath = `${path}${filename.replace(/\//g, '')}`;
|
||||
const originFilePath = join(
|
||||
path,
|
||||
`${originFilename.replace(/\//g, '')}`,
|
||||
);
|
||||
const filePath = join(path, `${filename.replace(/\//g, '')}`);
|
||||
if (fs.existsSync(originFilePath)) {
|
||||
fs.copyFileSync(
|
||||
originFilePath,
|
||||
@@ -159,17 +170,23 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
filename: Joi.string().required(),
|
||||
path: Joi.string().allow(''),
|
||||
type: Joi.string().optional()
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
let { filename, path } = req.body as {
|
||||
let { filename, path, type } = req.body as {
|
||||
filename: string;
|
||||
path: string;
|
||||
type: string;
|
||||
};
|
||||
const filePath = join(config.scriptPath, path, filename);
|
||||
fs.unlinkSync(filePath);
|
||||
if (type === 'directory') {
|
||||
emptyDir(filePath);
|
||||
} else {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ async function startServer() {
|
||||
|
||||
const server = app
|
||||
.listen(config.port, () => {
|
||||
Logger.debug(`✌️ Back server launched on port ${config.port}`);
|
||||
Logger.debug(`✌️ 后端服务启动成功!`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
@@ -11,7 +11,7 @@ if (!process.env.QL_DIR) {
|
||||
if (qlHomePath.endsWith('/static/')) {
|
||||
qlHomePath = path.join(qlHomePath, '../');
|
||||
}
|
||||
process.env.QL_DIR = qlHomePath;
|
||||
process.env.QL_DIR = qlHomePath.replace(/\/$/g, '');
|
||||
}
|
||||
|
||||
const lastVersionFile = `http://qn.whyour.cn/version.ts?v=${Date.now()}`;
|
||||
|
||||
+82
-1
@@ -3,6 +3,7 @@ import * as path from 'path';
|
||||
import got from 'got';
|
||||
import iconv from 'iconv-lite';
|
||||
import { exec } from 'child_process';
|
||||
import FormData from 'form-data';
|
||||
|
||||
export function getFileContentByName(fileName: string) {
|
||||
if (fs.existsSync(fileName)) {
|
||||
@@ -301,7 +302,6 @@ export function readDirs(
|
||||
title: file,
|
||||
key,
|
||||
type: 'directory',
|
||||
disabled: true,
|
||||
parent: relativePath,
|
||||
children: readDirs(subPath, baseDir).sort(
|
||||
(a: any, b: any) =>
|
||||
@@ -312,6 +312,7 @@ export function readDirs(
|
||||
return {
|
||||
title: file,
|
||||
type: 'file',
|
||||
isLeaf: true,
|
||||
key,
|
||||
parent: relativePath,
|
||||
};
|
||||
@@ -344,6 +345,20 @@ export function readDir(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function emptyDir(path: string) {
|
||||
const files = fs.readdirSync(path);
|
||||
files.forEach((file) => {
|
||||
const filePath = `${path}/${file}`;
|
||||
const stats = fs.statSync(filePath);
|
||||
if (stats.isDirectory()) {
|
||||
emptyDir(filePath);
|
||||
} else {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(path);
|
||||
}
|
||||
|
||||
export function promiseExec(command: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
@@ -355,3 +370,69 @@ export function promiseExec(command: string): Promise<string> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function parseHeaders(headers: string) {
|
||||
if (!headers) return {};
|
||||
|
||||
const parsed: any = {};
|
||||
let key;
|
||||
let val;
|
||||
let i;
|
||||
|
||||
headers &&
|
||||
headers.split('\n').forEach(function parser(line) {
|
||||
i = line.indexOf(':');
|
||||
key = line.substring(0, i).trim().toLowerCase();
|
||||
val = line.substring(i + 1).trim();
|
||||
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
|
||||
});
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function parseBody(
|
||||
body: string,
|
||||
contentType:
|
||||
| 'application/json'
|
||||
| 'multipart/form-data'
|
||||
| 'application/x-www-form-urlencoded',
|
||||
) {
|
||||
if (!body) return '';
|
||||
|
||||
const parsed: any = {};
|
||||
let key;
|
||||
let val;
|
||||
let i;
|
||||
|
||||
body &&
|
||||
body.split('\n').forEach(function parser(line) {
|
||||
i = line.indexOf(':');
|
||||
key = line.substring(0, i).trim().toLowerCase();
|
||||
val = line.substring(i + 1).trim();
|
||||
|
||||
if (!key || parsed[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
parsed[key] = val;
|
||||
});
|
||||
|
||||
switch (contentType) {
|
||||
case 'multipart/form-data':
|
||||
return Object.keys(parsed).reduce((p, c) => {
|
||||
p.append(c, parsed[c]);
|
||||
return p;
|
||||
}, new FormData());
|
||||
case 'application/x-www-form-urlencoded':
|
||||
return Object.keys(parsed).reduce((p, c) => {
|
||||
return p ? `${p}&${c}=${parsed[c]}` : `${c}=${parsed[c]}`;
|
||||
});
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
+20
-1
@@ -1,9 +1,12 @@
|
||||
import { IncomingHttpHeaders } from 'http';
|
||||
|
||||
export enum NotificationMode {
|
||||
'gotify' = 'gotify',
|
||||
'goCqHttpBot' = 'goCqHttpBot',
|
||||
'serverChan' = 'serverChan',
|
||||
'pushDeer' = 'pushDeer',
|
||||
'bark' = 'bark',
|
||||
'chat' = 'chat',
|
||||
'telegramBot' = 'telegramBot',
|
||||
'dingtalkBot' = 'dingtalkBot',
|
||||
'weWorkBot' = 'weWorkBot',
|
||||
@@ -11,6 +14,7 @@ export enum NotificationMode {
|
||||
'iGot' = 'iGot',
|
||||
'pushPlus' = 'pushPlus',
|
||||
'email' = 'email',
|
||||
'webhook' = 'webhook',
|
||||
}
|
||||
|
||||
abstract class NotificationBaseInfo {
|
||||
@@ -37,6 +41,11 @@ export class PushDeerNotification extends NotificationBaseInfo {
|
||||
public pushDeerKey = '';
|
||||
}
|
||||
|
||||
export class ChatNotification extends NotificationBaseInfo {
|
||||
public chatUrl = '';
|
||||
public chatToken = '';
|
||||
}
|
||||
|
||||
export class BarkNotification extends NotificationBaseInfo {
|
||||
public barkPush = '';
|
||||
public barkIcon = 'http://qn.whyour.cn/logo.png';
|
||||
@@ -81,11 +90,20 @@ export class EmailNotification extends NotificationBaseInfo {
|
||||
public emailPass: string = '';
|
||||
}
|
||||
|
||||
export class WebhookNotification extends NotificationBaseInfo {
|
||||
public webhookHeaders: string = '';
|
||||
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';
|
||||
}
|
||||
|
||||
export interface NotificationInfo
|
||||
extends GoCqHttpBotNotification,
|
||||
GotifyNotification,
|
||||
ServerChanNotification,
|
||||
PushDeerNotification,
|
||||
ChatNotification,
|
||||
BarkNotification,
|
||||
TelegramBotNotification,
|
||||
DingtalkBotNotification,
|
||||
@@ -93,4 +111,5 @@ export interface NotificationInfo
|
||||
WeWorkAppNotification,
|
||||
IGotNotification,
|
||||
PushPlusNotification,
|
||||
EmailNotification {}
|
||||
EmailNotification,
|
||||
WebhookNotification {}
|
||||
|
||||
+19
-7
@@ -3,7 +3,7 @@ import bodyParser from 'body-parser';
|
||||
import cors from 'cors';
|
||||
import routes from '../api';
|
||||
import config from '../config';
|
||||
import jwt from 'express-jwt';
|
||||
import jwt, { UnauthorizedError } from 'express-jwt';
|
||||
import fs from 'fs';
|
||||
import { getPlatform, getToken } from '../config/util';
|
||||
import Container from 'typedi';
|
||||
@@ -75,22 +75,24 @@ export default ({ app }: { app: Application }) => {
|
||||
if (
|
||||
!headerToken &&
|
||||
originPath &&
|
||||
config.apiWhiteList.includes(originPath) &&
|
||||
originPath !== '/api/crons/status'
|
||||
config.apiWhiteList.includes(originPath)
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
||||
if (data) {
|
||||
if (data && headerToken) {
|
||||
const { token = '', tokens = {} } = JSON.parse(data);
|
||||
if (headerToken === token || tokens[req.platform] === headerToken) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
const err: any = new Error('UnauthorizedError');
|
||||
err.status = 401;
|
||||
const errorCode = headerToken ? 'invalid_token' : 'credentials_required';
|
||||
const errorMessage = headerToken
|
||||
? 'jwt malformed'
|
||||
: 'No authorization token was found';
|
||||
const err = new UnauthorizedError(errorCode, { message: errorMessage });
|
||||
next(err);
|
||||
});
|
||||
|
||||
@@ -168,7 +170,17 @@ export default ({ app }: { app: Application }) => {
|
||||
},
|
||||
);
|
||||
|
||||
app.use(Sentry.Handlers.errorHandler());
|
||||
app.use(
|
||||
Sentry.Handlers.errorHandler({
|
||||
shouldHandleError(error) {
|
||||
// 排除 SequelizeUniqueConstraintError / NotFound
|
||||
return (
|
||||
!['SequelizeUniqueConstraintError'].includes(error.name) ||
|
||||
!['Not Found'].includes(error.message)
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
app.use(
|
||||
(
|
||||
|
||||
@@ -13,18 +13,17 @@ export default async () => {
|
||||
|
||||
// 生成内置token
|
||||
let tokenCommand = `ts-node-transpile-only ${config.rootPath}/back/token.ts`;
|
||||
const tokenFile = `${config.rootPath}/static/build/token.js`;
|
||||
const tokenFile = `${config.rootPath}static/build/token.js`;
|
||||
if (await fileExist(tokenFile)) {
|
||||
tokenCommand = `node ${tokenFile}`;
|
||||
}
|
||||
const cron = {
|
||||
id: 'token',
|
||||
id: NaN,
|
||||
name: '生成token',
|
||||
command: tokenCommand,
|
||||
};
|
||||
scheduleService.createIntervalTask(cron as any, {
|
||||
scheduleService.createIntervalTask(cron, {
|
||||
days: 28,
|
||||
runImmediately: true,
|
||||
});
|
||||
|
||||
// 运行删除日志任务
|
||||
@@ -37,7 +36,6 @@ export default async () => {
|
||||
};
|
||||
scheduleService.createIntervalTask(cron, {
|
||||
days: data.info.frequency,
|
||||
runImmediately: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,23 @@ import { Application } from 'express';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import * as Tracing from '@sentry/tracing';
|
||||
import Logger from './logger';
|
||||
import config from '../config';
|
||||
import fs from 'fs';
|
||||
|
||||
export default ({ expressApp }: { expressApp: Application }) => {
|
||||
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
||||
|
||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
||||
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
||||
integrations: [
|
||||
new Sentry.Integrations.Http({ tracing: true }),
|
||||
new Tracing.Integrations.Express({ app: expressApp }),
|
||||
],
|
||||
|
||||
tracesSampleRate: 1.0,
|
||||
tracesSampleRate: 0.1,
|
||||
release: currentVersion,
|
||||
});
|
||||
|
||||
expressApp.use(Sentry.Handlers.requestHandler());
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ app
|
||||
await require('./loaders/sentry').default({ expressApp: app });
|
||||
await require('./loaders/db').default();
|
||||
|
||||
Logger.debug(`✌️ Back server launched on port ${config.publicPort}`);
|
||||
Logger.debug(`✌️ 公共服务启动成功!`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
+4
-7
@@ -18,14 +18,15 @@ const run = async () => {
|
||||
_schedule &&
|
||||
_schedule.length > 5 &&
|
||||
task.status !== CrontabStatus.disabled &&
|
||||
!task.isDisabled
|
||||
!task.isDisabled &&
|
||||
task.schedule
|
||||
) {
|
||||
schedule.scheduleJob(task.schedule, function () {
|
||||
let command = task.command as string;
|
||||
if (!command.includes('task ') && !command.includes('ql ')) {
|
||||
command = `task ${command}`;
|
||||
}
|
||||
exec(command);
|
||||
exec(`ID=${task.id} ${command}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -43,11 +44,7 @@ app
|
||||
await require('./loaders/db').default();
|
||||
|
||||
await run();
|
||||
Logger.info(`
|
||||
################################################
|
||||
🛡️ Schedule listening on port: ${config.cronPort} 🛡️
|
||||
################################################
|
||||
`);
|
||||
Logger.debug('定时任务服务启动成功!');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
+64
-35
@@ -115,18 +115,25 @@ export default class CronService {
|
||||
|
||||
private formatViewQuery(query: any, viewQuery: any) {
|
||||
if (viewQuery.filters && viewQuery.filters.length > 0) {
|
||||
if (!query[Op.and]) {
|
||||
query[Op.and] = [];
|
||||
}
|
||||
for (const col of viewQuery.filters) {
|
||||
const { property, value, operation } = col;
|
||||
let q: any = {};
|
||||
let operate2 = null;
|
||||
let operate = null;
|
||||
switch (operation) {
|
||||
case 'Reg':
|
||||
operate = Op.like;
|
||||
operate2 = Op.or;
|
||||
break;
|
||||
case 'NotReg':
|
||||
operate = Op.notLike;
|
||||
operate2 = Op.and;
|
||||
break;
|
||||
case 'In':
|
||||
query[Op.or] = [
|
||||
q[Op.or] = [
|
||||
{
|
||||
[property]: value,
|
||||
},
|
||||
@@ -136,7 +143,7 @@ export default class CronService {
|
||||
];
|
||||
break;
|
||||
case 'Nin':
|
||||
query[Op.and] = [
|
||||
q[Op.and] = [
|
||||
{
|
||||
[property]: {
|
||||
[Op.notIn]: value,
|
||||
@@ -150,20 +157,25 @@ export default class CronService {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (operate) {
|
||||
query[property] = {
|
||||
[Op.or]: [
|
||||
if (operate && operate2) {
|
||||
q[property] = {
|
||||
[operate2]: [
|
||||
{ [operate]: `%${value}%` },
|
||||
{ [operate]: `%${encodeURIComponent(value)}%` },
|
||||
],
|
||||
};
|
||||
}
|
||||
query[Op.and].push(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private formatSearchText(query: any, searchText: string | undefined) {
|
||||
if (searchText) {
|
||||
if (!query[Op.and]) {
|
||||
query[Op.and] = [];
|
||||
}
|
||||
let q: any = {};
|
||||
const textArray = searchText.split(':');
|
||||
switch (textArray[0]) {
|
||||
case 'name':
|
||||
@@ -171,13 +183,11 @@ export default class CronService {
|
||||
case 'schedule':
|
||||
case 'label':
|
||||
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
|
||||
query = {
|
||||
[column]: {
|
||||
[Op.or]: [
|
||||
{ [Op.like]: `%${textArray[1]}%` },
|
||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
||||
],
|
||||
},
|
||||
q[column] = {
|
||||
[Op.or]: [
|
||||
{ [Op.like]: `%${textArray[1]}%` },
|
||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
||||
],
|
||||
};
|
||||
break;
|
||||
default:
|
||||
@@ -187,24 +197,39 @@ export default class CronService {
|
||||
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
||||
],
|
||||
};
|
||||
query = {
|
||||
[Op.or]: [
|
||||
{
|
||||
name: reg,
|
||||
},
|
||||
{
|
||||
command: reg,
|
||||
},
|
||||
{
|
||||
schedule: reg,
|
||||
},
|
||||
{
|
||||
labels: reg,
|
||||
},
|
||||
],
|
||||
};
|
||||
q[Op.or] = [
|
||||
{
|
||||
name: reg,
|
||||
},
|
||||
{
|
||||
command: reg,
|
||||
},
|
||||
{
|
||||
schedule: reg,
|
||||
},
|
||||
{
|
||||
labels: reg,
|
||||
},
|
||||
];
|
||||
break;
|
||||
}
|
||||
query[Op.and].push(q);
|
||||
}
|
||||
}
|
||||
|
||||
private formatFilterQuery(query: any, filterQuery: any) {
|
||||
if (filterQuery) {
|
||||
if (!query[Op.and]) {
|
||||
query[Op.and] = [];
|
||||
}
|
||||
const filterKeys: any = Object.keys(filterQuery);
|
||||
for (const key of filterKeys) {
|
||||
let q: any = {};
|
||||
if (filterKeys[key]) {
|
||||
q[key] = filterKeys[key];
|
||||
}
|
||||
query[Op.and].push(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,16 +245,16 @@ export default class CronService {
|
||||
searchValue: string;
|
||||
page: string;
|
||||
size: string;
|
||||
sortField: string;
|
||||
sortType: string;
|
||||
sorter: string;
|
||||
filters: string;
|
||||
queryString: string;
|
||||
}): Promise<{ data: Crontab[]; total: number }> {
|
||||
const searchText = params?.searchValue;
|
||||
const page = Number(params?.page || '0');
|
||||
const size = Number(params?.size || '0');
|
||||
const sortField = params?.sortField || '';
|
||||
const sortType = params?.sortType || '';
|
||||
const viewQuery = JSON.parse(params?.queryString || '{}');
|
||||
const filterQuery = JSON.parse(params?.filters || '{}');
|
||||
const sorterQuery = JSON.parse(params?.sorter || '{}');
|
||||
|
||||
let query: any = {};
|
||||
let order = [
|
||||
@@ -241,10 +266,14 @@ export default class CronService {
|
||||
|
||||
this.formatViewQuery(query, viewQuery);
|
||||
this.formatSearchText(query, searchText);
|
||||
this.formatFilterQuery(query, filterQuery);
|
||||
this.formatViewSort(order, viewQuery);
|
||||
|
||||
if (sortType && sortField) {
|
||||
order.unshift([sortField, sortType]);
|
||||
if (sorterQuery) {
|
||||
const { field, type } = sorterQuery;
|
||||
if (field && type) {
|
||||
order.unshift([field, type]);
|
||||
}
|
||||
}
|
||||
let condition: any = {
|
||||
where: query,
|
||||
@@ -383,7 +412,7 @@ export default class CronService {
|
||||
cmdStr = `${cmdStr} now`;
|
||||
}
|
||||
|
||||
const cp = spawn(cmdStr, { shell: '/bin/bash' });
|
||||
const cp = spawn(`ID=${id} ${cmdStr}`, { shell: '/bin/bash' });
|
||||
|
||||
await CrontabModel.update(
|
||||
{ status: CrontabStatus.running, pid: cp.pid },
|
||||
|
||||
@@ -125,8 +125,11 @@ export default class DependenceService {
|
||||
return docs;
|
||||
}
|
||||
|
||||
private async find(query: any, sort?: any): Promise<Dependence[]> {
|
||||
const docs = await DependenceModel.findAll({ where: { ...query } });
|
||||
private async find(query: any, sort: any = []): Promise<Dependence[]> {
|
||||
const docs = await DependenceModel.findAll({
|
||||
where: { ...query },
|
||||
order: [...sort, ['createdAt', 'DESC']],
|
||||
});
|
||||
return docs;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,11 @@ export default class EnvService {
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await this.find(condition, [['position', 'DESC']]);
|
||||
const result = await this.find(condition, [
|
||||
['status', 'ASC'],
|
||||
['position', 'DESC'],
|
||||
['createdAt', 'DESC'],
|
||||
]);
|
||||
return result as any;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
+67
-4
@@ -6,6 +6,7 @@ import got from 'got';
|
||||
import nodemailer from 'nodemailer';
|
||||
import crypto from 'crypto';
|
||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
||||
import { parseBody, parseHeaders } from '../config/util';
|
||||
|
||||
@Service()
|
||||
export default class NotificationService {
|
||||
@@ -17,6 +18,7 @@ export default class NotificationService {
|
||||
['goCqHttpBot', this.goCqHttpBot],
|
||||
['serverChan', this.serverChan],
|
||||
['pushDeer', this.pushDeer],
|
||||
['chat', this.chat],
|
||||
['bark', this.bark],
|
||||
['telegramBot', this.telegramBot],
|
||||
['dingtalkBot', this.dingtalkBot],
|
||||
@@ -25,6 +27,7 @@ export default class NotificationService {
|
||||
['iGot', this.iGot],
|
||||
['pushPlus', this.pushPlus],
|
||||
['email', this.email],
|
||||
['webhook', this.webhook],
|
||||
]);
|
||||
|
||||
private timeout = 10000;
|
||||
@@ -32,7 +35,7 @@ export default class NotificationService {
|
||||
private content = '';
|
||||
private params!: Omit<NotificationInfo, 'type'>;
|
||||
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
constructor(@Inject('logger') private logger: winston.Logger) { }
|
||||
|
||||
public async notify(
|
||||
title: string,
|
||||
@@ -135,6 +138,20 @@ export default class NotificationService {
|
||||
);
|
||||
}
|
||||
|
||||
private async chat() {
|
||||
const { chatUrl, chatToken } = this.params;
|
||||
const url = `${chatUrl}${chatToken}`;
|
||||
const res: any = await got
|
||||
.post(url, {
|
||||
timeout: this.timeout,
|
||||
retry: 0,
|
||||
body: `payload={"text":"${this.title}\n${this.content}"}`,
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
.json();
|
||||
return res.success;
|
||||
}
|
||||
|
||||
private async bark() {
|
||||
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
||||
if (!barkPush.startsWith('http') && !barkPush.startsWith('https')) {
|
||||
@@ -165,9 +182,8 @@ 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 = {
|
||||
@@ -368,4 +384,51 @@ export default class NotificationService {
|
||||
|
||||
return !!info.messageId;
|
||||
}
|
||||
|
||||
private async webhook() {
|
||||
const { webhookUrl, webhookBody, webhookHeaders, webhookMethod, webhookContentType } =
|
||||
this.params;
|
||||
|
||||
const { formatBody, formatUrl } = this.formatNotifyContent(webhookUrl, webhookBody);
|
||||
if (!formatUrl && !formatBody) {
|
||||
return false;
|
||||
}
|
||||
const headers = parseHeaders(webhookHeaders);
|
||||
const body = parseBody(formatBody, webhookContentType);
|
||||
const bodyParam = this.formatBody(webhookContentType, body);
|
||||
const options = {
|
||||
method: webhookMethod,
|
||||
headers,
|
||||
timeout: this.timeout,
|
||||
retry: 0,
|
||||
allowGetBody: true,
|
||||
...bodyParam
|
||||
}
|
||||
const res = await got(formatUrl, options);
|
||||
return String(res.statusCode).startsWith('20');
|
||||
}
|
||||
|
||||
private formatBody(contentType: string, body: any): object {
|
||||
if (!body) return {};
|
||||
switch (contentType) {
|
||||
case 'application/json':
|
||||
return { json: body };
|
||||
case 'multipart/form-data':
|
||||
return { form: body };
|
||||
case 'application/x-www-form-urlencoded':
|
||||
return { body };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private formatNotifyContent(url: string, body: string) {
|
||||
if (!url.includes('$title') && !body.includes('$title')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
formatUrl: url.replaceAll('$title', encodeURIComponent(this.title)).replaceAll('$content', encodeURIComponent(this.content)),
|
||||
formatBody: body.replaceAll('$title', this.title).replaceAll('$content', this.content),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,10 @@ export default class SubscriptionService {
|
||||
try {
|
||||
const result = await SubscriptionModel.findAll({
|
||||
where: query,
|
||||
order: [['createdAt', 'DESC']],
|
||||
order: [
|
||||
['is_disabled', 'ASC'],
|
||||
['createdAt', 'DESC'],
|
||||
],
|
||||
});
|
||||
return result as any;
|
||||
} catch (error) {
|
||||
|
||||
@@ -70,9 +70,8 @@ export default class SystemService {
|
||||
};
|
||||
await this.scheduleService.cancelIntervalTask(cron);
|
||||
if (frequency > 0) {
|
||||
await this.scheduleService.createIntervalTask(cron, {
|
||||
this.scheduleService.createIntervalTask(cron, {
|
||||
days: frequency,
|
||||
runImmediately: true,
|
||||
});
|
||||
}
|
||||
return { code: 200, data: { ...cron } };
|
||||
|
||||
+20
-15
@@ -24,7 +24,7 @@ export default class UserService {
|
||||
@Inject('logger') private logger: winston.Logger,
|
||||
private scheduleService: ScheduleService,
|
||||
private sockService: SockService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
public async login(
|
||||
payloads: {
|
||||
@@ -65,15 +65,13 @@ export default class UserService {
|
||||
return this.initAuthInfo();
|
||||
}
|
||||
|
||||
if (retries > 2 && Date.now() - lastlogon < Math.pow(3, retries) * 1000) {
|
||||
const retriesTime = Math.pow(3, retries) * 1000;
|
||||
if (retries > 2 && timestamp - lastlogon < retriesTime) {
|
||||
const waitTime = Math.ceil((retriesTime - (timestamp - lastlogon)) / 1000);
|
||||
return {
|
||||
code: 410,
|
||||
message: `失败次数过多,请${Math.round(
|
||||
(Math.pow(3, retries) * 1000 - Date.now() + lastlogon) / 1000,
|
||||
)}秒后重试`,
|
||||
data: Math.round(
|
||||
(Math.pow(3, retries) * 1000 - Date.now() + lastlogon) / 1000,
|
||||
),
|
||||
message: `失败次数过多,请${waitTime}秒后重试`,
|
||||
data: waitTime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,12 +83,12 @@ export default class UserService {
|
||||
});
|
||||
return {
|
||||
code: 420,
|
||||
message: '请输入两步验证token',
|
||||
message: '',
|
||||
};
|
||||
}
|
||||
|
||||
const data = createRandomString(50, 100);
|
||||
const expiration = twoFactorActivated ? 30 : 3;
|
||||
const expiration = twoFactorActivated ? 60 : 20;
|
||||
let token = jwt.sign({ data }, config.secret as any, {
|
||||
expiresIn: 60 * 60 * 24 * expiration,
|
||||
algorithm: 'HS384',
|
||||
@@ -111,8 +109,7 @@ 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();
|
||||
@@ -140,8 +137,7 @@ 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();
|
||||
@@ -155,7 +151,16 @@ export default class UserService {
|
||||
status: LoginStatus.fail,
|
||||
},
|
||||
});
|
||||
return { code: 400, message: config.authError };
|
||||
if (retries > 2) {
|
||||
const waitTime = Math.round(Math.pow(3, retries + 1));
|
||||
return {
|
||||
code: 410,
|
||||
message: `失败次数过多,请${waitTime}秒后重试`,
|
||||
data: waitTime,
|
||||
};
|
||||
} else {
|
||||
return { code: 400, message: config.authError };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return this.initAuthInfo();
|
||||
|
||||
@@ -4,6 +4,8 @@ dir_shell=/ql/shell
|
||||
. $dir_shell/share.sh
|
||||
link_shell
|
||||
|
||||
export isFirstStartServer=true
|
||||
|
||||
echo -e "======================1. 检测配置文件========================\n"
|
||||
make_dir /etc/nginx/conf.d
|
||||
make_dir /run/nginx
|
||||
@@ -11,6 +13,8 @@ cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
||||
pm2 l &>/dev/null
|
||||
|
||||
patch_version &>/dev/null
|
||||
echo
|
||||
|
||||
echo -e "======================2. 安装依赖========================\n"
|
||||
@@ -38,13 +42,13 @@ echo -e "定时任务启动成功...\n"
|
||||
|
||||
if [[ $AutoStartBot == true ]]; then
|
||||
echo -e "======================7. 启动bot========================\n"
|
||||
nohup ql bot >>$dir_log/start.log 2>&1 &
|
||||
nohup ql bot >$dir_log/bot.log 2>&1 &
|
||||
echo -e "bot后台启动中...\n"
|
||||
fi
|
||||
|
||||
if [[ $EnableExtraShell == true ]]; then
|
||||
echo -e "======================8. 执行自定义脚本========================\n"
|
||||
nohup ql extra >>$dir_log/start.log 2>&1 &
|
||||
nohup ql extra >$dir_log/extra.log 2>&1 &
|
||||
echo -e "自定义脚本后台执行中...\n"
|
||||
fi
|
||||
|
||||
|
||||
+34
-18
@@ -2,16 +2,16 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "concurrently -n w: npm:start:*",
|
||||
"start:front": "umi dev",
|
||||
"start:front": "max dev",
|
||||
"start:back": "nodemon",
|
||||
"start:public": "ts-node back/public.ts",
|
||||
"build:front": "umi build",
|
||||
"build:front": "max build",
|
||||
"build:back": "tsc -p tsconfig.back.json",
|
||||
"panel": "npm run build:back && node static/build/app.js",
|
||||
"schedule": "npm run build:back && node static/build/schedule.js",
|
||||
"public": "npm run build:back && node static/build/public.js",
|
||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||
"postinstall": "umi generate tmp 2>/dev/null || true",
|
||||
"postinstall": "max setup 2>/dev/null || true",
|
||||
"test": "umi-test",
|
||||
"test:coverage": "umi-test --coverage"
|
||||
},
|
||||
@@ -28,16 +28,33 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"antd",
|
||||
"dva",
|
||||
"postcss",
|
||||
"webpack",
|
||||
"eslint",
|
||||
"stylelint",
|
||||
"redux",
|
||||
"@babel/core",
|
||||
"monaco-editor",
|
||||
"rc-field-form",
|
||||
"@types/lodash.merge",
|
||||
"rollup"
|
||||
],
|
||||
"allowedVersions": {
|
||||
"react": "17",
|
||||
"react-dom": "17"
|
||||
"react": "18",
|
||||
"react-dom": "18",
|
||||
"dva-core": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^7.0.0",
|
||||
"@sentry/tracing": "^7.0.0",
|
||||
"@sentry/node": "^7.12.1",
|
||||
"@sentry/tracing": "^7.12.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"celebrate": "^15.0.1",
|
||||
"chokidar": "^3.5.3",
|
||||
@@ -48,6 +65,7 @@
|
||||
"express": "^4.17.3",
|
||||
"express-jwt": "^6.1.1",
|
||||
"express-urlrewrite": "^1.4.0",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.8.2",
|
||||
"hpagent": "^0.1.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
@@ -72,8 +90,8 @@
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@ant-design/pro-layout": "^6.33.1",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@sentry/react": "^7.0.0",
|
||||
"@monaco-editor/react": "4.2.1",
|
||||
"@sentry/react": "^7.12.1",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
@@ -86,18 +104,16 @@
|
||||
"@types/node-schedule": "^1.3.2",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.13",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/serve-handler": "^6.1.1",
|
||||
"@types/sockjs": "^0.3.33",
|
||||
"@types/sockjs-client": "^1.5.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@umijs/plugin-antd": "^0.15.0",
|
||||
"@umijs/plugin-esbuild": "^1.4.1",
|
||||
"@umijs/max": "^4.0.21",
|
||||
"@umijs/ssr-darkreader": "^4.9.45",
|
||||
"@umijs/test": "^3.5.21",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"antd": "^4.20.5",
|
||||
"antd": "^4.23.0",
|
||||
"antd-img-crop": "^4.2.3",
|
||||
"codemirror": "^5.65.2",
|
||||
"compression-webpack-plugin": "9.2.0",
|
||||
@@ -107,19 +123,19 @@
|
||||
"prettier": "^2.5.1",
|
||||
"qiniu": "^7.4.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"react": "17.x",
|
||||
"react": "18.2.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dom": "17.x",
|
||||
"react-dom": "18.2.0",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.2",
|
||||
"umi": "^3.5.21",
|
||||
"umi-request": "^1.4.0",
|
||||
"vh-check": "^2.0.5",
|
||||
"webpack": "^5.70.0",
|
||||
|
||||
+15
-1
@@ -19,6 +19,11 @@ RepoFileExtensions="js py"
|
||||
## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890
|
||||
ProxyUrl=""
|
||||
|
||||
## 资源告警阙值,默认CPU 80%、内存80%、磁盘90%
|
||||
CpuWarn=80
|
||||
MemoryWarn=80
|
||||
DiskWarn=90
|
||||
|
||||
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||
CommandTimeoutTime="1h"
|
||||
|
||||
@@ -46,9 +51,12 @@ AutoStartBot=""
|
||||
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
||||
BotRepoUrl=""
|
||||
|
||||
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
|
||||
## 安装python依赖时指定pip源
|
||||
PipMirror="https://pypi.doubanio.com/simple/"
|
||||
|
||||
## 安装node依赖时指定npm源
|
||||
NpmMirror="https://registry.npmmirror.com"
|
||||
|
||||
## 通知环境变量
|
||||
## 1. Server酱
|
||||
## https://sct.ftqq.com
|
||||
@@ -137,4 +145,10 @@ export GOTIFY_PRIORITY=0
|
||||
## deer_key 填写PushDeer的key
|
||||
export DEER_KEY=""
|
||||
|
||||
## 12. Chat
|
||||
## chat_url 填写synology chat地址,http://IP:PORT/webapi/***token=
|
||||
## chat_token 填写后面的token
|
||||
export CHAT_URL=""
|
||||
export CHAT_TOKEN=""
|
||||
|
||||
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
||||
|
||||
+60
-5
@@ -39,6 +39,12 @@ let SCKEY = '';
|
||||
//(环境变量名 DEER_KEY)
|
||||
let PUSHDEER_KEY = '';
|
||||
|
||||
// =======================================Synology Chat通知设置区域===========================================
|
||||
//此处填你申请的CHAT_URL与CHAT_TOKEN
|
||||
//(环境变量名 CHAT_URL CHAT_TOKEN)
|
||||
let CHAT_URL = '';
|
||||
let CHAT_TOKEN = '';
|
||||
|
||||
// =======================================Bark App通知设置区域===========================================
|
||||
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
||||
let BARK_PUSH = '';
|
||||
@@ -133,6 +139,14 @@ if (process.env.DEER_KEY) {
|
||||
PUSHDEER_KEY = process.env.DEER_KEY;
|
||||
}
|
||||
|
||||
if (process.env.CHAT_URL) {
|
||||
CHAT_URL = process.env.CHAT_URL;
|
||||
}
|
||||
|
||||
if (process.env.CHAT_TOKEN) {
|
||||
CHAT_TOKEN = process.env.CHAT_TOKEN;
|
||||
}
|
||||
|
||||
if (process.env.QQ_SKEY) {
|
||||
QQ_SKEY = process.env.QQ_SKEY;
|
||||
}
|
||||
@@ -239,6 +253,8 @@ async function sendNotify(
|
||||
iGotNotify(text, desp, params), //iGot
|
||||
gobotNotify(text, desp), //go-cqhttp
|
||||
gotifyNotify(text, desp), //gotify
|
||||
ChatNotify(text, desp), //synolog chat
|
||||
PushDeerNotify(text, desp), //PushDeer
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -369,7 +385,7 @@ function serverNotify(text, desp, time = 2100) {
|
||||
});
|
||||
}
|
||||
|
||||
function PushDeerNotify(text, desp, time = 2100) {
|
||||
function PushDeerNotify(text, desp) {
|
||||
return new Promise((resolve) => {
|
||||
if (PUSHDEER_KEY) {
|
||||
// PushDeer 建议对消息内容进行 urlencode
|
||||
@@ -382,8 +398,9 @@ function PushDeerNotify(text, desp, time = 2100) {
|
||||
},
|
||||
timeout,
|
||||
};
|
||||
setTimeout(() => {
|
||||
$.post(options, (err, resp, data) => {
|
||||
$.post(
|
||||
options,
|
||||
(err, resp, data) => {
|
||||
try {
|
||||
if (err) {
|
||||
console.log('发送通知调用API失败!!\n');
|
||||
@@ -407,8 +424,46 @@ function PushDeerNotify(text, desp, time = 2100) {
|
||||
} finally {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
}, time);
|
||||
},
|
||||
time,
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ChatNotify(text, desp) {
|
||||
return new Promise((resolve) => {
|
||||
if (CHAT_URL && CHAT_TOKEN) {
|
||||
// 对消息内容进行 urlencode
|
||||
desp = encodeURI(desp);
|
||||
const options = {
|
||||
url: `${CHAT_URL}${CHAT_TOKEN}`,
|
||||
body: `payload={"text":"${text}\n${desp}"}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
};
|
||||
$.post(options, (err, resp, data) => {
|
||||
try {
|
||||
if (err) {
|
||||
console.log('发送通知调用API失败!!\n');
|
||||
console.log(err);
|
||||
} else {
|
||||
data = JSON.parse(data);
|
||||
if (data.success) {
|
||||
console.log('Chat发送通知消息成功🎉\n');
|
||||
} else {
|
||||
console.log(`Chat发送通知消息异常\n${JSON.stringify(data)}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
$.logErr(e);
|
||||
} finally {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ push_config = {
|
||||
|
||||
'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY
|
||||
|
||||
'CHAT_URL': '', # synology chat url
|
||||
'CHAT_TOKEN': '', # synology chat token
|
||||
|
||||
'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌
|
||||
'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码
|
||||
|
||||
@@ -281,6 +284,25 @@ def pushdeer(title: str, content: str) -> None:
|
||||
print("PushDeer 推送失败!错误信息:", response)
|
||||
|
||||
|
||||
def chat(title: str, content: str) -> None:
|
||||
"""
|
||||
通过Chat 推送消息
|
||||
"""
|
||||
if not push_config.get("CHAT_URL") or not push_config.get("CHAT_TOKEN"):
|
||||
print("chat 服务的 CHAT_URL或CHAT_TOKEN 未设置!!\n取消推送")
|
||||
return
|
||||
print("chat 服务启动")
|
||||
data = 'payload=' + json.dumps({'text': title + '\n' + content})
|
||||
url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN")
|
||||
response = requests.post(url, data=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("Chat 推送成功!")
|
||||
else:
|
||||
print("Chat 推送失败!错误信息:", response)
|
||||
|
||||
|
||||
|
||||
def pushplus_bot(title: str, content: str) -> None:
|
||||
"""
|
||||
通过 push+ 推送消息。
|
||||
@@ -527,6 +549,8 @@ if push_config.get("PUSH_KEY"):
|
||||
notify_function.append(serverJ)
|
||||
if push_config.get("DEER_KEY"):
|
||||
notify_function.append(pushdeer)
|
||||
if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"):
|
||||
notify_function.append(chat)
|
||||
if push_config.get("PUSH_PLUS_TOKEN"):
|
||||
notify_function.append(pushplus_bot)
|
||||
if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"):
|
||||
|
||||
+160
-160
@@ -1,187 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
get_token() {
|
||||
token=$(cat $file_auth_token | jq -r .value)
|
||||
token=$(cat $file_auth_token | jq -r .value)
|
||||
}
|
||||
|
||||
add_cron_api() {
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $# -eq 1 ]]; then
|
||||
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
||||
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||
else
|
||||
local schedule=$1
|
||||
local command=$2
|
||||
local name=$3
|
||||
fi
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $# -eq 1 ]]; then
|
||||
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
||||
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||
else
|
||||
local schedule=$1
|
||||
local command=$2
|
||||
local name=$3
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "$name -> 添加成功"
|
||||
else
|
||||
echo -e "$name -> 添加失败(${message})"
|
||||
fi
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "$name -> 添加成功"
|
||||
else
|
||||
echo -e "$name -> 添加失败(${message})"
|
||||
fi
|
||||
}
|
||||
|
||||
update_cron_api() {
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $# -eq 1 ]]; then
|
||||
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
||||
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||
local id=$(echo "$1" | awk -F ":" '{print $4}')
|
||||
else
|
||||
local schedule=$1
|
||||
local command=$2
|
||||
local name=$3
|
||||
local id=$4
|
||||
fi
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $# -eq 1 ]]; then
|
||||
local schedule=$(echo "$1" | awk -F ":" '{print $1}')
|
||||
local command=$(echo "$1" | awk -F ":" '{print $2}')
|
||||
local name=$(echo "$1" | awk -F ":" '{print $3}')
|
||||
local id=$(echo "$1" | awk -F ":" '{print $4}')
|
||||
else
|
||||
local schedule=$1
|
||||
local command=$2
|
||||
local name=$3
|
||||
local id=$4
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "$name -> 更新成功"
|
||||
else
|
||||
echo -e "$name -> 更新失败(${message})"
|
||||
fi
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"name\":\"$name\",\"command\":\"$command\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "$name -> 更新成功"
|
||||
else
|
||||
echo -e "$name -> 更新失败(${message})"
|
||||
fi
|
||||
}
|
||||
|
||||
update_cron_command_api() {
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $# -eq 1 ]]; then
|
||||
local command=$(echo "$1" | awk -F ":" '{print $1}')
|
||||
local id=$(echo "$1" | awk -F ":" '{print $2}')
|
||||
else
|
||||
local command=$1
|
||||
local id=$2
|
||||
fi
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $# -eq 1 ]]; then
|
||||
local command=$(echo "$1" | awk -F ":" '{print $1}')
|
||||
local id=$(echo "$1" | awk -F ":" '{print $2}')
|
||||
else
|
||||
local command=$1
|
||||
local id=$2
|
||||
fi
|
||||
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"command\":\"$command\",\"id\":\"$id\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "$command -> 更新成功"
|
||||
else
|
||||
echo -e "$command -> 更新失败(${message})"
|
||||
fi
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"command\":\"$command\",\"id\":\"$id\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "$command -> 更新成功"
|
||||
else
|
||||
echo -e "$command -> 更新失败(${message})"
|
||||
fi
|
||||
}
|
||||
|
||||
del_cron_api() {
|
||||
local ids=$1
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'DELETE' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "[$ids]" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "成功"
|
||||
else
|
||||
echo -e "失败(${message})"
|
||||
fi
|
||||
local ids=$1
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
|
||||
-X 'DELETE' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "[$ids]" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "成功"
|
||||
else
|
||||
echo -e "失败(${message})"
|
||||
fi
|
||||
}
|
||||
|
||||
update_cron() {
|
||||
local ids="$1"
|
||||
local status="$2"
|
||||
local pid="${3:-''}"
|
||||
local logPath="$4"
|
||||
local lastExecutingTime="${5:-0}"
|
||||
local runningTime="${6:-0}"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code != 200 ]]; then
|
||||
echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path
|
||||
fi
|
||||
local ids="$1"
|
||||
local status="$2"
|
||||
local pid="${3:-''}"
|
||||
local logPath="$4"
|
||||
local lastExecutingTime="${5:-0}"
|
||||
local runningTime="${6:-0}"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code != 200 ]]; then
|
||||
echo -e "\n## 更新任务状态失败(${message})\n" >>$dir_log/$log_path
|
||||
fi
|
||||
}
|
||||
|
||||
notify_api() {
|
||||
local title=$1
|
||||
local content=$2
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"title\":\"$title\",\"content\":\"$content\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "通知发送成功"
|
||||
else
|
||||
echo -e "通知失败(${message})"
|
||||
fi
|
||||
local title=$1
|
||||
local content=$2
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Origin: http://0.0.0.0:5700" \
|
||||
-H "Referer: http://0.0.0.0:5700/crontab" \
|
||||
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
|
||||
--data-raw "{\"title\":\"$title\",\"content\":\"$content\"}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo $api | jq -r .code)
|
||||
message=$(echo $api | jq -r .message)
|
||||
if [[ $code == 200 ]]; then
|
||||
echo -e "通知发送成功"
|
||||
else
|
||||
echo -e "通知失败(${message})"
|
||||
fi
|
||||
}
|
||||
|
||||
get_token
|
||||
|
||||
+4
-5
@@ -35,11 +35,10 @@ fi
|
||||
cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
|
||||
|
||||
cd $dir_data
|
||||
cat requirements.txt | while read LREAD
|
||||
do
|
||||
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
|
||||
pip3 --default-timeout=100 install ${LREAD}
|
||||
fi
|
||||
cat requirements.txt | while read LREAD; do
|
||||
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
|
||||
pip3 --default-timeout=100 install ${LREAD}
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "\npython3依赖安装成功...\n"
|
||||
|
||||
@@ -90,6 +90,9 @@ check_pm2() {
|
||||
main() {
|
||||
echo -e "=====> 开始检测"
|
||||
npm i -g pnpm
|
||||
pnpm setup &>/dev/null
|
||||
source ~/.bashrc
|
||||
pnpm install -g &>/dev/null
|
||||
pnpm add -g pm2
|
||||
copy_dep
|
||||
check_ql
|
||||
|
||||
+23
-23
@@ -8,35 +8,35 @@ days=$1
|
||||
|
||||
## 删除运行js脚本的旧日志
|
||||
remove_js_log() {
|
||||
local log_full_path_list=$(find $dir_log/ -name "*.log")
|
||||
local diff_time
|
||||
for log in $log_full_path_list; do
|
||||
local log_date=$(echo $log | awk -F "/" '{print $NF}' | cut -c1-10) #文件名比文件属性获得的日期要可靠
|
||||
if [[ $(date +%s -d $log_date 2>/dev/null) ]]; then
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
diff_time=$(($(date +%s) - $(date -j -f "%Y-%m-%d" "$log_date" +%s)))
|
||||
else
|
||||
diff_time=$(($(date +%s) - $(date +%s -d "$log_date")))
|
||||
fi
|
||||
[[ $diff_time -gt $((${days} * 86400)) ]] && rm -vf $log
|
||||
fi
|
||||
done
|
||||
local log_full_path_list=$(find $dir_log/ -name "*.log")
|
||||
local diff_time
|
||||
for log in $log_full_path_list; do
|
||||
local log_date=$(echo $log | awk -F "/" '{print $NF}' | cut -c1-10) #文件名比文件属性获得的日期要可靠
|
||||
if [[ $(date +%s -d $log_date 2>/dev/null) ]]; then
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
diff_time=$(($(date +%s) - $(date -j -f "%Y-%m-%d" "$log_date" +%s)))
|
||||
else
|
||||
diff_time=$(($(date +%s) - $(date +%s -d "$log_date")))
|
||||
fi
|
||||
[[ $diff_time -gt $((${days} * 86400)) ]] && rm -vf $log
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
## 删除空文件夹
|
||||
remove_empty_dir() {
|
||||
cd $dir_log
|
||||
for dir in $(ls); do
|
||||
if [[ -d $dir ]] && [[ -z $(ls $dir) ]]; then
|
||||
rm -rf $dir
|
||||
fi
|
||||
done
|
||||
cd $dir_log
|
||||
for dir in $(ls); do
|
||||
if [[ -d $dir ]] && [[ -z $(ls $dir) ]]; then
|
||||
rm -rf $dir
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
## 运行
|
||||
if [[ ${days} ]]; then
|
||||
echo -e "查找旧日志文件中...\n"
|
||||
remove_js_log
|
||||
remove_empty_dir
|
||||
echo -e "删除旧日志执行完毕\n"
|
||||
echo -e "查找旧日志文件中...\n"
|
||||
remove_js_log
|
||||
remove_empty_dir
|
||||
echo -e "删除旧日志执行完毕\n"
|
||||
fi
|
||||
|
||||
+313
-258
@@ -50,379 +50,434 @@ list_own_drop=$dir_list_tmp/own_drop.list
|
||||
|
||||
## 软连接及其原始文件对应关系
|
||||
link_name=(
|
||||
task
|
||||
ql
|
||||
task
|
||||
ql
|
||||
)
|
||||
original_name=(
|
||||
task.sh
|
||||
update.sh
|
||||
task.sh
|
||||
update.sh
|
||||
)
|
||||
|
||||
init_env() {
|
||||
export NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules:/root/.local/share/pnpm/global/5/node_modules
|
||||
export PYTHONUNBUFFERED=1
|
||||
export NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules:/root/.local/share/pnpm/global/5/node_modules
|
||||
export PYTHONUNBUFFERED=1
|
||||
}
|
||||
|
||||
import_config() {
|
||||
[[ -f $file_config_user ]] && . $file_config_user
|
||||
[[ -f $file_env ]] && . $file_env
|
||||
[[ -f $file_config_user ]] && . $file_config_user
|
||||
[[ -f $file_env ]] && . $file_env
|
||||
|
||||
ql_base_url=${QlBaseUrl:-""}
|
||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
||||
proxy_url=${ProxyUrl:-""}
|
||||
file_extensions=${RepoFileExtensions:-"js py"}
|
||||
current_branch=${QL_BRANCH}
|
||||
ql_base_url=${QlBaseUrl:-""}
|
||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
||||
proxy_url=${ProxyUrl:-""}
|
||||
file_extensions=${RepoFileExtensions:-"js py"}
|
||||
current_branch=${QL_BRANCH}
|
||||
|
||||
if [[ -n "${DefaultCronRule}" ]]; then
|
||||
default_cron="${DefaultCronRule}"
|
||||
else
|
||||
default_cron="$(random_range 0 59) $(random_range 0 23) * * *"
|
||||
fi
|
||||
if [[ -n "${DefaultCronRule}" ]]; then
|
||||
default_cron="${DefaultCronRule}"
|
||||
else
|
||||
default_cron="$(random_range 0 59) $(random_range 0 23) * * *"
|
||||
fi
|
||||
|
||||
cpu_warn=${CpuWarn:-80}
|
||||
mem_warn=${MemoryWarn:-80}
|
||||
disk_warn=${DiskWarn:-90}
|
||||
}
|
||||
|
||||
set_proxy() {
|
||||
if [[ $proxy_url ]]; then
|
||||
export http_proxy="${proxy_url}"
|
||||
export https_proxy="${proxy_url}"
|
||||
fi
|
||||
if [[ $proxy_url ]]; then
|
||||
export http_proxy="${proxy_url}"
|
||||
export https_proxy="${proxy_url}"
|
||||
fi
|
||||
}
|
||||
|
||||
unset_proxy() {
|
||||
unset http_proxy
|
||||
unset https_proxy
|
||||
unset http_proxy
|
||||
unset https_proxy
|
||||
}
|
||||
|
||||
make_dir() {
|
||||
local dir=$1
|
||||
if [[ ! -d $dir ]]; then
|
||||
mkdir -p $dir
|
||||
fi
|
||||
local dir=$1
|
||||
if [[ ! -d $dir ]]; then
|
||||
mkdir -p $dir
|
||||
fi
|
||||
}
|
||||
|
||||
detect_termux() {
|
||||
if [[ $PATH == *com.termux* ]]; then
|
||||
is_termux=1
|
||||
else
|
||||
is_termux=0
|
||||
fi
|
||||
if [[ $PATH == *com.termux* ]]; then
|
||||
is_termux=1
|
||||
else
|
||||
is_termux=0
|
||||
fi
|
||||
}
|
||||
|
||||
detect_macos() {
|
||||
[[ $(uname -s) == Darwin ]] && is_macos=1 || is_macos=0
|
||||
[[ $(uname -s) == Darwin ]] && is_macos=1 || is_macos=0
|
||||
}
|
||||
|
||||
gen_random_num() {
|
||||
local divi=$1
|
||||
echo $((${RANDOM} % $divi))
|
||||
local divi=$1
|
||||
echo $((${RANDOM} % $divi))
|
||||
}
|
||||
|
||||
link_shell_sub() {
|
||||
local link_path="$1"
|
||||
local original_path="$2"
|
||||
if [[ ! -L $link_path ]] || [[ $(readlink -f $link_path) != $original_path ]]; then
|
||||
rm -f $link_path 2>/dev/null
|
||||
ln -sf $original_path $link_path
|
||||
fi
|
||||
local link_path="$1"
|
||||
local original_path="$2"
|
||||
if [[ ! -L $link_path ]] || [[ $(readlink -f $link_path) != $original_path ]]; then
|
||||
rm -f $link_path 2>/dev/null
|
||||
ln -sf $original_path $link_path
|
||||
fi
|
||||
}
|
||||
|
||||
link_shell() {
|
||||
if [[ $is_termux -eq 1 ]]; then
|
||||
local path="/data/data/com.termux/files/usr/bin/"
|
||||
elif [[ $PATH == */usr/local/bin* ]] && [[ -d /usr/local/bin ]]; then
|
||||
local path="/usr/local/bin/"
|
||||
else
|
||||
local path=""
|
||||
echo -e "脚本功能受限,请自行添加命令的软连接...\n"
|
||||
fi
|
||||
if [[ $path ]]; then
|
||||
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
||||
link_shell_sub "$path${link_name[i]}" "$dir_shell/${original_name[i]}"
|
||||
done
|
||||
fi
|
||||
if [[ $is_termux -eq 1 ]]; then
|
||||
local path="/data/data/com.termux/files/usr/bin/"
|
||||
elif [[ $PATH == */usr/local/bin* ]] && [[ -d /usr/local/bin ]]; then
|
||||
local path="/usr/local/bin/"
|
||||
else
|
||||
local path=""
|
||||
echo -e "脚本功能受限,请自行添加命令的软连接...\n"
|
||||
fi
|
||||
if [[ $path ]]; then
|
||||
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
||||
link_shell_sub "$path${link_name[i]}" "$dir_shell/${original_name[i]}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
define_cmd() {
|
||||
local cmd_prefix cmd_suffix
|
||||
if type task &>/dev/null; then
|
||||
cmd_suffix=""
|
||||
if [[ -f "$dir_shell/task.sh" ]]; then
|
||||
cmd_prefix=""
|
||||
else
|
||||
cmd_prefix="bash "
|
||||
fi
|
||||
local cmd_prefix cmd_suffix
|
||||
if type task &>/dev/null; then
|
||||
cmd_suffix=""
|
||||
if [[ -f "$dir_shell/task.sh" ]]; then
|
||||
cmd_prefix=""
|
||||
else
|
||||
cmd_suffix=".sh"
|
||||
if [[ -f "$dir_shell/task.sh" ]]; then
|
||||
cmd_prefix="$dir_shell/"
|
||||
else
|
||||
cmd_prefix="bash $dir_shell/"
|
||||
fi
|
||||
cmd_prefix="bash "
|
||||
fi
|
||||
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
||||
export cmd_${link_name[i]}="${cmd_prefix}${link_name[i]}${cmd_suffix}"
|
||||
done
|
||||
else
|
||||
cmd_suffix=".sh"
|
||||
if [[ -f "$dir_shell/task.sh" ]]; then
|
||||
cmd_prefix="$dir_shell/"
|
||||
else
|
||||
cmd_prefix="bash $dir_shell/"
|
||||
fi
|
||||
fi
|
||||
for ((i = 0; i < ${#link_name[*]}; i++)); do
|
||||
export cmd_${link_name[i]}="${cmd_prefix}${link_name[i]}${cmd_suffix}"
|
||||
done
|
||||
}
|
||||
|
||||
fix_config() {
|
||||
make_dir $dir_static
|
||||
make_dir $dir_data
|
||||
make_dir $dir_config
|
||||
make_dir $dir_log
|
||||
make_dir $dir_db
|
||||
make_dir $dir_scripts
|
||||
make_dir $dir_list_tmp
|
||||
make_dir $dir_repo
|
||||
make_dir $dir_raw
|
||||
make_dir $dir_update_log
|
||||
make_dir $dir_dep
|
||||
make_dir $dir_static
|
||||
make_dir $dir_data
|
||||
make_dir $dir_config
|
||||
make_dir $dir_log
|
||||
make_dir $dir_db
|
||||
make_dir $dir_scripts
|
||||
make_dir $dir_list_tmp
|
||||
make_dir $dir_repo
|
||||
make_dir $dir_raw
|
||||
make_dir $dir_update_log
|
||||
make_dir $dir_dep
|
||||
|
||||
if [[ ! -s $file_config_user ]]; then
|
||||
echo -e "复制一份 $file_config_sample 为 $file_config_user,随后请按注释编辑你的配置文件:$file_config_user\n"
|
||||
cp -fv $file_config_sample $file_config_user
|
||||
echo
|
||||
fi
|
||||
if [[ ! -s $file_config_user ]]; then
|
||||
echo -e "复制一份 $file_config_sample 为 $file_config_user,随后请按注释编辑你的配置文件:$file_config_user\n"
|
||||
cp -fv $file_config_sample $file_config_user
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -f $file_env ]]; then
|
||||
echo -e "检测到config配置目录下不存在env.sh,创建一个空文件用于初始化...\n"
|
||||
touch $file_env
|
||||
echo
|
||||
fi
|
||||
if [[ ! -f $file_env ]]; then
|
||||
echo -e "检测到config配置目录下不存在env.sh,创建一个空文件用于初始化...\n"
|
||||
touch $file_env
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -f $file_task_before ]]; then
|
||||
echo -e "复制一份 $file_task_sample 为 $file_task_before\n"
|
||||
cp -fv $file_task_sample $file_task_before
|
||||
echo
|
||||
fi
|
||||
if [[ ! -f $file_task_before ]]; then
|
||||
echo -e "复制一份 $file_task_sample 为 $file_task_before\n"
|
||||
cp -fv $file_task_sample $file_task_before
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -f $file_task_after ]]; then
|
||||
echo -e "复制一份 $file_task_sample 为 $file_task_after\n"
|
||||
cp -fv $file_task_sample $file_task_after
|
||||
echo
|
||||
fi
|
||||
if [[ ! -f $file_task_after ]]; then
|
||||
echo -e "复制一份 $file_task_sample 为 $file_task_after\n"
|
||||
cp -fv $file_task_sample $file_task_after
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -f $file_extra_shell ]]; then
|
||||
echo -e "复制一份 $file_extra_sample 为 $file_extra_shell\n"
|
||||
cp -fv $file_extra_sample $file_extra_shell
|
||||
echo
|
||||
fi
|
||||
if [[ ! -f $file_extra_shell ]]; then
|
||||
echo -e "复制一份 $file_extra_sample 为 $file_extra_shell\n"
|
||||
cp -fv $file_extra_sample $file_extra_shell
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -s $file_auth_user ]]; then
|
||||
echo -e "复制一份 $file_auth_sample 为 $file_auth_user\n"
|
||||
cp -fv $file_auth_sample $file_auth_user
|
||||
echo
|
||||
fi
|
||||
if [[ ! -s $file_auth_user ]]; then
|
||||
echo -e "复制一份 $file_auth_sample 为 $file_auth_user\n"
|
||||
cp -fv $file_auth_sample $file_auth_user
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -s $file_notify_py ]]; then
|
||||
echo -e "复制一份 $file_notify_py_sample 为 $file_notify_py\n"
|
||||
cp -fv $file_notify_py_sample $file_notify_py
|
||||
echo
|
||||
fi
|
||||
if [[ ! -s $file_notify_py ]]; then
|
||||
echo -e "复制一份 $file_notify_py_sample 为 $file_notify_py\n"
|
||||
cp -fv $file_notify_py_sample $file_notify_py
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -s $file_notify_js ]]; then
|
||||
echo -e "复制一份 $file_notify_js_sample 为 $file_notify_js\n"
|
||||
cp -fv $file_notify_js_sample $file_notify_js
|
||||
echo
|
||||
fi
|
||||
if [[ ! -s $file_notify_js ]]; then
|
||||
echo -e "复制一份 $file_notify_js_sample 为 $file_notify_js\n"
|
||||
cp -fv $file_notify_js_sample $file_notify_js
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ -s /etc/nginx/conf.d/default.conf ]]; then
|
||||
echo -e "检测到默认nginx配置文件,清空...\n"
|
||||
cat /dev/null >/etc/nginx/conf.d/default.conf
|
||||
echo
|
||||
fi
|
||||
if [[ -s /etc/nginx/conf.d/default.conf ]]; then
|
||||
echo -e "检测到默认nginx配置文件,清空...\n"
|
||||
cat /dev/null >/etc/nginx/conf.d/default.conf
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -s $dep_notify_js ]]; then
|
||||
echo -e "复制一份 $file_notify_js_sample 为 $dep_notify_js\n"
|
||||
cp -fv $file_notify_js_sample $dep_notify_js
|
||||
echo
|
||||
fi
|
||||
if [[ ! -s $dep_notify_js ]]; then
|
||||
echo -e "复制一份 $file_notify_js_sample 为 $dep_notify_js\n"
|
||||
cp -fv $file_notify_js_sample $dep_notify_js
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ ! -s $dep_notify_py ]]; then
|
||||
echo -e "复制一份 $file_notify_py_sample 为 $dep_notify_py\n"
|
||||
cp -fv $file_notify_py_sample $dep_notify_py
|
||||
echo
|
||||
fi
|
||||
if [[ ! -s $dep_notify_py ]]; then
|
||||
echo -e "复制一份 $file_notify_py_sample 为 $dep_notify_py\n"
|
||||
cp -fv $file_notify_py_sample $dep_notify_py
|
||||
echo
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
npm_install_sub() {
|
||||
set_proxy
|
||||
if [ $is_termux -eq 1 ]; then
|
||||
npm install --production --no-bin-links --registry=https://registry.npmmirror.com || npm install --production --no-bin-links
|
||||
elif ! type pnpm &>/dev/null; then
|
||||
npm install --production --registry=https://registry.npmmirror.com || npm install --production
|
||||
else
|
||||
pnpm install --loglevel error --production --registry=https://registry.npmmirror.com || pnpm install --production --loglevel error
|
||||
fi
|
||||
unset_proxy
|
||||
if [ $is_termux -eq 1 ]; then
|
||||
npm install --production --no-bin-links
|
||||
elif ! type pnpm &>/dev/null; then
|
||||
npm install --production
|
||||
else
|
||||
pnpm install --loglevel error --production
|
||||
fi
|
||||
}
|
||||
|
||||
npm_install_1() {
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
|
||||
cd $dir_work
|
||||
echo -e "运行 npm install...\n"
|
||||
npm_install_sub
|
||||
[[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 npm install...\n"
|
||||
cd $dir_current
|
||||
cd $dir_work
|
||||
echo -e "运行 npm install...\n"
|
||||
npm_install_sub
|
||||
[[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 npm install...\n"
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
npm_install_2() {
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
|
||||
cd $dir_work
|
||||
echo -e "检测到 $dir_work 的依赖包有变化,运行 npm install...\n"
|
||||
npm_install_sub
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n"
|
||||
npm_install_1 $dir_work
|
||||
fi
|
||||
cd $dir_current
|
||||
cd $dir_work
|
||||
echo -e "检测到 $dir_work 的依赖包有变化,运行 npm install...\n"
|
||||
npm_install_sub
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n"
|
||||
npm_install_1 $dir_work
|
||||
fi
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
diff_and_copy() {
|
||||
local copy_source=$1
|
||||
local copy_to=$2
|
||||
if [[ ! -s $copy_to ]] || [[ $(diff $copy_source $copy_to) ]]; then
|
||||
cp -f $copy_source $copy_to
|
||||
fi
|
||||
local copy_source=$1
|
||||
local copy_to=$2
|
||||
if [[ ! -s $copy_to ]] || [[ $(diff $copy_source $copy_to) ]]; then
|
||||
cp -f $copy_source $copy_to
|
||||
fi
|
||||
}
|
||||
|
||||
update_depend() {
|
||||
local dir_current=$(pwd)
|
||||
local dir_current=$(pwd)
|
||||
|
||||
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
|
||||
cp -f $dir_sample/package.json $dir_scripts/package.json
|
||||
npm_install_2 $dir_scripts
|
||||
fi
|
||||
if [[ ! -s $dir_scripts/package.json ]] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then
|
||||
cp -f $dir_sample/package.json $dir_scripts/package.json
|
||||
npm_install_2 $dir_scripts
|
||||
fi
|
||||
|
||||
cd $dir_current
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
git_clone_scripts() {
|
||||
local url=$1
|
||||
local dir=$2
|
||||
local branch=$3
|
||||
[[ $branch ]] && local part_cmd="-b $branch "
|
||||
echo -e "开始克隆仓库 $url 到 $dir\n"
|
||||
local url=$1
|
||||
local dir=$2
|
||||
local branch=$3
|
||||
[[ $branch ]] && local part_cmd="-b $branch "
|
||||
echo -e "开始克隆仓库 $url 到 $dir\n"
|
||||
|
||||
set_proxy
|
||||
git clone $part_cmd $url $dir
|
||||
exit_status=$?
|
||||
unset_proxy
|
||||
set_proxy
|
||||
git clone $part_cmd $url $dir
|
||||
exit_status=$?
|
||||
unset_proxy
|
||||
}
|
||||
|
||||
git_pull_scripts() {
|
||||
local dir_current=$(pwd)
|
||||
local dir_work="$1"
|
||||
local branch="$2"
|
||||
cd $dir_work
|
||||
echo -e "开始更新仓库:$dir_work\n"
|
||||
local dir_current=$(pwd)
|
||||
local dir_work="$1"
|
||||
local branch="$2"
|
||||
cd $dir_work
|
||||
echo -e "开始更新仓库:$dir_work\n"
|
||||
|
||||
set_proxy
|
||||
git fetch --all
|
||||
exit_status=$?
|
||||
git pull &>/dev/null
|
||||
unset_proxy
|
||||
reset_branch "$branch"
|
||||
set_proxy
|
||||
git fetch --all
|
||||
exit_status=$?
|
||||
git pull &>/dev/null
|
||||
unset_proxy
|
||||
reset_branch "$branch"
|
||||
|
||||
cd $dir_current
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
reset_romote_url() {
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
local url=$2
|
||||
local branch="$3"
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
local url=$2
|
||||
local branch="$3"
|
||||
|
||||
if [[ -d "$dir_work/.git" ]]; then
|
||||
cd $dir_work
|
||||
[[ -f ".git/index.lock" ]] && rm -f .git/index.lock >/dev/null
|
||||
git remote set-url origin $url &>/dev/null
|
||||
if [[ -d "$dir_work/.git" ]]; then
|
||||
cd $dir_work
|
||||
[[ -f ".git/index.lock" ]] && rm -f .git/index.lock >/dev/null
|
||||
git remote set-url origin $url &>/dev/null
|
||||
|
||||
local part_cmd=""
|
||||
reset_branch "$branch"
|
||||
cd $dir_current
|
||||
fi
|
||||
local part_cmd=""
|
||||
reset_branch "$branch"
|
||||
cd $dir_current
|
||||
fi
|
||||
}
|
||||
|
||||
reset_branch() {
|
||||
local branch="$1"
|
||||
if [[ $branch ]]; then
|
||||
part_cmd="origin/${branch}"
|
||||
git checkout -B "$branch"
|
||||
git branch --set-upstream-to=$part_cmd $branch
|
||||
fi
|
||||
git reset --hard $part_cmd &>/dev/null
|
||||
local branch="$1"
|
||||
if [[ $branch ]]; then
|
||||
part_cmd="origin/${branch}"
|
||||
git checkout -B "$branch" &>/dev/null
|
||||
git branch --set-upstream-to=$part_cmd $branch &>/dev/null
|
||||
fi
|
||||
git reset --hard $part_cmd &>/dev/null
|
||||
}
|
||||
|
||||
random_range() {
|
||||
local beg=$1
|
||||
local end=$2
|
||||
echo $((RANDOM % ($end - $beg) + $beg))
|
||||
local beg=$1
|
||||
local end=$2
|
||||
echo $((RANDOM % ($end - $beg) + $beg))
|
||||
}
|
||||
|
||||
reload_pm2() {
|
||||
pm2 l &>/dev/null
|
||||
pm2 l &>/dev/null
|
||||
|
||||
echo -e "启动面板服务\n"
|
||||
pm2 delete panel --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null
|
||||
echo -e "启动面板服务\n"
|
||||
pm2 delete panel --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null
|
||||
|
||||
echo -e "启动定时任务服务\n"
|
||||
pm2 delete schedule --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
||||
echo -e "启动定时任务服务\n"
|
||||
pm2 delete schedule --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
||||
|
||||
echo -e "启动公开服务\n"
|
||||
pm2 delete public --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
||||
echo -e "启动公开服务\n"
|
||||
pm2 delete public --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
||||
}
|
||||
|
||||
diff_time() {
|
||||
local format="$1"
|
||||
local begin_time="$2"
|
||||
local end_time="$3"
|
||||
local format="$1"
|
||||
local begin_time="$2"
|
||||
local end_time="$3"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
|
||||
else
|
||||
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
||||
fi
|
||||
echo "$diff_time"
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
|
||||
else
|
||||
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
|
||||
fi
|
||||
echo "$diff_time"
|
||||
}
|
||||
|
||||
format_time() {
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
else
|
||||
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
fi
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
else
|
||||
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
|
||||
fi
|
||||
}
|
||||
|
||||
format_log_time() {
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
else
|
||||
echo $(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
fi
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
else
|
||||
echo $(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
|
||||
fi
|
||||
}
|
||||
|
||||
format_timestamp() {
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
local format="$1"
|
||||
local time="$2"
|
||||
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%s")
|
||||
else
|
||||
echo $(date -d "$time" "+%s")
|
||||
fi
|
||||
if [[ $is_macos -eq 1 ]]; then
|
||||
echo $(date -j -f "$format" "$time" "+%s")
|
||||
else
|
||||
echo $(date -d "$time" "+%s")
|
||||
fi
|
||||
}
|
||||
|
||||
patch_version() {
|
||||
if [[ $PipMirror ]]; then
|
||||
pip3 config set global.index-url $PipMirror
|
||||
fi
|
||||
if [[ $NpmMirror ]]; then
|
||||
npm config set registry $NpmMirror
|
||||
fi
|
||||
|
||||
# 兼容pnpm@7
|
||||
pnpm setup &>/dev/null
|
||||
source ~/.bashrc
|
||||
pnpm install -g &>/dev/null
|
||||
|
||||
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
||||
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
||||
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
||||
rm -rf $dir_root/db/cookie.db
|
||||
echo
|
||||
fi
|
||||
|
||||
if ! type ts-node &>/dev/null; then
|
||||
pnpm add -g ts-node typescript tslib
|
||||
fi
|
||||
|
||||
git config --global pull.rebase false
|
||||
|
||||
cp -f $dir_root/.env.example $dir_root/.env
|
||||
|
||||
if [[ -d "$dir_root/db" ]]; then
|
||||
echo -e "检测到旧的db目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/config $dir_root/data
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ -d "$dir_root/scripts" ]]; then
|
||||
echo -e "检测到旧的scripts目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/scripts $dir_root/data
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ -d "$dir_root/log" ]]; then
|
||||
echo -e "检测到旧的log目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/log $dir_root/data
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ -d "$dir_root/config" ]]; then
|
||||
echo -e "检测到旧的config目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/config $dir_root/data
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
init_env
|
||||
|
||||
+268
-332
@@ -7,399 +7,335 @@ dir_shell=$QL_DIR/shell
|
||||
|
||||
## 选择python3还是node
|
||||
define_program() {
|
||||
local file_param=$1
|
||||
if [[ $file_param == *.js ]]; then
|
||||
which_program="node"
|
||||
elif [[ $file_param == *.py ]] || [[ $file_param == *.pyc ]]; then
|
||||
which_program="python3"
|
||||
elif [[ $file_param == *.sh ]]; then
|
||||
which_program="bash"
|
||||
elif [[ $file_param == *.ts ]]; then
|
||||
which_program="ts-node-transpile-only"
|
||||
else
|
||||
which_program=""
|
||||
fi
|
||||
local file_param=$1
|
||||
if [[ $file_param == *.js ]]; then
|
||||
which_program="node"
|
||||
elif [[ $file_param == *.py ]] || [[ $file_param == *.pyc ]]; then
|
||||
which_program="python3"
|
||||
elif [[ $file_param == *.sh ]]; then
|
||||
which_program="bash"
|
||||
elif [[ $file_param == *.ts ]]; then
|
||||
which_program="ts-node-transpile-only"
|
||||
else
|
||||
which_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"}
|
||||
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
|
||||
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
|
||||
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
|
||||
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 &
|
||||
local file_name=$1
|
||||
nohup node $file_name &>$log_path &
|
||||
}
|
||||
|
||||
handle_log_path() {
|
||||
define_program "$file_param"
|
||||
|
||||
local suffix=""
|
||||
if [[ ! -z $ID ]]; then
|
||||
suffix="_${ID}"
|
||||
fi
|
||||
time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
log_dir_tmp_path="${file_param:1}"
|
||||
else
|
||||
log_dir_tmp_path="${file_param}"
|
||||
fi
|
||||
fi
|
||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}${suffix}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
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
|
||||
define_program "$file_param"
|
||||
if [[ $# -eq 1 ]]; then
|
||||
random_delay "$file_param"
|
||||
fi
|
||||
local file_param=$1
|
||||
if [[ $# -eq 1 ]]; then
|
||||
random_delay "$file_param"
|
||||
fi
|
||||
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
log_dir_tmp_path="${file_param:1}"
|
||||
else
|
||||
log_dir_tmp_path="${file_param}"
|
||||
fi
|
||||
fi
|
||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
handle_task_before "$@"
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
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 echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
eval $timeoutCmd $which_program $file_param $cmd
|
||||
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||
eval . $file_task_before "$@" $cmd
|
||||
|
||||
cd $dir_scripts
|
||||
local relative_path="${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
|
||||
|
||||
eval . $file_task_after "$@" $cmd
|
||||
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local end_timestamp=$(date "+%s")
|
||||
local diff_time=$(expr $end_timestamp - $begin_timestamp)
|
||||
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time 秒" $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
|
||||
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 | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
|
||||
local runArr=($(eval echo $tempArr))
|
||||
runArr=($(awk -v RS=' ' '!a[$1]++' <<< ${runArr[@]}))
|
||||
handle_task_before "$@"
|
||||
|
||||
local n=0
|
||||
for i in ${runArr[@]}; do
|
||||
array_run[n]=${array[$i - 1]}
|
||||
let n++
|
||||
done
|
||||
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 cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||
local n=0
|
||||
for i in ${runArr[@]}; do
|
||||
array_run[n]=${array[$i - 1]}
|
||||
let n++
|
||||
done
|
||||
|
||||
define_program "$file_param"
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
log_dir_tmp_path="${file_param:1}"
|
||||
else
|
||||
log_dir_tmp_path="${file_param}"
|
||||
fi
|
||||
fi
|
||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
local envs=$(eval echo "\$${env_param}")
|
||||
local array=($(echo $envs | sed 's/&/ /g'))
|
||||
single_log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N")
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
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
|
||||
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||
eval . $file_task_before "$@" $cmd
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
define_program "$file_param"
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
log_dir_tmp_path="${file_param:1}"
|
||||
else
|
||||
log_dir_tmp_path="${file_param}"
|
||||
fi
|
||||
fi
|
||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
handle_task_before "$@"
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
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 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 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 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
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $file_param" | perl -pe "s|.*ID=(.*) $cmd_task $file_param\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||
eval . $file_task_before "$@" $cmd
|
||||
|
||||
cd $dir_scripts
|
||||
local relative_path="${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
|
||||
|
||||
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
|
||||
handle_task_after "$@"
|
||||
}
|
||||
|
||||
## 运行其他命令
|
||||
run_else() {
|
||||
local file_param="$1"
|
||||
define_program "$file_param"
|
||||
local time=$(date "+$time_format")
|
||||
log_time=$(format_log_time "$time_format" "$time")
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
if [[ $file_param == /* ]]; then
|
||||
log_dir_tmp_path="${file_param:1}"
|
||||
else
|
||||
log_dir_tmp_path="${file_param}"
|
||||
fi
|
||||
fi
|
||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||
log_dir="${log_dir_tmp%.*}"
|
||||
log_path="$log_dir/$log_time.log"
|
||||
cmd=">> $dir_log/$log_path 2>&1"
|
||||
[[ "$show_log" == "true" ]] && cmd=""
|
||||
make_dir "$dir_log/$log_dir"
|
||||
local file_param="$1"
|
||||
|
||||
local begin_time=$(format_time "$time_format" "$time")
|
||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
handle_task_before "$@"
|
||||
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
|
||||
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
|
||||
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $@" | perl -pe "s|.*ID=(.*) $cmd_task $@\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
[[ $id ]] && update_cron "\"$id\"" "0" "$$" "$log_path" "$begin_timestamp"
|
||||
eval . $file_task_before "$@" $cmd
|
||||
shift
|
||||
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
|
||||
|
||||
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
|
||||
|
||||
eval . $file_task_after "$file_param" "$@" $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
|
||||
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))
|
||||
show_log="false"
|
||||
while getopts ":l" opt; do
|
||||
case $opt in
|
||||
l)
|
||||
show_log="true"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
||||
|
||||
timeoutCmd=""
|
||||
if type timeout &>/dev/null; then
|
||||
timeoutCmd="timeout -k 10s $command_timeout_time "
|
||||
fi
|
||||
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
|
||||
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 "$@"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
[[ -f "$dir_log/$log_path" ]] && cat "$dir_log/$log_path"
|
||||
elif [[ $# -eq 0 ]]; then
|
||||
echo
|
||||
usage
|
||||
else
|
||||
run_else "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
+394
-435
@@ -8,544 +8,503 @@ send_mark=$dir_shell/send_mark
|
||||
|
||||
## 检测cron的差异,$1:脚本清单文件路径,$2:cron任务清单文件路径,$3:增加任务清单文件路径,$4:删除任务清单文件路径
|
||||
diff_cron() {
|
||||
local list_scripts="$1"
|
||||
local list_task="$2"
|
||||
local list_add="$3"
|
||||
local list_drop="$4"
|
||||
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||
grep -vwf $list_task $list_scripts >$list_add
|
||||
grep -vwf $list_scripts $list_task >$list_drop
|
||||
fi
|
||||
local list_scripts="$1"
|
||||
local list_task="$2"
|
||||
local list_add="$3"
|
||||
local list_drop="$4"
|
||||
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||
grep -vwf $list_task $list_scripts >$list_add
|
||||
grep -vwf $list_scripts $list_task >$list_drop
|
||||
fi
|
||||
|
||||
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||
cp -f $list_scripts $list_add
|
||||
fi
|
||||
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
|
||||
cp -f $list_scripts $list_add
|
||||
fi
|
||||
|
||||
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
|
||||
cp -f $list_task $list_drop
|
||||
fi
|
||||
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
|
||||
cp -f $list_task $list_drop
|
||||
fi
|
||||
}
|
||||
|
||||
## 检测配置文件版本
|
||||
detect_config_version() {
|
||||
## 识别出两个文件的版本号
|
||||
ver_config_sample=$(grep " Version: " $file_config_sample | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
||||
[[ -f $file_config_user ]] && ver_config_user=$(grep " Version: " $file_config_user | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
||||
## 识别出两个文件的版本号
|
||||
ver_config_sample=$(grep " Version: " $file_config_sample | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
||||
[[ -f $file_config_user ]] && ver_config_user=$(grep " Version: " $file_config_user | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
||||
|
||||
## 删除旧的发送记录文件
|
||||
[[ -f $send_mark ]] && [[ $(cat $send_mark) != $ver_config_sample ]] && rm -f $send_mark
|
||||
## 删除旧的发送记录文件
|
||||
[[ -f $send_mark ]] && [[ $(cat $send_mark) != $ver_config_sample ]] && rm -f $send_mark
|
||||
|
||||
## 识别出更新日期和更新内容
|
||||
update_date=$(grep " Date: " $file_config_sample | awk -F ": " '{print $2}')
|
||||
update_content=$(grep " Update Content: " $file_config_sample | awk -F ": " '{print $2}')
|
||||
## 识别出更新日期和更新内容
|
||||
update_date=$(grep " Date: " $file_config_sample | awk -F ": " '{print $2}')
|
||||
update_content=$(grep " Update Content: " $file_config_sample | awk -F ": " '{print $2}')
|
||||
|
||||
## 如果是今天,并且版本号不一致,则发送通知
|
||||
if [[ -f $file_config_user ]] && [[ $ver_config_user != $ver_config_sample ]] && [[ $update_date == $(date "+%Y-%m-%d") ]]; then
|
||||
if [[ ! -f $send_mark ]]; then
|
||||
local notify_title="配置文件更新通知"
|
||||
local notify_content="更新日期: $update_date\n用户版本: $ver_config_user\n新的版本: $ver_config_sample\n更新内容: $update_content\n更新说明: 如需使用新功能请对照config.sample.sh,将相关新参数手动增加到你自己的config.sh中,否则请无视本消息。本消息只在该新版本配置文件更新当天发送一次。\n"
|
||||
echo -e $notify_content
|
||||
notify_api "$notify_title" "$notify_content"
|
||||
[[ $? -eq 0 ]] && echo $ver_config_sample >$send_mark
|
||||
fi
|
||||
else
|
||||
[[ -f $send_mark ]] && rm -f $send_mark
|
||||
## 如果是今天,并且版本号不一致,则发送通知
|
||||
if [[ -f $file_config_user ]] && [[ $ver_config_user != $ver_config_sample ]] && [[ $update_date == $(date "+%Y-%m-%d") ]]; then
|
||||
if [[ ! -f $send_mark ]]; then
|
||||
local notify_title="配置文件更新通知"
|
||||
local notify_content="更新日期: $update_date\n用户版本: $ver_config_user\n新的版本: $ver_config_sample\n更新内容: $update_content\n更新说明: 如需使用新功能请对照config.sample.sh,将相关新参数手动增加到你自己的config.sh中,否则请无视本消息。本消息只在该新版本配置文件更新当天发送一次。\n"
|
||||
echo -e $notify_content
|
||||
notify_api "$notify_title" "$notify_content"
|
||||
[[ $? -eq 0 ]] && echo $ver_config_sample >$send_mark
|
||||
fi
|
||||
else
|
||||
[[ -f $send_mark ]] && rm -f $send_mark
|
||||
fi
|
||||
}
|
||||
|
||||
## 输出是否有新的或失效的定时任务,$1:新的或失效的任务清单文件路径,$2:新/失效
|
||||
output_list_add_drop() {
|
||||
local list=$1
|
||||
local type=$2
|
||||
if [[ -s $list ]]; then
|
||||
echo -e "检测到有$type的定时任务:\n"
|
||||
cat $list
|
||||
echo
|
||||
fi
|
||||
local list=$1
|
||||
local type=$2
|
||||
if [[ -s $list ]]; then
|
||||
echo -e "检测到有$type的定时任务:\n"
|
||||
cat $list
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
## 自动删除失效的脚本与定时任务,需要:1.AutoDelCron 设置为 true;2.正常更新js脚本,没有报错;3.存在失效任务
|
||||
## $1:失效任务清单文件路径
|
||||
del_cron() {
|
||||
local list_drop=$1
|
||||
local path=$2
|
||||
local detail=""
|
||||
local ids=""
|
||||
echo -e "开始尝试自动删除失效的定时任务...\n"
|
||||
for cron in $(cat $list_drop); do
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $cron" | perl -pe "s|.*ID=(.*) $cmd_task $cron\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
||||
if [[ $ids ]]; then
|
||||
ids="$ids,\"$id\""
|
||||
else
|
||||
ids="\"$id\""
|
||||
fi
|
||||
cron_file="$dir_scripts/${cron}"
|
||||
if [[ -f $cron_file ]]; then
|
||||
cron_name=$(grep "new Env" $cron_file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||
rm -f $cron_file
|
||||
fi
|
||||
[[ -z $cron_name ]] && cron_name="$cron"
|
||||
if [[ $detail ]]; then
|
||||
detail="${detail}\n${cron_name}"
|
||||
else
|
||||
detail="${cron_name}"
|
||||
fi
|
||||
done
|
||||
local list_drop=$1
|
||||
local path=$2
|
||||
local detail=""
|
||||
local ids=""
|
||||
echo -e "开始尝试自动删除失效的定时任务...\n"
|
||||
for cron in $(cat $list_drop); do
|
||||
local id=$(cat $list_crontab_user | grep -E "$cmd_task $cron" | perl -pe "s|.*ID=(.*) $cmd_task $cron\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
||||
if [[ $ids ]]; then
|
||||
result=$(del_cron_api "$ids")
|
||||
notify_api "$path 删除任务${result}" "$detail"
|
||||
ids="$ids,\"$id\""
|
||||
else
|
||||
ids="\"$id\""
|
||||
fi
|
||||
cron_file="$dir_scripts/${cron}"
|
||||
if [[ -f $cron_file ]]; then
|
||||
cron_name=$(grep "new Env" $cron_file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||
rm -f $cron_file
|
||||
fi
|
||||
[[ -z $cron_name ]] && cron_name="$cron"
|
||||
if [[ $detail ]]; then
|
||||
detail="${detail}\n${cron_name}"
|
||||
else
|
||||
detail="${cron_name}"
|
||||
fi
|
||||
done
|
||||
if [[ $ids ]]; then
|
||||
result=$(del_cron_api "$ids")
|
||||
notify_api "$path 删除任务${result}" "$detail"
|
||||
fi
|
||||
}
|
||||
|
||||
## 自动增加定时任务,需要:1.AutoAddCron 设置为 true;2.正常更新js脚本,没有报错;3.存在新任务;4.crontab.list存在并且不为空
|
||||
## $1:新任务清单文件路径
|
||||
add_cron() {
|
||||
local list_add=$1
|
||||
local path=$2
|
||||
echo -e "开始尝试自动添加定时任务...\n"
|
||||
local detail=""
|
||||
cd $dir_scripts
|
||||
for file in $(cat $list_add); do
|
||||
local file_name=${file/${path}\//}
|
||||
file_name=${file_name/${path}\_/}
|
||||
if [[ -f $file ]]; then
|
||||
cron_line=$(
|
||||
perl -ne "{
|
||||
local list_add=$1
|
||||
local path=$2
|
||||
echo -e "开始尝试自动添加定时任务...\n"
|
||||
local detail=""
|
||||
cd $dir_scripts
|
||||
for file in $(cat $list_add); do
|
||||
local file_name=${file/${path}\//}
|
||||
file_name=${file_name/${path}\_/}
|
||||
if [[ -f $file ]]; then
|
||||
cron_line=$(
|
||||
perl -ne "{
|
||||
print if /.*([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*]( |,|\").*$file_name/
|
||||
}" $file |
|
||||
perl -pe "{
|
||||
perl -pe "{
|
||||
s|[^\d\*]*(([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*])( \|,\|\").*/?$file_name.*|\1|g;
|
||||
s|\*([\d\*])(.*)|\1\2|g;
|
||||
s| | |g;
|
||||
}" | sort -u | head -1
|
||||
)
|
||||
cron_name=$(grep "new Env" $file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||
[[ -z $cron_name ]] && cron_name="$file_name"
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $file | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $file | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line="$default_cron"
|
||||
result=$(add_cron_api "$cron_line:$cmd_task $file:$cron_name")
|
||||
echo -e "$result"
|
||||
if [[ $detail ]]; then
|
||||
detail="${detail}${result}\n"
|
||||
else
|
||||
detail="${result}\n"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
notify_api "$path 新增任务" "$detail"
|
||||
)
|
||||
cron_name=$(grep "new Env" $file | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||
[[ -z $cron_name ]] && cron_name="$file_name"
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $file | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $file | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line="$default_cron"
|
||||
result=$(add_cron_api "$cron_line:$cmd_task $file:$cron_name")
|
||||
echo -e "$result"
|
||||
if [[ $detail ]]; then
|
||||
detail="${detail}${result}\n"
|
||||
else
|
||||
detail="${result}\n"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
notify_api "$path 新增任务" "$detail"
|
||||
}
|
||||
|
||||
## 更新仓库
|
||||
update_repo() {
|
||||
local url="$1"
|
||||
local path="$2"
|
||||
local blackword="$3"
|
||||
local dependence="$4"
|
||||
local branch="$5"
|
||||
local extensions="$6"
|
||||
local tmp="${url%/*}"
|
||||
local authorTmp1="${tmp##*/}"
|
||||
local authorTmp2="${authorTmp1##*:}"
|
||||
local author="${authorTmp2##*.}"
|
||||
local url="$1"
|
||||
local path="$2"
|
||||
local blackword="$3"
|
||||
local dependence="$4"
|
||||
local branch="$5"
|
||||
local extensions="$6"
|
||||
local tmp="${url%/*}"
|
||||
local authorTmp1="${tmp##*/}"
|
||||
local authorTmp2="${authorTmp1##*:}"
|
||||
local author="${authorTmp2##*.}"
|
||||
|
||||
local repo_path="${dir_repo}/${uniq_path}"
|
||||
local repo_path="${dir_repo}/${uniq_path}"
|
||||
|
||||
make_dir "${dir_scripts}/${uniq_path}"
|
||||
make_dir "${dir_scripts}/${uniq_path}"
|
||||
|
||||
local formatUrl="$url"
|
||||
if [[ -d ${repo_path}/.git ]]; then
|
||||
reset_romote_url ${repo_path} "${formatUrl}" "${branch}"
|
||||
git_pull_scripts ${repo_path} "${branch}"
|
||||
else
|
||||
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}"
|
||||
fi
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新${repo_path}成功...\n"
|
||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
||||
else
|
||||
echo -e "\n更新${repo_path}失败,请检查网络...\n"
|
||||
fi
|
||||
local formatUrl="$url"
|
||||
if [[ -d ${repo_path}/.git ]]; then
|
||||
reset_romote_url ${repo_path} "${formatUrl}" "${branch}"
|
||||
git_pull_scripts ${repo_path} "${branch}"
|
||||
else
|
||||
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}"
|
||||
fi
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新${repo_path}成功...\n"
|
||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
||||
else
|
||||
echo -e "\n更新${repo_path}失败,请检查网络...\n"
|
||||
fi
|
||||
}
|
||||
|
||||
## 更新所有 raw 文件
|
||||
update_raw() {
|
||||
echo -e "--------------------------------------------------------------\n"
|
||||
local url="$1"
|
||||
local raw_url="$url"
|
||||
local suffix="${raw_url##*.}"
|
||||
local raw_file_name="${uniq_path}.${suffix}"
|
||||
echo -e "开始下载:${raw_url} \n\n保存路径:$dir_raw/${raw_file_name}\n"
|
||||
echo -e "--------------------------------------------------------------\n"
|
||||
local url="$1"
|
||||
local raw_url="$url"
|
||||
local suffix="${raw_url##*.}"
|
||||
local raw_file_name="${uniq_path}.${suffix}"
|
||||
echo -e "开始下载:${raw_url} \n\n保存路径:$dir_raw/${raw_file_name}\n"
|
||||
|
||||
set_proxy
|
||||
wget -q --no-check-certificate -O "$dir_raw/${raw_file_name}.new" ${raw_url}
|
||||
unset_proxy
|
||||
set_proxy
|
||||
wget -q --no-check-certificate -O "$dir_raw/${raw_file_name}.new" ${raw_url}
|
||||
unset_proxy
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}"
|
||||
echo -e "下载 ${raw_file_name} 成功...\n"
|
||||
cd $dir_raw
|
||||
local filename="raw_${raw_file_name}"
|
||||
local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task $filename" | perl -pe "s|.*ID=(.*) $cmd_task $filename\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
||||
cp -f $raw_file_name $dir_scripts/${filename}
|
||||
cron_line=$(
|
||||
perl -ne "{
|
||||
if [[ $? -eq 0 ]]; then
|
||||
mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}"
|
||||
echo -e "下载 ${raw_file_name} 成功...\n"
|
||||
cd $dir_raw
|
||||
local filename="raw_${raw_file_name}"
|
||||
local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task $filename" | perl -pe "s|.*ID=(.*) $cmd_task $filename\.*|\1|" | head -1 | head -1 | awk -F " " '{print $1}')
|
||||
cp -f $raw_file_name $dir_scripts/${filename}
|
||||
cron_line=$(
|
||||
perl -ne "{
|
||||
print if /.*([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*]( |,|\").*$raw_file_name/
|
||||
}" $raw_file_name |
|
||||
perl -pe "{
|
||||
perl -pe "{
|
||||
s|[^\d\*]*(([\d\*]*[\*-\/,\d]*[\d\*] ){4,5}[\d\*]*[\*-\/,\d]*[\d\*])( \|,\|\").*/?$raw_file_name.*|\1|g;
|
||||
s|\*([\d\*])(.*)|\1\2|g;
|
||||
s| | |g;
|
||||
}" | sort -u | head -1
|
||||
)
|
||||
cron_name=$(grep "new Env" $raw_file_name | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||
[[ -z $cron_name ]] && cron_name="$raw_file_name"
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $raw_file_name | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $raw_file_name | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line="$default_cron"
|
||||
if [[ -z $cron_id ]]; then
|
||||
result=$(add_cron_api "$cron_line:$cmd_task $filename:$cron_name")
|
||||
echo -e "$result\n"
|
||||
notify_api "新增任务通知" "\n$result"
|
||||
# update_cron_api "$cron_line:$cmd_task $filename:$cron_name:$cron_id"
|
||||
fi
|
||||
else
|
||||
echo -e "下载 ${raw_file_name} 失败,保留之前正常下载的版本...\n"
|
||||
[[ -f "$dir_raw/${raw_file_name}.new" ]] && rm -f "$dir_raw/${raw_file_name}.new"
|
||||
)
|
||||
cron_name=$(grep "new Env" $raw_file_name | awk -F "\(" '{print $2}' | awk -F "\)" '{print $1}' | sed 's:^.\(.*\).$:\1:' | head -1)
|
||||
[[ -z $cron_name ]] && cron_name="$raw_file_name"
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron:" $raw_file_name | awk -F ":" '{print $2}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line=$(grep "cron " $raw_file_name | awk -F "cron \"" '{print $2}' | awk -F "\" " '{print $1}' | head -1 | xargs)
|
||||
[[ -z $cron_line ]] && cron_line="$default_cron"
|
||||
if [[ -z $cron_id ]]; then
|
||||
result=$(add_cron_api "$cron_line:$cmd_task $filename:$cron_name")
|
||||
echo -e "$result\n"
|
||||
notify_api "新增任务通知" "\n$result"
|
||||
# update_cron_api "$cron_line:$cmd_task $filename:$cron_name:$cron_id"
|
||||
fi
|
||||
else
|
||||
echo -e "下载 ${raw_file_name} 失败,保留之前正常下载的版本...\n"
|
||||
[[ -f "$dir_raw/${raw_file_name}.new" ]] && rm -f "$dir_raw/${raw_file_name}.new"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
## 调用用户自定义的extra.sh
|
||||
run_extra_shell() {
|
||||
if [[ ${EnableExtraShell} == true ]]; then
|
||||
if [[ -f $file_extra_shell ]]; then
|
||||
echo -e "--------------------------------------------------------------\n"
|
||||
. $file_extra_shell
|
||||
else
|
||||
echo -e "$file_extra_shell文件不存在,跳过执行...\n"
|
||||
fi
|
||||
if [[ ${EnableExtraShell} == true ]]; then
|
||||
if [[ -f $file_extra_shell ]]; then
|
||||
echo -e "--------------------------------------------------------------\n"
|
||||
. $file_extra_shell
|
||||
else
|
||||
echo -e "$file_extra_shell文件不存在,跳过执行...\n"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
## 脚本用法
|
||||
usage() {
|
||||
echo -e "本脚本用法:"
|
||||
echo -e "1. $cmd_update update # 更新并重启青龙"
|
||||
echo -e "2. $cmd_update extra # 运行自定义脚本"
|
||||
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"
|
||||
echo -e "7. $cmd_update check # 检测青龙环境并修复"
|
||||
echo -e "8. $cmd_update resetlet # 重置登录错误次数"
|
||||
echo -e "9. $cmd_update resettfa # 禁用两步登录"
|
||||
echo -e "本脚本用法:"
|
||||
echo -e "1. $cmd_update update # 更新并重启青龙"
|
||||
echo -e "2. $cmd_update extra # 运行自定义脚本"
|
||||
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"
|
||||
echo -e "7. $cmd_update check # 检测青龙环境并修复"
|
||||
echo -e "8. $cmd_update resetlet # 重置登录错误次数"
|
||||
echo -e "9. $cmd_update resettfa # 禁用两步登录"
|
||||
}
|
||||
|
||||
## 更新qinglong
|
||||
update_qinglong() {
|
||||
patch_version
|
||||
patch_version &>/dev/null
|
||||
|
||||
local no_restart="$1"
|
||||
local all_branch=$(git branch -a)
|
||||
local primary_branch="master"
|
||||
if [[ "${all_branch}" =~ "${current_branch}" ]]; then
|
||||
primary_branch="${current_branch}"
|
||||
fi
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
||||
reset_romote_url ${dir_root} "https://github.com/whyour/qinglong.git" ${primary_branch}
|
||||
git_pull_scripts $dir_root ${primary_branch}
|
||||
export isFirstStartServer=false
|
||||
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新青龙源文件成功...\n"
|
||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
||||
detect_config_version
|
||||
update_depend
|
||||
local all_branch=$(git branch -a)
|
||||
local primary_branch="master"
|
||||
if [[ "${all_branch}" =~ "${current_branch}" ]]; then
|
||||
primary_branch="${current_branch}"
|
||||
fi
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
||||
reset_romote_url ${dir_root} "https://github.com/whyour/qinglong.git" ${primary_branch}
|
||||
git_pull_scripts $dir_root ${primary_branch}
|
||||
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
||||
[[ "$ql_depend_old" != "$ql_depend_new" ]] && npm_install_2 $dir_root
|
||||
else
|
||||
echo -e "\n更新青龙源文件失败,请检查原因...\n"
|
||||
fi
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新青龙源文件成功...\n"
|
||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
||||
detect_config_version
|
||||
update_depend
|
||||
|
||||
local url="https://github.com/whyour/qinglong-static.git"
|
||||
if [[ -d ${ql_static_repo}/.git ]]; then
|
||||
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
||||
git_pull_scripts ${ql_static_repo} ${primary_branch}
|
||||
else
|
||||
git_clone_scripts ${url} ${ql_static_repo} ${primary_branch}
|
||||
fi
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新青龙静态资源成功...\n"
|
||||
local static_version=$(cat $dir_root/src/version.ts | perl -pe "s|.*\'(.*)\';\.*|\1|" | head -1)
|
||||
echo -e "\n当前版本 $static_version...\n"
|
||||
|
||||
rm -rf $dir_static/*
|
||||
cp -rf $ql_static_repo/* $dir_static
|
||||
if [[ $no_restart != "no-restart" ]]; then
|
||||
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
||||
echo -e "重启面板中..."
|
||||
sleep 3
|
||||
reload_pm2
|
||||
fi
|
||||
else
|
||||
echo -e "\n更新青龙静态资源失败,请检查原因...\n"
|
||||
fi
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
||||
[[ "$ql_depend_old" != "$ql_depend_new" ]] && npm_install_2 $dir_root
|
||||
|
||||
update_qinglong_static "$1" "$primary_branch"
|
||||
else
|
||||
echo -e "\n更新青龙源文件失败,请检查网络...\n"
|
||||
fi
|
||||
}
|
||||
|
||||
patch_version() {
|
||||
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
||||
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
||||
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
||||
rm -rf $dir_root/db/cookie.db
|
||||
echo
|
||||
update_qinglong_static() {
|
||||
local no_restart="$1"
|
||||
local primary_branch="$2"
|
||||
local url="https://github.com/whyour/qinglong-static.git"
|
||||
if [[ -d ${ql_static_repo}/.git ]]; then
|
||||
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
||||
git_pull_scripts ${ql_static_repo} ${primary_branch}
|
||||
else
|
||||
git_clone_scripts ${url} ${ql_static_repo} ${primary_branch}
|
||||
fi
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新青龙静态资源成功...\n"
|
||||
local static_version=$(cat $dir_root/src/version.ts | perl -pe "s|.*\'(.*)\';\.*|\1|" | head -1)
|
||||
echo -e "\n当前版本 $static_version...\n"
|
||||
|
||||
rm -rf $dir_static/*
|
||||
cp -rf $ql_static_repo/* $dir_static
|
||||
if [[ $no_restart != "no-restart" ]]; then
|
||||
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
||||
echo -e "重启面板中..."
|
||||
sleep 3
|
||||
reload_pm2
|
||||
fi
|
||||
|
||||
if ! type ts-node &>/dev/null; then
|
||||
pnpm i -g ts-node typescript tslib
|
||||
fi
|
||||
|
||||
# 兼容pnpm@7
|
||||
pnpm setup
|
||||
source ~/.bashrc
|
||||
|
||||
git config --global pull.rebase false
|
||||
|
||||
cp -f $dir_root/.env.example $dir_root/.env
|
||||
|
||||
if [[ -d "$dir_root/db" ]]; then
|
||||
echo -e "检测到旧的db目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/config $dir_root/data
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ -d "$dir_root/scripts" ]]; then
|
||||
echo -e "检测到旧的scripts目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/scripts $dir_root/data
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ -d "$dir_root/log" ]]; then
|
||||
echo -e "检测到旧的log目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/log $dir_root/data
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ -d "$dir_root/config" ]]; then
|
||||
echo -e "检测到旧的config目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/config $dir_root/data
|
||||
echo
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\n更新青龙静态资源失败,请检查网络...\n"
|
||||
fi
|
||||
}
|
||||
|
||||
## 对比脚本
|
||||
diff_scripts() {
|
||||
local dir_current=$(pwd)
|
||||
local repo_path="$1"
|
||||
local author="$2"
|
||||
local path="$3"
|
||||
local blackword="$4"
|
||||
local dependence="$5"
|
||||
local extensions="$6"
|
||||
local dir_current=$(pwd)
|
||||
local repo_path="$1"
|
||||
local author="$2"
|
||||
local path="$3"
|
||||
local blackword="$4"
|
||||
local dependence="$5"
|
||||
local extensions="$6"
|
||||
|
||||
gen_list_repo "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
||||
gen_list_repo "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions"
|
||||
|
||||
local list_add="$dir_list_tmp/${uniq_path}_add.list"
|
||||
local list_drop="$dir_list_tmp/${uniq_path}_drop.list"
|
||||
diff_cron "$dir_list_tmp/${uniq_path}_scripts.list" "$dir_list_tmp/${uniq_path}_user.list" $list_add $list_drop
|
||||
local list_add="$dir_list_tmp/${uniq_path}_add.list"
|
||||
local list_drop="$dir_list_tmp/${uniq_path}_drop.list"
|
||||
diff_cron "$dir_list_tmp/${uniq_path}_scripts.list" "$dir_list_tmp/${uniq_path}_user.list" $list_add $list_drop
|
||||
|
||||
if [[ -s $list_drop ]]; then
|
||||
output_list_add_drop $list_drop "失效"
|
||||
if [[ ${AutoDelCron} == true ]]; then
|
||||
del_cron $list_drop $uniq_path
|
||||
fi
|
||||
if [[ -s $list_drop ]]; then
|
||||
output_list_add_drop $list_drop "失效"
|
||||
if [[ ${AutoDelCron} == true ]]; then
|
||||
del_cron $list_drop $uniq_path
|
||||
fi
|
||||
if [[ -s $list_add ]]; then
|
||||
output_list_add_drop $list_add "新"
|
||||
if [[ ${AutoAddCron} == true ]]; then
|
||||
add_cron $list_add $uniq_path
|
||||
fi
|
||||
fi
|
||||
if [[ -s $list_add ]]; then
|
||||
output_list_add_drop $list_add "新"
|
||||
if [[ ${AutoAddCron} == true ]]; then
|
||||
add_cron $list_add $uniq_path
|
||||
fi
|
||||
cd $dir_current
|
||||
fi
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
## 生成脚本的路径清单文件
|
||||
gen_list_repo() {
|
||||
local dir_current=$(pwd)
|
||||
local repo_path="$1"
|
||||
local author="$2"
|
||||
local path="$3"
|
||||
local blackword="$4"
|
||||
local dependence="$5"
|
||||
local dir_current=$(pwd)
|
||||
local repo_path="$1"
|
||||
local author="$2"
|
||||
local path="$3"
|
||||
local blackword="$4"
|
||||
local dependence="$5"
|
||||
|
||||
rm -f $dir_list_tmp/${uniq_path}*.list &>/dev/null
|
||||
rm -f $dir_list_tmp/${uniq_path}*.list &>/dev/null
|
||||
|
||||
cd ${repo_path}
|
||||
|
||||
local cmd="find ."
|
||||
local index=0
|
||||
if [[ $6 ]]; then
|
||||
file_extensions="$6"
|
||||
if [[ $file_extensions =~ "|" ]]; then
|
||||
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
|
||||
fi
|
||||
fi
|
||||
for extension in $file_extensions; do
|
||||
if [[ $index -eq 0 ]]; then
|
||||
cmd="${cmd} -name \"*.${extension}\""
|
||||
else
|
||||
cmd="${cmd} -o -name \"*.${extension}\""
|
||||
fi
|
||||
let index+=1
|
||||
done
|
||||
files=$(eval $cmd | sed 's/^..//')
|
||||
if [[ $path ]]; then
|
||||
files=$(echo "$files" | egrep $path)
|
||||
fi
|
||||
if [[ $blackword ]]; then
|
||||
files=$(echo "$files" | egrep -v $blackword)
|
||||
fi
|
||||
|
||||
cp -f $file_notify_js "${dir_scripts}/${uniq_path}"
|
||||
cp -f $file_notify_py "${dir_scripts}/${uniq_path}"
|
||||
|
||||
if [[ $dependence ]]; then
|
||||
cd ${repo_path}
|
||||
|
||||
local cmd="find ."
|
||||
local index=0
|
||||
if [[ $6 ]]; then
|
||||
file_extensions="$6"
|
||||
if [[ $file_extensions =~ "|" ]]; then
|
||||
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
|
||||
fi
|
||||
fi
|
||||
for extension in $file_extensions; do
|
||||
if [[ $index -eq 0 ]]; then
|
||||
cmd="${cmd} -name \"*.${extension}\""
|
||||
else
|
||||
cmd="${cmd} -o -name \"*.${extension}\""
|
||||
fi
|
||||
let index+=1
|
||||
results=$(eval $cmd | sed 's/^..//' | egrep $dependence)
|
||||
for _file in ${results}; do
|
||||
file_path=$(dirname $_file)
|
||||
make_dir "${dir_scripts}/${uniq_path}/${file_path}"
|
||||
cp -f $_file "${dir_scripts}/${uniq_path}/${file_path}"
|
||||
done
|
||||
files=$(eval $cmd | sed 's/^..//')
|
||||
if [[ $path ]]; then
|
||||
files=$(echo "$files" | egrep $path)
|
||||
fi
|
||||
if [[ $blackword ]]; then
|
||||
files=$(echo "$files" | egrep -v $blackword)
|
||||
fi
|
||||
fi
|
||||
|
||||
cp -f $file_notify_js "${dir_scripts}/${uniq_path}"
|
||||
cp -f $file_notify_py "${dir_scripts}/${uniq_path}"
|
||||
if [[ -d $dir_dep ]]; then
|
||||
cp -rf $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null
|
||||
fi
|
||||
|
||||
if [[ $dependence ]]; then
|
||||
cd ${repo_path}
|
||||
results=$(eval $cmd | sed 's/^..//' | egrep $dependence)
|
||||
for _file in ${results}; do
|
||||
file_path=$(dirname $_file)
|
||||
make_dir "${dir_scripts}/${uniq_path}/${file_path}"
|
||||
cp -f $_file "${dir_scripts}/${uniq_path}/${file_path}"
|
||||
done
|
||||
for file in ${files}; do
|
||||
filename=$(basename $file)
|
||||
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
||||
echo "${uniq_path}/${filename}" >>"$dir_list_tmp/${uniq_path}_scripts.list"
|
||||
cron_id=$(cat $list_crontab_user | grep -E "$cmd_task ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
if [[ $cron_id ]]; then
|
||||
result=$(update_cron_command_api "$cmd_task ${uniq_path}/${filename}:$cron_id")
|
||||
fi
|
||||
|
||||
if [[ -d $dir_dep ]]; then
|
||||
cp -rf $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null
|
||||
fi
|
||||
|
||||
for file in ${files}; do
|
||||
filename=$(basename $file)
|
||||
cp -f $file "$dir_scripts/${uniq_path}/${filename}"
|
||||
echo "${uniq_path}/${filename}" >>"$dir_list_tmp/${uniq_path}_scripts.list"
|
||||
cron_id=$(cat $list_crontab_user | grep -E "$cmd_task ${uniq_path}_${filename}" | perl -pe "s|.*ID=(.*) $cmd_task ${uniq_path}_${filename}\.*|\1|" | head -1 | awk -F " " '{print $1}')
|
||||
if [[ $cron_id ]]; then
|
||||
result=$(update_cron_command_api "$cmd_task ${uniq_path}/${filename}:$cron_id")
|
||||
fi
|
||||
done
|
||||
grep -E "${cmd_task} ${uniq_path}" ${list_crontab_user} | perl -pe "s|.*ID=(.*) ${cmd_task} (${uniq_path}.*)\.*|\2|" | awk -F " " '{print $1}' | sort -u >"$dir_list_tmp/${uniq_path}_user.list"
|
||||
cd $dir_current
|
||||
done
|
||||
grep -E "${cmd_task} ${uniq_path}" ${list_crontab_user} | perl -pe "s|.*ID=(.*) ${cmd_task} (${uniq_path}.*)\.*|\2|" | awk -F " " '{print $1}' | sort -u >"$dir_list_tmp/${uniq_path}_user.list"
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
get_uniq_path() {
|
||||
local url="$1"
|
||||
local branch="$2"
|
||||
local urlTmp="${url%*/}"
|
||||
local repoTmp="${urlTmp##*/}"
|
||||
local repo="${repoTmp%.*}"
|
||||
local tmp="${url%/*}"
|
||||
local authorTmp1="${tmp##*/}"
|
||||
local authorTmp2="${authorTmp1##*:}"
|
||||
local author="${authorTmp2##*.}"
|
||||
local url="$1"
|
||||
local branch="$2"
|
||||
local urlTmp="${url%*/}"
|
||||
local repoTmp="${urlTmp##*/}"
|
||||
local repo="${repoTmp%.*}"
|
||||
local tmp="${url%/*}"
|
||||
local authorTmp1="${tmp##*/}"
|
||||
local authorTmp2="${authorTmp1##*:}"
|
||||
local author="${authorTmp2##*.}"
|
||||
|
||||
uniq_path="${author}_${repo}"
|
||||
[[ $branch ]] && uniq_path="${uniq_path}_${branch}"
|
||||
uniq_path="${author}_${repo}"
|
||||
[[ $branch ]] && uniq_path="${uniq_path}_${branch}"
|
||||
}
|
||||
|
||||
main() {
|
||||
## for ql update
|
||||
show_log="false"
|
||||
while getopts ":l" opt
|
||||
do
|
||||
case $opt in
|
||||
l)
|
||||
show_log="true"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
||||
|
||||
local p1=$1
|
||||
local p2=$2
|
||||
local p3=$3
|
||||
local p4=$4
|
||||
local p5=$5
|
||||
local p6=$6
|
||||
local p7=$7
|
||||
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')
|
||||
|
||||
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
|
||||
;;
|
||||
repo)
|
||||
get_uniq_path "$p2" "$p6"
|
||||
if [[ -n $p2 ]]; then
|
||||
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
|
||||
else
|
||||
echo -e "命令输入错误...\n"
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
raw)
|
||||
get_uniq_path "$p2"
|
||||
if [[ -n $p2 ]]; then
|
||||
update_raw "$p2"
|
||||
else
|
||||
echo -e "命令输入错误...\n"
|
||||
usage
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
*)
|
||||
echo -e "命令输入错误...\n"
|
||||
usage
|
||||
;;
|
||||
## for ql update
|
||||
show_log="false"
|
||||
while getopts ":l" opt; do
|
||||
case $opt in
|
||||
l)
|
||||
show_log="true"
|
||||
;;
|
||||
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
|
||||
done
|
||||
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
|
||||
|
||||
local p1=$1
|
||||
local p2=$2
|
||||
local p3=$3
|
||||
local p4=$4
|
||||
local p5=$5
|
||||
local p6=$6
|
||||
local p7=$7
|
||||
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')
|
||||
|
||||
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
|
||||
;;
|
||||
repo)
|
||||
get_uniq_path "$p2" "$p6"
|
||||
if [[ -n $p2 ]]; then
|
||||
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
|
||||
else
|
||||
echo -e "命令输入错误...\n"
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
raw)
|
||||
get_uniq_path "$p2"
|
||||
if [[ -n $p2 ]]; then
|
||||
update_raw "$p2"
|
||||
else
|
||||
echo -e "命令输入错误...\n"
|
||||
usage
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
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
|
||||
;;
|
||||
*)
|
||||
echo -e "命令输入错误...\n"
|
||||
usage
|
||||
;;
|
||||
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
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -50,6 +50,7 @@ const EditableTagGroup = ({
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
return (
|
||||
<span key={tag} style={{ display: 'inline-block', marginBottom: 8 }}>
|
||||
{tagElem}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
export default (
|
||||
treeData: any[],
|
||||
searchValue: string,
|
||||
{
|
||||
treeNodeFilterProp,
|
||||
}: {
|
||||
treeNodeFilterProp: string;
|
||||
},
|
||||
) => {
|
||||
return useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
|
||||
if (!searchValue) {
|
||||
return { treeData, keys };
|
||||
}
|
||||
|
||||
const upperStr = searchValue.toUpperCase();
|
||||
function filterOptionFunc(_: string, dataNode: any[]) {
|
||||
const value = dataNode[treeNodeFilterProp as any];
|
||||
|
||||
return String(value).toUpperCase().includes(upperStr);
|
||||
}
|
||||
|
||||
function dig(list: any[], keepAll: boolean = false): any[] {
|
||||
return list
|
||||
.map((dataNode) => {
|
||||
const children = dataNode.children;
|
||||
|
||||
const match = keepAll || filterOptionFunc!(searchValue, dataNode);
|
||||
const childList = dig(children || [], match);
|
||||
|
||||
if (match || childList.length) {
|
||||
childList.length && keys.push(dataNode.key);
|
||||
return {
|
||||
...dataNode,
|
||||
children: childList,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((node) => node);
|
||||
}
|
||||
|
||||
return { treeData: dig(treeData), keys };
|
||||
}, [treeData, searchValue, treeNodeFilterProp]);
|
||||
};
|
||||
@@ -68,18 +68,18 @@ export default {
|
||||
icon: <IconFont type="ql-icon-dependence" />,
|
||||
component: '@/pages/dependence/index',
|
||||
},
|
||||
{
|
||||
path: '/log',
|
||||
name: '日志管理',
|
||||
icon: <IconFont type="ql-icon-log" />,
|
||||
component: '@/pages/log/index',
|
||||
},
|
||||
{
|
||||
path: '/diff',
|
||||
name: '对比工具',
|
||||
icon: <IconFont type="ql-icon-diff" />,
|
||||
component: '@/pages/diff/index',
|
||||
},
|
||||
{
|
||||
path: '/log',
|
||||
name: '任务日志',
|
||||
icon: <IconFont type="ql-icon-log" />,
|
||||
component: '@/pages/log/index',
|
||||
},
|
||||
{
|
||||
path: '/setting',
|
||||
name: '系统设置',
|
||||
|
||||
+21
-28
@@ -161,7 +161,7 @@
|
||||
&.env-wrapper,
|
||||
&.config-wrapper {
|
||||
.CodeMirror {
|
||||
width: calc(100vw - 80px);
|
||||
width: calc(100vw - 24px);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,31 +243,28 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
[data-dark='true'] {
|
||||
/* Change autocomplete styles in WebKit */
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
textarea:-webkit-autofill,
|
||||
textarea:-webkit-autofill:hover,
|
||||
textarea:-webkit-autofill:focus,
|
||||
select:-webkit-autofill,
|
||||
select:-webkit-autofill:hover,
|
||||
select:-webkit-autofill:focus {
|
||||
border: 1px solid @border-color-base;
|
||||
box-shadow: none;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
-webkit-text-fill-color: @text-color;
|
||||
caret-color: #e8e6f3 !important;
|
||||
}
|
||||
/* Change autocomplete styles in WebKit */
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
textarea:-webkit-autofill,
|
||||
textarea:-webkit-autofill:hover,
|
||||
textarea:-webkit-autofill:focus,
|
||||
select:-webkit-autofill,
|
||||
select:-webkit-autofill:hover,
|
||||
select:-webkit-autofill:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
-webkit-text-fill-color: @text-color;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
::placeholder {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
.ant-select-selection-placeholder {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.ant-pro-basicLayout-content {
|
||||
@@ -326,10 +323,6 @@
|
||||
background-color: #373739;
|
||||
}
|
||||
|
||||
.ant-layout-sider {
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||
}
|
||||
|
||||
.ant-pro-sider-logo {
|
||||
padding: 16px 8px !important;
|
||||
|
||||
|
||||
+54
-39
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react';
|
||||
import ProLayout, { PageLoading } from '@ant-design/pro-layout';
|
||||
import * as DarkReader from '@umijs/ssr-darkreader';
|
||||
import defaultProps from './defaultProps';
|
||||
import { Link, history } from 'umi';
|
||||
import { Link, history, Outlet, useLocation } from '@umijs/max';
|
||||
import {
|
||||
LogoutOutlined,
|
||||
MenuFoldOutlined,
|
||||
@@ -20,8 +20,22 @@ import { message, Badge, Modal, Avatar, Dropdown, Menu, Image } from 'antd';
|
||||
import SockJS from 'sockjs-client';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { init } from '../utils/init';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
|
||||
export default function (props: any) {
|
||||
export interface SharedContext {
|
||||
headerStyle: React.CSSProperties;
|
||||
isPhone: boolean;
|
||||
theme: 'vs' | 'vs-dark';
|
||||
user: any;
|
||||
reloadUser: (needLoading?: boolean) => void;
|
||||
reloadTheme: () => void;
|
||||
socketMessage: any;
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const location = useLocation();
|
||||
const ctx = useCtx();
|
||||
const { theme, reloadTheme } = useTheme();
|
||||
const [user, setUser] = useState<any>({});
|
||||
@@ -30,6 +44,7 @@ export default function (props: any) {
|
||||
const ws = useRef<any>(null);
|
||||
const [socketMessage, setSocketMessage] = useState<any>();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [initLoading, setInitLoading] = useState<boolean>(true);
|
||||
const {
|
||||
enable: enableDarkMode,
|
||||
disable: disableDarkMode,
|
||||
@@ -53,17 +68,15 @@ export default function (props: any) {
|
||||
setSystemInfo(data);
|
||||
if (!data.isInitialized) {
|
||||
history.push('/initialization');
|
||||
setLoading(false);
|
||||
} else {
|
||||
getUser();
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
})
|
||||
.finally(() => setInitLoading(false));
|
||||
};
|
||||
|
||||
const getUser = (needLoading = true) => {
|
||||
@@ -73,11 +86,9 @@ export default function (props: any) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && data.username) {
|
||||
setUser(data);
|
||||
if (props.location.pathname === '/') {
|
||||
if (location.pathname === '/') {
|
||||
history.push('/crontab');
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
needLoading && setLoading(false);
|
||||
})
|
||||
@@ -94,7 +105,7 @@ export default function (props: any) {
|
||||
if (systemInfo && systemInfo.isInitialized && !user) {
|
||||
getUser();
|
||||
}
|
||||
}, [props.location.pathname]);
|
||||
}, [location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemInfo) {
|
||||
@@ -136,9 +147,11 @@ export default function (props: any) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
if (!user || !user.username) return;
|
||||
ws.current = new SockJS(
|
||||
`${location.origin}/api/ws?token=${localStorage.getItem(config.authKey)}`,
|
||||
`${window.location.origin}/api/ws?token=${localStorage.getItem(
|
||||
config.authKey,
|
||||
)}`,
|
||||
);
|
||||
|
||||
ws.current.onmessage = (e: any) => {
|
||||
@@ -146,9 +159,9 @@ export default function (props: any) {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'ping') {
|
||||
if (data && data.message === 'hanhh') {
|
||||
console.log('websocket连接成功', e);
|
||||
console.log('WS connection succeeded !!!');
|
||||
} else {
|
||||
console.log('websocket连接失败', e);
|
||||
console.log('WS connection Failed !!!', e);
|
||||
}
|
||||
}
|
||||
setSocketMessage(data);
|
||||
@@ -183,29 +196,31 @@ export default function (props: any) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (
|
||||
['/login', '/initialization', '/error'].includes(props.location.pathname)
|
||||
) {
|
||||
document.title = `${(config.documentTitleMap as any)[props.location.pathname]
|
||||
} - 控制面板`;
|
||||
if (
|
||||
systemInfo?.isInitialized &&
|
||||
props.location.pathname === '/initialization'
|
||||
) {
|
||||
if (initLoading) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
if (['/login', '/initialization', '/error'].includes(location.pathname)) {
|
||||
document.title = `${
|
||||
(config.documentTitleMap as any)[location.pathname]
|
||||
} - 控制面板`;
|
||||
if (systemInfo?.isInitialized && location.pathname === '/initialization') {
|
||||
history.push('/crontab');
|
||||
}
|
||||
|
||||
if (systemInfo || props.location.pathname === '/error') {
|
||||
return React.Children.map(props.children, (child) => {
|
||||
return React.cloneElement(child, {
|
||||
...ctx,
|
||||
theme,
|
||||
user,
|
||||
reloadUser,
|
||||
reloadTheme,
|
||||
ws: ws.current,
|
||||
});
|
||||
});
|
||||
if (systemInfo || location.pathname === '/error') {
|
||||
return (
|
||||
<Outlet
|
||||
context={{
|
||||
...ctx,
|
||||
theme,
|
||||
user,
|
||||
reloadUser,
|
||||
reloadTheme,
|
||||
ws: ws.current,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +241,7 @@ export default function (props: any) {
|
||||
<PageLoading />
|
||||
) : (
|
||||
<ProLayout
|
||||
selectedKeys={[props.location.pathname]}
|
||||
selectedKeys={[location.pathname]}
|
||||
loading={loading}
|
||||
ErrorBoundary={Sentry.ErrorBoundary}
|
||||
logo={<Image preview={false} src="http://qn.whyour.cn/logo.png" />}
|
||||
@@ -319,16 +334,16 @@ export default function (props: any) {
|
||||
)}
|
||||
{...defaultProps}
|
||||
>
|
||||
{React.Children.map(props.children, (child) => {
|
||||
return React.cloneElement(child, {
|
||||
<Outlet
|
||||
context={{
|
||||
...ctx,
|
||||
theme,
|
||||
user,
|
||||
reloadUser,
|
||||
reloadTheme,
|
||||
socketMessage,
|
||||
});
|
||||
})}
|
||||
}}
|
||||
/>
|
||||
</ProLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ import { PageContainer } from '@ant-design/pro-layout';
|
||||
import { request } from '@/utils/http';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import { useOutletContext } from '@umijs/max';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const Config = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const Config = () => {
|
||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||
const [value, setValue] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [title, setTitle] = useState('config.sh');
|
||||
@@ -22,8 +25,10 @@ const Config = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const getConfig = (name: string) => {
|
||||
request.get(`${config.apiPrefix}configs/${name}`).then((data: any) => {
|
||||
setValue(data.data);
|
||||
request.get(`${config.apiPrefix}configs/${name}`).then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -31,8 +36,10 @@ const Config = ({ headerStyle, isPhone, theme }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/files`)
|
||||
.then((data: any) => {
|
||||
setData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -47,8 +54,10 @@ const Config = ({ headerStyle, isPhone, theme }: any) => {
|
||||
.post(`${config.apiPrefix}configs/save`, {
|
||||
data: { content, name: select },
|
||||
})
|
||||
.then((data: any) => {
|
||||
message.success(data.message);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('保存成功');
|
||||
}
|
||||
setConfirmLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -103,7 +103,6 @@ const CronDetailModal = ({
|
||||
fontSize: 12,
|
||||
lineNumbersMinChars: 3,
|
||||
fontFamily: 'Source Code Pro',
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
@@ -123,9 +122,11 @@ const CronDetailModal = ({
|
||||
.get(
|
||||
`${config.apiPrefix}logs/${item.filename}?path=${item.directory || ''}`,
|
||||
)
|
||||
.then((data) => {
|
||||
setLog(data.data);
|
||||
setIsLogModalVisible(true);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLog(data);
|
||||
setIsLogModalVisible(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -137,9 +138,9 @@ const CronDetailModal = ({
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/${cron.id}/logs`)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
setLogs(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLogs(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -153,16 +154,22 @@ const CronDetailModal = ({
|
||||
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
||||
}
|
||||
|
||||
let [p, s] = cmd[1].split('/');
|
||||
if (!s) {
|
||||
s = p;
|
||||
let p: string, s: string;
|
||||
let index = cmd[1].lastIndexOf('/');
|
||||
if (index >= 0) {
|
||||
s = cmd[1].slice(index + 1);
|
||||
p = cmd[1].slice(0, index);
|
||||
} else {
|
||||
s = cmd[1];
|
||||
p = '';
|
||||
}
|
||||
setScriptInfo({ parent: p, filename: s });
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setValidTabs([validTabs[0]]);
|
||||
@@ -194,12 +201,10 @@ const CronDetailModal = ({
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(content);
|
||||
message.success(`保存成功`);
|
||||
} else {
|
||||
message.error(_data);
|
||||
}
|
||||
resolve(null);
|
||||
})
|
||||
@@ -227,14 +232,12 @@ const CronDetailModal = ({
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
|
||||
setTimeout(() => {
|
||||
getLogs();
|
||||
}, 1000);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -259,11 +262,9 @@ const CronDetailModal = ({
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -296,14 +297,12 @@ const CronDetailModal = ({
|
||||
data: [currentCron.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({
|
||||
...currentCron,
|
||||
isDisabled: currentCron.isDisabled === 1 ? 0 : 1,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -336,14 +335,12 @@ const CronDetailModal = ({
|
||||
data: [currentCron.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({
|
||||
...currentCron,
|
||||
isPinned: currentCron.isPinned === 1 ? 0 : 1,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -378,7 +375,7 @@ const CronDetailModal = ({
|
||||
{currentCron.labels?.length > 0 &&
|
||||
currentCron.labels[0] !== '' &&
|
||||
currentCron.labels?.map((label: string, i: number) => (
|
||||
<Tag color="blue" style={{ marginRight: 5 }}>
|
||||
<Tag key={label} color="blue" style={{ marginRight: 5 }}>
|
||||
{label}
|
||||
</Tag>
|
||||
))}
|
||||
@@ -441,7 +438,7 @@ const CronDetailModal = ({
|
||||
</div>
|
||||
}
|
||||
centered
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
footer={false}
|
||||
onCancel={() => handleCancel()}
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.cron {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.crontab-detail {
|
||||
.card-wrapper {
|
||||
height: 80vh;
|
||||
@@ -161,6 +155,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.view-create-modal-sorts {
|
||||
display: flex;
|
||||
|
||||
.ant-space-item:nth-child(2) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
tr.drop-over-downward td {
|
||||
border-bottom: 2px dashed #1890ff;
|
||||
}
|
||||
|
||||
+109
-81
@@ -44,12 +44,13 @@ import CronDetailModal from './detail';
|
||||
import cron_parser from 'cron-parser';
|
||||
import { diffTime } from '@/utils/date';
|
||||
import { getTableScroll } from '@/utils/index';
|
||||
import { history } from 'umi';
|
||||
import { history, useOutletContext } from '@umijs/max';
|
||||
import './index.less';
|
||||
import ViewCreateModal from './viewCreateModal';
|
||||
import ViewManageModal from './viewManageModal';
|
||||
import pagination from 'antd/lib/pagination';
|
||||
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { Search } = Input;
|
||||
@@ -81,7 +82,8 @@ enum OperationPath {
|
||||
'unpin',
|
||||
}
|
||||
|
||||
const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const Crontab = () => {
|
||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||
const columns: any = [
|
||||
{
|
||||
title: '名称',
|
||||
@@ -93,10 +95,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
<>
|
||||
<a
|
||||
onClick={() => {
|
||||
goToScriptManager(record);
|
||||
setDetailCron(record);
|
||||
setIsDetailModalVisible(true);
|
||||
}}
|
||||
>
|
||||
{record.labels?.length > 0 && record.labels[0] !== '' ? (
|
||||
{record.labels?.length > 0 && record.labels[0] !== '' && false ? (
|
||||
<Popover
|
||||
placement="right"
|
||||
trigger={isPhone ? 'click' : 'hover'}
|
||||
@@ -105,10 +108,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
{record.labels?.map((label: string) => (
|
||||
<Tag
|
||||
color="blue"
|
||||
key={label}
|
||||
style={{ cursor: 'point' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSearchValue(`label:${label}`);
|
||||
setSearchText(`label:${label}`);
|
||||
}}
|
||||
>
|
||||
<a>{label}</a>
|
||||
@@ -140,7 +145,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
title: '命令',
|
||||
dataIndex: 'command',
|
||||
key: 'command',
|
||||
width: 250,
|
||||
width: 300,
|
||||
align: 'center' as const,
|
||||
render: (text: string, record: any) => {
|
||||
return (
|
||||
@@ -152,7 +157,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
}}
|
||||
ellipsis={{ tooltip: text, rows: 2 }}
|
||||
>
|
||||
{text}
|
||||
<a
|
||||
onClick={() => {
|
||||
goToScriptManager(record);
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
</Paragraph>
|
||||
);
|
||||
},
|
||||
@@ -173,6 +184,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
{
|
||||
title: '最后运行时间',
|
||||
align: 'center' as const,
|
||||
dataIndex: 'last_execution_time',
|
||||
key: 'last_execution_time',
|
||||
width: 150,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => {
|
||||
@@ -202,6 +215,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
title: '最后运行时长',
|
||||
align: 'center' as const,
|
||||
width: 120,
|
||||
dataIndex: 'last_running_time',
|
||||
key: 'last_running_time',
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => {
|
||||
return a.last_running_time - b.last_running_time;
|
||||
@@ -358,6 +373,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
page: number;
|
||||
size: number;
|
||||
sorter: any;
|
||||
filters: any;
|
||||
}>({} as any);
|
||||
const [viewConf, setViewConf] = useState<any>();
|
||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||
@@ -380,9 +396,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
||||
}
|
||||
|
||||
let [p, s] = cmd[1].split('/');
|
||||
if (!s) {
|
||||
s = p;
|
||||
let p: string, s: string;
|
||||
let index = cmd[1].lastIndexOf('/');
|
||||
if (index >= 0) {
|
||||
s = cmd[1].slice(index + 1);
|
||||
p = cmd[1].slice(0, index);
|
||||
} else {
|
||||
s = cmd[1];
|
||||
p = '';
|
||||
}
|
||||
history.push(`/script?p=${p}&s=${s}`);
|
||||
@@ -393,12 +413,17 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
|
||||
const getCrons = () => {
|
||||
setLoading(true);
|
||||
const { page, size, sorter } = pageConf;
|
||||
let url = `${config.apiPrefix}crons?searchValue=${searchText}&page=${page}&size=${size}`;
|
||||
const { page, size, sorter, filters } = pageConf;
|
||||
let url = `${
|
||||
config.apiPrefix
|
||||
}crons?searchValue=${searchText}&page=${page}&size=${size}&filters=${JSON.stringify(
|
||||
filters,
|
||||
)}`;
|
||||
if (sorter && sorter.field) {
|
||||
url += `&sortField=${sorter.field}&sortType=${
|
||||
sorter.order === 'ascend' ? 'ASC' : 'DESC'
|
||||
}`;
|
||||
url += `&sorter=${JSON.stringify({
|
||||
field: sorter.field,
|
||||
type: sorter.order === 'ascend' ? 'ASC' : 'DESC',
|
||||
})}`;
|
||||
}
|
||||
if (viewConf) {
|
||||
url += `&queryString=${JSON.stringify({
|
||||
@@ -408,20 +433,22 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
}
|
||||
request
|
||||
.get(url)
|
||||
.then((_data: any) => {
|
||||
const { data, total } = _data.data;
|
||||
setValue(
|
||||
data.map((x) => {
|
||||
return {
|
||||
...x,
|
||||
nextRunTime: cron_parser
|
||||
.parseExpression(x.schedule)
|
||||
.next()
|
||||
.toDate(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
setTotal(total);
|
||||
.then(({ code, data: _data }) => {
|
||||
if (code === 200) {
|
||||
const { data, total } = _data;
|
||||
setValue(
|
||||
data.map((x) => {
|
||||
return {
|
||||
...x,
|
||||
nextRunTime: cron_parser
|
||||
.parseExpression(x.schedule)
|
||||
.next()
|
||||
.toDate(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
setTotal(total);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -451,8 +478,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -460,8 +487,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
result.splice(i, 1);
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -486,8 +511,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/run`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -497,8 +522,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -523,8 +546,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}crons/stop`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -535,8 +558,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -569,8 +590,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newStatus = record.isDisabled === 1 ? 0 : 1;
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -581,8 +602,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -615,8 +634,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newStatus = record.isPinned === 1 ? 0 : 1;
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -627,8 +646,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -730,19 +747,21 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const getCronDetail = (cron: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/${cron.id}`)
|
||||
.then((data: any) => {
|
||||
const index = value.findIndex((x) => x.id === cron.id);
|
||||
const result = [...value];
|
||||
data.data.nextRunTime = cron_parser
|
||||
.parseExpression(data.data.schedule)
|
||||
.next()
|
||||
.toDate();
|
||||
if (index !== -1) {
|
||||
result.splice(index, 1, {
|
||||
...cron,
|
||||
...data.data,
|
||||
});
|
||||
setValue(result);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const index = value.findIndex((x) => x.id === cron.id);
|
||||
const result = [...value];
|
||||
data.nextRunTime = cron_parser
|
||||
.parseExpression(data.schedule)
|
||||
.next()
|
||||
.toDate();
|
||||
if (index !== -1) {
|
||||
result.splice(index, 1, {
|
||||
...cron,
|
||||
...data,
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -770,13 +789,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons`, { data: selectedRowIds })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('批量删除成功');
|
||||
setSelectedRowIds([]);
|
||||
getCrons();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -795,11 +812,9 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
.put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
getCrons();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -815,7 +830,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
sorter: SorterResult<any> | SorterResult<any>[],
|
||||
) => {
|
||||
const { current, pageSize } = pagination;
|
||||
setPageConf({ page: current as number, size: pageSize as number, sorter });
|
||||
setPageConf({
|
||||
page: current as number,
|
||||
size: pageSize as number,
|
||||
sorter,
|
||||
filters,
|
||||
});
|
||||
localStorage.setItem('pageSize', String(pageSize));
|
||||
};
|
||||
|
||||
@@ -852,6 +872,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
page: 1,
|
||||
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
||||
sorter: {},
|
||||
filters: {},
|
||||
});
|
||||
setTimeout(() => {
|
||||
setTableScrollHeight(getTableScroll());
|
||||
@@ -931,14 +952,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
(a, b) => a - b,
|
||||
),
|
||||
}}
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: (event) => {
|
||||
setDetailCron(record);
|
||||
setIsDetailModalVisible(true);
|
||||
},
|
||||
};
|
||||
}}
|
||||
dataSource={value}
|
||||
rowKey="id"
|
||||
size="middle"
|
||||
@@ -1006,9 +1019,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/views`)
|
||||
.then((data: any) => {
|
||||
setCronViews(data.data);
|
||||
setEnabledCronViews(data.data.filter((x) => !x.isDisabled));
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCronViews(data);
|
||||
setEnabledCronViews(data.filter((x) => !x.isDisabled));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -1017,6 +1032,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
|
||||
const tabClick = (key: string) => {
|
||||
const view = enabledCronViews.find((x) => x.id == key);
|
||||
setPageConf({ ...pageConf, page: 1 });
|
||||
setViewConf(view ? view : null);
|
||||
};
|
||||
|
||||
@@ -1064,6 +1080,18 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||
</Dropdown>
|
||||
}
|
||||
onTabClick={tabClick}
|
||||
items={[
|
||||
{
|
||||
key: 'all',
|
||||
label: '全部任务',
|
||||
children: panelContent,
|
||||
},
|
||||
...[...enabledCronViews].slice(0, 2).map((x) => ({
|
||||
key: x.id,
|
||||
label: x.name,
|
||||
children: panelContent,
|
||||
})),
|
||||
]}
|
||||
>
|
||||
<Tabs.TabPane tab="全部任务" key="all">
|
||||
{panelContent}
|
||||
|
||||
@@ -41,9 +41,12 @@ const CronLogModal = ({
|
||||
}
|
||||
request
|
||||
.get(logUrl ? logUrl : `${config.apiPrefix}crons/${cron.id}/log`)
|
||||
.then((data: any) => {
|
||||
if (localStorage.getItem('logCron') === String(cron.id)) {
|
||||
const log = data.data as string;
|
||||
.then(({ code, data }) => {
|
||||
if (
|
||||
code === 200 &&
|
||||
localStorage.getItem('logCron') === String(cron.id)
|
||||
) {
|
||||
const log = data as string;
|
||||
setValue(log || '暂无日志');
|
||||
setExecuting(
|
||||
log && !log.includes('执行结束') && !log.includes('重启面板'),
|
||||
@@ -119,7 +122,7 @@ const CronLogModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={titleElement()}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
centered
|
||||
className="log-modal"
|
||||
bodyStyle={{
|
||||
|
||||
+20
-18
@@ -28,13 +28,12 @@ const CronModal = ({
|
||||
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
|
||||
data: payload,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -47,7 +46,7 @@ const CronModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={cron ? '编辑任务' : '新建任务'}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
@@ -128,21 +127,24 @@ const CronLabelModal = ({
|
||||
.then(async (values) => {
|
||||
setLoading(true);
|
||||
const payload = { ids, labels: values.labels };
|
||||
const { code, data } = await request[action](
|
||||
`${config.apiPrefix}crons/labels`,
|
||||
{
|
||||
data: payload,
|
||||
},
|
||||
);
|
||||
if (code === 200) {
|
||||
message.success(
|
||||
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
||||
try {
|
||||
const { code, data } = await request[action](
|
||||
`${config.apiPrefix}crons/labels`,
|
||||
{
|
||||
data: payload,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
message.error(data);
|
||||
|
||||
if (code === 200) {
|
||||
message.success(
|
||||
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
||||
);
|
||||
handleCancel(true);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(true);
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log('Validate Failed:', info);
|
||||
@@ -166,7 +168,7 @@ const CronLabelModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title="批量修改标签"
|
||||
visible={visible}
|
||||
open={visible}
|
||||
footer={buttons}
|
||||
centered
|
||||
maskClosable={false}
|
||||
|
||||
@@ -65,11 +65,11 @@ const ViewCreateModal = ({
|
||||
data: view ? { ...values, id: view.id } : values,
|
||||
},
|
||||
);
|
||||
if (code !== 200) {
|
||||
message.error(data);
|
||||
|
||||
if (code === 200) {
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -101,9 +101,9 @@ const ViewCreateModal = ({
|
||||
</Select>
|
||||
);
|
||||
|
||||
const propertyElement = (props: any) => {
|
||||
const propertyElement = (props: any, style: React.CSSProperties = {}) => {
|
||||
return (
|
||||
<Select style={{ width: 120 }}>
|
||||
<Select style={style}>
|
||||
{props.map((x) => (
|
||||
<Select.Option key={x.name} value={x.value}>
|
||||
{x.name}
|
||||
@@ -114,7 +114,7 @@ const ViewCreateModal = ({
|
||||
};
|
||||
|
||||
const typeElement = (
|
||||
<Select style={{ width: 120 }}>
|
||||
<Select>
|
||||
{SORTTYPES.map((x) => (
|
||||
<Select.Option key={x.name} value={x.value}>
|
||||
{x.name}
|
||||
@@ -136,7 +136,7 @@ const ViewCreateModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={view ? '编辑视图' : '新建视图'}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
width={580}
|
||||
centered
|
||||
@@ -170,6 +170,7 @@ const ViewCreateModal = ({
|
||||
label={index === 0 ? '筛选条件' : ''}
|
||||
key={key}
|
||||
style={{ marginBottom: 0 }}
|
||||
required
|
||||
>
|
||||
<Space className="view-create-modal-filters" align="baseline">
|
||||
<Form.Item
|
||||
@@ -177,7 +178,7 @@ const ViewCreateModal = ({
|
||||
name={[name, 'property']}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
{propertyElement(PROPERTIES)}
|
||||
{propertyElement(PROPERTIES, { width: 120 })}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
@@ -225,13 +226,13 @@ const ViewCreateModal = ({
|
||||
key={key}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Space className="view-create-modal-filters" align="baseline">
|
||||
<Space className="view-create-modal-sorts" align="baseline">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'property']}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
{propertyElement(PROPERTIES)}
|
||||
{propertyElement(PROPERTIES, { width: 240 })}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
@@ -240,9 +241,7 @@ const ViewCreateModal = ({
|
||||
>
|
||||
{typeElement}
|
||||
</Form.Item>
|
||||
{index !== 0 && (
|
||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||
)}
|
||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||
</Space>
|
||||
</Form.Item>
|
||||
))}
|
||||
|
||||
@@ -141,12 +141,10 @@ const ViewManageModal = ({
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}crons/views`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
cronViewChange();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -162,14 +160,12 @@ const ViewManageModal = ({
|
||||
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
|
||||
data: [record.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const _list = [...list];
|
||||
_list.splice(index, 1, { ...list[index], isDisabled: !checked });
|
||||
setList(_list);
|
||||
cronViewChange();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -190,15 +186,13 @@ const ViewManageModal = ({
|
||||
.put(`${config.apiPrefix}crons/views/move`, {
|
||||
data: { fromIndex: dragIndex, toIndex: hoverIndex, id: dragRow.id },
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newData = [...list];
|
||||
newData.splice(dragIndex, 1);
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data });
|
||||
setList(newData);
|
||||
cronViewChange();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -212,7 +206,7 @@ const ViewManageModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title="视图管理"
|
||||
visible={visible}
|
||||
open={visible}
|
||||
centered
|
||||
width={620}
|
||||
onCancel={() => handleCancel()}
|
||||
@@ -231,7 +225,10 @@ const ViewManageModal = ({
|
||||
<Button
|
||||
key="2"
|
||||
type="primary"
|
||||
onClick={() => setIsCreateViewModalVisible(true)}
|
||||
onClick={() => {
|
||||
setEditedView(null);
|
||||
setIsCreateViewModalVisible(true);
|
||||
}}
|
||||
>
|
||||
新建视图
|
||||
</Button>
|
||||
@@ -258,8 +255,8 @@ const ViewManageModal = ({
|
||||
view={editedView}
|
||||
visible={isCreateViewModalVisible}
|
||||
handleCancel={(data) => {
|
||||
cronViewChange(data);
|
||||
setIsCreateViewModalVisible(false);
|
||||
cronViewChange(data);
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -29,6 +29,8 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import './index.less';
|
||||
import { getTableScroll } from '@/utils/index';
|
||||
import DependenceLogModal from './logModal';
|
||||
import { useOutletContext } from '@umijs/max';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Search } = Input;
|
||||
@@ -48,7 +50,9 @@ enum StatusColor {
|
||||
'error',
|
||||
}
|
||||
|
||||
const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
const Dependence = () => {
|
||||
const { headerStyle, isPhone, socketMessage } =
|
||||
useOutletContext<SharedContext>();
|
||||
const columns: any = [
|
||||
{
|
||||
title: '序号',
|
||||
@@ -171,8 +175,10 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
.get(
|
||||
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
|
||||
)
|
||||
.then((data: any) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -208,18 +214,14 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
.delete(`${config.apiPrefix}dependencies${force ? '/force' : ''}`, {
|
||||
data: [record.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
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);
|
||||
}
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && force) {
|
||||
const i = value.findIndex((x) => x.id === data.data[0].id);
|
||||
if (i !== -1) {
|
||||
const result = [...value];
|
||||
result.splice(i, 1);
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -246,11 +248,9 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||
data: [record.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
handleDependence(data.data[0]);
|
||||
} else {
|
||||
message.error(data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
handleDependence(data[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -305,12 +305,32 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
.delete(`${config.apiPrefix}dependencies${forceUrl}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setSelectedRowIds([]);
|
||||
getDependencies();
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handlereInstallDependencies = () => {
|
||||
Modal.confirm({
|
||||
title: '确认重新安装',
|
||||
content: <>确认重新安装选中的依赖吗</>,
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setSelectedRowIds([]);
|
||||
getDependencies();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -323,15 +343,17 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
const getDependenceDetail = (dependence: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||
.then((data: any) => {
|
||||
const index = value.findIndex((x) => x.id === dependence.id);
|
||||
const result = [...value];
|
||||
if (index !== -1) {
|
||||
result.splice(index, 1, {
|
||||
...dependence,
|
||||
...data.data,
|
||||
});
|
||||
setValue(result);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const index = value.findIndex((x) => x.id === dependence.id);
|
||||
const result = [...value];
|
||||
if (index !== -1) {
|
||||
result.splice(index, 1, {
|
||||
...dependence,
|
||||
...data,
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -403,6 +425,13 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
<>
|
||||
{selectedRowIds.length > 0 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||
onClick={() => handlereInstallDependencies()}
|
||||
>
|
||||
批量安装
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||
@@ -467,17 +496,24 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
size="small"
|
||||
tabPosition="top"
|
||||
onChange={onTabChange}
|
||||
>
|
||||
<Tabs.TabPane tab="NodeJs" key="nodejs">
|
||||
{panelContent()}
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Python3" key="python3">
|
||||
{panelContent()}
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Linux" key="linux">
|
||||
{panelContent()}
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
items={[
|
||||
{
|
||||
key: 'nodejs',
|
||||
label: 'NodeJs',
|
||||
children: panelContent(),
|
||||
},
|
||||
{
|
||||
key: 'python3',
|
||||
label: 'Python3',
|
||||
children: panelContent(),
|
||||
},
|
||||
{
|
||||
key: 'linux',
|
||||
label: 'Linux',
|
||||
children: panelContent(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<DependenceModal
|
||||
visible={isModalVisible}
|
||||
handleCancel={handleCancel}
|
||||
|
||||
@@ -48,9 +48,12 @@ const DependenceLogModal = ({
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||
.then((data: any) => {
|
||||
if (localStorage.getItem('logDependence') === String(dependence.id)) {
|
||||
const log = (data.data.log || []).join('') as string;
|
||||
.then(({ code, data }) => {
|
||||
if (
|
||||
code === 200 &&
|
||||
localStorage.getItem('logDependence') === String(dependence.id)
|
||||
) {
|
||||
const log = (data.log || []).join('') as string;
|
||||
setValue(log);
|
||||
setExecuting(!log.includes('结束时间'));
|
||||
setIsRemoveFailed(log.includes('删除失败'));
|
||||
@@ -67,8 +70,10 @@ const DependenceLogModal = ({
|
||||
.delete(`${config.apiPrefix}dependencies/force`, {
|
||||
data: [dependence.id],
|
||||
})
|
||||
.then((data: any) => {
|
||||
cancel(true);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
cancel(true);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setRemoveLoading(false);
|
||||
@@ -112,7 +117,7 @@ const DependenceLogModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={titleElement()}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
centered
|
||||
className="log-modal"
|
||||
bodyStyle={{
|
||||
|
||||
@@ -52,11 +52,11 @@ const DependenceModal = ({
|
||||
data: payload,
|
||||
},
|
||||
);
|
||||
if (code !== 200) {
|
||||
message.error(data);
|
||||
|
||||
if (code === 200) {
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ const DependenceModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={dependence ? '编辑依赖' : '新建依赖'}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
@@ -99,7 +99,9 @@ const DependenceModal = ({
|
||||
>
|
||||
<Select>
|
||||
{config.dependenceTypes.map((x, i) => (
|
||||
<Option value={i}>{x}</Option>
|
||||
<Option key={i} value={i}>
|
||||
{x}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
@@ -17,4 +17,8 @@
|
||||
.ant-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
+ section {
|
||||
height: calc(100% - 40px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
+32
-13
@@ -6,10 +6,13 @@ import { request } from '@/utils/http';
|
||||
import './index.less';
|
||||
import { DiffEditor } from '@monaco-editor/react';
|
||||
import ReactDiffViewer from 'react-diff-viewer';
|
||||
import { useOutletContext } from '@umijs/max';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const Diff = () => {
|
||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||
const [origin, setOrigin] = useState('config.sample.sh');
|
||||
const [current, setCurrent] = useState('config.sh');
|
||||
const [originValue, setOriginValue] = useState('');
|
||||
@@ -19,15 +22,23 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const getConfig = () => {
|
||||
request.get(`${config.apiPrefix}configs/${current}`).then((data) => {
|
||||
setCurrentValue(data.data);
|
||||
});
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/${current}`)
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getSample = () => {
|
||||
request.get(`${config.apiPrefix}configs/${origin}`).then((data) => {
|
||||
setOriginValue(data.data);
|
||||
});
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/${origin}`)
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setOriginValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const updateConfig = () => {
|
||||
@@ -39,8 +50,10 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
||||
.post(`${config.apiPrefix}configs/save`, {
|
||||
data: { content, name: current },
|
||||
})
|
||||
.then((data: any) => {
|
||||
message.success(data.message);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('保存成功');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -48,8 +61,10 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}configs/files`)
|
||||
.then((data: any) => {
|
||||
setFiles(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setFiles(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -95,7 +110,9 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
||||
<Form.Item label="源文件">
|
||||
<Select value={origin} onChange={originFileChange}>
|
||||
{files.map((x) => (
|
||||
<Option value={x.value}>{x.title}</Option>
|
||||
<Option key={x.value} value={x.value}>
|
||||
{x.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
@@ -104,7 +121,9 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
||||
<Form.Item label="当前文件">
|
||||
<Select value={current} onChange={currentFileChange}>
|
||||
{files.map((x) => (
|
||||
<Option value={x.value}>{x.title}</Option>
|
||||
<Option key={x.value} value={x.value}>
|
||||
{x.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
Vendored
+16
-13
@@ -17,19 +17,22 @@ const EditNameModal = ({
|
||||
|
||||
const handleOk = async (values: any) => {
|
||||
setLoading(true);
|
||||
const { code, data } = await request.put(`${config.apiPrefix}envs/name`, {
|
||||
data: {
|
||||
ids,
|
||||
name: values.name,
|
||||
},
|
||||
});
|
||||
if (code === 200) {
|
||||
message.success('更新环境变量名称成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
try {
|
||||
const { code, data } = await request.put(`${config.apiPrefix}envs/name`, {
|
||||
data: {
|
||||
ids,
|
||||
name: values.name,
|
||||
},
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
message.success('更新环境变量名称成功');
|
||||
handleCancel();
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -39,7 +42,7 @@ const EditNameModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title="修改环境变量名称"
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
|
||||
Vendored
+18
-23
@@ -26,6 +26,8 @@ import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import './index.less';
|
||||
import { exportJson, getTableScroll } from '@/utils/index';
|
||||
import { useOutletContext } from '@umijs/max';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { Search } = Input;
|
||||
@@ -96,7 +98,8 @@ const DragableBodyRow = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const Env = () => {
|
||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||
const columns: any = [
|
||||
{
|
||||
title: '序号',
|
||||
@@ -252,8 +255,10 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}envs?searchValue=${searchText}`)
|
||||
.then((data: any) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -281,8 +286,8 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success(
|
||||
`${record.status === Status.已禁用 ? '启用' : '禁用'}成功`,
|
||||
);
|
||||
@@ -294,8 +299,6 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
status: newStatus,
|
||||
});
|
||||
setValue(result);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -330,14 +333,12 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}envs`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...value];
|
||||
result.splice(index, 1);
|
||||
setValue(result);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -387,14 +388,12 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
.put(`${config.apiPrefix}envs/${dragRow.id}/move`, {
|
||||
data: { fromIndex: dragIndex, toIndex: hoverIndex },
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newData = [...value];
|
||||
newData.splice(dragIndex, 1);
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
||||
setValue([...newData]);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -423,13 +422,11 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}envs`, { data: selectedRowIds })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('批量删除成功');
|
||||
setSelectedRowIds([]);
|
||||
getEnvs();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -448,11 +445,9 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
.put(`${config.apiPrefix}envs/${OperationPath[operationStatus]}`, {
|
||||
data: selectedRowIds,
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
getEnvs();
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Vendored
+3
-4
@@ -40,13 +40,12 @@ const EnvModal = ({
|
||||
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
||||
data: payload,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -59,7 +58,7 @@ const EnvModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={env ? '编辑变量' : '新建变量'}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
|
||||
@@ -3,11 +3,13 @@ import config from '@/utils/config';
|
||||
import { request } from '@/utils/http';
|
||||
import Terminal, { ColorMode, LineType } from '../../components/terminal';
|
||||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
import { history } from 'umi';
|
||||
import { history, useOutletContext } from '@umijs/max';
|
||||
import Ansi from 'ansi-to-react';
|
||||
import './index.less';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const Error = ({ user, theme }: any) => {
|
||||
const Error = () => {
|
||||
const { user, theme } = useOutletContext<SharedContext>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState('暂无日志');
|
||||
|
||||
@@ -15,8 +17,10 @@ const Error = ({ user, theme }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}public/panel/log`)
|
||||
.then((data: any) => {
|
||||
setData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Select,
|
||||
} from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { history } from 'umi';
|
||||
import { history } from '@umijs/max';
|
||||
import styles from './index.less';
|
||||
import { request } from '@/utils/http';
|
||||
|
||||
@@ -41,11 +41,9 @@ const Initialization = () => {
|
||||
password: values.password,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
next();
|
||||
} else {
|
||||
message.error(data.message);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -59,11 +57,9 @@ const Initialization = () => {
|
||||
...values,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data && _data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
next();
|
||||
} else {
|
||||
message.error(_data.message);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -116,12 +112,15 @@ const Initialization = () => {
|
||||
{config.notificationModes
|
||||
.filter((x) => x.value !== 'closed')
|
||||
.map((x) => (
|
||||
<Option value={x.value}>{x.label}</Option>
|
||||
<Option key={x.value} value={x.value}>
|
||||
{x.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{fields.map((x) => (
|
||||
<Form.Item
|
||||
key={x.label}
|
||||
label={x.label}
|
||||
name={x.label}
|
||||
extra={x.tip}
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: @component-background;
|
||||
height: calc(100vh - 128px);
|
||||
height: calc(100vh - var(--vh-offset, 0px) - 128px);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
+154
-61
@@ -1,5 +1,15 @@
|
||||
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
||||
import { TreeSelect, Tree, Input, Empty } from 'antd';
|
||||
import {
|
||||
TreeSelect,
|
||||
Tree,
|
||||
Input,
|
||||
Empty,
|
||||
Button,
|
||||
message,
|
||||
Modal,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
import Editor from '@monaco-editor/react';
|
||||
@@ -7,53 +17,35 @@ import { request } from '@/utils/http';
|
||||
import styles from './index.module.less';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import SplitPane from 'react-split-pane';
|
||||
import { useOutletContext } from '@umijs/max';
|
||||
import { SharedContext } from '@/layouts';
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { depthFirstSearch } from '@/utils';
|
||||
import { debounce, uniq } from 'lodash';
|
||||
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||
|
||||
function getFilterData(keyword: string, data: any) {
|
||||
const expandedKeys: string[] = [];
|
||||
if (keyword) {
|
||||
const tree: any = [];
|
||||
data.forEach((item: any) => {
|
||||
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
||||
tree.push(item);
|
||||
} else {
|
||||
const children: any[] = [];
|
||||
(item.children || []).forEach((subItem: any) => {
|
||||
if (subItem.title.toLocaleLowerCase().includes(keyword)) {
|
||||
children.push(subItem);
|
||||
}
|
||||
});
|
||||
if (children.length > 0) {
|
||||
tree.push({
|
||||
...item,
|
||||
children,
|
||||
});
|
||||
expandedKeys.push(item.key);
|
||||
}
|
||||
}
|
||||
});
|
||||
return { tree, expandedKeys };
|
||||
}
|
||||
return { tree: data, expandedKeys };
|
||||
}
|
||||
const { Text } = Typography;
|
||||
|
||||
const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const [title, setTitle] = useState('请选择日志文件');
|
||||
const Log = () => {
|
||||
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||
const [value, setValue] = useState('请选择日志文件');
|
||||
const [select, setSelect] = useState<any>();
|
||||
const [select, setSelect] = useState<string>('');
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [filterData, setFilterData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [height, setHeight] = useState<number>();
|
||||
const treeDom = useRef<any>();
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const [currentNode, setCurrentNode] = useState<any>();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
const getLogs = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}logs`)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
setFilterData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -61,18 +53,27 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const getLog = (node: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onSelect = (value: any, node: any) => {
|
||||
setCurrentNode(node);
|
||||
setSelect(value);
|
||||
|
||||
if (node.key === select || !value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === 'directory') {
|
||||
setValue('请选择日志文件');
|
||||
return;
|
||||
}
|
||||
|
||||
setValue('加载中...');
|
||||
setSelect(value);
|
||||
setTitle(node.key);
|
||||
getLog(node);
|
||||
};
|
||||
|
||||
@@ -83,41 +84,129 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
const onSearch = useCallback(
|
||||
(e) => {
|
||||
const keyword = e.target.value;
|
||||
const { tree, expandedKeys } = getFilterData(
|
||||
keyword.toLocaleLowerCase(),
|
||||
data,
|
||||
);
|
||||
setFilterData(tree);
|
||||
setExpandedKeys(expandedKeys);
|
||||
debounceSearch(keyword);
|
||||
},
|
||||
[data, setFilterData],
|
||||
[data],
|
||||
);
|
||||
|
||||
const debounceSearch = useCallback(
|
||||
debounce((keyword) => {
|
||||
setSearchValue(keyword);
|
||||
}, 300),
|
||||
[data],
|
||||
);
|
||||
|
||||
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
|
||||
data,
|
||||
searchValue,
|
||||
{ treeNodeFilterProp: 'title' },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
|
||||
}, [searchExpandedKeys]);
|
||||
|
||||
const deleteFile = () => {
|
||||
Modal.confirm({
|
||||
title: `确认删除`,
|
||||
content: (
|
||||
<>
|
||||
确认删除
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{select}
|
||||
</Text>
|
||||
文件{currentNode.type === 'directory' ? '夹下所以日志' : ''}
|
||||
,删除后不可恢复
|
||||
</>
|
||||
),
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}logs`, {
|
||||
data: {
|
||||
filename: currentNode.title,
|
||||
path: currentNode.parent || '',
|
||||
type: currentNode.type,
|
||||
},
|
||||
})
|
||||
.then(({ code }) => {
|
||||
if (code === 200) {
|
||||
message.success(`删除成功`);
|
||||
let newData = [...data];
|
||||
if (currentNode.parent) {
|
||||
newData = depthFirstSearch(
|
||||
newData,
|
||||
(c) => c.key === currentNode.key,
|
||||
);
|
||||
} else {
|
||||
const index = newData.findIndex(
|
||||
(x) => x.key === currentNode.key,
|
||||
);
|
||||
if (index !== -1) {
|
||||
newData.splice(index, 1);
|
||||
}
|
||||
}
|
||||
setData(newData);
|
||||
initState();
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const initState = () => {
|
||||
setSelect('');
|
||||
setCurrentNode(null);
|
||||
setValue('请选择脚本文件');
|
||||
};
|
||||
|
||||
const onExpand = (expKeys: any) => {
|
||||
setExpandedKeys(expKeys);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getLogs();
|
||||
if (treeDom && treeDom.current) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (treeDom.current) {
|
||||
setHeight(treeDom.current.clientHeight);
|
||||
}
|
||||
}, []);
|
||||
}, [treeDom.current, data]);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper log-wrapper"
|
||||
title={title}
|
||||
title={select}
|
||||
loading={loading}
|
||||
extra={
|
||||
isPhone && [
|
||||
<TreeSelect
|
||||
className="log-select"
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择日志"
|
||||
fieldNames={{ value: 'key', label: 'title' }}
|
||||
showSearch
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
]
|
||||
isPhone
|
||||
? [
|
||||
<TreeSelect
|
||||
className="log-select"
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择日志"
|
||||
fieldNames={{ value: 'key' }}
|
||||
treeNodeFilterProp="title"
|
||||
showSearch
|
||||
allowClear
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
]
|
||||
: [
|
||||
<Tooltip title="删除">
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!select}
|
||||
onClick={deleteFile}
|
||||
icon={<DeleteOutlined />}
|
||||
/>
|
||||
</Tooltip>,
|
||||
]
|
||||
}
|
||||
header={{
|
||||
style: headerStyle,
|
||||
@@ -125,6 +214,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
>
|
||||
<div className={`${styles['log-container']} log-container`}>
|
||||
{!isPhone && (
|
||||
/*// @ts-ignore*/
|
||||
<SplitPane split="vertical" size={200} maxSize={-100}>
|
||||
<div className={styles['left-tree-container']}>
|
||||
{data.length > 0 ? (
|
||||
@@ -137,6 +227,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
></Input.Search>
|
||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||
<Tree
|
||||
expandAction="click"
|
||||
className={styles['left-tree']}
|
||||
treeData={filterData}
|
||||
showIcon={true}
|
||||
@@ -144,6 +235,8 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
selectedKeys={[select]}
|
||||
showLine={{ showLeafIcon: true }}
|
||||
onSelect={onTreeSelect}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={onExpand}
|
||||
></Tree>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+20
-26
@@ -9,16 +9,18 @@ import {
|
||||
Statistic,
|
||||
} from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { history, Link } from 'umi';
|
||||
import { history, useOutletContext } from '@umijs/max';
|
||||
import styles from './index.less';
|
||||
import { request } from '@/utils/http';
|
||||
import { useTheme } from '@/utils/hooks';
|
||||
import { MobileOutlined } from '@ant-design/icons';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Countdown } = Statistic;
|
||||
|
||||
const Login = ({ reloadUser }: any) => {
|
||||
const Login = () => {
|
||||
const { reloadUser } = useOutletContext<SharedContext>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [waitTime, setWaitTime] = useState<any>();
|
||||
const { theme } = useTheme();
|
||||
@@ -38,15 +40,7 @@ const Login = ({ reloadUser }: any) => {
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code === 420) {
|
||||
setLoginInfo({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
});
|
||||
setTwoFactor(true);
|
||||
} else {
|
||||
checkResponse(data);
|
||||
}
|
||||
checkResponse(data, values);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(function (error) {
|
||||
@@ -62,11 +56,7 @@ const Login = ({ reloadUser }: any) => {
|
||||
data: { ...loginInfo, code: values.code },
|
||||
})
|
||||
.then((data: any) => {
|
||||
if (data.code === 430) {
|
||||
message.error(data.message);
|
||||
} else {
|
||||
checkResponse(data);
|
||||
}
|
||||
checkResponse(data);
|
||||
setVerifying(false);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -75,8 +65,11 @@ const Login = ({ reloadUser }: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const checkResponse = (data: any) => {
|
||||
if (data.code === 200) {
|
||||
const checkResponse = (
|
||||
{ code, data, message: _message }: any,
|
||||
values?: any,
|
||||
) => {
|
||||
if (code === 200) {
|
||||
const {
|
||||
token,
|
||||
lastip,
|
||||
@@ -84,7 +77,7 @@ const Login = ({ reloadUser }: any) => {
|
||||
lastlogon,
|
||||
retries = 0,
|
||||
platform,
|
||||
} = data.data;
|
||||
} = data;
|
||||
localStorage.setItem(config.authKey, token);
|
||||
notification.success({
|
||||
message: '登录成功!',
|
||||
@@ -103,13 +96,14 @@ const Login = ({ reloadUser }: any) => {
|
||||
});
|
||||
reloadUser(true);
|
||||
history.push('/crontab');
|
||||
} else if (data.code === 100) {
|
||||
message.warn(data.message);
|
||||
} else if (data.code === 410) {
|
||||
message.error(data.message);
|
||||
setWaitTime(data.data);
|
||||
} else {
|
||||
message.error(data.message);
|
||||
} else if (code === 410) {
|
||||
setWaitTime(data);
|
||||
} else if (code === 420) {
|
||||
setLoginInfo({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
});
|
||||
setTwoFactor(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -67,8 +67,10 @@ const EditModal = ({
|
||||
const getDetail = (node: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -83,8 +85,10 @@ const EditModal = ({
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
setIsRunning(true);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setIsRunning(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -101,8 +105,10 @@ const EditModal = ({
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
setIsRunning(false);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setIsRunning(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -214,8 +220,9 @@ const EditModal = ({
|
||||
width={'100%'}
|
||||
headerStyle={{ padding: '11px 24px' }}
|
||||
onClose={cancel}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
>
|
||||
{/* @ts-ignore */}
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
minSize={200}
|
||||
|
||||
@@ -32,32 +32,31 @@ const EditScriptNameModal = ({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dirs, setDirs] = useState<any[]>([]);
|
||||
const [file, setFile] = useState<File>();
|
||||
const [type, setType] = useState<'blank' | 'upload'>('blank');
|
||||
const [type, setType] = useState<'blank' | 'upload' | 'directory'>('blank');
|
||||
|
||||
const handleOk = async (values: any) => {
|
||||
setLoading(true);
|
||||
values.path = values.path || '';
|
||||
const { path = '', filename: inputFilename, directory = '' } = values;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file as any);
|
||||
formData.append('filename', values.filename);
|
||||
formData.append('path', values.path);
|
||||
formData.append('filename', inputFilename);
|
||||
formData.append('path', path);
|
||||
formData.append('content', '');
|
||||
formData.append('directory', directory);
|
||||
request
|
||||
.post(`${config.apiPrefix}scripts`, {
|
||||
data: formData,
|
||||
})
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('新建文件成功');
|
||||
const key = values.path ? `${values.path}/` : '';
|
||||
const filename = file ? file.name : values.filename;
|
||||
message.success(directory ? '新建文件夹成功' : '新建文件成功');
|
||||
const key = path ? `${path}/` : '';
|
||||
const filename = file ? file.name : inputFilename;
|
||||
handleCancel({
|
||||
filename,
|
||||
path: values.path,
|
||||
path,
|
||||
key: `${key}${filename}`,
|
||||
});
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
@@ -99,8 +98,8 @@ const EditScriptNameModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="新建脚本"
|
||||
visible={visible}
|
||||
title="新建"
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
@@ -126,18 +125,36 @@ const EditScriptNameModal = ({
|
||||
>
|
||||
<Radio.Group onChange={typeChange}>
|
||||
<Radio value="blank">空文件</Radio>
|
||||
<Radio value="upload">本地上传</Radio>
|
||||
<Radio value="upload">本地文件</Radio>
|
||||
<Radio value="directory">文件夹</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{type === 'blank' && (
|
||||
<Form.Item
|
||||
name="filename"
|
||||
label="文件名"
|
||||
rules={[{ required: true, message: '请输入文件名' }]}
|
||||
rules={[
|
||||
{ required: true, message: '请输入文件名' },
|
||||
{
|
||||
validator: (_, value) =>
|
||||
value.includes('/')
|
||||
? Promise.reject(new Error('文件名不能包含斜杠'))
|
||||
: Promise.resolve(),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入文件名" />
|
||||
</Form.Item>
|
||||
)}
|
||||
{type === 'directory' && (
|
||||
<Form.Item
|
||||
name="directory"
|
||||
label="文件夹名"
|
||||
rules={[{ required: true, message: '请输入文件夹名' }]}
|
||||
>
|
||||
<Input placeholder="请输入文件夹名" />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item label="父目录" name="path">
|
||||
<TreeSelect
|
||||
allowClear
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: @component-background;
|
||||
height: calc(100vh - 128px);
|
||||
height: calc(100vh - var(--vh-offset, 0px) - 128px);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
+92
-108
@@ -33,38 +33,15 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import EditScriptNameModal from './editNameModal';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { history } from 'umi';
|
||||
import { history, useOutletContext, useLocation } from '@umijs/max';
|
||||
import { parse } from 'query-string';
|
||||
import { depthFirstSearch } from '@/utils';
|
||||
import { SharedContext } from '@/layouts';
|
||||
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
function getFilterData(keyword: string, data: any) {
|
||||
const expandedKeys: string[] = [];
|
||||
if (keyword) {
|
||||
const tree: any = [];
|
||||
data.forEach((item: any) => {
|
||||
if (item.title.toLocaleLowerCase().includes(keyword)) {
|
||||
tree.push(item);
|
||||
} else {
|
||||
const children: any[] = [];
|
||||
(item.children || []).forEach((subItem: any) => {
|
||||
if (subItem.title.toLocaleLowerCase().includes(keyword)) {
|
||||
children.push(subItem);
|
||||
}
|
||||
});
|
||||
if (children.length > 0) {
|
||||
tree.push({
|
||||
...item,
|
||||
children,
|
||||
});
|
||||
expandedKeys.push(item.key);
|
||||
}
|
||||
}
|
||||
});
|
||||
return { tree, expandedKeys };
|
||||
}
|
||||
return { tree: data, expandedKeys };
|
||||
}
|
||||
|
||||
const LangMap: any = {
|
||||
'.py': 'python',
|
||||
'.js': 'javascript',
|
||||
@@ -72,12 +49,12 @@ const LangMap: any = {
|
||||
'.ts': 'typescript',
|
||||
};
|
||||
|
||||
const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
const [title, setTitle] = useState('请选择脚本文件');
|
||||
const Script = () => {
|
||||
const { headerStyle, isPhone, theme, socketMessage } =
|
||||
useOutletContext<SharedContext>();
|
||||
const [value, setValue] = useState('请选择脚本文件');
|
||||
const [select, setSelect] = useState<any>();
|
||||
const [select, setSelect] = useState<string>('');
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [filterData, setFilterData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [mode, setMode] = useState('');
|
||||
const [height, setHeight] = useState<number>();
|
||||
@@ -94,10 +71,11 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts`)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
setFilterData(data.data);
|
||||
initGetScript();
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setData(data);
|
||||
initGetScript();
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -105,13 +83,15 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
const getDetail = (node: any) => {
|
||||
request
|
||||
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
|
||||
.then((data) => {
|
||||
setValue(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initGetScript = () => {
|
||||
const { p, s } = history.location.query as any;
|
||||
const { p, s } = parse(history.location.search);
|
||||
if (s) {
|
||||
const vkey = `${p}/${s}`;
|
||||
const obj = {
|
||||
@@ -127,22 +107,24 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
};
|
||||
|
||||
const onSelect = (value: any, node: any) => {
|
||||
setSelect(node.key);
|
||||
setCurrentNode(node);
|
||||
|
||||
if (node.key === select || !value) {
|
||||
return;
|
||||
}
|
||||
setValue('加载中...');
|
||||
|
||||
if (node.type === 'directory') {
|
||||
setValue('请选择脚本文件');
|
||||
return;
|
||||
}
|
||||
|
||||
const newMode = value ? LangMap[value.slice(-3)] : '';
|
||||
setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode);
|
||||
setSelect(node.key);
|
||||
setTitle(node.key);
|
||||
setCurrentNode(node);
|
||||
setValue('加载中...');
|
||||
getDetail(node);
|
||||
};
|
||||
|
||||
const onExpand = (expKeys: any) => {
|
||||
setExpandedKeys(expKeys);
|
||||
};
|
||||
|
||||
const onTreeSelect = useCallback(
|
||||
(keys: Key[], e: any) => {
|
||||
const content = editorRef.current
|
||||
@@ -173,22 +155,30 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
const keyword = e.target.value;
|
||||
debounceSearch(keyword);
|
||||
},
|
||||
[data, setFilterData],
|
||||
[data],
|
||||
);
|
||||
|
||||
const debounceSearch = useCallback(
|
||||
debounce((keyword) => {
|
||||
setSearchValue(keyword);
|
||||
const { tree, expandedKeys } = getFilterData(
|
||||
keyword.toLocaleLowerCase(),
|
||||
data,
|
||||
);
|
||||
setExpandedKeys(expandedKeys);
|
||||
setFilterData(tree);
|
||||
}, 300),
|
||||
[data, setFilterData],
|
||||
[data],
|
||||
);
|
||||
|
||||
const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData(
|
||||
data,
|
||||
searchValue,
|
||||
{ treeNodeFilterProp: 'title' },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys]));
|
||||
}, [searchExpandedKeys]);
|
||||
|
||||
const onExpand = (expKeys: any) => {
|
||||
setExpandedKeys(expKeys);
|
||||
};
|
||||
|
||||
const editFile = () => {
|
||||
setTimeout(() => {
|
||||
setIsEditing(true);
|
||||
@@ -226,13 +216,11 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success(`保存成功`);
|
||||
setValue(content);
|
||||
setIsEditing(false);
|
||||
} else {
|
||||
message.error(_data);
|
||||
}
|
||||
resolve(null);
|
||||
})
|
||||
@@ -250,10 +238,11 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
title: `确认删除`,
|
||||
content: (
|
||||
<>
|
||||
确认删除文件
|
||||
确认删除
|
||||
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||
{select}
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
文件{currentNode.type === 'directory' ? '夹及其子文件' : ''}
|
||||
,删除后不可恢复
|
||||
</>
|
||||
),
|
||||
@@ -263,24 +252,18 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
data: {
|
||||
filename: currentNode.title,
|
||||
path: currentNode.parent || '',
|
||||
type: currentNode.type,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data.code === 200) {
|
||||
.then(({ code }) => {
|
||||
if (code === 200) {
|
||||
message.success(`删除成功`);
|
||||
let newData = [...data];
|
||||
if (currentNode.parent) {
|
||||
const parentNodeIndex = newData.findIndex(
|
||||
(x) => x.key === currentNode.parent,
|
||||
newData = depthFirstSearch(
|
||||
newData,
|
||||
(c) => c.key === currentNode.key,
|
||||
);
|
||||
const parentNode = newData[parentNodeIndex];
|
||||
const index = parentNode.children.findIndex(
|
||||
(y) => y.key === currentNode.key,
|
||||
);
|
||||
if (index !== -1 && parentNodeIndex !== -1) {
|
||||
parentNode.children.splice(index, 1);
|
||||
newData.splice(parentNodeIndex, 1, { ...parentNode });
|
||||
}
|
||||
} else {
|
||||
const index = newData.findIndex(
|
||||
(x) => x.key === currentNode.key,
|
||||
@@ -290,8 +273,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
}
|
||||
}
|
||||
setData(newData);
|
||||
} else {
|
||||
message.error(_data);
|
||||
initState();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -313,20 +295,17 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
},
|
||||
) => {
|
||||
if (filename) {
|
||||
const newData = [...data];
|
||||
let newData = [...data];
|
||||
const _file = { title: filename, key, parent: path };
|
||||
if (path) {
|
||||
// TODO: 更新左侧树数据
|
||||
const parentNodeIndex = newData.findIndex((x) => x.key === path);
|
||||
if (parentNodeIndex !== -1) {
|
||||
const parentNode = newData[parentNodeIndex];
|
||||
if (parentNode.children && parentNode.children.length > 0) {
|
||||
parentNode.children.unshift(_file);
|
||||
} else {
|
||||
parentNode.children = [_file];
|
||||
}
|
||||
newData.splice(parentNodeIndex, 1, { ...parentNode });
|
||||
}
|
||||
newData = depthFirstSearch(newData, (c) => c.key === path, _file);
|
||||
const keys = path.split('/');
|
||||
const sKeys: string[] = [];
|
||||
keys.reduce((p, c) => {
|
||||
sKeys.push(p);
|
||||
return `${p}/${c}`;
|
||||
});
|
||||
setExpandedKeys([...expandedKeys, ...sKeys, path]);
|
||||
} else {
|
||||
newData.unshift(_file);
|
||||
}
|
||||
@@ -344,34 +323,35 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
filename: currentNode.title,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
const blob = new Blob([_data], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = currentNode.title;
|
||||
document.documentElement.appendChild(a);
|
||||
a.click();
|
||||
document.documentElement.removeChild(a);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const blob = new Blob([data], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = currentNode.title;
|
||||
document.documentElement.appendChild(a);
|
||||
a.click();
|
||||
document.documentElement.removeChild(a);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const word = searchValue || '';
|
||||
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
||||
setFilterData(tree);
|
||||
const initState = () => {
|
||||
setSelect('');
|
||||
setCurrentNode(null);
|
||||
setTitle('请选择脚本文件');
|
||||
setValue('请选择脚本文件');
|
||||
}, [data]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getScripts();
|
||||
if (treeDom && treeDom.current) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (treeDom.current) {
|
||||
setHeight(treeDom.current.clientHeight);
|
||||
}
|
||||
}, []);
|
||||
}, [treeDom.current, data]);
|
||||
|
||||
const action = (key: string | number) => {
|
||||
switch (key) {
|
||||
@@ -440,19 +420,22 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper log-wrapper"
|
||||
title={title}
|
||||
title={select}
|
||||
loading={loading}
|
||||
extra={
|
||||
isPhone
|
||||
? [
|
||||
<TreeSelect
|
||||
treeExpandAction="click"
|
||||
className="log-select"
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择脚本"
|
||||
fieldNames={{ value: 'key', label: 'title' }}
|
||||
fieldNames={{ value: 'key' }}
|
||||
treeNodeFilterProp="title"
|
||||
showSearch
|
||||
allowClear
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
<Dropdown overlay={menu} trigger={['click']}>
|
||||
@@ -508,6 +491,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
>
|
||||
<div className={`${styles['log-container']} log-container`}>
|
||||
{!isPhone && (
|
||||
/*// @ts-ignore*/
|
||||
<SplitPane split="vertical" size={200} maxSize={-100}>
|
||||
<div className={styles['left-tree-container']}>
|
||||
{data.length > 0 ? (
|
||||
@@ -520,6 +504,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
></Input.Search>
|
||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||
<Tree
|
||||
expandAction="click"
|
||||
className={styles['left-tree']}
|
||||
treeData={filterData}
|
||||
showIcon={true}
|
||||
@@ -556,7 +541,6 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
readOnly: !isEditing,
|
||||
fontSize: 12,
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
|
||||
@@ -26,8 +26,6 @@ const SaveModal = ({
|
||||
if (code === 200) {
|
||||
message.success('保存文件成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
@@ -41,7 +39,7 @@ const SaveModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title="保存文件"
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
|
||||
@@ -26,8 +26,6 @@ const SettingModal = ({
|
||||
if (code === 200) {
|
||||
message.success('保存文件成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
@@ -41,7 +39,7 @@ const SettingModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title="运行设置"
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
onCancel={() => handleCancel()}
|
||||
|
||||
@@ -22,16 +22,19 @@ const AppModal = ({
|
||||
if (app) {
|
||||
payload.id = app.id;
|
||||
}
|
||||
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
||||
data: payload,
|
||||
});
|
||||
if (code === 200) {
|
||||
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
try {
|
||||
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
||||
data: payload,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -41,7 +44,7 @@ const AppModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={app ? '编辑应用' : '新建应用'}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
@@ -64,7 +67,18 @@ const AppModal = ({
|
||||
name="form_app_modal"
|
||||
initialValues={app}
|
||||
>
|
||||
<Form.Item name="name" label="名称">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="名称"
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) =>
|
||||
['system'].includes(value)
|
||||
? Promise.reject(new Error('名称不能为保留关键字'))
|
||||
: Promise.resolve(),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入应用名称" />
|
||||
</Form.Item>
|
||||
<Form.Item name="scopes" label="权限" rules={[{ required: true }]}>
|
||||
@@ -75,7 +89,11 @@ const AppModal = ({
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{config.scopes.map((x) => {
|
||||
return <Select.Option value={x.value}>{x.name}</Select.Option>;
|
||||
return (
|
||||
<Select.Option key={x.value} value={x.value}>
|
||||
{x.name}
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
@@ -17,17 +17,14 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
message.loading('检查更新中...', 0);
|
||||
request
|
||||
.put(`${config.apiPrefix}system/update-check`)
|
||||
.then((_data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
message.destroy();
|
||||
const { code, data } = _data;
|
||||
if (code === 200) {
|
||||
if (data.hasNewVersion) {
|
||||
showConfirmUpdateModal(data);
|
||||
} else {
|
||||
showForceUpdateModal();
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -105,6 +102,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
};
|
||||
|
||||
const showUpdatingModal = () => {
|
||||
setValue('');
|
||||
modalRef.current = Modal.info({
|
||||
width: 600,
|
||||
maskClosable: false,
|
||||
@@ -140,7 +138,12 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
}
|
||||
|
||||
const newMessage = `${value}${_message}`;
|
||||
const updateFailed = newMessage.includes('失败,请检查');
|
||||
|
||||
modalRef.current.update({
|
||||
maskClosable: updateFailed,
|
||||
closable: updateFailed,
|
||||
okButtonProps: { disabled: !updateFailed },
|
||||
content: (
|
||||
<div style={{ height: '60vh', overflowY: 'auto' }}>
|
||||
<pre
|
||||
@@ -157,6 +160,11 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
if (updateFailed && !value.includes('失败,请检查')) {
|
||||
message.error('更新失败,请检查网络及日志或稍后再试');
|
||||
}
|
||||
|
||||
setValue(newMessage);
|
||||
|
||||
document.getElementById('log-identifier') &&
|
||||
|
||||
+109
-91
@@ -29,6 +29,8 @@ import LoginLog from './loginLog';
|
||||
import NotificationSetting from './notification';
|
||||
import CheckUpdate from './checkUpdate';
|
||||
import About from './about';
|
||||
import { useOutletContext } from '@umijs/max';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const { Text } = Typography;
|
||||
const optionsWithDisabled = [
|
||||
@@ -37,14 +39,9 @@ const optionsWithDisabled = [
|
||||
{ label: '跟随系统', value: 'auto' },
|
||||
];
|
||||
|
||||
const Setting = ({
|
||||
headerStyle,
|
||||
isPhone,
|
||||
user,
|
||||
reloadUser,
|
||||
reloadTheme,
|
||||
socketMessage,
|
||||
}: any) => {
|
||||
const Setting = () => {
|
||||
const { headerStyle, isPhone, user, reloadUser, reloadTheme, socketMessage } =
|
||||
useOutletContext<SharedContext>();
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
@@ -147,8 +144,10 @@ const Setting = ({
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}apps`)
|
||||
.then((data: any) => {
|
||||
setDataSource(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setDataSource(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -178,14 +177,12 @@ const Setting = ({
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}apps`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...dataSource];
|
||||
result.splice(index, 1);
|
||||
setDataSource(result);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -212,12 +209,10 @@ const Setting = ({
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}apps/${record.id}/reset-secret`)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('重置成功');
|
||||
handleApp(data.data);
|
||||
} else {
|
||||
message.error(data);
|
||||
handleApp(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -250,8 +245,10 @@ const Setting = ({
|
||||
const getLoginLog = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}user/login-log`)
|
||||
.then((data: any) => {
|
||||
setLoginLogData(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLoginLogData(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -274,8 +271,10 @@ const Setting = ({
|
||||
const getNotification = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}user/notification`)
|
||||
.then((data: any) => {
|
||||
setNotificationInfo(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setNotificationInfo(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -285,9 +284,9 @@ const Setting = ({
|
||||
const getLogRemoveFrequency = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}system/log/remove`)
|
||||
.then((data: any) => {
|
||||
if (data.data.info) {
|
||||
const { frequency } = data.data.info;
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && data.info) {
|
||||
const { frequency } = data.info;
|
||||
setLogRemoveFrequency(frequency);
|
||||
}
|
||||
})
|
||||
@@ -302,8 +301,10 @@ const Setting = ({
|
||||
.put(`${config.apiPrefix}system/log/remove`, {
|
||||
data: { frequency: logRemoveFrequency },
|
||||
})
|
||||
.then((data: any) => {
|
||||
message.success('更新成功');
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('更新成功');
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -333,70 +334,87 @@ const Setting = ({
|
||||
size="small"
|
||||
tabPosition="top"
|
||||
onChange={tabChange}
|
||||
>
|
||||
<Tabs.TabPane tab="安全设置" key="security">
|
||||
<SecuritySettings user={user} userChange={reloadUser} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="应用设置" key="app">
|
||||
<Table
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
dataSource={dataSource}
|
||||
rowKey="id"
|
||||
size="middle"
|
||||
scroll={{ x: 768 }}
|
||||
loading={loading}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="通知设置" key="notification">
|
||||
<NotificationSetting data={notificationInfo} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="登录日志" key="login">
|
||||
<LoginLog data={loginLogData} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="其他设置" key="other">
|
||||
<Form layout="vertical" form={form}>
|
||||
<Form.Item
|
||||
label="主题设置"
|
||||
name="theme"
|
||||
initialValue={defaultTheme}
|
||||
>
|
||||
<Radio.Group
|
||||
options={optionsWithDisabled}
|
||||
onChange={themeChange}
|
||||
value={defaultTheme}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
items={[
|
||||
{
|
||||
key: 'security',
|
||||
label: '安全设置',
|
||||
children: <SecuritySettings user={user} userChange={reloadUser} />,
|
||||
},
|
||||
{
|
||||
key: 'app',
|
||||
label: '应用设置',
|
||||
children: (
|
||||
<Table
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
dataSource={dataSource}
|
||||
rowKey="id"
|
||||
size="middle"
|
||||
scroll={{ x: 768 }}
|
||||
loading={loading}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="日志删除频率"
|
||||
name="frequency"
|
||||
tooltip="每x天自动删除x天以前的日志"
|
||||
>
|
||||
<Input.Group compact>
|
||||
<InputNumber
|
||||
addonBefore="每"
|
||||
addonAfter="天"
|
||||
style={{ width: 150 }}
|
||||
min={0}
|
||||
value={logRemoveFrequency}
|
||||
onChange={(value) => setLogRemoveFrequency(value)}
|
||||
/>
|
||||
<Button type="primary" onClick={updateRemoveLogFrequency}>
|
||||
确认
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="检查更新" name="update">
|
||||
<CheckUpdate socketMessage={socketMessage} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="关于" key="about">
|
||||
<About />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'notification',
|
||||
label: '通知设置',
|
||||
children: <NotificationSetting data={notificationInfo} />,
|
||||
},
|
||||
{
|
||||
key: 'login',
|
||||
label: '登录日志',
|
||||
children: <LoginLog data={loginLogData} />,
|
||||
},
|
||||
{
|
||||
key: 'other',
|
||||
label: '其他设置',
|
||||
children: (
|
||||
<Form layout="vertical" form={form}>
|
||||
<Form.Item
|
||||
label="主题设置"
|
||||
name="theme"
|
||||
initialValue={defaultTheme}
|
||||
>
|
||||
<Radio.Group
|
||||
options={optionsWithDisabled}
|
||||
onChange={themeChange}
|
||||
value={defaultTheme}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="日志删除频率"
|
||||
name="frequency"
|
||||
tooltip="每x天自动删除x天以前的日志"
|
||||
>
|
||||
<Input.Group compact>
|
||||
<InputNumber
|
||||
addonBefore="每"
|
||||
addonAfter="天"
|
||||
style={{ width: 150 }}
|
||||
min={0}
|
||||
value={logRemoveFrequency}
|
||||
onChange={(value) => setLogRemoveFrequency(value)}
|
||||
/>
|
||||
<Button type="primary" onClick={updateRemoveLogFrequency}>
|
||||
确认
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="检查更新" name="update">
|
||||
<CheckUpdate socketMessage={socketMessage} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'about',
|
||||
label: '关于',
|
||||
children: <About />,
|
||||
},
|
||||
]}
|
||||
></Tabs>
|
||||
<AppModal
|
||||
visible={isModalVisible}
|
||||
handleCancel={handleCancel}
|
||||
|
||||
@@ -12,20 +12,20 @@ const NotificationSetting = ({ data }: any) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleOk = (values: any) => {
|
||||
if (values.type == 'closed') {
|
||||
const { type } = values;
|
||||
if (type == 'closed') {
|
||||
values.type = '';
|
||||
}
|
||||
|
||||
request
|
||||
.put(`${config.apiPrefix}user/notification`, {
|
||||
data: {
|
||||
...values,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data && _data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success(values.type ? '通知发送成功' : '通知关闭成功');
|
||||
} else {
|
||||
message.error(_data.data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -58,19 +58,35 @@ const NotificationSetting = ({ data }: any) => {
|
||||
>
|
||||
<Select onChange={notificationModeChange}>
|
||||
{config.notificationModes.map((x) => (
|
||||
<Option value={x.value}>{x.label}</Option>
|
||||
<Option key={x.value} value={x.value}>
|
||||
{x.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{fields.map((x) => (
|
||||
<Form.Item
|
||||
key={x.label}
|
||||
label={x.label}
|
||||
name={x.label}
|
||||
extra={x.tip}
|
||||
rules={[{ required: x.required }]}
|
||||
style={{ maxWidth: 400 }}
|
||||
>
|
||||
<Input.TextArea autoSize={true} placeholder={`请输入${x.label}`} />
|
||||
{x.items ? (
|
||||
<Select placeholder={x.placeholder || `请选择${x.label}`}>
|
||||
{x.items.map((y) => (
|
||||
<Option key={y.value} value={y.value}>
|
||||
{y.label || y.value}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
) : (
|
||||
<Input.TextArea
|
||||
autoSize={true}
|
||||
placeholder={x.placeholder || `请输入${x.label}`}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
))}
|
||||
<Button type="primary" htmlType="submit">
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Typography, Input, Form, Button, message, Avatar, Upload } from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
import { history } from 'umi';
|
||||
import { history } from '@umijs/max';
|
||||
import QRCode from 'qrcode.react';
|
||||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
import { UploadOutlined, UserOutlined } from '@ant-design/icons';
|
||||
@@ -27,9 +27,11 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
password: values.password,
|
||||
},
|
||||
})
|
||||
.then((data: any) => {
|
||||
localStorage.removeItem(config.authKey);
|
||||
history.push('/login');
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
localStorage.removeItem(config.authKey);
|
||||
history.push('/login');
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
@@ -48,8 +50,8 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
const deactiveTowFactor = () => {
|
||||
request
|
||||
.put(`${config.apiPrefix}user/two-factor/deactive`)
|
||||
.then((data: any) => {
|
||||
if (data.data) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && data) {
|
||||
setTwoFactorActivated(false);
|
||||
userChange();
|
||||
}
|
||||
@@ -63,14 +65,16 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.put(`${config.apiPrefix}user/two-factor/active`, { data: { code } })
|
||||
.then((data: any) => {
|
||||
if (data.data) {
|
||||
message.success('激活成功');
|
||||
setTwoFactoring(false);
|
||||
setTwoFactorActivated(true);
|
||||
userChange();
|
||||
} else {
|
||||
message.success('验证失败');
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
if (data) {
|
||||
message.success('激活成功');
|
||||
setTwoFactoring(false);
|
||||
setTwoFactorActivated(true);
|
||||
userChange();
|
||||
} else {
|
||||
message.success('验证失败');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -82,8 +86,10 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
||||
const getTwoFactorInfo = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}user/two-factor/init`)
|
||||
.then((data: any) => {
|
||||
setTwoFactorInfo(data.data);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setTwoFactorInfo(data);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error);
|
||||
|
||||
@@ -30,9 +30,10 @@ import { PageContainer } from '@ant-design/pro-layout';
|
||||
import { request } from '@/utils/http';
|
||||
import SubscriptionModal from './modal';
|
||||
import { getTableScroll } from '@/utils/index';
|
||||
import { history } from 'umi';
|
||||
import { history, useOutletContext } from '@umijs/max';
|
||||
import './index.less';
|
||||
import SubscriptionLogModal from './logModal';
|
||||
import { SharedContext } from '@/layouts';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { Search } = Input;
|
||||
@@ -57,7 +58,10 @@ export enum SubscriptionType {
|
||||
'file' = '单文件',
|
||||
}
|
||||
|
||||
const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
const Subscription = () => {
|
||||
const { headerStyle, isPhone, socketMessage } =
|
||||
useOutletContext<SharedContext>();
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: '名称',
|
||||
@@ -259,8 +263,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}subscriptions/run`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -270,8 +274,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -296,8 +298,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.put(`${config.apiPrefix}subscriptions/stop`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
if (i !== -1) {
|
||||
@@ -308,8 +310,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -323,9 +323,11 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}subscriptions?searchValue=${searchText}`)
|
||||
.then((data: any) => {
|
||||
setValue(data.data);
|
||||
setCurrentPage(1);
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setValue(data);
|
||||
setCurrentPage(1);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@@ -355,8 +357,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
onOk() {
|
||||
request
|
||||
.delete(`${config.apiPrefix}subscriptions`, { data: [record.id] })
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('删除成功');
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -364,8 +366,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
result.splice(i, 1);
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -398,8 +398,8 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
data: [record.id],
|
||||
},
|
||||
)
|
||||
.then((data: any) => {
|
||||
if (data.code === 200) {
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
const newStatus = record.is_disabled === 1 ? 0 : 1;
|
||||
const result = [...value];
|
||||
const i = result.findIndex((x) => x.id === record.id);
|
||||
@@ -410,8 +410,6 @@ const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
});
|
||||
setValue(result);
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -36,11 +36,12 @@ const SubscriptionLogModal = ({
|
||||
? logUrl
|
||||
: `${config.apiPrefix}subscriptions/${subscription.id}/log`,
|
||||
)
|
||||
.then((data: any) => {
|
||||
.then(({ code, data }) => {
|
||||
if (
|
||||
code === 200 &&
|
||||
localStorage.getItem('logSubscription') === String(subscription.id)
|
||||
) {
|
||||
const log = data.data as string;
|
||||
const log = data as string;
|
||||
setValue(log || '暂无日志');
|
||||
setExecuting(log && !log.includes('执行结束'));
|
||||
if (log && !log.includes('执行结束')) {
|
||||
@@ -93,7 +94,7 @@ const SubscriptionLogModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title={titleElement()}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
centered
|
||||
className="log-modal"
|
||||
bodyStyle={{
|
||||
|
||||
@@ -39,11 +39,9 @@ const SubscriptionModal = ({
|
||||
);
|
||||
if (code === 200) {
|
||||
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
handleCancel(data);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -190,8 +188,8 @@ const SubscriptionModal = ({
|
||||
};
|
||||
|
||||
const onPaste = useCallback((e: any) => {
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (!subscription && text.includes('ql ')) {
|
||||
const text = e.clipboardData.getData('text') as string;
|
||||
if (text.startsWith('ql ')) {
|
||||
const [
|
||||
,
|
||||
type,
|
||||
@@ -225,6 +223,13 @@ const SubscriptionModal = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onNamePaste = useCallback((e) => {
|
||||
const text = e.clipboardData.getData('text') as string;
|
||||
if (text.startsWith('ql ')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
window.addEventListener('paste', onPaste);
|
||||
@@ -243,36 +248,10 @@ const SubscriptionModal = ({
|
||||
}
|
||||
}, [subscription, visible]);
|
||||
|
||||
const isFirefox = navigator.userAgent.includes('Firefox');
|
||||
const isSafari =
|
||||
navigator.userAgent.includes('Safari') &&
|
||||
!navigator.userAgent.includes('Chrome');
|
||||
const isQQBrowser = navigator.userAgent.includes('QQBrowser');
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
subscription ? (
|
||||
'编辑订阅'
|
||||
) : (
|
||||
<span>
|
||||
新建订阅
|
||||
<small
|
||||
style={{
|
||||
color: '#999',
|
||||
fontWeight: 400,
|
||||
fontSize: isFirefox ? 9 : 12,
|
||||
marginLeft: 2,
|
||||
zoom: isSafari ? 0.66 : 0.8,
|
||||
letterSpacing: isQQBrowser ? -2 : 0,
|
||||
}}
|
||||
>
|
||||
支持repo/raw命令,粘贴导入
|
||||
</small>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
visible={visible}
|
||||
title={subscription ? '编辑订阅' : '新建订阅'}
|
||||
open={visible}
|
||||
forceRender
|
||||
centered
|
||||
maskClosable={false}
|
||||
@@ -291,7 +270,10 @@ const SubscriptionModal = ({
|
||||
>
|
||||
<Form form={form} name="form_in_modal" layout="vertical">
|
||||
<Form.Item name="name" label="名称">
|
||||
<Input placeholder="请输入订阅名" />
|
||||
<Input
|
||||
placeholder="支持拷贝ql repo/raw命令,粘贴导入"
|
||||
onPaste={onNamePaste}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="type"
|
||||
|
||||
+44
-4
@@ -56,7 +56,7 @@ export default {
|
||||
value: 'scripts',
|
||||
},
|
||||
{
|
||||
name: '任务日志',
|
||||
name: '日志管理',
|
||||
value: 'logs',
|
||||
},
|
||||
{
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
subscriptions: '订阅管理',
|
||||
configs: '配置文件',
|
||||
scripts: '脚本管理',
|
||||
logs: '任务日志',
|
||||
logs: '日志管理',
|
||||
dependencies: '依赖管理',
|
||||
system: '系统信息',
|
||||
},
|
||||
@@ -90,7 +90,9 @@ export default {
|
||||
{ value: 'weWorkApp', label: '企业微信应用' },
|
||||
{ value: 'iGot', label: 'IGot' },
|
||||
{ value: 'pushPlus', label: 'PushPlus' },
|
||||
{ value: 'chat', label: '群辉chat' },
|
||||
{ value: 'email', label: '邮箱' },
|
||||
{ value: 'webhook', label: '自定义通知' },
|
||||
{ value: 'closed', label: '已关闭' },
|
||||
],
|
||||
notificationModeMap: {
|
||||
@@ -103,6 +105,14 @@ export default {
|
||||
{ label: 'gotifyToken', tip: 'gotify的消息应用token码', required: true },
|
||||
{ label: 'gotifyPriority', tip: '推送消息的优先级' },
|
||||
],
|
||||
chat: [
|
||||
{
|
||||
label: 'chatUrl',
|
||||
tip: 'chat的url地址',
|
||||
required: true,
|
||||
},
|
||||
{ label: 'chatToken', tip: 'chat的token码', required: true },
|
||||
],
|
||||
goCqHttpBot: [
|
||||
{
|
||||
label: 'goCqHttpBotUrl',
|
||||
@@ -196,7 +206,7 @@ export default {
|
||||
pushPlus: [
|
||||
{
|
||||
label: 'pushPlusToken',
|
||||
tip: '微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送',
|
||||
tip: '微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@@ -213,6 +223,36 @@ export default {
|
||||
{ label: 'emailUser', tip: '邮箱地址', required: true },
|
||||
{ label: 'emailPass', tip: '邮箱SMTP授权码', required: true },
|
||||
],
|
||||
webhook: [
|
||||
{
|
||||
label: 'webhookMethod',
|
||||
tip: '请求方法',
|
||||
required: true,
|
||||
items: [{ value: 'GET' }, { value: 'POST' }, { value: 'PUT' }],
|
||||
},
|
||||
{
|
||||
label: 'webhookContentType',
|
||||
tip: '请求头Content-Type',
|
||||
required: true,
|
||||
items: [{ value: 'application/json' }, { value: 'multipart/form-data' }, { value: 'application/x-www-form-urlencoded' }],
|
||||
},
|
||||
{
|
||||
label: 'webhookUrl',
|
||||
tip: '请求链接以http或者https开头。url或者body中必须包含$title,$content可选,对应api内容的位置',
|
||||
required: true,
|
||||
placeholder: 'https://xxx.cn/api?content=$title\n',
|
||||
},
|
||||
{
|
||||
label: 'webhookHeaders',
|
||||
tip: '请求头格式Custom-Header1: Header1,多个换行分割',
|
||||
placeholder: 'Custom-Header1: Header1\nCustom-Header2: Header2',
|
||||
},
|
||||
{
|
||||
label: 'webhookBody',
|
||||
tip: '请求体格式key1: value1,多个换行分割。url或者body中必须包含$title,$content可选,对应api内容的位置',
|
||||
placeholder: 'key1: $title\nkey2: $content',
|
||||
},
|
||||
],
|
||||
},
|
||||
documentTitleMap: {
|
||||
'/login': '登录',
|
||||
@@ -223,7 +263,7 @@ export default {
|
||||
'/config': '配置文件',
|
||||
'/script': '脚本管理',
|
||||
'/diff': '对比工具',
|
||||
'/log': '任务日志',
|
||||
'/log': '日志管理',
|
||||
'/setting': '系统设置',
|
||||
'/error': '错误日志',
|
||||
},
|
||||
|
||||
+18
-2
@@ -1,7 +1,7 @@
|
||||
import { extend } from 'umi-request';
|
||||
import { message } from 'antd';
|
||||
import config from './config';
|
||||
import { history } from 'umi';
|
||||
import { history } from '@umijs/max';
|
||||
|
||||
message.config({
|
||||
duration: 1.5,
|
||||
@@ -54,7 +54,23 @@ _request.interceptors.request.use((url, options) => {
|
||||
});
|
||||
|
||||
_request.interceptors.response.use(async (response) => {
|
||||
const res = await response.clone();
|
||||
const responseStatus = response.status;
|
||||
if ([502, 504].includes(responseStatus)) {
|
||||
message.error('服务异常,请稍后刷新!');
|
||||
history.push('/error');
|
||||
} else if (responseStatus === 401) {
|
||||
if (history.location.pathname !== '/login') {
|
||||
localStorage.removeItem(config.authKey);
|
||||
history.push('/login');
|
||||
}
|
||||
} else {
|
||||
const res = await response.clone().json();
|
||||
if (res.code !== 200) {
|
||||
const msg = res.message || res.data;
|
||||
msg && message.error(msg);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
|
||||
+37
-5
@@ -150,9 +150,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 +188,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');
|
||||
@@ -241,3 +241,35 @@ export function exportJson(name: string, data: string) {
|
||||
createA.download = name;
|
||||
automaticClick(createA);
|
||||
}
|
||||
|
||||
export function depthFirstSearch<
|
||||
T extends Record<string, any> & { children?: T[] },
|
||||
>(children: T[], condition: (column: T) => boolean, item?: T) {
|
||||
const c = [...children];
|
||||
const keys = [];
|
||||
|
||||
(function find(cls: T[] | undefined) {
|
||||
if (!cls) return;
|
||||
for (let i = 0; i < cls?.length; i++) {
|
||||
if (condition(cls[i])) {
|
||||
if (!item) {
|
||||
cls.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cls[i].children) {
|
||||
cls[i].children!.unshift(item);
|
||||
} else {
|
||||
cls[i].children = [item];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cls[i].children) {
|
||||
keys.push(cls[i].key);
|
||||
find(cls[i].children);
|
||||
}
|
||||
}
|
||||
})(c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
+5
-7
@@ -1,8 +1,6 @@
|
||||
export const version = '2.14.0';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/325';
|
||||
export const changeLog = `2.14.0 版本说明
|
||||
1. 定时任务自定义视图功能来啦,支持自定义筛选条件、排序规则等
|
||||
2. 配置文件可设置需要延迟的脚本后缀,默认只有js脚本随机延迟执行,感谢 https://github.com/cddjr
|
||||
3. 修复定时任务搜索及搜索分页
|
||||
4. 其他bug修复
|
||||
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. 修改资源判定逻辑
|
||||
`;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"lib": ["es2017", "esnext.asynciterable", "DOM"],
|
||||
"lib": ["es2021", "esnext.asynciterable", "DOM"],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./src/types",
|
||||
|
||||
Reference in New Issue
Block a user