mirror of
https://github.com/whyour/qinglong.git
synced 2025-07-06 03:06:08 +08:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c2f823911f | ||
![]() |
0587644a6b | ||
![]() |
87b934aafe | ||
![]() |
7a92e7c6ab | ||
![]() |
1d8403c0ec | ||
![]() |
ef9e38f167 | ||
![]() |
c9bd053fbd | ||
![]() |
57939391b9 | ||
![]() |
394e96bbf8 | ||
![]() |
47c194c1f4 | ||
![]() |
7d65d96ebd | ||
![]() |
224000b63b | ||
![]() |
1c18668bad | ||
![]() |
f94582b68d | ||
![]() |
eb1c00984c | ||
![]() |
1a185f5682 |
|
@ -232,7 +232,7 @@ export default (app: Router) => {
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
filename: Joi.string().required(),
|
filename: Joi.string().required(),
|
||||||
path: Joi.string().allow(''),
|
path: Joi.string().optional().allow(''),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
@ -241,6 +241,9 @@ export default (app: Router) => {
|
||||||
filename: string;
|
filename: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
if (!path) {
|
||||||
|
path = '';
|
||||||
|
}
|
||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const filePath = scriptService.checkFilePath(path, filename);
|
const filePath = scriptService.checkFilePath(path, filename);
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
|
|
|
@ -273,6 +273,7 @@ export default (app: Router) => {
|
||||||
{
|
{
|
||||||
onStart: async (cp, startTime) => {
|
onStart: async (cp, startTime) => {
|
||||||
res.setHeader('QL-Task-Pid', `${cp.pid}`);
|
res.setHeader('QL-Task-Pid', `${cp.pid}`);
|
||||||
|
res.setHeader('QL-Task-Log', `${logPath}`);
|
||||||
},
|
},
|
||||||
onEnd: async (cp, endTime, diff) => {
|
onEnd: async (cp, endTime, diff) => {
|
||||||
res.end();
|
res.end();
|
||||||
|
@ -316,10 +317,15 @@ export default (app: Router) => {
|
||||||
|
|
||||||
route.put(
|
route.put(
|
||||||
'/data/export',
|
'/data/export',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
type: Joi.array().items(Joi.string()).optional(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
await systemService.exportData(res);
|
await systemService.exportData(res, req.body.type);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
|
@ -416,4 +422,22 @@ export default (app: Router) => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/config/dependence-clean',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
type: Joi.string().allow(''),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const result = await systemService.cleanDependence(req.body.type);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
197
back/app.ts
197
back/app.ts
|
@ -1,4 +1,5 @@
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
import cluster, { type Worker } from 'cluster';
|
||||||
import compression from 'compression';
|
import compression from 'compression';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
@ -10,11 +11,19 @@ import { monitoringMiddleware } from './middlewares/monitoring';
|
||||||
import { type GrpcServerService } from './services/grpc';
|
import { type GrpcServerService } from './services/grpc';
|
||||||
import { type HttpServerService } from './services/http';
|
import { type HttpServerService } from './services/http';
|
||||||
|
|
||||||
|
interface WorkerMetadata {
|
||||||
|
id: number;
|
||||||
|
pid: number;
|
||||||
|
serviceType: string;
|
||||||
|
startTime: Date;
|
||||||
|
}
|
||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
private app: express.Application;
|
private app: express.Application;
|
||||||
private httpServerService?: HttpServerService;
|
private httpServerService?: HttpServerService;
|
||||||
private grpcServerService?: GrpcServerService;
|
private grpcServerService?: GrpcServerService;
|
||||||
private isShuttingDown = false;
|
private isShuttingDown = false;
|
||||||
|
private workerMetadataMap = new Map<number, WorkerMetadata>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
|
@ -22,24 +31,57 @@ class Application {
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
try {
|
try {
|
||||||
await this.initializeDatabase();
|
if (cluster.isPrimary) {
|
||||||
await this.initServer();
|
await this.initializeDatabase();
|
||||||
this.setupMiddlewares();
|
}
|
||||||
await this.initializeServices();
|
if (cluster.isPrimary) {
|
||||||
this.setupGracefulShutdown();
|
this.startMasterProcess();
|
||||||
|
} else {
|
||||||
process.send?.('ready');
|
await this.startWorkerProcess();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error('Failed to start application:', error);
|
Logger.error('Failed to start application:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async initServer() {
|
private startMasterProcess() {
|
||||||
const { HttpServerService } = await import('./services/http');
|
this.forkWorker('http');
|
||||||
const { GrpcServerService } = await import('./services/grpc');
|
this.forkWorker('grpc');
|
||||||
this.httpServerService = Container.get(HttpServerService);
|
|
||||||
this.grpcServerService = Container.get(GrpcServerService);
|
cluster.on('exit', (worker, code, signal) => {
|
||||||
|
const metadata = this.workerMetadataMap.get(worker.id);
|
||||||
|
if (metadata) {
|
||||||
|
if (!this.isShuttingDown) {
|
||||||
|
Logger.error(
|
||||||
|
`${metadata.serviceType} worker ${worker.process.pid} died (${
|
||||||
|
signal || code
|
||||||
|
}). Restarting...`,
|
||||||
|
);
|
||||||
|
const newWorker = this.forkWorker(metadata.serviceType);
|
||||||
|
Logger.info(
|
||||||
|
`Restarted ${metadata.serviceType} worker (New PID: ${newWorker.process.pid})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.workerMetadataMap.delete(worker.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setupMasterShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private forkWorker(serviceType: string): Worker {
|
||||||
|
const worker = cluster.fork({ SERVICE_TYPE: serviceType });
|
||||||
|
|
||||||
|
this.workerMetadataMap.set(worker.id, {
|
||||||
|
id: worker.id,
|
||||||
|
pid: worker.process.pid!,
|
||||||
|
serviceType,
|
||||||
|
startTime: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeDatabase() {
|
private async initializeDatabase() {
|
||||||
|
@ -53,33 +95,49 @@ class Application {
|
||||||
this.app.use(monitoringMiddleware);
|
this.app.use(monitoringMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeServices() {
|
private setupMasterShutdown() {
|
||||||
await this.grpcServerService?.initialize();
|
|
||||||
|
|
||||||
await require('./loaders/app').default({ app: this.app });
|
|
||||||
|
|
||||||
const server = await this.httpServerService?.initialize(
|
|
||||||
this.app,
|
|
||||||
config.port,
|
|
||||||
);
|
|
||||||
|
|
||||||
await require('./loaders/server').default({ server });
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupGracefulShutdown() {
|
|
||||||
const shutdown = async () => {
|
const shutdown = async () => {
|
||||||
if (this.isShuttingDown) return;
|
if (this.isShuttingDown) return;
|
||||||
this.isShuttingDown = true;
|
this.isShuttingDown = true;
|
||||||
|
|
||||||
Logger.info('Shutting down services...');
|
const workers = Object.values(cluster.workers || {});
|
||||||
|
const workerPromises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
workers.forEach((worker) => {
|
||||||
|
if (worker) {
|
||||||
|
const exitPromise = new Promise<void>((resolve) => {
|
||||||
|
worker.once('exit', () => {
|
||||||
|
Logger.info(`Worker ${worker.process.pid} exited`);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
worker.send('shutdown');
|
||||||
|
} catch (error) {
|
||||||
|
Logger.warn(
|
||||||
|
`Failed to send shutdown to worker ${worker.process.pid}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
workerPromises.push(exitPromise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.race([
|
||||||
this.grpcServerService?.shutdown(),
|
Promise.all(workerPromises),
|
||||||
this.httpServerService?.shutdown(),
|
new Promise<void>((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
Logger.warn('Worker shutdown timeout reached');
|
||||||
|
resolve();
|
||||||
|
}, 10000);
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error('Error during shutdown:', error);
|
Logger.error('Error during worker shutdown:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -87,6 +145,83 @@ class Application {
|
||||||
process.on('SIGTERM', shutdown);
|
process.on('SIGTERM', shutdown);
|
||||||
process.on('SIGINT', shutdown);
|
process.on('SIGINT', shutdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async startWorkerProcess() {
|
||||||
|
const serviceType = process.env.SERVICE_TYPE;
|
||||||
|
if (!serviceType || !['http', 'grpc'].includes(serviceType)) {
|
||||||
|
Logger.error('Invalid SERVICE_TYPE:', serviceType);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info(`✌️ ${serviceType} worker started (PID: ${process.pid})`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (serviceType === 'http') {
|
||||||
|
await this.startHttpService();
|
||||||
|
} else {
|
||||||
|
await this.startGrpcService();
|
||||||
|
}
|
||||||
|
|
||||||
|
process.send?.('ready');
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`${serviceType} worker failed:`, error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startHttpService() {
|
||||||
|
this.setupMiddlewares();
|
||||||
|
|
||||||
|
const { HttpServerService } = await import('./services/http');
|
||||||
|
this.httpServerService = Container.get(HttpServerService);
|
||||||
|
|
||||||
|
await require('./loaders/app').default({ app: this.app });
|
||||||
|
|
||||||
|
const server = await this.httpServerService.initialize(
|
||||||
|
this.app,
|
||||||
|
config.port,
|
||||||
|
);
|
||||||
|
|
||||||
|
await require('./loaders/server').default({ server });
|
||||||
|
this.setupWorkerShutdown('http');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startGrpcService() {
|
||||||
|
const { GrpcServerService } = await import('./services/grpc');
|
||||||
|
this.grpcServerService = Container.get(GrpcServerService);
|
||||||
|
|
||||||
|
await this.grpcServerService.initialize();
|
||||||
|
this.setupWorkerShutdown('grpc');
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupWorkerShutdown(serviceType: string) {
|
||||||
|
process.on('message', (msg) => {
|
||||||
|
if (msg === 'shutdown') {
|
||||||
|
this.gracefulShutdown(serviceType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const shutdown = () => this.gracefulShutdown(serviceType);
|
||||||
|
process.on('SIGTERM', shutdown);
|
||||||
|
process.on('SIGINT', shutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async gracefulShutdown(serviceType: string) {
|
||||||
|
if (this.isShuttingDown) return;
|
||||||
|
this.isShuttingDown = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (serviceType === 'http') {
|
||||||
|
await this.httpServerService?.shutdown();
|
||||||
|
} else {
|
||||||
|
await this.grpcServerService?.shutdown();
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`[${serviceType}] Error during shutdown:`, error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new Application();
|
const app = new Application();
|
||||||
|
|
|
@ -25,3 +25,27 @@ export const SAMPLE_FILES = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PYTHON_INSTALL_DIR = process.env.PYTHON_HOME;
|
export const PYTHON_INSTALL_DIR = process.env.PYTHON_HOME;
|
||||||
|
|
||||||
|
export const NotificationModeStringMap = {
|
||||||
|
0: 'gotify',
|
||||||
|
1: 'goCqHttpBot',
|
||||||
|
2: 'serverChan',
|
||||||
|
3: 'pushDeer',
|
||||||
|
4: 'bark',
|
||||||
|
5: 'chat',
|
||||||
|
6: 'telegramBot',
|
||||||
|
7: 'dingtalkBot',
|
||||||
|
8: 'weWorkBot',
|
||||||
|
9: 'weWorkApp',
|
||||||
|
10: 'aibotk',
|
||||||
|
11: 'iGot',
|
||||||
|
12: 'pushPlus',
|
||||||
|
13: 'wePlusBot',
|
||||||
|
14: 'email',
|
||||||
|
15: 'pushMe',
|
||||||
|
16: 'feishu',
|
||||||
|
17: 'webhook',
|
||||||
|
18: 'chronocat',
|
||||||
|
19: 'ntfy',
|
||||||
|
20: 'wxPusherBot',
|
||||||
|
} as const;
|
||||||
|
|
|
@ -41,7 +41,7 @@ const config: Config = {
|
||||||
prefix: '/api',
|
prefix: '/api',
|
||||||
},
|
},
|
||||||
jwt: {
|
jwt: {
|
||||||
secret: process.env.JWT_SECRET || createRandomString(16, 32),
|
secret: process.env.JWT_SECRET || 'whyour-secret',
|
||||||
expiresIn: process.env.JWT_EXPIRES_IN,
|
expiresIn: process.env.JWT_EXPIRES_IN,
|
||||||
},
|
},
|
||||||
cors: {
|
cors: {
|
||||||
|
@ -86,6 +86,7 @@ const dbPath = path.join(dataPath, 'db/');
|
||||||
const uploadPath = path.join(dataPath, 'upload/');
|
const uploadPath = path.join(dataPath, 'upload/');
|
||||||
const sshdPath = path.join(dataPath, 'ssh.d/');
|
const sshdPath = path.join(dataPath, 'ssh.d/');
|
||||||
const systemLogPath = path.join(dataPath, 'syslog/');
|
const systemLogPath = path.join(dataPath, 'syslog/');
|
||||||
|
const dependenceCachePath = path.join(dataPath, 'dep_cache/');
|
||||||
|
|
||||||
const envFile = path.join(preloadPath, 'env.sh');
|
const envFile = path.join(preloadPath, 'env.sh');
|
||||||
const jsEnvFile = path.join(preloadPath, 'env.js');
|
const jsEnvFile = path.join(preloadPath, 'env.js');
|
||||||
|
@ -174,4 +175,5 @@ export default {
|
||||||
sqliteFile,
|
sqliteFile,
|
||||||
sshdPath,
|
sshdPath,
|
||||||
systemLogPath,
|
systemLogPath,
|
||||||
|
dependenceCachePath,
|
||||||
};
|
};
|
||||||
|
|
|
@ -514,6 +514,27 @@ export async function setSystemTimezone(timezone: string): Promise<boolean> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGetCommand(type: DependenceTypes, name: string): string {
|
||||||
|
const baseCommands = {
|
||||||
|
[DependenceTypes.nodejs]: `pnpm ls -g | grep "${name}" | head -1`,
|
||||||
|
[DependenceTypes.python3]: `
|
||||||
|
python3 -c "exec('''
|
||||||
|
name='${name}'
|
||||||
|
try:
|
||||||
|
from importlib.metadata import version
|
||||||
|
print(version(name))
|
||||||
|
except:
|
||||||
|
import importlib.util as u
|
||||||
|
import importlib.metadata as m
|
||||||
|
spec=u.find_spec(name)
|
||||||
|
print(name if spec else '')
|
||||||
|
''')"`,
|
||||||
|
[DependenceTypes.linux]: `apk info -es ${name}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return baseCommands[type];
|
||||||
|
}
|
||||||
|
|
||||||
export function getInstallCommand(type: DependenceTypes, name: string): string {
|
export function getInstallCommand(type: DependenceTypes, name: string): string {
|
||||||
const baseCommands = {
|
const baseCommands = {
|
||||||
[DependenceTypes.nodejs]: 'pnpm add -g',
|
[DependenceTypes.nodejs]: 'pnpm add -g',
|
||||||
|
|
|
@ -41,12 +41,6 @@ export enum DependenceTypes {
|
||||||
'linux',
|
'linux',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum GetDependenceCommandTypes {
|
|
||||||
'pnpm ls -g ',
|
|
||||||
'pip3 show --disable-pip-version-check',
|
|
||||||
'apk info -es',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum versionDependenceCommandTypes {
|
export enum versionDependenceCommandTypes {
|
||||||
'@',
|
'@',
|
||||||
'==',
|
'==',
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { IncomingHttpHeaders } from 'http';
|
|
||||||
|
|
||||||
export enum NotificationMode {
|
export enum NotificationMode {
|
||||||
'gotify' = 'gotify',
|
'gotify' = 'gotify',
|
||||||
'goCqHttpBot' = 'goCqHttpBot',
|
'goCqHttpBot' = 'goCqHttpBot',
|
||||||
|
@ -150,6 +148,10 @@ export class NtfyNotification extends NotificationBaseInfo {
|
||||||
public ntfyUrl = '';
|
public ntfyUrl = '';
|
||||||
public ntfyTopic = '';
|
public ntfyTopic = '';
|
||||||
public ntfyPriority = '';
|
public ntfyPriority = '';
|
||||||
|
public ntfyToken = '';
|
||||||
|
public ntfyUsername = '';
|
||||||
|
public ntfyPassword = '';
|
||||||
|
public ntfyActions = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WxPusherBotNotification extends NotificationBaseInfo {
|
export class WxPusherBotNotification extends NotificationBaseInfo {
|
||||||
|
|
|
@ -116,7 +116,9 @@ export default async () => {
|
||||||
`Neither content nor source specified for ${item.target}`,
|
`Neither content nor source specified for ${item.target}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const content = item.content || (await fs.readFile(item.source!));
|
const content =
|
||||||
|
item.content ||
|
||||||
|
(await fs.readFile(item.source!, { encoding: 'utf-8' }));
|
||||||
await writeFileWithLock(item.target, content);
|
await writeFileWithLock(item.target, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,14 +53,7 @@ message Response {
|
||||||
optional string message = 2;
|
optional string message = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SystemNotifyRequest {
|
message ExtraScheduleItem { string schedule = 1; }
|
||||||
string title = 1;
|
|
||||||
string content = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ExtraScheduleItem {
|
|
||||||
string schedule = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CronItem {
|
message CronItem {
|
||||||
optional int32 id = 1;
|
optional int32 id = 1;
|
||||||
|
@ -124,6 +117,128 @@ message CronDetailResponse {
|
||||||
optional string message = 3;
|
optional string message = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NotificationMode {
|
||||||
|
gotify = 0;
|
||||||
|
goCqHttpBot = 1;
|
||||||
|
serverChan = 2;
|
||||||
|
pushDeer = 3;
|
||||||
|
bark = 4;
|
||||||
|
chat = 5;
|
||||||
|
telegramBot = 6;
|
||||||
|
dingtalkBot = 7;
|
||||||
|
weWorkBot = 8;
|
||||||
|
weWorkApp = 9;
|
||||||
|
aibotk = 10;
|
||||||
|
iGot = 11;
|
||||||
|
pushPlus = 12;
|
||||||
|
wePlusBot = 13;
|
||||||
|
email = 14;
|
||||||
|
pushMe = 15;
|
||||||
|
feishu = 16;
|
||||||
|
webhook = 17;
|
||||||
|
chronocat = 18;
|
||||||
|
ntfy = 19;
|
||||||
|
wxPusherBot = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NotificationInfo {
|
||||||
|
NotificationMode type = 1;
|
||||||
|
|
||||||
|
optional string gotifyUrl = 2;
|
||||||
|
optional string gotifyToken = 3;
|
||||||
|
optional int32 gotifyPriority = 4;
|
||||||
|
|
||||||
|
optional string goCqHttpBotUrl = 5;
|
||||||
|
optional string goCqHttpBotToken = 6;
|
||||||
|
optional string goCqHttpBotQq = 7;
|
||||||
|
|
||||||
|
optional string serverChanKey = 8;
|
||||||
|
|
||||||
|
optional string pushDeerKey = 9;
|
||||||
|
optional string pushDeerUrl = 10;
|
||||||
|
|
||||||
|
optional string synologyChatUrl = 11;
|
||||||
|
|
||||||
|
optional string barkPush = 12;
|
||||||
|
optional string barkIcon = 13;
|
||||||
|
optional string barkSound = 14;
|
||||||
|
optional string barkGroup = 15;
|
||||||
|
optional string barkLevel = 16;
|
||||||
|
optional string barkUrl = 17;
|
||||||
|
optional string barkArchive = 18;
|
||||||
|
|
||||||
|
optional string telegramBotToken = 19;
|
||||||
|
optional string telegramBotUserId = 20;
|
||||||
|
optional string telegramBotProxyHost = 21;
|
||||||
|
optional string telegramBotProxyPort = 22;
|
||||||
|
optional string telegramBotProxyAuth = 23;
|
||||||
|
optional string telegramBotApiHost = 24;
|
||||||
|
|
||||||
|
optional string dingtalkBotToken = 25;
|
||||||
|
optional string dingtalkBotSecret = 26;
|
||||||
|
|
||||||
|
optional string weWorkBotKey = 27;
|
||||||
|
optional string weWorkOrigin = 28;
|
||||||
|
|
||||||
|
optional string weWorkAppKey = 29;
|
||||||
|
|
||||||
|
optional string aibotkKey = 30;
|
||||||
|
optional string aibotkType = 31;
|
||||||
|
optional string aibotkName = 32;
|
||||||
|
|
||||||
|
optional string iGotPushKey = 33;
|
||||||
|
|
||||||
|
optional string pushPlusToken = 34;
|
||||||
|
optional string pushPlusUser = 35;
|
||||||
|
optional string pushPlusTemplate = 36;
|
||||||
|
optional string pushplusChannel = 37;
|
||||||
|
optional string pushplusWebhook = 38;
|
||||||
|
optional string pushplusCallbackUrl = 39;
|
||||||
|
optional string pushplusTo = 40;
|
||||||
|
|
||||||
|
optional string wePlusBotToken = 41;
|
||||||
|
optional string wePlusBotReceiver = 42;
|
||||||
|
optional string wePlusBotVersion = 43;
|
||||||
|
|
||||||
|
optional string emailService = 44;
|
||||||
|
optional string emailUser = 45;
|
||||||
|
optional string emailPass = 46;
|
||||||
|
optional string emailTo = 47;
|
||||||
|
|
||||||
|
optional string pushMeKey = 48;
|
||||||
|
optional string pushMeUrl = 49;
|
||||||
|
|
||||||
|
optional string chronocatURL = 50;
|
||||||
|
optional string chronocatQQ = 51;
|
||||||
|
optional string chronocatToken = 52;
|
||||||
|
|
||||||
|
optional string webhookHeaders = 53;
|
||||||
|
optional string webhookBody = 54;
|
||||||
|
optional string webhookUrl = 55;
|
||||||
|
optional string webhookMethod = 56;
|
||||||
|
optional string webhookContentType = 57;
|
||||||
|
|
||||||
|
optional string larkKey = 58;
|
||||||
|
|
||||||
|
optional string ntfyUrl = 59;
|
||||||
|
optional string ntfyTopic = 60;
|
||||||
|
optional string ntfyPriority = 61;
|
||||||
|
optional string ntfyToken = 62;
|
||||||
|
optional string ntfyUsername = 63;
|
||||||
|
optional string ntfyPassword = 64;
|
||||||
|
optional string ntfyActions = 65;
|
||||||
|
|
||||||
|
optional string wxPusherBotAppToken = 66;
|
||||||
|
optional string wxPusherBotTopicIds = 67;
|
||||||
|
optional string wxPusherBotUids = 68;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SystemNotifyRequest {
|
||||||
|
string title = 1;
|
||||||
|
string content = 2;
|
||||||
|
optional NotificationInfo notificationInfo = 3;
|
||||||
|
}
|
||||||
|
|
||||||
service Api {
|
service Api {
|
||||||
rpc GetEnvs(GetEnvsRequest) returns (EnvsResponse) {}
|
rpc GetEnvs(GetEnvsRequest) returns (EnvsResponse) {}
|
||||||
rpc CreateEnv(CreateEnvRequest) returns (EnvsResponse) {}
|
rpc CreateEnv(CreateEnvRequest) returns (EnvsResponse) {}
|
||||||
|
|
1612
back/protos/api.ts
1612
back/protos/api.ts
File diff suppressed because it is too large
Load Diff
|
@ -31,6 +31,7 @@ import {
|
||||||
DeleteCronsRequest,
|
DeleteCronsRequest,
|
||||||
CronResponse,
|
CronResponse,
|
||||||
} from '../protos/api';
|
} from '../protos/api';
|
||||||
|
import { NotificationInfo } from '../data/notify';
|
||||||
|
|
||||||
Container.set('logger', LoggerInstance);
|
Container.set('logger', LoggerInstance);
|
||||||
|
|
||||||
|
@ -227,7 +228,11 @@ export const systemNotify = async (
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const data = await systemService.notify(call.request);
|
const data = await systemService.notify({
|
||||||
|
title: call.request.title,
|
||||||
|
content: call.request.content,
|
||||||
|
notificationInfo: call.request.notificationInfo as unknown as NotificationInfo,
|
||||||
|
});
|
||||||
callback(null, data);
|
callback(null, data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
callback(e);
|
callback(e);
|
||||||
|
|
|
@ -215,14 +215,24 @@ export default class CronService {
|
||||||
operate2 = Op.and;
|
operate2 = Op.and;
|
||||||
break;
|
break;
|
||||||
case 'In':
|
case 'In':
|
||||||
q[Op.or] = [
|
if (
|
||||||
{
|
property === 'status' &&
|
||||||
[property]: Array.isArray(value) ? value : [value],
|
!value.includes(CrontabStatus.disabled)
|
||||||
},
|
) {
|
||||||
property === 'status' && value.includes(2)
|
q[Op.and] = [
|
||||||
? { isDisabled: 1 }
|
{ [property]: Array.isArray(value) ? value : [value] },
|
||||||
: {},
|
{ isDisabled: 0 },
|
||||||
];
|
];
|
||||||
|
} else {
|
||||||
|
q[Op.or] = [
|
||||||
|
{
|
||||||
|
[property]: Array.isArray(value) ? value : [value],
|
||||||
|
},
|
||||||
|
property === 'status' && value.includes(CrontabStatus.disabled)
|
||||||
|
? { isDisabled: 1 }
|
||||||
|
: {},
|
||||||
|
];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'Nin':
|
case 'Nin':
|
||||||
q[Op.and] = [
|
q[Op.and] = [
|
||||||
|
@ -560,7 +570,10 @@ export default class CronService {
|
||||||
if (logFileExist) {
|
if (logFileExist) {
|
||||||
return await getFileContentByName(`${absolutePath}`);
|
return await getFileContentByName(`${absolutePath}`);
|
||||||
} else {
|
} else {
|
||||||
return '任务未运行';
|
return typeof doc.status === 'number' &&
|
||||||
|
[CrontabStatus.queued, CrontabStatus.running].includes(doc.status)
|
||||||
|
? '运行中...'
|
||||||
|
: '任务空闲中';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,6 +707,7 @@ export default class CronService {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (isDemoEnv()) {
|
if (isDemoEnv()) {
|
||||||
|
await writeFileWithLock(config.crontabFile, '');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await cronClient.addCron(regularCrons);
|
await cronClient.addCron(regularCrons);
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {
|
||||||
DependenceStatus,
|
DependenceStatus,
|
||||||
DependenceTypes,
|
DependenceTypes,
|
||||||
DependenceModel,
|
DependenceModel,
|
||||||
GetDependenceCommandTypes,
|
|
||||||
versionDependenceCommandTypes,
|
versionDependenceCommandTypes,
|
||||||
} from '../data/dependence';
|
} from '../data/dependence';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'cross-spawn';
|
||||||
|
@ -19,6 +18,7 @@ import {
|
||||||
promiseExecSuccess,
|
promiseExecSuccess,
|
||||||
getInstallCommand,
|
getInstallCommand,
|
||||||
getUninstallCommand,
|
getUninstallCommand,
|
||||||
|
getGetCommand,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import taskLimit from '../shared/pLimit';
|
import taskLimit from '../shared/pLimit';
|
||||||
|
@ -163,11 +163,9 @@ export default class DependenceService {
|
||||||
taskLimit.removeQueuedDependency(doc);
|
taskLimit.removeQueuedDependency(doc);
|
||||||
const depInstallCommand = getInstallCommand(doc.type, doc.name);
|
const depInstallCommand = getInstallCommand(doc.type, doc.name);
|
||||||
const depUnInstallCommand = getUninstallCommand(doc.type, doc.name);
|
const depUnInstallCommand = getUninstallCommand(doc.type, doc.name);
|
||||||
const installCmd = `${depInstallCommand} ${doc.name.trim()}`;
|
|
||||||
const unInstallCmd = `${depUnInstallCommand} ${doc.name.trim()}`;
|
|
||||||
const pids = await Promise.all([
|
const pids = await Promise.all([
|
||||||
getPid(installCmd),
|
getPid(depInstallCommand),
|
||||||
getPid(unInstallCmd),
|
getPid(depUnInstallCommand),
|
||||||
]);
|
]);
|
||||||
for (const pid of pids) {
|
for (const pid of pids) {
|
||||||
pid && (await killTask(pid));
|
pid && (await killTask(pid));
|
||||||
|
@ -252,7 +250,7 @@ export default class DependenceService {
|
||||||
|
|
||||||
// 判断是否已经安装过依赖
|
// 判断是否已经安装过依赖
|
||||||
if (isInstall && !force) {
|
if (isInstall && !force) {
|
||||||
const getCommandPrefix = GetDependenceCommandTypes[dependency.type];
|
const getCommand = getGetCommand(dependency.type, depName);
|
||||||
const depVersionStr = versionDependenceCommandTypes[dependency.type];
|
const depVersionStr = versionDependenceCommandTypes[dependency.type];
|
||||||
let depVersion = '';
|
let depVersion = '';
|
||||||
if (depName.includes(depVersionStr)) {
|
if (depName.includes(depVersionStr)) {
|
||||||
|
@ -269,13 +267,7 @@ export default class DependenceService {
|
||||||
const isLinuxDependence = dependency.type === DependenceTypes.linux;
|
const isLinuxDependence = dependency.type === DependenceTypes.linux;
|
||||||
const isPythonDependence =
|
const isPythonDependence =
|
||||||
dependency.type === DependenceTypes.python3;
|
dependency.type === DependenceTypes.python3;
|
||||||
const depInfo = (
|
const depInfo = (await promiseExecSuccess(getCommand))
|
||||||
await promiseExecSuccess(
|
|
||||||
isNodeDependence
|
|
||||||
? `${getCommandPrefix} | grep "${depName}" | head -1`
|
|
||||||
: `${getCommandPrefix} ${depName}`,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.replace(/\s{2,}/, ' ')
|
.replace(/\s{2,}/, ' ')
|
||||||
.replace(/\s+$/, '');
|
.replace(/\s+$/, '');
|
||||||
|
|
||||||
|
|
|
@ -49,12 +49,21 @@ export default class NotificationService {
|
||||||
public async notify(
|
public async notify(
|
||||||
title: string,
|
title: string,
|
||||||
content: string,
|
content: string,
|
||||||
|
notificationInfo?: NotificationInfo,
|
||||||
): Promise<boolean | undefined> {
|
): Promise<boolean | undefined> {
|
||||||
const { type, ...rest } = await this.userService.getNotificationMode();
|
let { type, ...rest } = await this.userService.getNotificationMode();
|
||||||
|
if (notificationInfo?.type) {
|
||||||
|
type = notificationInfo?.type;
|
||||||
|
}
|
||||||
if (type) {
|
if (type) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.params = rest;
|
let params = rest;
|
||||||
|
if (notificationInfo) {
|
||||||
|
const { type: _, ...others } = notificationInfo;
|
||||||
|
params = { ...rest, ...others };
|
||||||
|
}
|
||||||
|
this.params = params;
|
||||||
const notificationModeAction = this.modeMap.get(type);
|
const notificationModeAction = this.modeMap.get(type);
|
||||||
try {
|
try {
|
||||||
return await notificationModeAction?.call(this);
|
return await notificationModeAction?.call(this);
|
||||||
|
@ -623,20 +632,42 @@ export default class NotificationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ntfy() {
|
private async ntfy() {
|
||||||
const { ntfyUrl, ntfyTopic, ntfyPriority } = this.params;
|
const {
|
||||||
|
ntfyUrl,
|
||||||
|
ntfyTopic,
|
||||||
|
ntfyPriority,
|
||||||
|
ntfyToken,
|
||||||
|
ntfyUsername,
|
||||||
|
ntfyPassword,
|
||||||
|
ntfyActions,
|
||||||
|
} = this.params;
|
||||||
// 编码函数
|
// 编码函数
|
||||||
const encodeRfc2047 = (text: string, charset: string = 'UTF-8'): string => {
|
const encodeRfc2047 = (text: string, charset: string = 'UTF-8'): string => {
|
||||||
const encodedText = Buffer.from(text).toString('base64');
|
const encodedText = Buffer.from(text).toString('base64');
|
||||||
return `=?${charset}?B?${encodedText}?=`;
|
return `=?${charset}?B?${encodedText}?=`;
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const encodedTitle = encodeRfc2047(this.title);
|
const headers: Record<string, string> = {
|
||||||
|
Title: encodeRfc2047(this.title),
|
||||||
|
Priority: `${ntfyPriority || '3'}`,
|
||||||
|
Icon: 'https://qn.whyour.cn/logo.png',
|
||||||
|
};
|
||||||
|
if (ntfyToken) {
|
||||||
|
headers['Authorization'] = `Bearer ${ntfyToken}`;
|
||||||
|
} else if (ntfyUsername && ntfyPassword) {
|
||||||
|
headers['Authorization'] = `Basic ${Buffer.from(
|
||||||
|
`${ntfyUsername}:${ntfyPassword}`,
|
||||||
|
).toString('base64')}`;
|
||||||
|
}
|
||||||
|
if (ntfyActions) {
|
||||||
|
headers['Actions'] = encodeRfc2047(ntfyActions);
|
||||||
|
}
|
||||||
const res = await httpClient.request(
|
const res = await httpClient.request(
|
||||||
`${ntfyUrl || 'https://ntfy.sh'}/${ntfyTopic}`,
|
`${ntfyUrl || 'https://ntfy.sh'}/${ntfyTopic}`,
|
||||||
{
|
{
|
||||||
...this.gotOption,
|
...this.gotOption,
|
||||||
body: `${this.content}`,
|
body: `${this.content}`,
|
||||||
headers: { Title: encodedTitle, Priority: `${ntfyPriority || '3'}` },
|
headers: headers,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import path from 'path';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { TASK_COMMAND } from '../config/const';
|
import { NotificationModeStringMap, TASK_COMMAND } from '../config/const';
|
||||||
import {
|
import {
|
||||||
getPid,
|
getPid,
|
||||||
killTask,
|
killTask,
|
||||||
|
@ -373,8 +373,27 @@ export default class SystemService {
|
||||||
return { code: 200 };
|
return { code: 200 };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async notify({ title, content }: { title: string; content: string }) {
|
public async notify({
|
||||||
const isSuccess = await this.notificationService.notify(title, content);
|
title,
|
||||||
|
content,
|
||||||
|
notificationInfo,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
notificationInfo?: NotificationInfo;
|
||||||
|
}) {
|
||||||
|
const typeString =
|
||||||
|
typeof notificationInfo?.type === 'number'
|
||||||
|
? NotificationModeStringMap[notificationInfo.type]
|
||||||
|
: undefined;
|
||||||
|
if (notificationInfo && typeString) {
|
||||||
|
notificationInfo.type = typeString;
|
||||||
|
}
|
||||||
|
const isSuccess = await this.notificationService.notify(
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
notificationInfo,
|
||||||
|
);
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
return { code: 200, message: '通知发送成功' };
|
return { code: 200, message: '通知发送成功' };
|
||||||
} else {
|
} else {
|
||||||
|
@ -415,10 +434,17 @@ export default class SystemService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async exportData(res: Response) {
|
public async exportData(res: Response, type?: string[]) {
|
||||||
try {
|
try {
|
||||||
|
let dataDirs = ['db', 'upload'];
|
||||||
|
if (type && type.length) {
|
||||||
|
dataDirs = dataDirs.concat(type.filter((x) => x !== 'base'));
|
||||||
|
}
|
||||||
|
const dataPaths = dataDirs.map((dir) => `data/${dir}`);
|
||||||
await promiseExec(
|
await promiseExec(
|
||||||
`cd ${config.dataPath} && cd ../ && tar -zcvf ${config.dataTgzFile} data/`,
|
`cd ${config.dataPath} && cd ../ && tar -zcvf ${
|
||||||
|
config.dataTgzFile
|
||||||
|
} ${dataPaths.join(' ')}`,
|
||||||
);
|
);
|
||||||
res.download(config.dataTgzFile);
|
res.download(config.dataTgzFile);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
@ -503,4 +529,15 @@ export default class SystemService {
|
||||||
return { code: 400, message: '设置时区失败' };
|
return { code: 400, message: '设置时区失败' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async cleanDependence(type: 'node' | 'python3') {
|
||||||
|
if (!type || !['node', 'python3'].includes(type)) {
|
||||||
|
return { code: 400, message: '参数错误' };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const finalPath = path.join(config.dependenceCachePath, type);
|
||||||
|
await fs.promises.rm(finalPath, { recursive: true });
|
||||||
|
} catch (error) {}
|
||||||
|
return { code: 200 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ function getUniqueLockPath(filePath: string) {
|
||||||
|
|
||||||
export async function writeFileWithLock(
|
export async function writeFileWithLock(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
content: string | Buffer,
|
content: string,
|
||||||
options: Parameters<typeof writeFile>[2] = {},
|
options: Parameters<typeof writeFile>[2] = {},
|
||||||
) {
|
) {
|
||||||
if (typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
|
|
|
@ -226,9 +226,17 @@ export QMSG_TYPE=""
|
||||||
## ntfy_url 填写ntfy地址,如https://ntfy.sh
|
## ntfy_url 填写ntfy地址,如https://ntfy.sh
|
||||||
## ntfy_topic 填写ntfy的消息应用topic
|
## ntfy_topic 填写ntfy的消息应用topic
|
||||||
## ntfy_priority 填写推送消息优先级,默认为3
|
## ntfy_priority 填写推送消息优先级,默认为3
|
||||||
|
## ntfy_token 填写推送token,可选
|
||||||
|
## ntfy_username 填写推送用户名称,可选
|
||||||
|
## ntfy_password 填写推送用户密码,可选
|
||||||
|
## ntfy_actions 填写推送用户动作,可选
|
||||||
export NTFY_URL=""
|
export NTFY_URL=""
|
||||||
export NTFY_TOPIC=""
|
export NTFY_TOPIC=""
|
||||||
export NTFY_PRIORITY="3"
|
export NTFY_PRIORITY="3"
|
||||||
|
export NTFY_TOKEN=""
|
||||||
|
export NTFY_USERNAME=""
|
||||||
|
export NTFY_PASSWORD=""
|
||||||
|
export NTFY_ACTIONS=""
|
||||||
|
|
||||||
## 21. wxPusher
|
## 21. wxPusher
|
||||||
## 官方文档: https://wxpusher.zjiecode.com/docs/
|
## 官方文档: https://wxpusher.zjiecode.com/docs/
|
||||||
|
|
|
@ -140,6 +140,10 @@ const push_config = {
|
||||||
NTFY_URL: '', // ntfy地址,如https://ntfy.sh,默认为https://ntfy.sh
|
NTFY_URL: '', // ntfy地址,如https://ntfy.sh,默认为https://ntfy.sh
|
||||||
NTFY_TOPIC: '', // ntfy的消息应用topic
|
NTFY_TOPIC: '', // ntfy的消息应用topic
|
||||||
NTFY_PRIORITY: '3', // 推送消息优先级,默认为3
|
NTFY_PRIORITY: '3', // 推送消息优先级,默认为3
|
||||||
|
NTFY_TOKEN: '', // 推送token,可选
|
||||||
|
NTFY_USERNAME: '', // 推送用户名称,可选
|
||||||
|
NTFY_PASSWORD: '', // 推送用户密码,可选
|
||||||
|
NTFY_ACTIONS: '', // 推送用户动作,可选
|
||||||
|
|
||||||
// 官方文档: https://wxpusher.zjiecode.com/docs/
|
// 官方文档: https://wxpusher.zjiecode.com/docs/
|
||||||
// 管理后台: https://wxpusher.zjiecode.com/admin/
|
// 管理后台: https://wxpusher.zjiecode.com/admin/
|
||||||
|
@ -1258,7 +1262,7 @@ function ntfyNotify(text, desp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const { NTFY_URL, NTFY_TOPIC, NTFY_PRIORITY } = push_config;
|
const { NTFY_URL, NTFY_TOPIC, NTFY_PRIORITY, NTFY_TOKEN, NTFY_USERNAME, NTFY_PASSWORD, NTFY_ACTIONS } = push_config;
|
||||||
if (NTFY_TOPIC) {
|
if (NTFY_TOPIC) {
|
||||||
const options = {
|
const options = {
|
||||||
url: `${NTFY_URL || 'https://ntfy.sh'}/${NTFY_TOPIC}`,
|
url: `${NTFY_URL || 'https://ntfy.sh'}/${NTFY_TOPIC}`,
|
||||||
|
@ -1266,9 +1270,19 @@ function ntfyNotify(text, desp) {
|
||||||
headers: {
|
headers: {
|
||||||
Title: `${encodeRFC2047(text)}`,
|
Title: `${encodeRFC2047(text)}`,
|
||||||
Priority: NTFY_PRIORITY || '3',
|
Priority: NTFY_PRIORITY || '3',
|
||||||
|
Icon: 'https://qn.whyour.cn/logo.png',
|
||||||
},
|
},
|
||||||
timeout,
|
timeout,
|
||||||
};
|
};
|
||||||
|
if (NTFY_TOKEN) {
|
||||||
|
options.headers['Authorization'] = `Bearer ${NTFY_TOKEN}`;
|
||||||
|
} else if (NTFY_USERNAME && NTFY_PASSWORD) {
|
||||||
|
options.headers['Authorization'] = `Basic ${Buffer.from(`${NTFY_USERNAME}:${NTFY_PASSWORD}`).toString('base64')}`;
|
||||||
|
}
|
||||||
|
if (NTFY_ACTIONS) {
|
||||||
|
options.headers['Actions'] = encodeRFC2047(NTFY_ACTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
$.post(options, (err, resp, data) => {
|
$.post(options, (err, resp, data) => {
|
||||||
try {
|
try {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
|
@ -126,6 +126,10 @@ push_config = {
|
||||||
'NTFY_URL': '', # ntfy地址,如https://ntfy.sh
|
'NTFY_URL': '', # ntfy地址,如https://ntfy.sh
|
||||||
'NTFY_TOPIC': '', # ntfy的消息应用topic
|
'NTFY_TOPIC': '', # ntfy的消息应用topic
|
||||||
'NTFY_PRIORITY':'3', # 推送消息优先级,默认为3
|
'NTFY_PRIORITY':'3', # 推送消息优先级,默认为3
|
||||||
|
'NTFY_TOKEN': '', # 推送token,可选
|
||||||
|
'NTFY_USERNAME': '', # 推送用户名称,可选
|
||||||
|
'NTFY_PASSWORD': '', # 推送用户密码,可选
|
||||||
|
'NTFY_ACTIONS': '', # 推送用户动作,可选
|
||||||
|
|
||||||
'WXPUSHER_APP_TOKEN': '', # wxpusher 的 appToken 官方文档: https://wxpusher.zjiecode.com/docs/ 管理后台: https://wxpusher.zjiecode.com/admin/
|
'WXPUSHER_APP_TOKEN': '', # wxpusher 的 appToken 官方文档: https://wxpusher.zjiecode.com/docs/ 管理后台: https://wxpusher.zjiecode.com/admin/
|
||||||
'WXPUSHER_TOPIC_IDS': '', # wxpusher 的 主题ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
|
'WXPUSHER_TOPIC_IDS': '', # wxpusher 的 主题ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
|
||||||
|
@ -806,7 +810,14 @@ def ntfy(title: str, content: str) -> None:
|
||||||
encoded_title = encode_rfc2047(title)
|
encoded_title = encode_rfc2047(title)
|
||||||
|
|
||||||
data = content.encode(encoding="utf-8")
|
data = content.encode(encoding="utf-8")
|
||||||
headers = {"Title": encoded_title, "Priority": priority} # 使用编码后的 title
|
headers = {"Title": encoded_title, "Priority": priority, "Icon": "https://qn.whyour.cn/logo.png"} # 使用编码后的 title
|
||||||
|
if push_config.get("NTFY_TOKEN"):
|
||||||
|
headers['Authorization'] = "Bearer " + push_config.get("NTFY_TOKEN")
|
||||||
|
elif push_config.get("NTFY_USERNAME") and push_config.get("NTFY_PASSWORD"):
|
||||||
|
authStr = push_config.get("NTFY_USERNAME") + ":" + push_config.get("NTFY_PASSWORD")
|
||||||
|
headers['Authorization'] = "Basic " + base64.b64encode(authStr.encode('utf-8')).decode('utf-8')
|
||||||
|
if push_config.get("NTFY_ACTIONS"):
|
||||||
|
headers['Actions'] = encode_rfc2047(push_config.get("NTFY_ACTIONS"))
|
||||||
|
|
||||||
url = push_config.get("NTFY_URL") + "/" + push_config.get("NTFY_TOPIC")
|
url = push_config.get("NTFY_URL") + "/" + push_config.get("NTFY_TOPIC")
|
||||||
response = requests.post(url, data=data, headers=headers)
|
response = requests.post(url, data=data, headers=headers)
|
||||||
|
|
|
@ -87,12 +87,10 @@ function run() {
|
||||||
console.log('执行前置命令结束\n');
|
console.log('执行前置命令结束\n');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!error.message.includes('spawnSync /bin/sh E2BIG')) {
|
if (!error.message.includes('spawnSync /bin/bash E2BIG')) {
|
||||||
console.log(`\ue926 run task before error: `, error);
|
console.log(`\ue926 run task before error: `, error);
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
// environment variable is too large
|
||||||
`\ue926 The environment variable is too large. It is recommended to use task_before.js instead of task_before.sh\n`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (task_before) {
|
if (task_before) {
|
||||||
console.log('执行前置命令结束\n');
|
console.log('执行前置命令结束\n');
|
||||||
|
|
|
@ -98,10 +98,8 @@ def run():
|
||||||
error_message = str(error)
|
error_message = str(error)
|
||||||
if "Argument list too long" not in error_message:
|
if "Argument list too long" not in error_message:
|
||||||
print(f"\ue926 run task before error: {error}")
|
print(f"\ue926 run task before error: {error}")
|
||||||
else:
|
# else:
|
||||||
print(
|
# environment variable is too large
|
||||||
"\ue926 The environment variable is too large. It is recommended to use task_before.py instead of task_before.sh\n"
|
|
||||||
)
|
|
||||||
if task_before:
|
if task_before:
|
||||||
print("执行前置命令结束\n")
|
print("执行前置命令结束\n")
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
|
10
shell/pub.sh
10
shell/pub.sh
|
@ -2,13 +2,9 @@
|
||||||
echo -e "开始发布"
|
echo -e "开始发布"
|
||||||
|
|
||||||
echo -e "切换master分支"
|
echo -e "切换master分支"
|
||||||
git checkout master
|
git branch -D master
|
||||||
|
git checkout -b master
|
||||||
echo -e "合并develop代码"
|
git push --set-upstream origin master -f
|
||||||
git merge origin/develop
|
|
||||||
|
|
||||||
echo -e "提交master代码"
|
|
||||||
git push
|
|
||||||
|
|
||||||
echo -e "更新cdn文件"
|
echo -e "更新cdn文件"
|
||||||
ts-node-transpile-only sample/tool.ts
|
ts-node-transpile-only sample/tool.ts
|
||||||
|
|
|
@ -113,6 +113,8 @@ export default function () {
|
||||||
const responseStatus = error.response.status;
|
const responseStatus = error.response.status;
|
||||||
if (responseStatus !== 401) {
|
if (responseStatus !== 401) {
|
||||||
history.push('/error');
|
history.push('/error');
|
||||||
|
} else {
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setInitLoading(false));
|
.finally(() => setInitLoading(false));
|
||||||
|
|
|
@ -395,7 +395,11 @@
|
||||||
"PushMe的Key,https://push.i-i.me/": "PushMe key, https://push.i-i.me/",
|
"PushMe的Key,https://push.i-i.me/": "PushMe key, https://push.i-i.me/",
|
||||||
"自建的PushMeServer消息接口地址,例如:http://127.0.0.1:3010,不填则使用官方消息接口": "The self built PushMeServer message interface address, for example: http://127.0.0.1:3010 If left blank, use the official message interface",
|
"自建的PushMeServer消息接口地址,例如:http://127.0.0.1:3010,不填则使用官方消息接口": "The self built PushMeServer message interface address, for example: http://127.0.0.1:3010 If left blank, use the official message interface",
|
||||||
"ntfy的url地址,例如 https://ntfy.sh": "The URL address of ntfy, for example, https://ntfy.sh.",
|
"ntfy的url地址,例如 https://ntfy.sh": "The URL address of ntfy, for example, https://ntfy.sh.",
|
||||||
"ntfy的消息应用topic": "The topic for ntfy's messaging application.",
|
"ntfy应用topic": "The topic for ntfy's application.",
|
||||||
|
"ntfy应用token": "The token for ntfy's application, see https://docs.ntfy.sh/config/#access-tokens",
|
||||||
|
"ntfy应用用户名": "The username for ntfy's application, see https://docs.ntfy.sh/config/#users-and-roles",
|
||||||
|
"ntfy应用密码": "The password for ntfy's application, see https://docs.ntfy.sh/config/#users-and-roles",
|
||||||
|
"ntfy用户动作": "The user actions for ntfy's application, up to three actions, see https://docs.ntfy.sh/publish/?h=actions#action-buttons",
|
||||||
"wxPusherBot的appToken": "wxPusherBot's appToken, obtain according to docs https://wxpusher.zjiecode.com/docs/",
|
"wxPusherBot的appToken": "wxPusherBot's appToken, obtain according to docs https://wxpusher.zjiecode.com/docs/",
|
||||||
"wxPusherBot的topicIds": "wxPusherBot's topicIds, at least one of topicIds or uids must be configured",
|
"wxPusherBot的topicIds": "wxPusherBot's topicIds, at least one of topicIds or uids must be configured",
|
||||||
"wxPusherBot的uids": "wxPusherBot's uids, at least one of topicIds or uids must be configured",
|
"wxPusherBot的uids": "wxPusherBot's uids, at least one of topicIds or uids must be configured",
|
||||||
|
@ -506,5 +510,16 @@
|
||||||
"强制打开可能会导致编辑器显示异常": "Force opening may cause display issues in the editor",
|
"强制打开可能会导致编辑器显示异常": "Force opening may cause display issues in the editor",
|
||||||
"确认离开": "Confirm Leave",
|
"确认离开": "Confirm Leave",
|
||||||
"当前文件未保存,确认离开吗": "Current file is not saved, are you sure to leave?",
|
"当前文件未保存,确认离开吗": "Current file is not saved, are you sure to leave?",
|
||||||
"收件邮箱地址,多个分号分隔,默认发送给发件邮箱地址": "Receiving email address, multiple semicolon separated, sent to the sending email address by default"
|
"收件邮箱地址,多个分号分隔,默认发送给发件邮箱地址": "Receiving email address, multiple semicolon separated, sent to the sending email address by default",
|
||||||
|
"选择备份模块": "Select backup module",
|
||||||
|
"开始备份": "Start backup",
|
||||||
|
"基础数据": "Basic data",
|
||||||
|
"脚本文件": "Script files",
|
||||||
|
"日志文件": "Log files",
|
||||||
|
"依赖缓存": "Dependency cache",
|
||||||
|
"远程脚本缓存": "Remote script cache",
|
||||||
|
"远程仓库缓存": "Remote repository cache",
|
||||||
|
"SSH 文件缓存": "SSH file cache",
|
||||||
|
"清除依赖缓存": "Clean dependency cache",
|
||||||
|
"清除成功": "Clean successful"
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,7 +395,11 @@
|
||||||
"PushMe的Key,https://push.i-i.me/": "PushMe的Key,https://push.i-i.me/",
|
"PushMe的Key,https://push.i-i.me/": "PushMe的Key,https://push.i-i.me/",
|
||||||
"自建的PushMeServer消息接口地址,例如:http://127.0.0.1:3010,不填则使用官方消息接口": "自建的PushMeServer消息接口地址,例如:http://127.0.0.1:3010,不填则使用官方消息接口",
|
"自建的PushMeServer消息接口地址,例如:http://127.0.0.1:3010,不填则使用官方消息接口": "自建的PushMeServer消息接口地址,例如:http://127.0.0.1:3010,不填则使用官方消息接口",
|
||||||
"ntfy的url地址,例如 https://ntfy.sh": "ntfy的url地址,例如 https://ntfy.sh",
|
"ntfy的url地址,例如 https://ntfy.sh": "ntfy的url地址,例如 https://ntfy.sh",
|
||||||
"ntfy的消息应用topic": "ntfy的消息应用topic",
|
"ntfy应用topic": "ntfy应用topic",
|
||||||
|
"ntfy应用token": "ntfy应用token,参考 https://docs.ntfy.sh/config/#access-tokens",
|
||||||
|
"ntfy应用用户名": "ntfy应用用户名,参考 https://docs.ntfy.sh/config/#users-and-roles",
|
||||||
|
"ntfy应用密码": "ntfy应用密码,参考 https://docs.ntfy.sh/config/#users-and-roles",
|
||||||
|
"ntfy用户动作": "ntfy用户动作,最多三个动作,参考 https://docs.ntfy.sh/publish/?h=actions#action-buttons",
|
||||||
"wxPusherBot的appToken": "wxPusherBot的appToken, 按照文档获取 https://wxpusher.zjiecode.com/docs/",
|
"wxPusherBot的appToken": "wxPusherBot的appToken, 按照文档获取 https://wxpusher.zjiecode.com/docs/",
|
||||||
"wxPusherBot的topicIds": "wxPusherBot的topicIds, topicIds 和 uids 至少配置一个才行",
|
"wxPusherBot的topicIds": "wxPusherBot的topicIds, topicIds 和 uids 至少配置一个才行",
|
||||||
"wxPusherBot的uids": "wxPusherBot的uids, topicIds 和 uids 至少配置一个才行",
|
"wxPusherBot的uids": "wxPusherBot的uids, topicIds 和 uids 至少配置一个才行",
|
||||||
|
@ -506,6 +510,16 @@
|
||||||
"强制打开可能会导致编辑器显示异常": "强制打开可能会导致编辑器显示异常",
|
"强制打开可能会导致编辑器显示异常": "强制打开可能会导致编辑器显示异常",
|
||||||
"确认离开": "确认离开",
|
"确认离开": "确认离开",
|
||||||
"当前文件未保存,确认离开吗": "当前文件未保存,确认离开吗",
|
"当前文件未保存,确认离开吗": "当前文件未保存,确认离开吗",
|
||||||
"收件邮箱地址,多个分号分隔,默认发送给发件邮箱地址": "收件邮箱地址,多个分号分隔,默认发送给发件邮箱地址"
|
"收件邮箱地址,多个分号分隔,默认发送给发件邮箱地址": "收件邮箱地址,多个分号分隔,默认发送给发件邮箱地址",
|
||||||
|
"选择备份模块": "选择备份模块",
|
||||||
|
"开始备份": "开始备份",
|
||||||
|
"基础数据": "基础数据",
|
||||||
|
"脚本文件": "脚本文件",
|
||||||
|
"日志文件": "日志文件",
|
||||||
|
"依赖缓存": "依赖缓存",
|
||||||
|
"远程脚本缓存": "远程脚本缓存",
|
||||||
|
"远程仓库缓存": "远程仓库缓存",
|
||||||
|
"SSH 文件缓存": "SSH 文件缓存",
|
||||||
|
"清除依赖缓存": "清除依赖缓存",
|
||||||
|
"清除成功": "清除成功"
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,12 +56,10 @@ interface LogItem {
|
||||||
const CronDetailModal = ({
|
const CronDetailModal = ({
|
||||||
cron = {},
|
cron = {},
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
theme,
|
theme,
|
||||||
isPhone,
|
isPhone,
|
||||||
}: {
|
}: {
|
||||||
cron?: any;
|
cron?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (needUpdate?: boolean) => void;
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
theme: string;
|
theme: string;
|
||||||
isPhone: boolean;
|
isPhone: boolean;
|
||||||
|
@ -440,7 +438,7 @@ const CronDetailModal = ({
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
centered
|
centered
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
footer={false}
|
footer={false}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
@ -559,15 +557,16 @@ const CronDetailModal = ({
|
||||||
{contentList[activeTabKey]}
|
{contentList[activeTabKey]}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<CronLogModal
|
{isLogModalVisible && (
|
||||||
visible={isLogModalVisible}
|
<CronLogModal
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
setIsLogModalVisible(false);
|
setIsLogModalVisible(false);
|
||||||
}}
|
}}
|
||||||
cron={cron}
|
cron={cron}
|
||||||
data={log}
|
data={log}
|
||||||
logUrl={logUrl}
|
logUrl={logUrl}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1037,55 +1037,58 @@ const Crontab = () => {
|
||||||
components={isPhone || pageConf.size < 50 ? undefined : vt}
|
components={isPhone || pageConf.size < 50 ? undefined : vt}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CronLogModal
|
{isLogModalVisible && (
|
||||||
visible={isLogModalVisible}
|
<CronLogModal
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
getCronDetail(logCron);
|
getCronDetail(logCron);
|
||||||
setIsLogModalVisible(false);
|
setIsLogModalVisible(false);
|
||||||
}}
|
}}
|
||||||
cron={logCron}
|
cron={logCron}
|
||||||
/>
|
/>
|
||||||
<CronModal
|
)}
|
||||||
visible={isModalVisible}
|
{isModalVisible && (
|
||||||
handleCancel={handleCancel}
|
<CronModal handleCancel={handleCancel} cron={editedCron} />
|
||||||
cron={editedCron}
|
)}
|
||||||
/>
|
{isLabelModalVisible && (
|
||||||
<CronLabelModal
|
<CronLabelModal
|
||||||
visible={isLabelModalVisible}
|
handleCancel={(needUpdate?: boolean) => {
|
||||||
handleCancel={(needUpdate?: boolean) => {
|
setIsLabelModalVisible(false);
|
||||||
setIsLabelModalVisible(false);
|
if (needUpdate) {
|
||||||
if (needUpdate) {
|
getCrons();
|
||||||
getCrons();
|
}
|
||||||
}
|
}}
|
||||||
}}
|
ids={selectedRowIds}
|
||||||
ids={selectedRowIds}
|
/>
|
||||||
/>
|
)}
|
||||||
<CronDetailModal
|
{isDetailModalVisible && (
|
||||||
visible={isDetailModalVisible}
|
<CronDetailModal
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
setIsDetailModalVisible(false);
|
setIsDetailModalVisible(false);
|
||||||
}}
|
}}
|
||||||
cron={detailCron}
|
cron={detailCron}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
isPhone={isPhone}
|
isPhone={isPhone}
|
||||||
/>
|
/>
|
||||||
<ViewCreateModal
|
)}
|
||||||
visible={isCreateViewModalVisible}
|
{isCreateViewModalVisible && (
|
||||||
handleCancel={(data) => {
|
<ViewCreateModal
|
||||||
setIsCreateViewModalVisible(false);
|
handleCancel={(data) => {
|
||||||
getCronViews();
|
setIsCreateViewModalVisible(false);
|
||||||
}}
|
getCronViews();
|
||||||
/>
|
}}
|
||||||
<ViewManageModal
|
/>
|
||||||
cronViews={cronViews}
|
)}
|
||||||
visible={isViewManageModalVisible}
|
{isViewManageModalVisible && (
|
||||||
handleCancel={() => {
|
<ViewManageModal
|
||||||
setIsViewManageModalVisible(false);
|
cronViews={cronViews}
|
||||||
}}
|
handleCancel={() => {
|
||||||
cronViewChange={(data) => {
|
setIsViewManageModalVisible(false);
|
||||||
getCronViews();
|
}}
|
||||||
}}
|
cronViewChange={(data) => {
|
||||||
/>
|
getCronViews();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,12 +25,10 @@ const { Countdown } = Statistic;
|
||||||
const CronLogModal = ({
|
const CronLogModal = ({
|
||||||
cron,
|
cron,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
data,
|
data,
|
||||||
logUrl,
|
logUrl,
|
||||||
}: {
|
}: {
|
||||||
cron?: any;
|
cron?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
data?: string;
|
data?: string;
|
||||||
logUrl?: string;
|
logUrl?: string;
|
||||||
|
@ -120,11 +118,10 @@ const CronLogModal = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cron && cron.id && visible) {
|
if (cron && cron.id) {
|
||||||
getCronLog(true);
|
getCronLog(true);
|
||||||
scrollInfoRef.current.down = true;
|
|
||||||
}
|
}
|
||||||
}, [cron, visible]);
|
}, [cron]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -139,7 +136,7 @@ const CronLogModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
open={visible}
|
open={true}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
forceRender
|
forceRender
|
||||||
|
|
|
@ -12,10 +12,8 @@ import { ScheduleType } from './type';
|
||||||
const CronModal = ({
|
const CronModal = ({
|
||||||
cron,
|
cron,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
cron?: any;
|
cron?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (needUpdate?: boolean) => void;
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -58,11 +56,6 @@ const CronModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
setScheduleType(getScheduleType(cron?.schedule));
|
|
||||||
}, [cron, visible]);
|
|
||||||
|
|
||||||
const handleScheduleTypeChange = (type: ScheduleType) => {
|
const handleScheduleTypeChange = (type: ScheduleType) => {
|
||||||
setScheduleType(type);
|
setScheduleType(type);
|
||||||
form.setFieldValue('schedule', '');
|
form.setFieldValue('schedule', '');
|
||||||
|
@ -146,7 +139,7 @@ const CronModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={cron?.id ? intl.get('编辑任务') : intl.get('创建任务')}
|
title={cron?.id ? intl.get('编辑任务') : intl.get('创建任务')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
@ -251,10 +244,8 @@ const CronModal = ({
|
||||||
const CronLabelModal = ({
|
const CronLabelModal = ({
|
||||||
ids,
|
ids,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
ids: Array<string>;
|
ids: Array<string>;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (needUpdate?: boolean) => void;
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -290,10 +281,6 @@ const CronLabelModal = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
}, [ids, visible]);
|
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button onClick={() => handleCancel(false)}>{intl.get('取消')}</Button>,
|
<Button onClick={() => handleCancel(false)}>{intl.get('取消')}</Button>,
|
||||||
<Button type="primary" danger onClick={() => update('delete')}>
|
<Button type="primary" danger onClick={() => update('delete')}>
|
||||||
|
@ -307,7 +294,7 @@ const CronLabelModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.get('批量修改标签')}
|
title={intl.get('批量修改标签')}
|
||||||
open={visible}
|
open={true}
|
||||||
footer={buttons}
|
footer={buttons}
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
|
@ -56,10 +56,8 @@ enum ViewFilterRelation {
|
||||||
const ViewCreateModal = ({
|
const ViewCreateModal = ({
|
||||||
view,
|
view,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
view?: any;
|
view?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (param?: any) => void;
|
handleCancel: (param?: any) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -101,17 +99,6 @@ const ViewCreateModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!view) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
form.setFieldsValue(
|
|
||||||
view || {
|
|
||||||
filters: [{ property: 'command' }],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}, [view, visible]);
|
|
||||||
|
|
||||||
const OperationElement = ({ name, ...others }: { name: number }) => {
|
const OperationElement = ({ name, ...others }: { name: number }) => {
|
||||||
const property = form.getFieldValue(['filters', name, 'property']);
|
const property = form.getFieldValue(['filters', name, 'property']);
|
||||||
return (
|
return (
|
||||||
|
@ -172,7 +159,7 @@ const ViewCreateModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={view ? intl.get('编辑视图') : intl.get('创建视图')}
|
title={view ? intl.get('编辑视图') : intl.get('创建视图')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
width={580}
|
width={580}
|
||||||
centered
|
centered
|
||||||
|
@ -190,7 +177,16 @@ const ViewCreateModal = ({
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
confirmLoading={loading}
|
confirmLoading={loading}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" name="env_modal">
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={
|
||||||
|
view || {
|
||||||
|
filters: [{ property: 'command' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name="env_modal"
|
||||||
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
label={intl.get('视图名称')}
|
label={intl.get('视图名称')}
|
||||||
|
|
|
@ -68,11 +68,9 @@ const DragableBodyRow = ({
|
||||||
const ViewManageModal = ({
|
const ViewManageModal = ({
|
||||||
cronViews,
|
cronViews,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
cronViewChange,
|
cronViewChange,
|
||||||
}: {
|
}: {
|
||||||
cronViews: any[];
|
cronViews: any[];
|
||||||
visible: boolean;
|
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
cronViewChange: (data?: any) => void;
|
cronViewChange: (data?: any) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -218,7 +216,7 @@ const ViewManageModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.get('视图管理')}
|
title={intl.get('视图管理')}
|
||||||
open={visible}
|
open={true}
|
||||||
centered
|
centered
|
||||||
width={620}
|
width={620}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
@ -263,14 +261,15 @@ const ViewManageModal = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
<ViewCreateModal
|
{isCreateViewModalVisible && (
|
||||||
view={editedView}
|
<ViewCreateModal
|
||||||
visible={isCreateViewModalVisible}
|
view={editedView}
|
||||||
handleCancel={(data) => {
|
handleCancel={(data) => {
|
||||||
setIsCreateViewModalVisible(false);
|
setIsCreateViewModalVisible(false);
|
||||||
cronViewChange(data);
|
cronViewChange(data);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -618,15 +618,15 @@ const Dependence = () => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
<DependenceModal
|
{isModalVisible && (
|
||||||
visible={isModalVisible}
|
<DependenceModal
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
dependence={editedDependence}
|
dependence={editedDependence}
|
||||||
defaultType={type}
|
defaultType={type}
|
||||||
/>
|
/>
|
||||||
{logDependence && (
|
)}
|
||||||
|
{logDependence && isLogModalVisible && (
|
||||||
<DependenceLogModal
|
<DependenceLogModal
|
||||||
visible={isLogModalVisible}
|
|
||||||
handleCancel={(needRemove?: boolean) => {
|
handleCancel={(needRemove?: boolean) => {
|
||||||
setIsLogModalVisible(false);
|
setIsLogModalVisible(false);
|
||||||
if (needRemove) {
|
if (needRemove) {
|
||||||
|
|
|
@ -15,10 +15,8 @@ import { Status } from './type';
|
||||||
const DependenceLogModal = ({
|
const DependenceLogModal = ({
|
||||||
dependence,
|
dependence,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
dependence?: any;
|
dependence?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (needRemove?: boolean) => void;
|
handleCancel: (needRemove?: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState<string>('');
|
const [value, setValue] = useState<string>('');
|
||||||
|
@ -128,7 +126,7 @@ const DependenceLogModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
open={visible}
|
open={true}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
forceRender
|
forceRender
|
||||||
|
|
|
@ -14,11 +14,9 @@ enum DependenceTypes {
|
||||||
const DependenceModal = ({
|
const DependenceModal = ({
|
||||||
dependence,
|
dependence,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
defaultType,
|
defaultType,
|
||||||
}: {
|
}: {
|
||||||
dependence?: any;
|
dependence?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (cks?: any[]) => void;
|
handleCancel: (cks?: any[]) => void;
|
||||||
defaultType: string;
|
defaultType: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -61,14 +59,10 @@ const DependenceModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
}, [dependence, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={dependence ? intl.get('编辑依赖') : intl.get('创建依赖')}
|
title={dependence ? intl.get('编辑依赖') : intl.get('创建依赖')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
8
src/pages/env/editNameModal.tsx
vendored
8
src/pages/env/editNameModal.tsx
vendored
|
@ -7,10 +7,8 @@ import config from '@/utils/config';
|
||||||
const EditNameModal = ({
|
const EditNameModal = ({
|
||||||
ids,
|
ids,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
ids?: string[];
|
ids?: string[];
|
||||||
visible: boolean;
|
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -34,14 +32,10 @@ const EditNameModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
}, [ids, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.get('修改环境变量名称')}
|
title={intl.get('修改环境变量名称')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
19
src/pages/env/index.tsx
vendored
19
src/pages/env/index.tsx
vendored
|
@ -616,16 +616,15 @@ const Env = () => {
|
||||||
/>
|
/>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
</div>
|
</div>
|
||||||
<EnvModal
|
{isModalVisible && (
|
||||||
visible={isModalVisible}
|
<EnvModal handleCancel={handleCancel} env={editedEnv} />
|
||||||
handleCancel={handleCancel}
|
)}
|
||||||
env={editedEnv}
|
{isEditNameModalVisible && (
|
||||||
/>
|
<EditNameModal
|
||||||
<EditNameModal
|
handleCancel={handleEditNameCancel}
|
||||||
visible={isEditNameModalVisible}
|
ids={selectedRowIds}
|
||||||
handleCancel={handleEditNameCancel}
|
/>
|
||||||
ids={selectedRowIds}
|
)}
|
||||||
/>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
8
src/pages/env/modal.tsx
vendored
8
src/pages/env/modal.tsx
vendored
|
@ -7,10 +7,8 @@ import config from '@/utils/config';
|
||||||
const EnvModal = ({
|
const EnvModal = ({
|
||||||
env,
|
env,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
env?: any;
|
env?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (cks?: any[]) => void;
|
handleCancel: (cks?: any[]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -55,14 +53,10 @@ const EnvModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
}, [env, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={env ? intl.get('编辑变量') : intl.get('创建变量')}
|
title={env ? intl.get('编辑变量') : intl.get('创建变量')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
|
@ -14,6 +14,17 @@ const Error = () => {
|
||||||
const [data, setData] = useState(intl.get('暂无日志'));
|
const [data, setData] = useState(intl.get('暂无日志'));
|
||||||
const retryTimes = useRef(1);
|
const retryTimes = useRef(1);
|
||||||
|
|
||||||
|
const loopStatus = (message: string) => {
|
||||||
|
if (retryTimes.current > 3) {
|
||||||
|
setData(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
retryTimes.current += 1;
|
||||||
|
setTimeout(() => {
|
||||||
|
getHealthStatus(false);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
const getHealthStatus = (needLoading: boolean = true) => {
|
const getHealthStatus = (needLoading: boolean = true) => {
|
||||||
needLoading && setLoading(true);
|
needLoading && setLoading(true);
|
||||||
request
|
request
|
||||||
|
@ -27,19 +38,15 @@ const Error = () => {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (retryTimes.current > 3) {
|
|
||||||
setData(error?.details);
|
loopStatus(error?.details);
|
||||||
return;
|
|
||||||
}
|
|
||||||
retryTimes.current += 1;
|
|
||||||
setTimeout(() => {
|
|
||||||
getHealthStatus(false);
|
|
||||||
}, 3000);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const responseStatus = error.response.status;
|
const responseStatus = error.response.status;
|
||||||
if (responseStatus === 401) {
|
if (responseStatus === 401) {
|
||||||
history.push('/login');
|
history.push('/login');
|
||||||
|
} else {
|
||||||
|
loopStatus(error.response?.message || error?.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => needLoading && setLoading(false));
|
.finally(() => needLoading && setLoading(false));
|
||||||
|
|
|
@ -25,11 +25,9 @@ const EditModal = ({
|
||||||
currentNode,
|
currentNode,
|
||||||
content,
|
content,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
treeData?: any;
|
treeData?: any;
|
||||||
content?: string;
|
content?: string;
|
||||||
visible: boolean;
|
|
||||||
currentNode: any;
|
currentNode: any;
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -223,7 +221,7 @@ const EditModal = ({
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
headerStyle={{ padding: '11px 24px' }}
|
headerStyle={{ padding: '11px 24px' }}
|
||||||
onClose={cancel}
|
onClose={cancel}
|
||||||
open={visible}
|
open={true}
|
||||||
>
|
>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<SplitPane
|
<SplitPane
|
||||||
|
@ -256,24 +254,26 @@ const EditModal = ({
|
||||||
<Ansi>{log}</Ansi>
|
<Ansi>{log}</Ansi>
|
||||||
</pre>
|
</pre>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
<SaveModal
|
{saveModalVisible && (
|
||||||
visible={saveModalVisible}
|
<SaveModal
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
setSaveModalVisible(false);
|
setSaveModalVisible(false);
|
||||||
}}
|
}}
|
||||||
file={{
|
file={{
|
||||||
content:
|
content:
|
||||||
editorRef.current &&
|
editorRef.current &&
|
||||||
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
editorRef.current.getValue().replace(/\r\n/g, '\n'),
|
||||||
...cNode,
|
...cNode,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SettingModal
|
)}
|
||||||
visible={settingModalVisible}
|
{settingModalVisible && (
|
||||||
handleCancel={() => {
|
<SettingModal
|
||||||
setSettingModalVisible(false);
|
handleCancel={() => {
|
||||||
}}
|
setSettingModalVisible(false);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,9 +19,7 @@ const { Option } = Select;
|
||||||
const EditScriptNameModal = ({
|
const EditScriptNameModal = ({
|
||||||
handleCancel,
|
handleCancel,
|
||||||
treeData,
|
treeData,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
visible: boolean;
|
|
||||||
treeData: any[];
|
treeData: any[];
|
||||||
handleCancel: (file?: {
|
handleCancel: (file?: {
|
||||||
filename: string;
|
filename: string;
|
||||||
|
@ -53,7 +51,7 @@ const EditScriptNameModal = ({
|
||||||
directory ? intl.get('创建文件夹成功') : intl.get('创建文件成功'),
|
directory ? intl.get('创建文件夹成功') : intl.get('创建文件成功'),
|
||||||
);
|
);
|
||||||
const key = path ? `${path}/` : '';
|
const key = path ? `${path}/` : '';
|
||||||
const filename = file ? file.name : (directory || inputFilename);
|
const filename = file ? file.name : directory || inputFilename;
|
||||||
handleCancel({
|
handleCancel({
|
||||||
filename,
|
filename,
|
||||||
path,
|
path,
|
||||||
|
@ -95,14 +93,10 @@ const EditScriptNameModal = ({
|
||||||
setDirs(dirs);
|
setDirs(dirs);
|
||||||
}, [treeData]);
|
}, [treeData]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.get('创建')}
|
title={intl.get('创建')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
|
@ -710,9 +710,8 @@ const Script = () => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLogModalVisible && (
|
{isLogModalVisible && isLogModalVisible && (
|
||||||
<EditModal
|
<EditModal
|
||||||
visible={isLogModalVisible}
|
|
||||||
treeData={data}
|
treeData={data}
|
||||||
currentNode={currentNode}
|
currentNode={currentNode}
|
||||||
content={value}
|
content={value}
|
||||||
|
@ -721,16 +720,18 @@ const Script = () => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<EditScriptNameModal
|
{isAddFileModalVisible && (
|
||||||
visible={isAddFileModalVisible}
|
<EditScriptNameModal
|
||||||
treeData={data}
|
treeData={data}
|
||||||
handleCancel={addFileModalClose}
|
handleCancel={addFileModalClose}
|
||||||
/>
|
/>
|
||||||
<RenameModal
|
)}
|
||||||
visible={isRenameFileModalVisible}
|
{isRenameFileModalVisible && (
|
||||||
handleCancel={handleRenameFileCancel}
|
<RenameModal
|
||||||
currentNode={currentNode}
|
handleCancel={handleRenameFileCancel}
|
||||||
/>
|
currentNode={currentNode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,10 +7,8 @@ import config from '@/utils/config';
|
||||||
const RenameModal = ({
|
const RenameModal = ({
|
||||||
currentNode,
|
currentNode,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
currentNode?: any;
|
currentNode?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -38,14 +36,10 @@ const RenameModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
}, [currentNode, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.get('重命名')}
|
title={intl.get('重命名')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
|
@ -7,10 +7,8 @@ import config from '@/utils/config';
|
||||||
const SaveModal = ({
|
const SaveModal = ({
|
||||||
file,
|
file,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
file?: any;
|
file?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (cks?: any[]) => void;
|
handleCancel: (cks?: any[]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -32,15 +30,10 @@ const SaveModal = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
setLoading(false);
|
|
||||||
}, [file, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.get('保存文件')}
|
title={intl.get('保存文件')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
|
@ -7,10 +7,8 @@ import config from '@/utils/config';
|
||||||
const SettingModal = ({
|
const SettingModal = ({
|
||||||
file,
|
file,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
file?: any;
|
file?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (cks?: any[]) => void;
|
handleCancel: (cks?: any[]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -30,15 +28,10 @@ const SettingModal = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
setLoading(false);
|
|
||||||
}, [file, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.get('运行设置')}
|
title={intl.get('运行设置')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
|
|
|
@ -7,10 +7,8 @@ import config from '@/utils/config';
|
||||||
const AppModal = ({
|
const AppModal = ({
|
||||||
app,
|
app,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
app?: any;
|
app?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (needUpdate?: boolean) => void;
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
@ -41,14 +39,10 @@ const AppModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.resetFields();
|
|
||||||
}, [app, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={app ? intl.get('编辑应用') : intl.get('创建应用')}
|
title={app ? intl.get('编辑应用') : intl.get('创建应用')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Button, InputNumber, Form, message, Input, Alert } from 'antd';
|
import { Button, InputNumber, Form, message, Input, Alert, Select } from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
@ -25,6 +25,7 @@ const Dependence = () => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [log, setLog] = useState<string>('');
|
const [log, setLog] = useState<string>('');
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [cleanType, setCleanType] = useState<string>('node');
|
||||||
|
|
||||||
const getSystemConfig = () => {
|
const getSystemConfig = () => {
|
||||||
request
|
request
|
||||||
|
@ -84,6 +85,24 @@ const Dependence = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanDependenceCache = (type: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
setLog('');
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}system/config/dependence-clean`, {
|
||||||
|
type,
|
||||||
|
})
|
||||||
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(intl.get('清除成功'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ws = WebSocketManager.getInstance();
|
const ws = WebSocketManager.getInstance();
|
||||||
ws.subscribe('updateNodeMirror', handleMessage);
|
ws.subscribe('updateNodeMirror', handleMessage);
|
||||||
|
@ -222,6 +241,38 @@ const Dependence = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={intl.get('清除依赖缓存')}
|
||||||
|
name="clean"
|
||||||
|
tooltip={{
|
||||||
|
title: intl.get('清除依赖缓存'),
|
||||||
|
placement: 'topLeft',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input.Group compact>
|
||||||
|
<Select
|
||||||
|
defaultValue={'node'}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
onChange={(value) => {
|
||||||
|
setCleanType(value);
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ label: 'node', value: 'node' },
|
||||||
|
{ label: 'python3', value: 'python3' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
loading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
cleanDependenceCache(cleanType);
|
||||||
|
}}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
<pre
|
<pre
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -363,11 +363,9 @@ const Setting = () => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AppModal
|
{isModalVisible && (
|
||||||
visible={isModalVisible}
|
<AppModal handleCancel={handleCancel} app={editedApp} />
|
||||||
handleCancel={handleCancel}
|
)}
|
||||||
app={editedApp}
|
|
||||||
/>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
Upload,
|
Upload,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
|
Checkbox,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import * as DarkReader from '@umijs/ssr-darkreader';
|
import * as DarkReader from '@umijs/ssr-darkreader';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
|
@ -31,6 +32,19 @@ const dataMap = {
|
||||||
timezone: 'timezone',
|
timezone: 'timezone',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportModules = [
|
||||||
|
{ value: 'base', label: intl.get('基础数据'), disabled: true },
|
||||||
|
{ value: 'config', label: intl.get('配置文件') },
|
||||||
|
{ value: 'scripts', label: intl.get('脚本文件') },
|
||||||
|
{ value: 'log', label: intl.get('日志文件') },
|
||||||
|
{ value: 'deps', label: intl.get('依赖文件') },
|
||||||
|
{ value: 'syslog', label: intl.get('系统日志') },
|
||||||
|
{ value: 'dep_cache', label: intl.get('依赖缓存') },
|
||||||
|
{ value: 'raw', label: intl.get('远程脚本缓存') },
|
||||||
|
{ value: 'repo', label: intl.get('远程仓库缓存') },
|
||||||
|
{ value: 'ssh.d', label: intl.get('SSH 文件缓存') },
|
||||||
|
];
|
||||||
|
|
||||||
const Other = ({
|
const Other = ({
|
||||||
systemInfo,
|
systemInfo,
|
||||||
reloadTheme,
|
reloadTheme,
|
||||||
|
@ -45,6 +59,8 @@ const Other = ({
|
||||||
const [exportLoading, setExportLoading] = useState(false);
|
const [exportLoading, setExportLoading] = useState(false);
|
||||||
const showUploadProgress = useProgress(intl.get('上传'));
|
const showUploadProgress = useProgress(intl.get('上传'));
|
||||||
const showDownloadProgress = useProgress(intl.get('下载'));
|
const showDownloadProgress = useProgress(intl.get('下载'));
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [selectedModules, setSelectedModules] = useState<string[]>(['base']);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
enable: enableDarkMode,
|
enable: enableDarkMode,
|
||||||
|
@ -110,7 +126,7 @@ const Other = ({
|
||||||
request
|
request
|
||||||
.put<Blob>(
|
.put<Blob>(
|
||||||
`${config.apiPrefix}system/data/export`,
|
`${config.apiPrefix}system/data/export`,
|
||||||
{},
|
{ type: selectedModules },
|
||||||
{
|
{
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
timeout: 86400000,
|
timeout: 86400000,
|
||||||
|
@ -127,7 +143,10 @@ const Other = ({
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
})
|
})
|
||||||
.finally(() => setExportLoading(false));
|
.finally(() => {
|
||||||
|
setExportLoading(false);
|
||||||
|
setVisible(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showReloadModal = () => {
|
const showReloadModal = () => {
|
||||||
|
@ -178,160 +197,205 @@ const Other = ({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form layout="vertical" form={form}>
|
<>
|
||||||
<Form.Item
|
<Form layout="vertical" form={form}>
|
||||||
label={intl.get('主题')}
|
<Form.Item
|
||||||
name="theme"
|
label={intl.get('主题')}
|
||||||
initialValue={defaultTheme}
|
name="theme"
|
||||||
>
|
initialValue={defaultTheme}
|
||||||
<Radio.Group
|
|
||||||
onChange={themeChange}
|
|
||||||
value={defaultTheme}
|
|
||||||
optionType="button"
|
|
||||||
buttonStyle="solid"
|
|
||||||
>
|
>
|
||||||
<Radio.Button
|
<Radio.Group
|
||||||
value="light"
|
onChange={themeChange}
|
||||||
style={{ width: 70, textAlign: 'center' }}
|
value={defaultTheme}
|
||||||
|
optionType="button"
|
||||||
|
buttonStyle="solid"
|
||||||
>
|
>
|
||||||
{intl.get('亮色')}
|
<Radio.Button
|
||||||
</Radio.Button>
|
value="light"
|
||||||
<Radio.Button value="dark" style={{ width: 66, textAlign: 'center' }}>
|
style={{ width: 70, textAlign: 'center' }}
|
||||||
{intl.get('暗色')}
|
>
|
||||||
</Radio.Button>
|
{intl.get('亮色')}
|
||||||
<Radio.Button
|
</Radio.Button>
|
||||||
value="auto"
|
<Radio.Button
|
||||||
style={{ width: 129, textAlign: 'center' }}
|
value="dark"
|
||||||
>
|
style={{ width: 66, textAlign: 'center' }}
|
||||||
{intl.get('跟随系统')}
|
>
|
||||||
</Radio.Button>
|
{intl.get('暗色')}
|
||||||
</Radio.Group>
|
</Radio.Button>
|
||||||
</Form.Item>
|
<Radio.Button
|
||||||
<Form.Item
|
value="auto"
|
||||||
label={intl.get('日志删除频率')}
|
style={{ width: 129, textAlign: 'center' }}
|
||||||
name="frequency"
|
>
|
||||||
tooltip={intl.get('每x天自动删除x天以前的日志')}
|
{intl.get('跟随系统')}
|
||||||
>
|
</Radio.Button>
|
||||||
<Input.Group compact>
|
</Radio.Group>
|
||||||
<InputNumber
|
</Form.Item>
|
||||||
addonBefore={intl.get('每')}
|
<Form.Item
|
||||||
addonAfter={intl.get('天')}
|
label={intl.get('日志删除频率')}
|
||||||
style={{ width: 180 }}
|
name="frequency"
|
||||||
min={0}
|
tooltip={intl.get('每x天自动删除x天以前的日志')}
|
||||||
value={systemConfig?.logRemoveFrequency}
|
>
|
||||||
onChange={(value) => {
|
<Input.Group compact>
|
||||||
setSystemConfig({ ...systemConfig, logRemoveFrequency: value });
|
<InputNumber
|
||||||
}}
|
addonBefore={intl.get('每')}
|
||||||
/>
|
addonAfter={intl.get('天')}
|
||||||
<Button
|
style={{ width: 180 }}
|
||||||
type="primary"
|
min={0}
|
||||||
onClick={() => {
|
value={systemConfig?.logRemoveFrequency}
|
||||||
updateSystemConfig('log-remove-frequency');
|
onChange={(value) => {
|
||||||
}}
|
setSystemConfig({ ...systemConfig, logRemoveFrequency: value });
|
||||||
style={{ width: 84 }}
|
}}
|
||||||
>
|
/>
|
||||||
{intl.get('确认')}
|
<Button
|
||||||
</Button>
|
type="primary"
|
||||||
</Input.Group>
|
onClick={() => {
|
||||||
</Form.Item>
|
updateSystemConfig('log-remove-frequency');
|
||||||
<Form.Item label={intl.get('定时任务并发数')} name="frequency">
|
}}
|
||||||
<Input.Group compact>
|
style={{ width: 84 }}
|
||||||
<InputNumber
|
>
|
||||||
style={{ width: 180 }}
|
{intl.get('确认')}
|
||||||
min={1}
|
</Button>
|
||||||
value={systemConfig?.cronConcurrency}
|
</Input.Group>
|
||||||
onChange={(value) => {
|
</Form.Item>
|
||||||
setSystemConfig({ ...systemConfig, cronConcurrency: value });
|
<Form.Item label={intl.get('定时任务并发数')} name="frequency">
|
||||||
}}
|
<Input.Group compact>
|
||||||
/>
|
<InputNumber
|
||||||
<Button
|
style={{ width: 180 }}
|
||||||
type="primary"
|
min={1}
|
||||||
onClick={() => {
|
value={systemConfig?.cronConcurrency}
|
||||||
updateSystemConfig('cron-concurrency');
|
onChange={(value) => {
|
||||||
}}
|
setSystemConfig({ ...systemConfig, cronConcurrency: value });
|
||||||
style={{ width: 84 }}
|
}}
|
||||||
>
|
/>
|
||||||
{intl.get('确认')}
|
<Button
|
||||||
</Button>
|
type="primary"
|
||||||
</Input.Group>
|
onClick={() => {
|
||||||
</Form.Item>
|
updateSystemConfig('cron-concurrency');
|
||||||
<Form.Item label={intl.get('时区')} name="timezone">
|
}}
|
||||||
<Input.Group compact>
|
style={{ width: 84 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={intl.get('时区')} name="timezone">
|
||||||
|
<Input.Group compact>
|
||||||
|
<Select
|
||||||
|
value={systemConfig?.timezone}
|
||||||
|
style={{ width: 180 }}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSystemConfig({ ...systemConfig, timezone: value });
|
||||||
|
}}
|
||||||
|
options={TIMEZONES.map((timezone) => ({
|
||||||
|
value: timezone,
|
||||||
|
label: timezone,
|
||||||
|
}))}
|
||||||
|
showSearch
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
option?.value?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
updateSystemConfig('timezone');
|
||||||
|
}}
|
||||||
|
style={{ width: 84 }}
|
||||||
|
>
|
||||||
|
{intl.get('确认')}
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={intl.get('语言')} name="lang">
|
||||||
<Select
|
<Select
|
||||||
value={systemConfig?.timezone}
|
defaultValue={localStorage.getItem('lang') || ''}
|
||||||
style={{ width: 180 }}
|
style={{ width: 264 }}
|
||||||
onChange={(value) => {
|
onChange={handleLangChange}
|
||||||
setSystemConfig({ ...systemConfig, timezone: value });
|
options={[
|
||||||
}}
|
{ value: '', label: intl.get('跟随系统') },
|
||||||
options={TIMEZONES.map((timezone) => ({
|
{ value: 'zh', label: '简体中文' },
|
||||||
value: timezone,
|
{ value: 'en', label: 'English' },
|
||||||
label: timezone,
|
]}
|
||||||
}))}
|
|
||||||
showSearch
|
|
||||||
filterOption={(input, option) =>
|
|
||||||
option?.value?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={intl.get('数据备份还原')} name="frequency">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateSystemConfig('timezone');
|
setSelectedModules(['base']);
|
||||||
|
setVisible(true);
|
||||||
}}
|
}}
|
||||||
style={{ width: 84 }}
|
loading={exportLoading}
|
||||||
>
|
>
|
||||||
{intl.get('确认')}
|
{exportLoading ? intl.get('生成数据中...') : intl.get('备份')}
|
||||||
</Button>
|
</Button>
|
||||||
</Input.Group>
|
<Upload
|
||||||
</Form.Item>
|
method="put"
|
||||||
<Form.Item label={intl.get('语言')} name="lang">
|
showUploadList={false}
|
||||||
<Select
|
maxCount={1}
|
||||||
defaultValue={localStorage.getItem('lang') || ''}
|
action={`${config.apiPrefix}system/data/import`}
|
||||||
style={{ width: 264 }}
|
onChange={({ file, event }) => {
|
||||||
onChange={handleLangChange}
|
if (event?.percent) {
|
||||||
options={[
|
showUploadProgress(
|
||||||
{ value: '', label: intl.get('跟随系统') },
|
Math.min(parseFloat(event?.percent.toFixed(1)), 99),
|
||||||
{ value: 'zh', label: '简体中文' },
|
);
|
||||||
{ value: 'en', label: 'English' },
|
}
|
||||||
]}
|
if (file.status === 'done') {
|
||||||
/>
|
showUploadProgress(100);
|
||||||
</Form.Item>
|
showReloadModal();
|
||||||
<Form.Item label={intl.get('数据备份还原')} name="frequency">
|
}
|
||||||
<Button type="primary" onClick={exportData} loading={exportLoading}>
|
if (file.status === 'error') {
|
||||||
{exportLoading ? intl.get('生成数据中...') : intl.get('备份')}
|
message.error('上传失败');
|
||||||
</Button>
|
}
|
||||||
<Upload
|
}}
|
||||||
method="put"
|
name="data"
|
||||||
showUploadList={false}
|
headers={{
|
||||||
maxCount={1}
|
Authorization: `Bearer ${localStorage.getItem(config.authKey)}`,
|
||||||
action={`${config.apiPrefix}system/data/import`}
|
}}
|
||||||
onChange={({ file, event }) => {
|
>
|
||||||
if (event?.percent) {
|
<Button icon={<UploadOutlined />} style={{ marginLeft: 8 }}>
|
||||||
showUploadProgress(
|
{intl.get('还原数据')}
|
||||||
Math.min(parseFloat(event?.percent.toFixed(1)), 99),
|
</Button>
|
||||||
);
|
</Upload>
|
||||||
}
|
</Form.Item>
|
||||||
if (file.status === 'done') {
|
<Form.Item label={intl.get('检查更新')} name="update">
|
||||||
showUploadProgress(100);
|
<CheckUpdate systemInfo={systemInfo} />
|
||||||
showReloadModal();
|
</Form.Item>
|
||||||
}
|
</Form>
|
||||||
if (file.status === 'error') {
|
<Modal
|
||||||
message.error('上传失败');
|
title={intl.get('选择备份模块')}
|
||||||
}
|
open={visible}
|
||||||
|
onOk={exportData}
|
||||||
|
onCancel={() => setVisible(false)}
|
||||||
|
okText={intl.get('开始备份')}
|
||||||
|
cancelText={intl.get('取消')}
|
||||||
|
okButtonProps={{ loading: exportLoading }} // 绑定加载状态到按钮
|
||||||
|
>
|
||||||
|
<Checkbox.Group
|
||||||
|
value={selectedModules}
|
||||||
|
onChange={(v) => {
|
||||||
|
setSelectedModules(v as string[]);
|
||||||
}}
|
}}
|
||||||
name="data"
|
style={{
|
||||||
headers={{
|
width: '100%',
|
||||||
Authorization: `Bearer ${localStorage.getItem(config.authKey)}`,
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px 16px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button icon={<UploadOutlined />} style={{ marginLeft: 8 }}>
|
{exportModules.map((module) => (
|
||||||
{intl.get('还原数据')}
|
<Checkbox
|
||||||
</Button>
|
key={module.value}
|
||||||
</Upload>
|
value={module.value}
|
||||||
</Form.Item>
|
disabled={module.disabled}
|
||||||
<Form.Item label={intl.get('检查更新')} name="update">
|
style={{ marginLeft: 0 }}
|
||||||
<CheckUpdate systemInfo={systemInfo} />
|
>
|
||||||
</Form.Item>
|
{module.label}
|
||||||
</Form>
|
</Checkbox>
|
||||||
|
))}
|
||||||
|
</Checkbox.Group>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -579,18 +579,20 @@ const Subscription = () => {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowClassName={getRowClassName}
|
rowClassName={getRowClassName}
|
||||||
/>
|
/>
|
||||||
<SubscriptionModal
|
{isModalVisible && (
|
||||||
visible={isModalVisible}
|
<SubscriptionModal
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
subscription={editedSubscription}
|
subscription={editedSubscription}
|
||||||
/>
|
/>
|
||||||
<SubscriptionLogModal
|
)}
|
||||||
visible={isLogModalVisible}
|
{isLogModalVisible && (
|
||||||
handleCancel={() => {
|
<SubscriptionLogModal
|
||||||
setIsLogModalVisible(false);
|
handleCancel={() => {
|
||||||
}}
|
setIsLogModalVisible(false);
|
||||||
subscription={logSubscription}
|
}}
|
||||||
/>
|
subscription={logSubscription}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,12 +14,10 @@ import Ansi from 'ansi-to-react';
|
||||||
const SubscriptionLogModal = ({
|
const SubscriptionLogModal = ({
|
||||||
subscription,
|
subscription,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
data,
|
data,
|
||||||
logUrl,
|
logUrl,
|
||||||
}: {
|
}: {
|
||||||
subscription?: any;
|
subscription?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
data?: string;
|
data?: string;
|
||||||
logUrl?: string;
|
logUrl?: string;
|
||||||
|
@ -79,10 +77,10 @@ const SubscriptionLogModal = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (subscription && subscription.id && visible) {
|
if (subscription && subscription.id) {
|
||||||
getCronLog(true);
|
getCronLog(true);
|
||||||
}
|
}
|
||||||
}, [subscription, visible]);
|
}, [subscription]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -97,7 +95,7 @@ const SubscriptionLogModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={titleElement()}
|
title={titleElement()}
|
||||||
open={visible}
|
open={true}
|
||||||
centered
|
centered
|
||||||
className="log-modal"
|
className="log-modal"
|
||||||
forceRender
|
forceRender
|
||||||
|
|
|
@ -22,17 +22,19 @@ const fileUrlRegx = /([^\/\:]+\/[^\/\.]+)\.[a-z]+$/;
|
||||||
const SubscriptionModal = ({
|
const SubscriptionModal = ({
|
||||||
subscription,
|
subscription,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
|
||||||
}: {
|
}: {
|
||||||
subscription?: any;
|
subscription?: any;
|
||||||
visible: boolean;
|
|
||||||
handleCancel: (needUpdate?: boolean) => void;
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [type, setType] = useState('public-repo');
|
const [type, setType] = useState(subscription?.type || 'public-repo');
|
||||||
const [scheduleType, setScheduleType] = useState('crontab');
|
const [scheduleType, setScheduleType] = useState(
|
||||||
const [pullType, setPullType] = useState<'ssh-key' | 'user-pwd'>('ssh-key');
|
subscription?.schedule_type || 'crontab',
|
||||||
|
);
|
||||||
|
const [pullType, setPullType] = useState<'ssh-key' | 'user-pwd'>(
|
||||||
|
subscription?.pull_type || 'ssh-key',
|
||||||
|
);
|
||||||
|
|
||||||
const handleOk = async (values: any) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -255,29 +257,17 @@ const SubscriptionModal = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
window.addEventListener('paste', onPaste);
|
||||||
window.addEventListener('paste', onPaste);
|
|
||||||
} else {
|
|
||||||
window.removeEventListener('paste', onPaste);
|
|
||||||
}
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
return () => {
|
||||||
form.setFieldsValue(
|
window.removeEventListener('paste', onPaste);
|
||||||
{ ...subscription, ...formatParams(subscription) } || {},
|
};
|
||||||
);
|
}, []);
|
||||||
setType((subscription && subscription.type) || 'public-repo');
|
|
||||||
setScheduleType((subscription && subscription.schedule_type) || 'crontab');
|
|
||||||
setPullType((subscription && subscription.pull_type) || 'ssh-key');
|
|
||||||
if (!subscription) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
}, [subscription, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={subscription ? intl.get('编辑订阅') : intl.get('创建订阅')}
|
title={subscription ? intl.get('编辑订阅') : intl.get('创建订阅')}
|
||||||
open={visible}
|
open={true}
|
||||||
forceRender
|
forceRender
|
||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
@ -294,7 +284,12 @@ const SubscriptionModal = ({
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
confirmLoading={loading}
|
confirmLoading={loading}
|
||||||
>
|
>
|
||||||
<Form form={form} name="form_in_modal" layout="vertical">
|
<Form
|
||||||
|
form={form}
|
||||||
|
name="form_in_modal"
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{ ...subscription, ...formatParams(subscription) }}
|
||||||
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
label={intl.get('名称')}
|
label={intl.get('名称')}
|
||||||
|
|
|
@ -128,10 +128,14 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ntfyTopic',
|
label: 'ntfyTopic',
|
||||||
tip: intl.get('ntfy的消息应用topic'),
|
tip: intl.get('ntfy应用topic'),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{ label: 'ntfyPriority', tip: intl.get('推送消息的优先级') },
|
{ label: 'ntfyPriority', tip: intl.get('推送消息的优先级') },
|
||||||
|
{ label: 'ntfyToken', tip: intl.get('ntfy应用token') },
|
||||||
|
{ label: 'ntfyUsername', tip: intl.get('ntfy应用用户名') },
|
||||||
|
{ label: 'ntfyPassword', tip: intl.get('ntfy应用密码') },
|
||||||
|
{ label: 'ntfyActions', tip: intl.get('ntfy用户动作') },
|
||||||
],
|
],
|
||||||
chat: [
|
chat: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,7 @@ export interface IResponseData {
|
||||||
code?: number;
|
code?: number;
|
||||||
data?: any;
|
data?: any;
|
||||||
message?: string;
|
message?: string;
|
||||||
errors?: any[];
|
error?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Override<
|
export type Override<
|
||||||
|
|
21
version.yaml
21
version.yaml
|
@ -1,13 +1,10 @@
|
||||||
version: 2.19.0
|
version: 2.19.2
|
||||||
changeLogLink: https://t.me/jiao_long/429
|
changeLogLink: https://t.me/jiao_long/431
|
||||||
publishTime: 2025-05-11 08:00
|
publishTime: 2025-06-27 23:59
|
||||||
changeLog: |
|
changeLog: |
|
||||||
1. 缓存 node 和 python 依赖,linux 依赖需要增加映射目录
|
1. 备份数据支持选择模块,支持清除依赖缓存
|
||||||
2. 减少启动服务数,节约启动内存约 50%
|
2. QLAPI 和 openapi 的 systemNotify 支持自定义通知类型和参数
|
||||||
3. 邮箱通知支持多个收件人
|
3. ntfy 增加可选的认证与用户动作,感谢 https://github.com/liheji
|
||||||
4. boot 任务改为在依赖安装完成后执行
|
4. 修复取消安装依赖
|
||||||
5. 修复脚本管理查询子目录逻辑
|
5. 修复环境变量过大解析报错
|
||||||
6. 修复脚本管理增加文件夹
|
6. 修改服务启动方式
|
||||||
7. 修复 QLAPI 修复环境变量 remarks
|
|
||||||
8. 修复 mjs 依赖查不到
|
|
||||||
9. 修复无法删除日志文件
|
|
Loading…
Reference in New Issue
Block a user