mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 11:22:58 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
hash: true,
|
hash: true,
|
||||||
layout: false,
|
antd: {},
|
||||||
outputPath: 'static/dist',
|
outputPath: 'static/dist',
|
||||||
nodeModulesTransform: {
|
fastRefresh: true,
|
||||||
type: 'none',
|
favicons: ['/images/favicon.svg'],
|
||||||
|
mfsu: {
|
||||||
|
strategy: 'eager',
|
||||||
},
|
},
|
||||||
fastRefresh: {},
|
|
||||||
esbuild: {},
|
|
||||||
webpack5: {},
|
|
||||||
dynamicImport: {
|
|
||||||
loading: '@/components/pageLoading',
|
|
||||||
},
|
|
||||||
favicon: '/images/favicon.svg',
|
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/public': {
|
'/api/public': {
|
||||||
target: 'http://127.0.0.1:5400/',
|
target: 'http://127.0.0.1:5400/',
|
||||||
@@ -26,7 +21,7 @@ export default defineConfig({
|
|||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chainWebpack: (config) => {
|
chainWebpack: ((config: any) => {
|
||||||
config.plugin('compression-webpack-plugin').use(
|
config.plugin('compression-webpack-plugin').use(
|
||||||
new CompressionPlugin({
|
new CompressionPlugin({
|
||||||
algorithm: 'gzip',
|
algorithm: 'gzip',
|
||||||
@@ -35,13 +30,13 @@ export default defineConfig({
|
|||||||
minRatio: 0.6,
|
minRatio: 0.6,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
}) as any,
|
||||||
externals: {
|
externals: {
|
||||||
react: 'window.React',
|
react: 'window.React',
|
||||||
'react-dom': 'window.ReactDOM',
|
'react-dom': 'window.ReactDOM',
|
||||||
},
|
},
|
||||||
scripts: [
|
headScripts: [
|
||||||
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
'https://gw.alipayobjects.com/os/lib/react/18.2.0/umd/react.production.min.js',
|
||||||
'https://gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js',
|
'https://gw.alipayobjects.com/os/lib/react-dom/18.2.0/umd/react-dom.production.min.js',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ export default (app: Router) => {
|
|||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
sorts: Joi.array().optional(),
|
sorts: Joi.array().optional().allow(null),
|
||||||
filters: Joi.array().optional(),
|
filters: Joi.array().optional(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -49,7 +49,7 @@ export default (app: Router) => {
|
|||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
id: Joi.number().required(),
|
id: Joi.number().required(),
|
||||||
sorts: Joi.array().optional(),
|
sorts: Joi.array().optional().allow(null),
|
||||||
filters: Joi.array().optional(),
|
filters: Joi.array().optional(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
+18
-8
@@ -77,12 +77,14 @@ export default (app: Router) => {
|
|||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
let { filename, path, content, originFilename } = req.body as {
|
let { filename, path, content, originFilename, directory } =
|
||||||
filename: string;
|
req.body as {
|
||||||
path: string;
|
filename: string;
|
||||||
content: string;
|
path: string;
|
||||||
originFilename: string;
|
content: string;
|
||||||
};
|
originFilename: string;
|
||||||
|
directory: string;
|
||||||
|
};
|
||||||
|
|
||||||
if (!path) {
|
if (!path) {
|
||||||
path = config.scriptPath;
|
path = config.scriptPath;
|
||||||
@@ -105,11 +107,19 @@ export default (app: Router) => {
|
|||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (directory) {
|
||||||
|
fs.mkdirSync(join(path, directory), { recursive: true });
|
||||||
|
return res.send({ code: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
if (!originFilename) {
|
if (!originFilename) {
|
||||||
originFilename = filename;
|
originFilename = filename;
|
||||||
}
|
}
|
||||||
const originFilePath = `${path}${originFilename.replace(/\//g, '')}`;
|
const originFilePath = join(
|
||||||
const filePath = `${path}${filename.replace(/\//g, '')}`;
|
path,
|
||||||
|
`${originFilename.replace(/\//g, '')}`,
|
||||||
|
);
|
||||||
|
const filePath = join(path, `${filename.replace(/\//g, '')}`);
|
||||||
if (fs.existsSync(originFilePath)) {
|
if (fs.existsSync(originFilePath)) {
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
originFilePath,
|
originFilePath,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as path from 'path';
|
|||||||
import got from 'got';
|
import got from 'got';
|
||||||
import iconv from 'iconv-lite';
|
import iconv from 'iconv-lite';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
|
import FormData from 'form-data';
|
||||||
|
|
||||||
export function getFileContentByName(fileName: string) {
|
export function getFileContentByName(fileName: string) {
|
||||||
if (fs.existsSync(fileName)) {
|
if (fs.existsSync(fileName)) {
|
||||||
@@ -355,3 +356,61 @@ 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 {
|
export enum NotificationMode {
|
||||||
'gotify' = 'gotify',
|
'gotify' = 'gotify',
|
||||||
'goCqHttpBot' = 'goCqHttpBot',
|
'goCqHttpBot' = 'goCqHttpBot',
|
||||||
'serverChan' = 'serverChan',
|
'serverChan' = 'serverChan',
|
||||||
'pushDeer' = 'pushDeer',
|
'pushDeer' = 'pushDeer',
|
||||||
'bark' = 'bark',
|
'bark' = 'bark',
|
||||||
|
'chat' = 'chat',
|
||||||
'telegramBot' = 'telegramBot',
|
'telegramBot' = 'telegramBot',
|
||||||
'dingtalkBot' = 'dingtalkBot',
|
'dingtalkBot' = 'dingtalkBot',
|
||||||
'weWorkBot' = 'weWorkBot',
|
'weWorkBot' = 'weWorkBot',
|
||||||
@@ -11,6 +14,7 @@ export enum NotificationMode {
|
|||||||
'iGot' = 'iGot',
|
'iGot' = 'iGot',
|
||||||
'pushPlus' = 'pushPlus',
|
'pushPlus' = 'pushPlus',
|
||||||
'email' = 'email',
|
'email' = 'email',
|
||||||
|
'webhook' = 'webhook',
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class NotificationBaseInfo {
|
abstract class NotificationBaseInfo {
|
||||||
@@ -37,6 +41,11 @@ export class PushDeerNotification extends NotificationBaseInfo {
|
|||||||
public pushDeerKey = '';
|
public pushDeerKey = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ChatNotification extends NotificationBaseInfo {
|
||||||
|
public chatUrl = '';
|
||||||
|
public chatToken = '';
|
||||||
|
}
|
||||||
|
|
||||||
export class BarkNotification extends NotificationBaseInfo {
|
export class BarkNotification extends NotificationBaseInfo {
|
||||||
public barkPush = '';
|
public barkPush = '';
|
||||||
public barkIcon = 'http://qn.whyour.cn/logo.png';
|
public barkIcon = 'http://qn.whyour.cn/logo.png';
|
||||||
@@ -81,11 +90,20 @@ export class EmailNotification extends NotificationBaseInfo {
|
|||||||
public emailPass: string = '';
|
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
|
export interface NotificationInfo
|
||||||
extends GoCqHttpBotNotification,
|
extends GoCqHttpBotNotification,
|
||||||
GotifyNotification,
|
GotifyNotification,
|
||||||
ServerChanNotification,
|
ServerChanNotification,
|
||||||
PushDeerNotification,
|
PushDeerNotification,
|
||||||
|
ChatNotification,
|
||||||
BarkNotification,
|
BarkNotification,
|
||||||
TelegramBotNotification,
|
TelegramBotNotification,
|
||||||
DingtalkBotNotification,
|
DingtalkBotNotification,
|
||||||
@@ -93,4 +111,5 @@ export interface NotificationInfo
|
|||||||
WeWorkAppNotification,
|
WeWorkAppNotification,
|
||||||
IGotNotification,
|
IGotNotification,
|
||||||
PushPlusNotification,
|
PushPlusNotification,
|
||||||
EmailNotification {}
|
EmailNotification,
|
||||||
|
WebhookNotification {}
|
||||||
|
|||||||
+19
-7
@@ -3,7 +3,7 @@ import bodyParser from 'body-parser';
|
|||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import routes from '../api';
|
import routes from '../api';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import jwt from 'express-jwt';
|
import jwt, { UnauthorizedError } from 'express-jwt';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { getPlatform, getToken } from '../config/util';
|
import { getPlatform, getToken } from '../config/util';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
@@ -75,22 +75,24 @@ export default ({ app }: { app: Application }) => {
|
|||||||
if (
|
if (
|
||||||
!headerToken &&
|
!headerToken &&
|
||||||
originPath &&
|
originPath &&
|
||||||
config.apiWhiteList.includes(originPath) &&
|
config.apiWhiteList.includes(originPath)
|
||||||
originPath !== '/api/crons/status'
|
|
||||||
) {
|
) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
||||||
if (data) {
|
if (data && headerToken) {
|
||||||
const { token = '', tokens = {} } = JSON.parse(data);
|
const { token = '', tokens = {} } = JSON.parse(data);
|
||||||
if (headerToken === token || tokens[req.platform] === headerToken) {
|
if (headerToken === token || tokens[req.platform] === headerToken) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const err: any = new Error('UnauthorizedError');
|
const errorCode = headerToken ? 'invalid_token' : 'credentials_required';
|
||||||
err.status = 401;
|
const errorMessage = headerToken
|
||||||
|
? 'jwt malformed'
|
||||||
|
: 'No authorization token was found';
|
||||||
|
const err = new UnauthorizedError(errorCode, { message: errorMessage });
|
||||||
next(err);
|
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(
|
app.use(
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default async () => {
|
|||||||
|
|
||||||
// 生成内置token
|
// 生成内置token
|
||||||
let tokenCommand = `ts-node-transpile-only ${config.rootPath}/back/token.ts`;
|
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)) {
|
if (await fileExist(tokenFile)) {
|
||||||
tokenCommand = `node ${tokenFile}`;
|
tokenCommand = `node ${tokenFile}`;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -18,14 +18,15 @@ const run = async () => {
|
|||||||
_schedule &&
|
_schedule &&
|
||||||
_schedule.length > 5 &&
|
_schedule.length > 5 &&
|
||||||
task.status !== CrontabStatus.disabled &&
|
task.status !== CrontabStatus.disabled &&
|
||||||
!task.isDisabled
|
!task.isDisabled &&
|
||||||
|
task.schedule
|
||||||
) {
|
) {
|
||||||
schedule.scheduleJob(task.schedule, function () {
|
schedule.scheduleJob(task.schedule, function () {
|
||||||
let command = task.command as string;
|
let command = task.command as string;
|
||||||
if (!command.includes('task ') && !command.includes('ql ')) {
|
if (!command.includes('task ') && !command.includes('ql ')) {
|
||||||
command = `task ${command}`;
|
command = `task ${command}`;
|
||||||
}
|
}
|
||||||
exec(command);
|
exec(`ID=${task.id} ${command}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-35
@@ -115,18 +115,25 @@ export default class CronService {
|
|||||||
|
|
||||||
private formatViewQuery(query: any, viewQuery: any) {
|
private formatViewQuery(query: any, viewQuery: any) {
|
||||||
if (viewQuery.filters && viewQuery.filters.length > 0) {
|
if (viewQuery.filters && viewQuery.filters.length > 0) {
|
||||||
|
if (!query[Op.and]) {
|
||||||
|
query[Op.and] = [];
|
||||||
|
}
|
||||||
for (const col of viewQuery.filters) {
|
for (const col of viewQuery.filters) {
|
||||||
const { property, value, operation } = col;
|
const { property, value, operation } = col;
|
||||||
|
let q: any = {};
|
||||||
|
let operate2 = null;
|
||||||
let operate = null;
|
let operate = null;
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case 'Reg':
|
case 'Reg':
|
||||||
operate = Op.like;
|
operate = Op.like;
|
||||||
|
operate2 = Op.or;
|
||||||
break;
|
break;
|
||||||
case 'NotReg':
|
case 'NotReg':
|
||||||
operate = Op.notLike;
|
operate = Op.notLike;
|
||||||
|
operate2 = Op.and;
|
||||||
break;
|
break;
|
||||||
case 'In':
|
case 'In':
|
||||||
query[Op.or] = [
|
q[Op.or] = [
|
||||||
{
|
{
|
||||||
[property]: value,
|
[property]: value,
|
||||||
},
|
},
|
||||||
@@ -136,7 +143,7 @@ export default class CronService {
|
|||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
case 'Nin':
|
case 'Nin':
|
||||||
query[Op.and] = [
|
q[Op.and] = [
|
||||||
{
|
{
|
||||||
[property]: {
|
[property]: {
|
||||||
[Op.notIn]: value,
|
[Op.notIn]: value,
|
||||||
@@ -150,20 +157,25 @@ export default class CronService {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (operate) {
|
if (operate && operate2) {
|
||||||
query[property] = {
|
q[property] = {
|
||||||
[Op.or]: [
|
[operate2]: [
|
||||||
{ [operate]: `%${value}%` },
|
{ [operate]: `%${value}%` },
|
||||||
{ [operate]: `%${encodeURIComponent(value)}%` },
|
{ [operate]: `%${encodeURIComponent(value)}%` },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
query[Op.and].push(q);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatSearchText(query: any, searchText: string | undefined) {
|
private formatSearchText(query: any, searchText: string | undefined) {
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
|
if (!query[Op.and]) {
|
||||||
|
query[Op.and] = [];
|
||||||
|
}
|
||||||
|
let q: any = {};
|
||||||
const textArray = searchText.split(':');
|
const textArray = searchText.split(':');
|
||||||
switch (textArray[0]) {
|
switch (textArray[0]) {
|
||||||
case 'name':
|
case 'name':
|
||||||
@@ -171,13 +183,11 @@ export default class CronService {
|
|||||||
case 'schedule':
|
case 'schedule':
|
||||||
case 'label':
|
case 'label':
|
||||||
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
|
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
|
||||||
query = {
|
q[column] = {
|
||||||
[column]: {
|
[Op.or]: [
|
||||||
[Op.or]: [
|
{ [Op.like]: `%${textArray[1]}%` },
|
||||||
{ [Op.like]: `%${textArray[1]}%` },
|
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
||||||
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
|
],
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -187,24 +197,39 @@ export default class CronService {
|
|||||||
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
{ [Op.like]: `%${encodeURIComponent(searchText)}%` },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
query = {
|
q[Op.or] = [
|
||||||
[Op.or]: [
|
{
|
||||||
{
|
name: reg,
|
||||||
name: reg,
|
},
|
||||||
},
|
{
|
||||||
{
|
command: reg,
|
||||||
command: reg,
|
},
|
||||||
},
|
{
|
||||||
{
|
schedule: reg,
|
||||||
schedule: reg,
|
},
|
||||||
},
|
{
|
||||||
{
|
labels: reg,
|
||||||
labels: reg,
|
},
|
||||||
},
|
];
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
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;
|
searchValue: string;
|
||||||
page: string;
|
page: string;
|
||||||
size: string;
|
size: string;
|
||||||
sortField: string;
|
sorter: string;
|
||||||
sortType: string;
|
filters: string;
|
||||||
queryString: string;
|
queryString: string;
|
||||||
}): Promise<{ data: Crontab[]; total: number }> {
|
}): Promise<{ data: Crontab[]; total: number }> {
|
||||||
const searchText = params?.searchValue;
|
const searchText = params?.searchValue;
|
||||||
const page = Number(params?.page || '0');
|
const page = Number(params?.page || '0');
|
||||||
const size = Number(params?.size || '0');
|
const size = Number(params?.size || '0');
|
||||||
const sortField = params?.sortField || '';
|
|
||||||
const sortType = params?.sortType || '';
|
|
||||||
const viewQuery = JSON.parse(params?.queryString || '{}');
|
const viewQuery = JSON.parse(params?.queryString || '{}');
|
||||||
|
const filterQuery = JSON.parse(params?.filters || '{}');
|
||||||
|
const sorterQuery = JSON.parse(params?.sorter || '{}');
|
||||||
|
|
||||||
let query: any = {};
|
let query: any = {};
|
||||||
let order = [
|
let order = [
|
||||||
@@ -241,10 +266,14 @@ export default class CronService {
|
|||||||
|
|
||||||
this.formatViewQuery(query, viewQuery);
|
this.formatViewQuery(query, viewQuery);
|
||||||
this.formatSearchText(query, searchText);
|
this.formatSearchText(query, searchText);
|
||||||
|
this.formatFilterQuery(query, filterQuery);
|
||||||
this.formatViewSort(order, viewQuery);
|
this.formatViewSort(order, viewQuery);
|
||||||
|
|
||||||
if (sortType && sortField) {
|
if (sorterQuery) {
|
||||||
order.unshift([sortField, sortType]);
|
const { field, type } = sorterQuery;
|
||||||
|
if (field && type) {
|
||||||
|
order.unshift([field, type]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let condition: any = {
|
let condition: any = {
|
||||||
where: query,
|
where: query,
|
||||||
@@ -383,7 +412,7 @@ export default class CronService {
|
|||||||
cmdStr = `${cmdStr} now`;
|
cmdStr = `${cmdStr} now`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cp = spawn(cmdStr, { shell: '/bin/bash' });
|
const cp = spawn(`ID=${id} ${cmdStr}`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
{ status: CrontabStatus.running, pid: cp.pid },
|
{ status: CrontabStatus.running, pid: cp.pid },
|
||||||
|
|||||||
@@ -125,8 +125,11 @@ export default class DependenceService {
|
|||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async find(query: any, sort?: any): Promise<Dependence[]> {
|
private async find(query: any, sort: any = []): Promise<Dependence[]> {
|
||||||
const docs = await DependenceModel.findAll({ where: { ...query } });
|
const docs = await DependenceModel.findAll({
|
||||||
|
where: { ...query },
|
||||||
|
order: [...sort, ['createdAt', 'DESC']],
|
||||||
|
});
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,11 @@ export default class EnvService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
try {
|
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;
|
return result as any;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
+67
-4
@@ -6,6 +6,7 @@ import got from 'got';
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
||||||
|
import { parseBody, parseHeaders } from '../config/util';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class NotificationService {
|
export default class NotificationService {
|
||||||
@@ -17,6 +18,7 @@ export default class NotificationService {
|
|||||||
['goCqHttpBot', this.goCqHttpBot],
|
['goCqHttpBot', this.goCqHttpBot],
|
||||||
['serverChan', this.serverChan],
|
['serverChan', this.serverChan],
|
||||||
['pushDeer', this.pushDeer],
|
['pushDeer', this.pushDeer],
|
||||||
|
['chat', this.chat],
|
||||||
['bark', this.bark],
|
['bark', this.bark],
|
||||||
['telegramBot', this.telegramBot],
|
['telegramBot', this.telegramBot],
|
||||||
['dingtalkBot', this.dingtalkBot],
|
['dingtalkBot', this.dingtalkBot],
|
||||||
@@ -25,6 +27,7 @@ export default class NotificationService {
|
|||||||
['iGot', this.iGot],
|
['iGot', this.iGot],
|
||||||
['pushPlus', this.pushPlus],
|
['pushPlus', this.pushPlus],
|
||||||
['email', this.email],
|
['email', this.email],
|
||||||
|
['webhook', this.webhook],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
private timeout = 10000;
|
private timeout = 10000;
|
||||||
@@ -32,7 +35,7 @@ export default class NotificationService {
|
|||||||
private content = '';
|
private content = '';
|
||||||
private params!: Omit<NotificationInfo, 'type'>;
|
private params!: Omit<NotificationInfo, 'type'>;
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) { }
|
||||||
|
|
||||||
public async notify(
|
public async notify(
|
||||||
title: string,
|
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() {
|
private async bark() {
|
||||||
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
||||||
if (!barkPush.startsWith('http') && !barkPush.startsWith('https')) {
|
if (!barkPush.startsWith('http') && !barkPush.startsWith('https')) {
|
||||||
@@ -165,9 +182,8 @@ export default class NotificationService {
|
|||||||
telegramBotUserId,
|
telegramBotUserId,
|
||||||
} = this.params;
|
} = this.params;
|
||||||
const authStr = telegramBotProxyAuth ? `${telegramBotProxyAuth}@` : '';
|
const authStr = telegramBotProxyAuth ? `${telegramBotProxyAuth}@` : '';
|
||||||
const url = `https://${
|
const url = `https://${telegramBotApiHost ? telegramBotApiHost : 'api.telegram.org'
|
||||||
telegramBotApiHost ? telegramBotApiHost : 'api.telegram.org'
|
}/bot${telegramBotToken}/sendMessage`;
|
||||||
}/bot${telegramBotToken}/sendMessage`;
|
|
||||||
let agent;
|
let agent;
|
||||||
if (telegramBotProxyHost && telegramBotProxyPort) {
|
if (telegramBotProxyHost && telegramBotProxyPort) {
|
||||||
const options: any = {
|
const options: any = {
|
||||||
@@ -368,4 +384,51 @@ export default class NotificationService {
|
|||||||
|
|
||||||
return !!info.messageId;
|
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 {
|
try {
|
||||||
const result = await SubscriptionModel.findAll({
|
const result = await SubscriptionModel.findAll({
|
||||||
where: query,
|
where: query,
|
||||||
order: [['createdAt', 'DESC']],
|
order: [
|
||||||
|
['is_disabled', 'ASC'],
|
||||||
|
['createdAt', 'DESC'],
|
||||||
|
],
|
||||||
});
|
});
|
||||||
return result as any;
|
return result as any;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ dir_shell=/ql/shell
|
|||||||
. $dir_shell/share.sh
|
. $dir_shell/share.sh
|
||||||
link_shell
|
link_shell
|
||||||
|
|
||||||
|
export isFirstStartServer=true
|
||||||
|
|
||||||
echo -e "======================1. 检测配置文件========================\n"
|
echo -e "======================1. 检测配置文件========================\n"
|
||||||
make_dir /etc/nginx/conf.d
|
make_dir /etc/nginx/conf.d
|
||||||
make_dir /run/nginx
|
make_dir /run/nginx
|
||||||
@@ -11,6 +13,13 @@ cp -fv $nginx_conf /etc/nginx/nginx.conf
|
|||||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.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
|
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
||||||
pm2 l &>/dev/null
|
pm2 l &>/dev/null
|
||||||
|
|
||||||
|
if [[ $PipMirror ]]; then
|
||||||
|
pip3 config set global.index-url $PipMirror
|
||||||
|
fi
|
||||||
|
if [[ $NpmMirror ]]; then
|
||||||
|
npm config set registry $NpmMirror
|
||||||
|
fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo -e "======================2. 安装依赖========================\n"
|
echo -e "======================2. 安装依赖========================\n"
|
||||||
|
|||||||
+34
-18
@@ -2,16 +2,16 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "concurrently -n w: npm:start:*",
|
"start": "concurrently -n w: npm:start:*",
|
||||||
"start:front": "umi dev",
|
"start:front": "max dev",
|
||||||
"start:back": "nodemon",
|
"start:back": "nodemon",
|
||||||
"start:public": "ts-node back/public.ts",
|
"start:public": "ts-node back/public.ts",
|
||||||
"build:front": "umi build",
|
"build:front": "max build",
|
||||||
"build:back": "tsc -p tsconfig.back.json",
|
"build:back": "tsc -p tsconfig.back.json",
|
||||||
"panel": "npm run build:back && node static/build/app.js",
|
"panel": "npm run build:back && node static/build/app.js",
|
||||||
"schedule": "npm run build:back && node static/build/schedule.js",
|
"schedule": "npm run build:back && node static/build/schedule.js",
|
||||||
"public": "npm run build:back && node static/build/public.js",
|
"public": "npm run build:back && node static/build/public.js",
|
||||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
"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": "umi-test",
|
||||||
"test:coverage": "umi-test --coverage"
|
"test:coverage": "umi-test --coverage"
|
||||||
},
|
},
|
||||||
@@ -28,16 +28,33 @@
|
|||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
|
"ignoreMissing": [
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"antd",
|
||||||
|
"dva",
|
||||||
|
"postcss",
|
||||||
|
"webpack",
|
||||||
|
"eslint",
|
||||||
|
"stylelint",
|
||||||
|
"redux",
|
||||||
|
"@babel/core",
|
||||||
|
"monaco-editor",
|
||||||
|
"rc-field-form",
|
||||||
|
"@types/lodash.merge",
|
||||||
|
"rollup"
|
||||||
|
],
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
"react": "17",
|
"react": "18",
|
||||||
"react-dom": "17"
|
"react-dom": "18",
|
||||||
|
"dva-core": "2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@otplib/preset-default": "^12.0.1",
|
"@otplib/preset-default": "^12.0.1",
|
||||||
"@sentry/node": "^7.0.0",
|
"@sentry/node": "^7.12.1",
|
||||||
"@sentry/tracing": "^7.0.0",
|
"@sentry/tracing": "^7.12.1",
|
||||||
"body-parser": "^1.19.2",
|
"body-parser": "^1.19.2",
|
||||||
"celebrate": "^15.0.1",
|
"celebrate": "^15.0.1",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
@@ -48,6 +65,7 @@
|
|||||||
"express": "^4.17.3",
|
"express": "^4.17.3",
|
||||||
"express-jwt": "^6.1.1",
|
"express-jwt": "^6.1.1",
|
||||||
"express-urlrewrite": "^1.4.0",
|
"express-urlrewrite": "^1.4.0",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"hpagent": "^0.1.2",
|
"hpagent": "^0.1.2",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
@@ -72,8 +90,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
"@ant-design/pro-layout": "^6.33.1",
|
"@ant-design/pro-layout": "^6.33.1",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "4.2.1",
|
||||||
"@sentry/react": "^7.0.0",
|
"@sentry/react": "^7.12.1",
|
||||||
"@types/body-parser": "^1.19.2",
|
"@types/body-parser": "^1.19.2",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
@@ -86,18 +104,16 @@
|
|||||||
"@types/node-schedule": "^1.3.2",
|
"@types/node-schedule": "^1.3.2",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/qrcode.react": "^1.0.2",
|
"@types/qrcode.react": "^1.0.2",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^18.0.20",
|
||||||
"@types/react-dom": "^17.0.13",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/serve-handler": "^6.1.1",
|
"@types/serve-handler": "^6.1.1",
|
||||||
"@types/sockjs": "^0.3.33",
|
"@types/sockjs": "^0.3.33",
|
||||||
"@types/sockjs-client": "^1.5.1",
|
"@types/sockjs-client": "^1.5.1",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@umijs/plugin-antd": "^0.15.0",
|
"@umijs/max": "^4.0.21",
|
||||||
"@umijs/plugin-esbuild": "^1.4.1",
|
|
||||||
"@umijs/ssr-darkreader": "^4.9.45",
|
"@umijs/ssr-darkreader": "^4.9.45",
|
||||||
"@umijs/test": "^3.5.21",
|
|
||||||
"ansi-to-react": "^6.1.6",
|
"ansi-to-react": "^6.1.6",
|
||||||
"antd": "^4.20.5",
|
"antd": "^4.23.0",
|
||||||
"antd-img-crop": "^4.2.3",
|
"antd-img-crop": "^4.2.3",
|
||||||
"codemirror": "^5.65.2",
|
"codemirror": "^5.65.2",
|
||||||
"compression-webpack-plugin": "9.2.0",
|
"compression-webpack-plugin": "9.2.0",
|
||||||
@@ -107,19 +123,19 @@
|
|||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"qiniu": "^7.4.0",
|
"qiniu": "^7.4.0",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
|
"query-string": "^7.1.1",
|
||||||
"rc-tween-one": "^3.0.6",
|
"rc-tween-one": "^3.0.6",
|
||||||
"react": "17.x",
|
"react": "18.x",
|
||||||
"react-codemirror2": "^7.2.1",
|
"react-codemirror2": "^7.2.1",
|
||||||
"react-diff-viewer": "^3.1.1",
|
"react-diff-viewer": "^3.1.1",
|
||||||
"react-dnd": "^14.0.2",
|
"react-dnd": "^14.0.2",
|
||||||
"react-dnd-html5-backend": "^14.0.0",
|
"react-dnd-html5-backend": "^14.0.0",
|
||||||
"react-dom": "17.x",
|
"react-dom": "18.x",
|
||||||
"react-split-pane": "^0.1.92",
|
"react-split-pane": "^0.1.92",
|
||||||
"sockjs-client": "^1.6.0",
|
"sockjs-client": "^1.6.0",
|
||||||
"ts-node": "^10.6.0",
|
"ts-node": "^10.6.0",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.6.2",
|
"typescript": "^4.6.2",
|
||||||
"umi": "^3.5.21",
|
|
||||||
"umi-request": "^1.4.0",
|
"umi-request": "^1.4.0",
|
||||||
"vh-check": "^2.0.5",
|
"vh-check": "^2.0.5",
|
||||||
"webpack": "^5.70.0",
|
"webpack": "^5.70.0",
|
||||||
|
|||||||
+10
-1
@@ -46,9 +46,12 @@ AutoStartBot=""
|
|||||||
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot
|
||||||
BotRepoUrl=""
|
BotRepoUrl=""
|
||||||
|
|
||||||
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
|
## 安装python依赖时指定pip源
|
||||||
PipMirror="https://pypi.doubanio.com/simple/"
|
PipMirror="https://pypi.doubanio.com/simple/"
|
||||||
|
|
||||||
|
## 安装node依赖时指定npm源
|
||||||
|
NpmMirror="https://registry.npmmirror.com"
|
||||||
|
|
||||||
## 通知环境变量
|
## 通知环境变量
|
||||||
## 1. Server酱
|
## 1. Server酱
|
||||||
## https://sct.ftqq.com
|
## https://sct.ftqq.com
|
||||||
@@ -137,4 +140,10 @@ export GOTIFY_PRIORITY=0
|
|||||||
## deer_key 填写PushDeer的key
|
## deer_key 填写PushDeer的key
|
||||||
export DEER_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 变量名= 声明即可
|
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
|
||||||
|
|||||||
+60
-5
@@ -39,6 +39,12 @@ let SCKEY = '';
|
|||||||
//(环境变量名 DEER_KEY)
|
//(环境变量名 DEER_KEY)
|
||||||
let PUSHDEER_KEY = '';
|
let PUSHDEER_KEY = '';
|
||||||
|
|
||||||
|
// =======================================Synology Chat通知设置区域===========================================
|
||||||
|
//此处填你申请的CHAT_URL与CHAT_TOKEN
|
||||||
|
//(环境变量名 CHAT_URL CHAT_TOKEN)
|
||||||
|
let CHAT_URL = '';
|
||||||
|
let CHAT_TOKEN = '';
|
||||||
|
|
||||||
// =======================================Bark App通知设置区域===========================================
|
// =======================================Bark App通知设置区域===========================================
|
||||||
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
||||||
let BARK_PUSH = '';
|
let BARK_PUSH = '';
|
||||||
@@ -133,6 +139,14 @@ if (process.env.DEER_KEY) {
|
|||||||
PUSHDEER_KEY = 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) {
|
if (process.env.QQ_SKEY) {
|
||||||
QQ_SKEY = process.env.QQ_SKEY;
|
QQ_SKEY = process.env.QQ_SKEY;
|
||||||
}
|
}
|
||||||
@@ -239,6 +253,8 @@ async function sendNotify(
|
|||||||
iGotNotify(text, desp, params), //iGot
|
iGotNotify(text, desp, params), //iGot
|
||||||
gobotNotify(text, desp), //go-cqhttp
|
gobotNotify(text, desp), //go-cqhttp
|
||||||
gotifyNotify(text, desp), //gotify
|
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) => {
|
return new Promise((resolve) => {
|
||||||
if (PUSHDEER_KEY) {
|
if (PUSHDEER_KEY) {
|
||||||
// PushDeer 建议对消息内容进行 urlencode
|
// PushDeer 建议对消息内容进行 urlencode
|
||||||
@@ -382,8 +398,9 @@ function PushDeerNotify(text, desp, time = 2100) {
|
|||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
$.post(
|
||||||
$.post(options, (err, resp, data) => {
|
options,
|
||||||
|
(err, resp, data) => {
|
||||||
try {
|
try {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('发送通知调用API失败!!\n');
|
console.log('发送通知调用API失败!!\n');
|
||||||
@@ -407,8 +424,46 @@ function PushDeerNotify(text, desp, time = 2100) {
|
|||||||
} finally {
|
} finally {
|
||||||
resolve(data);
|
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 {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-2
@@ -61,7 +61,10 @@ push_config = {
|
|||||||
'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版
|
'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版
|
||||||
|
|
||||||
'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY
|
'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY
|
||||||
|
|
||||||
|
'CHAT_URL': '', # synology chat url
|
||||||
|
'CHAT_TOKEN': '', # synology chat token
|
||||||
|
|
||||||
'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌
|
'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌
|
||||||
'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码
|
'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码
|
||||||
|
|
||||||
@@ -279,8 +282,27 @@ def pushdeer(title: str, content: str) -> None:
|
|||||||
print("PushDeer 推送成功!")
|
print("PushDeer 推送成功!")
|
||||||
else:
|
else:
|
||||||
print("PushDeer 推送失败!错误信息:", response)
|
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:
|
def pushplus_bot(title: str, content: str) -> None:
|
||||||
"""
|
"""
|
||||||
通过 push+ 推送消息。
|
通过 push+ 推送消息。
|
||||||
@@ -527,6 +549,8 @@ if push_config.get("PUSH_KEY"):
|
|||||||
notify_function.append(serverJ)
|
notify_function.append(serverJ)
|
||||||
if push_config.get("DEER_KEY"):
|
if push_config.get("DEER_KEY"):
|
||||||
notify_function.append(pushdeer)
|
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"):
|
if push_config.get("PUSH_PLUS_TOKEN"):
|
||||||
notify_function.append(pushplus_bot)
|
notify_function.append(pushplus_bot)
|
||||||
if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"):
|
if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"):
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ check_pm2() {
|
|||||||
main() {
|
main() {
|
||||||
echo -e "=====> 开始检测"
|
echo -e "=====> 开始检测"
|
||||||
npm i -g pnpm
|
npm i -g pnpm
|
||||||
|
pnpm setup &>/dev/null
|
||||||
|
source ~/.bashrc
|
||||||
|
pnpm install -g &>/dev/null
|
||||||
pnpm add -g pm2
|
pnpm add -g pm2
|
||||||
copy_dep
|
copy_dep
|
||||||
check_ql
|
check_ql
|
||||||
|
|||||||
+3
-5
@@ -245,15 +245,13 @@ fix_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
npm_install_sub() {
|
npm_install_sub() {
|
||||||
set_proxy
|
|
||||||
if [ $is_termux -eq 1 ]; then
|
if [ $is_termux -eq 1 ]; then
|
||||||
npm install --production --no-bin-links --registry=https://registry.npmmirror.com || npm install --production --no-bin-links
|
npm install --production --no-bin-links
|
||||||
elif ! type pnpm &>/dev/null; then
|
elif ! type pnpm &>/dev/null; then
|
||||||
npm install --production --registry=https://registry.npmmirror.com || npm install --production
|
npm install --production
|
||||||
else
|
else
|
||||||
pnpm install --loglevel error --production --registry=https://registry.npmmirror.com || pnpm install --production --loglevel error
|
pnpm install --loglevel error --production
|
||||||
fi
|
fi
|
||||||
unset_proxy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
npm_install_1() {
|
npm_install_1() {
|
||||||
|
|||||||
+21
-65
@@ -91,15 +91,14 @@ run_nohup() {
|
|||||||
nohup node $file_name &>$log_path &
|
nohup node $file_name &>$log_path &
|
||||||
}
|
}
|
||||||
|
|
||||||
## 正常运行单个脚本,$1:传入参数
|
handle_log_path() {
|
||||||
run_normal() {
|
|
||||||
local file_param=$1
|
|
||||||
define_program "$file_param"
|
define_program "$file_param"
|
||||||
if [[ $# -eq 1 ]]; then
|
|
||||||
random_delay "$file_param"
|
local suffix=""
|
||||||
|
if [[ ! -z $ID ]]; then
|
||||||
|
suffix="_${ID}"
|
||||||
fi
|
fi
|
||||||
|
time=$(date "+$time_format")
|
||||||
local time=$(date "+$time_format")
|
|
||||||
log_time=$(format_log_time "$time_format" "$time")
|
log_time=$(format_log_time "$time_format" "$time")
|
||||||
log_dir_tmp="${file_param##*/}"
|
log_dir_tmp="${file_param##*/}"
|
||||||
if [[ $file_param =~ "/" ]]; then
|
if [[ $file_param =~ "/" ]]; then
|
||||||
@@ -112,11 +111,21 @@ run_normal() {
|
|||||||
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
log_dir_tmp_path="${log_dir_tmp_path%/*}"
|
||||||
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
log_dir_tmp_path="${log_dir_tmp_path##*/}"
|
||||||
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
|
||||||
log_dir="${log_dir_tmp%.*}"
|
log_dir="${log_dir_tmp%.*}${suffix}"
|
||||||
log_path="$log_dir/$log_time.log"
|
log_path="$log_dir/$log_time.log"
|
||||||
cmd=">> $dir_log/$log_path 2>&1"
|
cmd=">> $dir_log/$log_path 2>&1"
|
||||||
[[ "$show_log" == "true" ]] && cmd=""
|
[[ "$show_log" == "true" ]] && cmd=""
|
||||||
make_dir "$dir_log/$log_dir"
|
make_dir "$dir_log/$log_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
## 正常运行单个脚本,$1:传入参数
|
||||||
|
run_normal() {
|
||||||
|
local file_param=$1
|
||||||
|
if [[ $# -eq 1 ]]; then
|
||||||
|
random_delay "$file_param"
|
||||||
|
fi
|
||||||
|
|
||||||
|
handle_log_path
|
||||||
|
|
||||||
local begin_time=$(format_time "$time_format" "$time")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
@@ -170,25 +179,7 @@ run_concurrent() {
|
|||||||
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
local cookieStr=$(echo ${array_run[*]} | sed 's/\ /\&/g')
|
||||||
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
|
||||||
|
|
||||||
define_program "$file_param"
|
handle_log_path
|
||||||
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 begin_time=$(format_time "$time_format" "$time")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
@@ -240,25 +231,7 @@ run_designated() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
define_program "$file_param"
|
handle_log_path
|
||||||
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 begin_time=$(format_time "$time_format" "$time")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
@@ -304,25 +277,8 @@ run_designated() {
|
|||||||
## 运行其他命令
|
## 运行其他命令
|
||||||
run_else() {
|
run_else() {
|
||||||
local file_param="$1"
|
local file_param="$1"
|
||||||
define_program "$file_param"
|
|
||||||
local time=$(date "+$time_format")
|
handle_log_path
|
||||||
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 begin_time=$(format_time "$time_format" "$time")
|
local begin_time=$(format_time "$time_format" "$time")
|
||||||
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
local begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||||
|
|||||||
+15
-5
@@ -246,6 +246,8 @@ usage() {
|
|||||||
update_qinglong() {
|
update_qinglong() {
|
||||||
patch_version
|
patch_version
|
||||||
|
|
||||||
|
export isFirstStartServer=false
|
||||||
|
|
||||||
local no_restart="$1"
|
local no_restart="$1"
|
||||||
local all_branch=$(git branch -a)
|
local all_branch=$(git branch -a)
|
||||||
local primary_branch="master"
|
local primary_branch="master"
|
||||||
@@ -295,6 +297,11 @@ update_qinglong() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
patch_version() {
|
patch_version() {
|
||||||
|
# 兼容pnpm@7
|
||||||
|
pnpm setup &>/dev/null
|
||||||
|
source ~/.bashrc
|
||||||
|
pnpm install -g &>/dev/null
|
||||||
|
|
||||||
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
||||||
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
||||||
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
mv $dir_root/db/cookie.db $dir_root/db/env.db
|
||||||
@@ -303,13 +310,9 @@ patch_version() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if ! type ts-node &>/dev/null; then
|
if ! type ts-node &>/dev/null; then
|
||||||
pnpm i -g ts-node typescript tslib
|
pnpm add -g ts-node typescript tslib
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 兼容pnpm@7
|
|
||||||
pnpm setup
|
|
||||||
source ~/.bashrc
|
|
||||||
|
|
||||||
git config --global pull.rebase false
|
git config --global pull.rebase false
|
||||||
|
|
||||||
cp -f $dir_root/.env.example $dir_root/.env
|
cp -f $dir_root/.env.example $dir_root/.env
|
||||||
@@ -338,6 +341,13 @@ patch_version() {
|
|||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ $PipMirror ]]; then
|
||||||
|
pip3 config set global.index-url $PipMirror
|
||||||
|
fi
|
||||||
|
if [[ $NpmMirror ]]; then
|
||||||
|
npm config set registry $NpmMirror
|
||||||
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## 对比脚本
|
## 对比脚本
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const EditableTagGroup = ({
|
|||||||
{tag}
|
{tag}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span key={tag} style={{ display: 'inline-block', marginBottom: 8 }}>
|
<span key={tag} style={{ display: 'inline-block', marginBottom: 8 }}>
|
||||||
{tagElem}
|
{tagElem}
|
||||||
|
|||||||
@@ -326,10 +326,6 @@
|
|||||||
background-color: #373739;
|
background-color: #373739;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-layout-sider {
|
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-pro-sider-logo {
|
.ant-pro-sider-logo {
|
||||||
padding: 16px 8px !important;
|
padding: 16px 8px !important;
|
||||||
|
|
||||||
|
|||||||
+44
-33
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react';
|
|||||||
import ProLayout, { PageLoading } from '@ant-design/pro-layout';
|
import ProLayout, { PageLoading } from '@ant-design/pro-layout';
|
||||||
import * as DarkReader from '@umijs/ssr-darkreader';
|
import * as DarkReader from '@umijs/ssr-darkreader';
|
||||||
import defaultProps from './defaultProps';
|
import defaultProps from './defaultProps';
|
||||||
import { Link, history } from 'umi';
|
import { Link, history, Outlet, useLocation } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
@@ -21,7 +21,18 @@ import SockJS from 'sockjs-client';
|
|||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { init } from '../utils/init';
|
import { init } from '../utils/init';
|
||||||
|
|
||||||
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 ctx = useCtx();
|
||||||
const { theme, reloadTheme } = useTheme();
|
const { theme, reloadTheme } = useTheme();
|
||||||
const [user, setUser] = useState<any>({});
|
const [user, setUser] = useState<any>({});
|
||||||
@@ -73,7 +84,7 @@ export default function (props: any) {
|
|||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200 && data.username) {
|
if (code === 200 && data.username) {
|
||||||
setUser(data);
|
setUser(data);
|
||||||
if (props.location.pathname === '/') {
|
if (location.pathname === '/') {
|
||||||
history.push('/crontab');
|
history.push('/crontab');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -94,7 +105,7 @@ export default function (props: any) {
|
|||||||
if (systemInfo && systemInfo.isInitialized && !user) {
|
if (systemInfo && systemInfo.isInitialized && !user) {
|
||||||
getUser();
|
getUser();
|
||||||
}
|
}
|
||||||
}, [props.location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!systemInfo) {
|
if (!systemInfo) {
|
||||||
@@ -136,9 +147,11 @@ export default function (props: any) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) return;
|
if (!user || !user.username) return;
|
||||||
ws.current = new SockJS(
|
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) => {
|
ws.current.onmessage = (e: any) => {
|
||||||
@@ -146,9 +159,9 @@ export default function (props: any) {
|
|||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
if (data.type === 'ping') {
|
if (data.type === 'ping') {
|
||||||
if (data && data.message === 'hanhh') {
|
if (data && data.message === 'hanhh') {
|
||||||
console.log('websocket连接成功', e);
|
console.log('WS connection succeeded !!!');
|
||||||
} else {
|
} else {
|
||||||
console.log('websocket连接失败', e);
|
console.log('WS connection Failed !!!', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSocketMessage(data);
|
setSocketMessage(data);
|
||||||
@@ -183,29 +196,27 @@ export default function (props: any) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (
|
if (['/login', '/initialization', '/error'].includes(location.pathname)) {
|
||||||
['/login', '/initialization', '/error'].includes(props.location.pathname)
|
document.title = `${
|
||||||
) {
|
(config.documentTitleMap as any)[location.pathname]
|
||||||
document.title = `${(config.documentTitleMap as any)[props.location.pathname]
|
} - 控制面板`;
|
||||||
} - 控制面板`;
|
if (systemInfo?.isInitialized && location.pathname === '/initialization') {
|
||||||
if (
|
|
||||||
systemInfo?.isInitialized &&
|
|
||||||
props.location.pathname === '/initialization'
|
|
||||||
) {
|
|
||||||
history.push('/crontab');
|
history.push('/crontab');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (systemInfo || props.location.pathname === '/error') {
|
if (systemInfo || location.pathname === '/error') {
|
||||||
return React.Children.map(props.children, (child) => {
|
return (
|
||||||
return React.cloneElement(child, {
|
<Outlet
|
||||||
...ctx,
|
context={{
|
||||||
theme,
|
...ctx,
|
||||||
user,
|
theme,
|
||||||
reloadUser,
|
user,
|
||||||
reloadTheme,
|
reloadUser,
|
||||||
ws: ws.current,
|
reloadTheme,
|
||||||
});
|
ws: ws.current,
|
||||||
});
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +237,7 @@ export default function (props: any) {
|
|||||||
<PageLoading />
|
<PageLoading />
|
||||||
) : (
|
) : (
|
||||||
<ProLayout
|
<ProLayout
|
||||||
selectedKeys={[props.location.pathname]}
|
selectedKeys={[location.pathname]}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
ErrorBoundary={Sentry.ErrorBoundary}
|
ErrorBoundary={Sentry.ErrorBoundary}
|
||||||
logo={<Image preview={false} src="http://qn.whyour.cn/logo.png" />}
|
logo={<Image preview={false} src="http://qn.whyour.cn/logo.png" />}
|
||||||
@@ -319,16 +330,16 @@ export default function (props: any) {
|
|||||||
)}
|
)}
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
>
|
>
|
||||||
{React.Children.map(props.children, (child) => {
|
<Outlet
|
||||||
return React.cloneElement(child, {
|
context={{
|
||||||
...ctx,
|
...ctx,
|
||||||
theme,
|
theme,
|
||||||
user,
|
user,
|
||||||
reloadUser,
|
reloadUser,
|
||||||
reloadTheme,
|
reloadTheme,
|
||||||
socketMessage,
|
socketMessage,
|
||||||
});
|
}}
|
||||||
})}
|
/>
|
||||||
</ProLayout>
|
</ProLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ import { PageContainer } from '@ant-design/pro-layout';
|
|||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
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 [value, setValue] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [title, setTitle] = useState('config.sh');
|
const [title, setTitle] = useState('config.sh');
|
||||||
|
|||||||
@@ -153,9 +153,13 @@ const CronDetailModal = ({
|
|||||||
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
let [p, s] = cmd[1].split('/');
|
let p: string, s: string;
|
||||||
if (!s) {
|
let index = cmd[1].lastIndexOf('/');
|
||||||
s = p;
|
if (index >= 0) {
|
||||||
|
s = cmd[1].slice(index + 1);
|
||||||
|
p = cmd[1].slice(0, index);
|
||||||
|
} else {
|
||||||
|
s = cmd[1];
|
||||||
p = '';
|
p = '';
|
||||||
}
|
}
|
||||||
setScriptInfo({ parent: p, filename: s });
|
setScriptInfo({ parent: p, filename: s });
|
||||||
@@ -378,7 +382,7 @@ const CronDetailModal = ({
|
|||||||
{currentCron.labels?.length > 0 &&
|
{currentCron.labels?.length > 0 &&
|
||||||
currentCron.labels[0] !== '' &&
|
currentCron.labels[0] !== '' &&
|
||||||
currentCron.labels?.map((label: string, i: number) => (
|
currentCron.labels?.map((label: string, i: number) => (
|
||||||
<Tag color="blue" style={{ marginRight: 5 }}>
|
<Tag key={label} color="blue" style={{ marginRight: 5 }}>
|
||||||
{label}
|
{label}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
@@ -441,7 +445,7 @@ const CronDetailModal = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
centered
|
centered
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
footer={false}
|
footer={false}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cron {
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.crontab-detail {
|
.crontab-detail {
|
||||||
.card-wrapper {
|
.card-wrapper {
|
||||||
height: 80vh;
|
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 {
|
tr.drop-over-downward td {
|
||||||
border-bottom: 2px dashed #1890ff;
|
border-bottom: 2px dashed #1890ff;
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-23
@@ -44,12 +44,13 @@ import CronDetailModal from './detail';
|
|||||||
import cron_parser from 'cron-parser';
|
import cron_parser from 'cron-parser';
|
||||||
import { diffTime } from '@/utils/date';
|
import { diffTime } from '@/utils/date';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { getTableScroll } from '@/utils/index';
|
||||||
import { history } from 'umi';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import ViewCreateModal from './viewCreateModal';
|
import ViewCreateModal from './viewCreateModal';
|
||||||
import ViewManageModal from './viewManageModal';
|
import ViewManageModal from './viewManageModal';
|
||||||
import pagination from 'antd/lib/pagination';
|
import pagination from 'antd/lib/pagination';
|
||||||
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -81,7 +82,8 @@ enum OperationPath {
|
|||||||
'unpin',
|
'unpin',
|
||||||
}
|
}
|
||||||
|
|
||||||
const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
const Crontab = () => {
|
||||||
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
@@ -93,10 +95,11 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
goToScriptManager(record);
|
setDetailCron(record);
|
||||||
|
setIsDetailModalVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{record.labels?.length > 0 && record.labels[0] !== '' ? (
|
{record.labels?.length > 0 && record.labels[0] !== '' && false ? (
|
||||||
<Popover
|
<Popover
|
||||||
placement="right"
|
placement="right"
|
||||||
trigger={isPhone ? 'click' : 'hover'}
|
trigger={isPhone ? 'click' : 'hover'}
|
||||||
@@ -105,10 +108,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
{record.labels?.map((label: string) => (
|
{record.labels?.map((label: string) => (
|
||||||
<Tag
|
<Tag
|
||||||
color="blue"
|
color="blue"
|
||||||
|
key={label}
|
||||||
style={{ cursor: 'point' }}
|
style={{ cursor: 'point' }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setSearchValue(`label:${label}`);
|
setSearchValue(`label:${label}`);
|
||||||
|
setSearchText(`label:${label}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a>{label}</a>
|
<a>{label}</a>
|
||||||
@@ -140,7 +145,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
title: '命令',
|
title: '命令',
|
||||||
dataIndex: 'command',
|
dataIndex: 'command',
|
||||||
key: 'command',
|
key: 'command',
|
||||||
width: 250,
|
width: 300,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -152,7 +157,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
}}
|
}}
|
||||||
ellipsis={{ tooltip: text, rows: 2 }}
|
ellipsis={{ tooltip: text, rows: 2 }}
|
||||||
>
|
>
|
||||||
{text}
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
goToScriptManager(record);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</a>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -173,6 +184,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
{
|
{
|
||||||
title: '最后运行时间',
|
title: '最后运行时间',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
dataIndex: 'last_execution_time',
|
||||||
|
key: 'last_execution_time',
|
||||||
width: 150,
|
width: 150,
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a: any, b: any) => {
|
compare: (a: any, b: any) => {
|
||||||
@@ -202,6 +215,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
title: '最后运行时长',
|
title: '最后运行时长',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
width: 120,
|
width: 120,
|
||||||
|
dataIndex: 'last_running_time',
|
||||||
|
key: 'last_running_time',
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a: any, b: any) => {
|
compare: (a: any, b: any) => {
|
||||||
return a.last_running_time - b.last_running_time;
|
return a.last_running_time - b.last_running_time;
|
||||||
@@ -358,6 +373,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
page: number;
|
page: number;
|
||||||
size: number;
|
size: number;
|
||||||
sorter: any;
|
sorter: any;
|
||||||
|
filters: any;
|
||||||
}>({} as any);
|
}>({} as any);
|
||||||
const [viewConf, setViewConf] = useState<any>();
|
const [viewConf, setViewConf] = useState<any>();
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||||
@@ -380,9 +396,13 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
let [p, s] = cmd[1].split('/');
|
let p: string, s: string;
|
||||||
if (!s) {
|
let index = cmd[1].lastIndexOf('/');
|
||||||
s = p;
|
if (index >= 0) {
|
||||||
|
s = cmd[1].slice(index + 1);
|
||||||
|
p = cmd[1].slice(0, index);
|
||||||
|
} else {
|
||||||
|
s = cmd[1];
|
||||||
p = '';
|
p = '';
|
||||||
}
|
}
|
||||||
history.push(`/script?p=${p}&s=${s}`);
|
history.push(`/script?p=${p}&s=${s}`);
|
||||||
@@ -393,12 +413,17 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const getCrons = () => {
|
const getCrons = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { page, size, sorter } = pageConf;
|
const { page, size, sorter, filters } = pageConf;
|
||||||
let url = `${config.apiPrefix}crons?searchValue=${searchText}&page=${page}&size=${size}`;
|
let url = `${
|
||||||
|
config.apiPrefix
|
||||||
|
}crons?searchValue=${searchText}&page=${page}&size=${size}&filters=${JSON.stringify(
|
||||||
|
filters,
|
||||||
|
)}`;
|
||||||
if (sorter && sorter.field) {
|
if (sorter && sorter.field) {
|
||||||
url += `&sortField=${sorter.field}&sortType=${
|
url += `&sorter=${JSON.stringify({
|
||||||
sorter.order === 'ascend' ? 'ASC' : 'DESC'
|
field: sorter.field,
|
||||||
}`;
|
type: sorter.order === 'ascend' ? 'ASC' : 'DESC',
|
||||||
|
})}`;
|
||||||
}
|
}
|
||||||
if (viewConf) {
|
if (viewConf) {
|
||||||
url += `&queryString=${JSON.stringify({
|
url += `&queryString=${JSON.stringify({
|
||||||
@@ -815,7 +840,12 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
sorter: SorterResult<any> | SorterResult<any>[],
|
sorter: SorterResult<any> | SorterResult<any>[],
|
||||||
) => {
|
) => {
|
||||||
const { current, pageSize } = pagination;
|
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));
|
localStorage.setItem('pageSize', String(pageSize));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -852,6 +882,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
page: 1,
|
page: 1,
|
||||||
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
size: parseInt(localStorage.getItem('pageSize') || '20'),
|
||||||
sorter: {},
|
sorter: {},
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setTableScrollHeight(getTableScroll());
|
setTableScrollHeight(getTableScroll());
|
||||||
@@ -931,14 +962,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
(a, b) => a - b,
|
(a, b) => a - b,
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
onRow={(record) => {
|
|
||||||
return {
|
|
||||||
onClick: (event) => {
|
|
||||||
setDetailCron(record);
|
|
||||||
setIsDetailModalVisible(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
dataSource={value}
|
dataSource={value}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
size="middle"
|
size="middle"
|
||||||
@@ -1017,6 +1040,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
|
|
||||||
const tabClick = (key: string) => {
|
const tabClick = (key: string) => {
|
||||||
const view = enabledCronViews.find((x) => x.id == key);
|
const view = enabledCronViews.find((x) => x.id == key);
|
||||||
|
setPageConf({ ...pageConf, page: 1 });
|
||||||
setViewConf(view ? view : null);
|
setViewConf(view ? view : null);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1064,6 +1088,18 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
}
|
}
|
||||||
onTabClick={tabClick}
|
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">
|
<Tabs.TabPane tab="全部任务" key="all">
|
||||||
{panelContent}
|
{panelContent}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ const CronLogModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
bodyStyle={{
|
bodyStyle={{
|
||||||
|
|||||||
+22
-16
@@ -28,13 +28,14 @@ const CronModal = ({
|
|||||||
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
|
const { code, data } = await request[method](`${config.apiPrefix}crons`, {
|
||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
message.success(cron ? '更新Cron成功' : '新建Cron成功');
|
||||||
|
handleCancel(data);
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -47,7 +48,7 @@ const CronModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={cron ? '编辑任务' : '新建任务'}
|
title={cron ? '编辑任务' : '新建任务'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -128,21 +129,26 @@ const CronLabelModal = ({
|
|||||||
.then(async (values) => {
|
.then(async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const payload = { ids, labels: values.labels };
|
const payload = { ids, labels: values.labels };
|
||||||
const { code, data } = await request[action](
|
try {
|
||||||
`${config.apiPrefix}crons/labels`,
|
const { code, data } = await request[action](
|
||||||
{
|
`${config.apiPrefix}crons/labels`,
|
||||||
data: payload,
|
{
|
||||||
},
|
data: payload,
|
||||||
);
|
},
|
||||||
if (code === 200) {
|
|
||||||
message.success(
|
|
||||||
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
message.error(data);
|
if (code === 200) {
|
||||||
|
message.success(
|
||||||
|
action === 'post' ? '添加Labels成功' : '删除Labels成功',
|
||||||
|
);
|
||||||
|
handleCancel(true);
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel(true);
|
|
||||||
})
|
})
|
||||||
.catch((info) => {
|
.catch((info) => {
|
||||||
console.log('Validate Failed:', info);
|
console.log('Validate Failed:', info);
|
||||||
@@ -166,7 +172,7 @@ const CronLabelModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="批量修改标签"
|
title="批量修改标签"
|
||||||
visible={visible}
|
open={visible}
|
||||||
footer={buttons}
|
footer={buttons}
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
@@ -65,11 +65,13 @@ const ViewCreateModal = ({
|
|||||||
data: view ? { ...values, id: view.id } : values,
|
data: view ? { ...values, id: view.id } : values,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (code !== 200) {
|
if (code !== 200) {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
|
} else {
|
||||||
|
handleCancel(data);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -101,9 +103,9 @@ const ViewCreateModal = ({
|
|||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
|
|
||||||
const propertyElement = (props: any) => {
|
const propertyElement = (props: any, style: React.CSSProperties = {}) => {
|
||||||
return (
|
return (
|
||||||
<Select style={{ width: 120 }}>
|
<Select style={style}>
|
||||||
{props.map((x) => (
|
{props.map((x) => (
|
||||||
<Select.Option key={x.name} value={x.value}>
|
<Select.Option key={x.name} value={x.value}>
|
||||||
{x.name}
|
{x.name}
|
||||||
@@ -114,7 +116,7 @@ const ViewCreateModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const typeElement = (
|
const typeElement = (
|
||||||
<Select style={{ width: 120 }}>
|
<Select>
|
||||||
{SORTTYPES.map((x) => (
|
{SORTTYPES.map((x) => (
|
||||||
<Select.Option key={x.name} value={x.value}>
|
<Select.Option key={x.name} value={x.value}>
|
||||||
{x.name}
|
{x.name}
|
||||||
@@ -136,7 +138,7 @@ const ViewCreateModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={view ? '编辑视图' : '新建视图'}
|
title={view ? '编辑视图' : '新建视图'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
width={580}
|
width={580}
|
||||||
centered
|
centered
|
||||||
@@ -170,6 +172,7 @@ const ViewCreateModal = ({
|
|||||||
label={index === 0 ? '筛选条件' : ''}
|
label={index === 0 ? '筛选条件' : ''}
|
||||||
key={key}
|
key={key}
|
||||||
style={{ marginBottom: 0 }}
|
style={{ marginBottom: 0 }}
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<Space className="view-create-modal-filters" align="baseline">
|
<Space className="view-create-modal-filters" align="baseline">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -177,7 +180,7 @@ const ViewCreateModal = ({
|
|||||||
name={[name, 'property']}
|
name={[name, 'property']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
{propertyElement(PROPERTIES)}
|
{propertyElement(PROPERTIES, { width: 120 })}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
{...restField}
|
{...restField}
|
||||||
@@ -225,13 +228,13 @@ const ViewCreateModal = ({
|
|||||||
key={key}
|
key={key}
|
||||||
style={{ marginBottom: 0 }}
|
style={{ marginBottom: 0 }}
|
||||||
>
|
>
|
||||||
<Space className="view-create-modal-filters" align="baseline">
|
<Space className="view-create-modal-sorts" align="baseline">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
{...restField}
|
{...restField}
|
||||||
name={[name, 'property']}
|
name={[name, 'property']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
{propertyElement(PROPERTIES)}
|
{propertyElement(PROPERTIES, { width: 240 })}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
{...restField}
|
{...restField}
|
||||||
@@ -240,9 +243,7 @@ const ViewCreateModal = ({
|
|||||||
>
|
>
|
||||||
{typeElement}
|
{typeElement}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{index !== 0 && (
|
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
|
||||||
)}
|
|
||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ const ViewManageModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="视图管理"
|
title="视图管理"
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
width={620}
|
width={620}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
@@ -231,7 +231,10 @@ const ViewManageModal = ({
|
|||||||
<Button
|
<Button
|
||||||
key="2"
|
key="2"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => setIsCreateViewModalVisible(true)}
|
onClick={() => {
|
||||||
|
setEditedView(null);
|
||||||
|
setIsCreateViewModalVisible(true);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
新建视图
|
新建视图
|
||||||
</Button>
|
</Button>
|
||||||
@@ -258,8 +261,8 @@ const ViewManageModal = ({
|
|||||||
view={editedView}
|
view={editedView}
|
||||||
visible={isCreateViewModalVisible}
|
visible={isCreateViewModalVisible}
|
||||||
handleCancel={(data) => {
|
handleCancel={(data) => {
|
||||||
cronViewChange(data);
|
|
||||||
setIsCreateViewModalVisible(false);
|
setIsCreateViewModalVisible(false);
|
||||||
|
cronViewChange(data);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
|
|||||||
import './index.less';
|
import './index.less';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { getTableScroll } from '@/utils/index';
|
||||||
import DependenceLogModal from './logModal';
|
import DependenceLogModal from './logModal';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -48,7 +50,9 @@ enum StatusColor {
|
|||||||
'error',
|
'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
const Dependence = () => {
|
||||||
|
const { headerStyle, isPhone, socketMessage } =
|
||||||
|
useOutletContext<SharedContext>();
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '序号',
|
title: '序号',
|
||||||
@@ -320,6 +324,30 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlereInstallDependencies = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认重新安装',
|
||||||
|
content: <>确认重新安装选中的依赖吗</>,
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||||
|
data: selectedRowIds,
|
||||||
|
})
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
setSelectedRowIds([]);
|
||||||
|
getDependencies();
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getDependenceDetail = (dependence: any) => {
|
const getDependenceDetail = (dependence: any) => {
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
.get(`${config.apiPrefix}dependencies/${dependence.id}`)
|
||||||
@@ -403,6 +431,13 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
<>
|
<>
|
||||||
{selectedRowIds.length > 0 && (
|
{selectedRowIds.length > 0 && (
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
|
onClick={() => handlereInstallDependencies()}
|
||||||
|
>
|
||||||
|
批量安装
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ marginBottom: 5, marginLeft: 8 }}
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
@@ -467,17 +502,24 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
|||||||
size="small"
|
size="small"
|
||||||
tabPosition="top"
|
tabPosition="top"
|
||||||
onChange={onTabChange}
|
onChange={onTabChange}
|
||||||
>
|
items={[
|
||||||
<Tabs.TabPane tab="NodeJs" key="nodejs">
|
{
|
||||||
{panelContent()}
|
key: 'nodejs',
|
||||||
</Tabs.TabPane>
|
label: 'NodeJs',
|
||||||
<Tabs.TabPane tab="Python3" key="python3">
|
children: panelContent(),
|
||||||
{panelContent()}
|
},
|
||||||
</Tabs.TabPane>
|
{
|
||||||
<Tabs.TabPane tab="Linux" key="linux">
|
key: 'python3',
|
||||||
{panelContent()}
|
label: 'Python3',
|
||||||
</Tabs.TabPane>
|
children: panelContent(),
|
||||||
</Tabs>
|
},
|
||||||
|
{
|
||||||
|
key: 'linux',
|
||||||
|
label: 'Linux',
|
||||||
|
children: panelContent(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<DependenceModal
|
<DependenceModal
|
||||||
visible={isModalVisible}
|
visible={isModalVisible}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ const DependenceLogModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
bodyStyle={{
|
bodyStyle={{
|
||||||
|
|||||||
@@ -52,11 +52,13 @@ const DependenceModal = ({
|
|||||||
data: payload,
|
data: payload,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (code !== 200) {
|
if (code !== 200) {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
|
} else {
|
||||||
|
handleCancel(data);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ const DependenceModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={dependence ? '编辑依赖' : '新建依赖'}
|
title={dependence ? '编辑依赖' : '新建依赖'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -99,7 +101,9 @@ const DependenceModal = ({
|
|||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{config.dependenceTypes.map((x, i) => (
|
{config.dependenceTypes.map((x, i) => (
|
||||||
<Option value={i}>{x}</Option>
|
<Option key={i} value={i}>
|
||||||
|
{x}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -17,4 +17,8 @@
|
|||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ section {
|
||||||
|
height: calc(100% - 40px) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ import { request } from '@/utils/http';
|
|||||||
import './index.less';
|
import './index.less';
|
||||||
import { DiffEditor } from '@monaco-editor/react';
|
import { DiffEditor } from '@monaco-editor/react';
|
||||||
import ReactDiffViewer from 'react-diff-viewer';
|
import ReactDiffViewer from 'react-diff-viewer';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Option } = Select;
|
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 [origin, setOrigin] = useState('config.sample.sh');
|
||||||
const [current, setCurrent] = useState('config.sh');
|
const [current, setCurrent] = useState('config.sh');
|
||||||
const [originValue, setOriginValue] = useState('');
|
const [originValue, setOriginValue] = useState('');
|
||||||
@@ -95,7 +98,9 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<Form.Item label="源文件">
|
<Form.Item label="源文件">
|
||||||
<Select value={origin} onChange={originFileChange}>
|
<Select value={origin} onChange={originFileChange}>
|
||||||
{files.map((x) => (
|
{files.map((x) => (
|
||||||
<Option value={x.value}>{x.title}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.title}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -104,7 +109,9 @@ const Diff = ({ headerStyle, isPhone, theme }: any) => {
|
|||||||
<Form.Item label="当前文件">
|
<Form.Item label="当前文件">
|
||||||
<Select value={current} onChange={currentFileChange}>
|
<Select value={current} onChange={currentFileChange}>
|
||||||
{files.map((x) => (
|
{files.map((x) => (
|
||||||
<Option value={x.value}>{x.title}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.title}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
Vendored
+18
-13
@@ -17,19 +17,24 @@ const EditNameModal = ({
|
|||||||
|
|
||||||
const handleOk = async (values: any) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { code, data } = await request.put(`${config.apiPrefix}envs/name`, {
|
try {
|
||||||
data: {
|
const { code, data } = await request.put(`${config.apiPrefix}envs/name`, {
|
||||||
ids,
|
data: {
|
||||||
name: values.name,
|
ids,
|
||||||
},
|
name: values.name,
|
||||||
});
|
},
|
||||||
if (code === 200) {
|
});
|
||||||
message.success('更新环境变量名称成功');
|
|
||||||
} else {
|
if (code === 200) {
|
||||||
message.error(data);
|
message.success('更新环境变量名称成功');
|
||||||
|
handleCancel();
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,7 +44,7 @@ const EditNameModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="修改环境变量名称"
|
title="修改环境变量名称"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
Vendored
+4
-1
@@ -26,6 +26,8 @@ import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
|||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { exportJson, getTableScroll } from '@/utils/index';
|
import { exportJson, getTableScroll } from '@/utils/index';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
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 = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '序号',
|
title: '序号',
|
||||||
|
|||||||
Vendored
+3
-2
@@ -40,13 +40,14 @@ const EnvModal = ({
|
|||||||
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
||||||
data: payload,
|
data: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(env ? '更新变量成功' : '新建变量成功');
|
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||||
|
handleCancel(data);
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,7 @@ const EnvModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={env ? '编辑变量' : '新建变量'}
|
title={env ? '编辑变量' : '新建变量'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import config from '@/utils/config';
|
|||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import Terminal, { ColorMode, LineType } from '../../components/terminal';
|
import Terminal, { ColorMode, LineType } from '../../components/terminal';
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
import { history } from 'umi';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import Ansi from 'ansi-to-react';
|
import Ansi from 'ansi-to-react';
|
||||||
import './index.less';
|
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 [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState('暂无日志');
|
const [data, setData] = useState('暂无日志');
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { history } from 'umi';
|
import { history } from '@umijs/max';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
|
|
||||||
@@ -116,12 +116,15 @@ const Initialization = () => {
|
|||||||
{config.notificationModes
|
{config.notificationModes
|
||||||
.filter((x) => x.value !== 'closed')
|
.filter((x) => x.value !== 'closed')
|
||||||
.map((x) => (
|
.map((x) => (
|
||||||
<Option value={x.value}>{x.label}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.label}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{fields.map((x) => (
|
{fields.map((x) => (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
key={x.label}
|
||||||
label={x.label}
|
label={x.label}
|
||||||
name={x.label}
|
name={x.label}
|
||||||
extra={x.tip}
|
extra={x.tip}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: @component-background;
|
background-color: @component-background;
|
||||||
height: calc(100vh - 128px);
|
height: 100%;
|
||||||
height: calc(100vh - var(--vh-offset, 0px) - 128px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { request } from '@/utils/http';
|
|||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||||
import SplitPane from 'react-split-pane';
|
import SplitPane from 'react-split-pane';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
function getFilterData(keyword: string, data: any) {
|
function getFilterData(keyword: string, data: any) {
|
||||||
const expandedKeys: string[] = [];
|
const expandedKeys: string[] = [];
|
||||||
@@ -36,7 +38,8 @@ function getFilterData(keyword: string, data: any) {
|
|||||||
return { tree: data, expandedKeys };
|
return { tree: data, expandedKeys };
|
||||||
}
|
}
|
||||||
|
|
||||||
const Log = ({ headerStyle, isPhone, theme }: any) => {
|
const Log = () => {
|
||||||
|
const { headerStyle, isPhone, theme } = useOutletContext<SharedContext>();
|
||||||
const [title, setTitle] = useState('请选择日志文件');
|
const [title, setTitle] = useState('请选择日志文件');
|
||||||
const [value, setValue] = useState('请选择日志文件');
|
const [value, setValue] = useState('请选择日志文件');
|
||||||
const [select, setSelect] = useState<any>();
|
const [select, setSelect] = useState<any>();
|
||||||
|
|||||||
@@ -9,16 +9,18 @@ import {
|
|||||||
Statistic,
|
Statistic,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { history, Link } from 'umi';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import { useTheme } from '@/utils/hooks';
|
import { useTheme } from '@/utils/hooks';
|
||||||
import { MobileOutlined } from '@ant-design/icons';
|
import { MobileOutlined } from '@ant-design/icons';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const { Countdown } = Statistic;
|
const { Countdown } = Statistic;
|
||||||
|
|
||||||
const Login = ({ reloadUser }: any) => {
|
const Login = () => {
|
||||||
|
const { reloadUser } = useOutletContext<SharedContext>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [waitTime, setWaitTime] = useState<any>();
|
const [waitTime, setWaitTime] = useState<any>();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ const EditModal = ({
|
|||||||
width={'100%'}
|
width={'100%'}
|
||||||
headerStyle={{ padding: '11px 24px' }}
|
headerStyle={{ padding: '11px 24px' }}
|
||||||
onClose={cancel}
|
onClose={cancel}
|
||||||
visible={visible}
|
open={visible}
|
||||||
>
|
>
|
||||||
<SplitPane
|
<SplitPane
|
||||||
split="vertical"
|
split="vertical"
|
||||||
|
|||||||
@@ -32,28 +32,29 @@ const EditScriptNameModal = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [dirs, setDirs] = useState<any[]>([]);
|
const [dirs, setDirs] = useState<any[]>([]);
|
||||||
const [file, setFile] = useState<File>();
|
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) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
values.path = values.path || '';
|
const { path = '', filename: inputFilename, directory = '' } = values;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file as any);
|
formData.append('file', file as any);
|
||||||
formData.append('filename', values.filename);
|
formData.append('filename', inputFilename);
|
||||||
formData.append('path', values.path);
|
formData.append('path', path);
|
||||||
formData.append('content', '');
|
formData.append('content', '');
|
||||||
|
formData.append('directory', directory);
|
||||||
request
|
request
|
||||||
.post(`${config.apiPrefix}scripts`, {
|
.post(`${config.apiPrefix}scripts`, {
|
||||||
data: formData,
|
data: formData,
|
||||||
})
|
})
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success('新建文件成功');
|
message.success(directory ? '新建文件夹成功' : '新建文件成功');
|
||||||
const key = values.path ? `${values.path}/` : '';
|
const key = path ? `${path}/` : '';
|
||||||
const filename = file ? file.name : values.filename;
|
const filename = file ? file.name : inputFilename;
|
||||||
handleCancel({
|
handleCancel({
|
||||||
filename,
|
filename,
|
||||||
path: values.path,
|
path,
|
||||||
key: `${key}${filename}`,
|
key: `${key}${filename}`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -99,8 +100,8 @@ const EditScriptNameModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="新建脚本"
|
title="新建"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -126,18 +127,36 @@ const EditScriptNameModal = ({
|
|||||||
>
|
>
|
||||||
<Radio.Group onChange={typeChange}>
|
<Radio.Group onChange={typeChange}>
|
||||||
<Radio value="blank">空文件</Radio>
|
<Radio value="blank">空文件</Radio>
|
||||||
<Radio value="upload">本地上传</Radio>
|
<Radio value="upload">本地文件</Radio>
|
||||||
|
<Radio value="directory">文件夹</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{type === 'blank' && (
|
{type === 'blank' && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="filename"
|
name="filename"
|
||||||
label="文件名"
|
label="文件名"
|
||||||
rules={[{ required: true, message: '请输入文件名' }]}
|
rules={[
|
||||||
|
{ required: true, message: '请输入文件名' },
|
||||||
|
{
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.includes('/')
|
||||||
|
? Promise.reject(new Error('文件名不能包含斜杠'))
|
||||||
|
: Promise.resolve(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入文件名" />
|
<Input placeholder="请输入文件名" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
{type === 'directory' && (
|
||||||
|
<Form.Item
|
||||||
|
name="directory"
|
||||||
|
label="文件夹名"
|
||||||
|
rules={[{ required: true, message: '请输入文件夹名' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入文件夹名" />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
<Form.Item label="父目录" name="path">
|
<Form.Item label="父目录" name="path">
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
allowClear
|
allowClear
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: @component-background;
|
background-color: @component-background;
|
||||||
height: calc(100vh - 128px);
|
height: 100%;
|
||||||
height: calc(100vh - var(--vh-offset, 0px) - 128px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-19
@@ -33,7 +33,10 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import EditScriptNameModal from './editNameModal';
|
import EditScriptNameModal from './editNameModal';
|
||||||
import debounce from 'lodash/debounce';
|
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';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -72,7 +75,9 @@ const LangMap: any = {
|
|||||||
'.ts': 'typescript',
|
'.ts': 'typescript',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
const Script = () => {
|
||||||
|
const { headerStyle, isPhone, theme, socketMessage } =
|
||||||
|
useOutletContext<SharedContext>();
|
||||||
const [title, setTitle] = useState('请选择脚本文件');
|
const [title, setTitle] = useState('请选择脚本文件');
|
||||||
const [value, setValue] = useState('请选择脚本文件');
|
const [value, setValue] = useState('请选择脚本文件');
|
||||||
const [select, setSelect] = useState<any>();
|
const [select, setSelect] = useState<any>();
|
||||||
@@ -111,7 +116,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initGetScript = () => {
|
const initGetScript = () => {
|
||||||
const { p, s } = history.location.query as any;
|
const { p, s } = parse(history.location.search);
|
||||||
if (s) {
|
if (s) {
|
||||||
const vkey = `${p}/${s}`;
|
const vkey = `${p}/${s}`;
|
||||||
const obj = {
|
const obj = {
|
||||||
@@ -313,20 +318,17 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const newData = [...data];
|
let newData = [...data];
|
||||||
const _file = { title: filename, key, parent: path };
|
const _file = { title: filename, key, parent: path };
|
||||||
if (path) {
|
if (path) {
|
||||||
// TODO: 更新左侧树数据
|
newData = depthFirstSearch(newData, (c) => c.key === path, _file);
|
||||||
const parentNodeIndex = newData.findIndex((x) => x.key === path);
|
const keys = path.split('/');
|
||||||
if (parentNodeIndex !== -1) {
|
const sKeys: string[] = [];
|
||||||
const parentNode = newData[parentNodeIndex];
|
keys.reduce((p, c) => {
|
||||||
if (parentNode.children && parentNode.children.length > 0) {
|
sKeys.push(p);
|
||||||
parentNode.children.unshift(_file);
|
return `${p}/${c}`;
|
||||||
} else {
|
});
|
||||||
parentNode.children = [_file];
|
setExpandedKeys([...expandedKeys, ...sKeys, path]);
|
||||||
}
|
|
||||||
newData.splice(parentNodeIndex, 1, { ...parentNode });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
newData.unshift(_file);
|
newData.unshift(_file);
|
||||||
}
|
}
|
||||||
@@ -360,10 +362,6 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
|||||||
const word = searchValue || '';
|
const word = searchValue || '';
|
||||||
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
const { tree } = getFilterData(word.toLocaleLowerCase(), data);
|
||||||
setFilterData(tree);
|
setFilterData(tree);
|
||||||
setSelect('');
|
|
||||||
setCurrentNode(null);
|
|
||||||
setTitle('请选择脚本文件');
|
|
||||||
setValue('请选择脚本文件');
|
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const SaveModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="保存文件"
|
title="保存文件"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const SettingModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="运行设置"
|
title="运行设置"
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
|||||||
@@ -22,16 +22,21 @@ const AppModal = ({
|
|||||||
if (app) {
|
if (app) {
|
||||||
payload.id = app.id;
|
payload.id = app.id;
|
||||||
}
|
}
|
||||||
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
try {
|
||||||
data: payload,
|
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
||||||
});
|
data: payload,
|
||||||
if (code === 200) {
|
});
|
||||||
message.success(app ? '更新应用成功' : '新建应用成功');
|
|
||||||
} else {
|
if (code === 200) {
|
||||||
message.error(data);
|
message.success(app ? '更新应用成功' : '新建应用成功');
|
||||||
|
handleCancel(data);
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
handleCancel(data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -41,7 +46,7 @@ const AppModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={app ? '编辑应用' : '新建应用'}
|
title={app ? '编辑应用' : '新建应用'}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -64,7 +69,18 @@ const AppModal = ({
|
|||||||
name="form_app_modal"
|
name="form_app_modal"
|
||||||
initialValues={app}
|
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="请输入应用名称" />
|
<Input placeholder="请输入应用名称" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="scopes" label="权限" rules={[{ required: true }]}>
|
<Form.Item name="scopes" label="权限" rules={[{ required: true }]}>
|
||||||
@@ -75,7 +91,11 @@ const AppModal = ({
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
{config.scopes.map((x) => {
|
{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>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
+85
-71
@@ -29,6 +29,8 @@ import LoginLog from './loginLog';
|
|||||||
import NotificationSetting from './notification';
|
import NotificationSetting from './notification';
|
||||||
import CheckUpdate from './checkUpdate';
|
import CheckUpdate from './checkUpdate';
|
||||||
import About from './about';
|
import About from './about';
|
||||||
|
import { useOutletContext } from '@umijs/max';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const optionsWithDisabled = [
|
const optionsWithDisabled = [
|
||||||
@@ -37,14 +39,9 @@ const optionsWithDisabled = [
|
|||||||
{ label: '跟随系统', value: 'auto' },
|
{ label: '跟随系统', value: 'auto' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Setting = ({
|
const Setting = () => {
|
||||||
headerStyle,
|
const { headerStyle, isPhone, user, reloadUser, reloadTheme, socketMessage } =
|
||||||
isPhone,
|
useOutletContext<SharedContext>();
|
||||||
user,
|
|
||||||
reloadUser,
|
|
||||||
reloadTheme,
|
|
||||||
socketMessage,
|
|
||||||
}: any) => {
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
@@ -333,70 +330,87 @@ const Setting = ({
|
|||||||
size="small"
|
size="small"
|
||||||
tabPosition="top"
|
tabPosition="top"
|
||||||
onChange={tabChange}
|
onChange={tabChange}
|
||||||
>
|
items={[
|
||||||
<Tabs.TabPane tab="安全设置" key="security">
|
{
|
||||||
<SecuritySettings user={user} userChange={reloadUser} />
|
key: 'security',
|
||||||
</Tabs.TabPane>
|
label: '安全设置',
|
||||||
<Tabs.TabPane tab="应用设置" key="app">
|
children: <SecuritySettings user={user} userChange={reloadUser} />,
|
||||||
<Table
|
},
|
||||||
columns={columns}
|
{
|
||||||
pagination={false}
|
key: 'app',
|
||||||
dataSource={dataSource}
|
label: '应用设置',
|
||||||
rowKey="id"
|
children: (
|
||||||
size="middle"
|
<Table
|
||||||
scroll={{ x: 768 }}
|
columns={columns}
|
||||||
loading={loading}
|
pagination={false}
|
||||||
/>
|
dataSource={dataSource}
|
||||||
</Tabs.TabPane>
|
rowKey="id"
|
||||||
<Tabs.TabPane tab="通知设置" key="notification">
|
size="middle"
|
||||||
<NotificationSetting data={notificationInfo} />
|
scroll={{ x: 768 }}
|
||||||
</Tabs.TabPane>
|
loading={loading}
|
||||||
<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"
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
),
|
||||||
<Form.Item
|
},
|
||||||
label="日志删除频率"
|
{
|
||||||
name="frequency"
|
key: 'notification',
|
||||||
tooltip="每x天自动删除x天以前的日志"
|
label: '通知设置',
|
||||||
>
|
children: <NotificationSetting data={notificationInfo} />,
|
||||||
<Input.Group compact>
|
},
|
||||||
<InputNumber
|
{
|
||||||
addonBefore="每"
|
key: 'login',
|
||||||
addonAfter="天"
|
label: '登录日志',
|
||||||
style={{ width: 150 }}
|
children: <LoginLog data={loginLogData} />,
|
||||||
min={0}
|
},
|
||||||
value={logRemoveFrequency}
|
{
|
||||||
onChange={(value) => setLogRemoveFrequency(value)}
|
key: 'other',
|
||||||
/>
|
label: '其他设置',
|
||||||
<Button type="primary" onClick={updateRemoveLogFrequency}>
|
children: (
|
||||||
确认
|
<Form layout="vertical" form={form}>
|
||||||
</Button>
|
<Form.Item
|
||||||
</Input.Group>
|
label="主题设置"
|
||||||
</Form.Item>
|
name="theme"
|
||||||
<Form.Item label="检查更新" name="update">
|
initialValue={defaultTheme}
|
||||||
<CheckUpdate socketMessage={socketMessage} />
|
>
|
||||||
</Form.Item>
|
<Radio.Group
|
||||||
</Form>
|
options={optionsWithDisabled}
|
||||||
</Tabs.TabPane>
|
onChange={themeChange}
|
||||||
<Tabs.TabPane tab="关于" key="about">
|
value={defaultTheme}
|
||||||
<About />
|
optionType="button"
|
||||||
</Tabs.TabPane>
|
buttonStyle="solid"
|
||||||
</Tabs>
|
/>
|
||||||
|
</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
|
<AppModal
|
||||||
visible={isModalVisible}
|
visible={isModalVisible}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const handleOk = (values: any) => {
|
const handleOk = (values: any) => {
|
||||||
if (values.type == 'closed') {
|
const { type } = values;
|
||||||
|
if (type == 'closed') {
|
||||||
values.type = '';
|
values.type = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
request
|
request
|
||||||
.put(`${config.apiPrefix}user/notification`, {
|
.put(`${config.apiPrefix}user/notification`, {
|
||||||
data: {
|
data: {
|
||||||
@@ -25,7 +27,7 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
if (_data && _data.code === 200) {
|
if (_data && _data.code === 200) {
|
||||||
message.success(values.type ? '通知发送成功' : '通知关闭成功');
|
message.success(values.type ? '通知发送成功' : '通知关闭成功');
|
||||||
} else {
|
} else {
|
||||||
message.error(_data.data);
|
message.error(_data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -58,19 +60,37 @@ const NotificationSetting = ({ data }: any) => {
|
|||||||
>
|
>
|
||||||
<Select onChange={notificationModeChange}>
|
<Select onChange={notificationModeChange}>
|
||||||
{config.notificationModes.map((x) => (
|
{config.notificationModes.map((x) => (
|
||||||
<Option value={x.value}>{x.label}</Option>
|
<Option key={x.value} value={x.value}>
|
||||||
|
{x.label}
|
||||||
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{fields.map((x) => (
|
{fields.map((x) => (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
key={x.label}
|
||||||
label={x.label}
|
label={x.label}
|
||||||
name={x.label}
|
name={x.label}
|
||||||
extra={x.tip}
|
extra={x.tip}
|
||||||
rules={[{ required: x.required }]}
|
rules={[{ required: x.required }]}
|
||||||
style={{ maxWidth: 400 }}
|
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>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
<Button type="primary" htmlType="submit">
|
<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 { Typography, Input, Form, Button, message, Avatar, Upload } from 'antd';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { history } from 'umi';
|
import { history } from '@umijs/max';
|
||||||
import QRCode from 'qrcode.react';
|
import QRCode from 'qrcode.react';
|
||||||
import { PageLoading } from '@ant-design/pro-layout';
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
import { UploadOutlined, UserOutlined } from '@ant-design/icons';
|
import { UploadOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ import { PageContainer } from '@ant-design/pro-layout';
|
|||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import SubscriptionModal from './modal';
|
import SubscriptionModal from './modal';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { getTableScroll } from '@/utils/index';
|
||||||
import { history } from 'umi';
|
import { history, useOutletContext } from '@umijs/max';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import SubscriptionLogModal from './logModal';
|
import SubscriptionLogModal from './logModal';
|
||||||
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -57,7 +58,10 @@ export enum SubscriptionType {
|
|||||||
'file' = '单文件',
|
'file' = '单文件',
|
||||||
}
|
}
|
||||||
|
|
||||||
const Subscription = ({ headerStyle, isPhone, socketMessage }: any) => {
|
const Subscription = () => {
|
||||||
|
const { headerStyle, isPhone, socketMessage } =
|
||||||
|
useOutletContext<SharedContext>();
|
||||||
|
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const SubscriptionLogModal = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
visible={visible}
|
open={visible}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
bodyStyle={{
|
bodyStyle={{
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ const SubscriptionModal = ({
|
|||||||
);
|
);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
|
message.success(subscription ? '更新订阅成功' : '新建订阅成功');
|
||||||
|
handleCancel(data);
|
||||||
} else {
|
} else {
|
||||||
message.error(data);
|
message.error(data);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
handleCancel(data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ const SubscriptionModal = ({
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
visible={visible}
|
open={visible}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
+41
-1
@@ -90,7 +90,9 @@ export default {
|
|||||||
{ value: 'weWorkApp', label: '企业微信应用' },
|
{ value: 'weWorkApp', label: '企业微信应用' },
|
||||||
{ value: 'iGot', label: 'IGot' },
|
{ value: 'iGot', label: 'IGot' },
|
||||||
{ value: 'pushPlus', label: 'PushPlus' },
|
{ value: 'pushPlus', label: 'PushPlus' },
|
||||||
|
{ value: 'chat', label: '群辉chat' },
|
||||||
{ value: 'email', label: '邮箱' },
|
{ value: 'email', label: '邮箱' },
|
||||||
|
{ value: 'webhook', label: '自定义通知' },
|
||||||
{ value: 'closed', label: '已关闭' },
|
{ value: 'closed', label: '已关闭' },
|
||||||
],
|
],
|
||||||
notificationModeMap: {
|
notificationModeMap: {
|
||||||
@@ -103,6 +105,14 @@ export default {
|
|||||||
{ label: 'gotifyToken', tip: 'gotify的消息应用token码', required: true },
|
{ label: 'gotifyToken', tip: 'gotify的消息应用token码', required: true },
|
||||||
{ label: 'gotifyPriority', tip: '推送消息的优先级' },
|
{ label: 'gotifyPriority', tip: '推送消息的优先级' },
|
||||||
],
|
],
|
||||||
|
chat: [
|
||||||
|
{
|
||||||
|
label: 'chatUrl',
|
||||||
|
tip: 'chat的url地址',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{ label: 'chatToken', tip: 'chat的token码', required: true },
|
||||||
|
],
|
||||||
goCqHttpBot: [
|
goCqHttpBot: [
|
||||||
{
|
{
|
||||||
label: 'goCqHttpBotUrl',
|
label: 'goCqHttpBotUrl',
|
||||||
@@ -196,7 +206,7 @@ export default {
|
|||||||
pushPlus: [
|
pushPlus: [
|
||||||
{
|
{
|
||||||
label: 'pushPlusToken',
|
label: 'pushPlusToken',
|
||||||
tip: '微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送',
|
tip: '微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送,参考 https://www.pushplus.plus/',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -213,6 +223,36 @@ export default {
|
|||||||
{ label: 'emailUser', tip: '邮箱地址', required: true },
|
{ label: 'emailUser', tip: '邮箱地址', required: true },
|
||||||
{ label: 'emailPass', tip: '邮箱SMTP授权码', 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: {
|
documentTitleMap: {
|
||||||
'/login': '登录',
|
'/login': '登录',
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import { extend } from 'umi-request';
|
import { extend } from 'umi-request';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import { history } from 'umi';
|
import { history } from '@umijs/max';
|
||||||
|
|
||||||
message.config({
|
message.config({
|
||||||
duration: 1.5,
|
duration: 1.5,
|
||||||
|
|||||||
+32
-5
@@ -150,9 +150,9 @@ export default function browserType() {
|
|||||||
shell === 'none'
|
shell === 'none'
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
shell, // wechat qq uc 360 2345 sougou liebao maxthon
|
shell, // wechat qq uc 360 2345 sougou liebao maxthon
|
||||||
shellVs,
|
shellVs,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -188,8 +188,8 @@ export function getTableScroll({
|
|||||||
if (id) {
|
if (id) {
|
||||||
tHeader = document.getElementById(id)
|
tHeader = document.getElementById(id)
|
||||||
? document
|
? document
|
||||||
.getElementById(id)!
|
.getElementById(id)!
|
||||||
.getElementsByClassName('ant-table-thead')[0]
|
.getElementsByClassName('ant-table-thead')[0]
|
||||||
: null;
|
: null;
|
||||||
} else {
|
} else {
|
||||||
tHeader = document.querySelector('.ant-table-wrapper');
|
tHeader = document.querySelector('.ant-table-wrapper');
|
||||||
@@ -241,3 +241,30 @@ export function exportJson(name: string, data: string) {
|
|||||||
createA.download = name;
|
createA.download = name;
|
||||||
automaticClick(createA);
|
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 (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;
|
||||||
|
}
|
||||||
|
|||||||
+7
-7
@@ -1,8 +1,8 @@
|
|||||||
export const version = '2.14.0';
|
export const version = '2.14.4';
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/325';
|
export const changeLogLink = 'https://t.me/jiao_long/331';
|
||||||
export const changeLog = `2.14.0 版本说明
|
export const changeLog = `2.14.4 版本说明
|
||||||
1. 定时任务自定义视图功能来啦,支持自定义筛选条件、排序规则等
|
1. 通知设置新增自定义通知
|
||||||
2. 配置文件可设置需要延迟的脚本后缀,默认只有js脚本随机延迟执行,感谢 https://github.com/cddjr
|
2. 脚本管理支持新建文件夹
|
||||||
3. 修复定时任务搜索及搜索分页
|
3. 依赖管理增加批量重新安装
|
||||||
4. 其他bug修复
|
4. 修复创建应用错误
|
||||||
`;
|
`;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"lib": ["es2017", "esnext.asynciterable", "DOM"],
|
"lib": ["es2021", "esnext.asynciterable", "DOM"],
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types",
|
"./node_modules/@types",
|
||||||
"./src/types",
|
"./src/types",
|
||||||
|
|||||||
Reference in New Issue
Block a user