mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-29 02:56:08 +08:00
添加依赖管理
This commit is contained in:
parent
10c07b0a09
commit
fbba44d482
127
back/api/dependence.ts
Normal file
127
back/api/dependence.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
import DependenceService from '../services/dependence';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { celebrate, Joi } from 'celebrate';
|
||||||
|
const route = Router();
|
||||||
|
|
||||||
|
export default (app: Router) => {
|
||||||
|
app.use('/', route);
|
||||||
|
route.get(
|
||||||
|
'/dependencies',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
const data = await dependenceService.dependencies(req.query as any);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
'/dependencies',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(
|
||||||
|
Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
type: Joi.number().required(),
|
||||||
|
remark: Joi.number().optional().allow(''),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
const data = await dependenceService.create(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/dependencies',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
_id: Joi.string().required(),
|
||||||
|
type: Joi.number().required(),
|
||||||
|
remark: Joi.number().optional().allow(''),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
const data = await dependenceService.update(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
'/dependencies',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.string().required()),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
const data = await dependenceService.remove(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/dependencies/:id',
|
||||||
|
celebrate({
|
||||||
|
params: Joi.object({
|
||||||
|
id: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
const data = await dependenceService.get(req.params.id);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/dependencies/reinstall',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.string().required()),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const dependenceService = Container.get(DependenceService);
|
||||||
|
const data = await dependenceService.reInstall(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
|
@ -6,6 +6,7 @@ import log from './log';
|
||||||
import cron from './cron';
|
import cron from './cron';
|
||||||
import script from './script';
|
import script from './script';
|
||||||
import open from './open';
|
import open from './open';
|
||||||
|
import dependence from './dependence';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
|
@ -16,6 +17,7 @@ export default () => {
|
||||||
cron(app);
|
cron(app);
|
||||||
script(app);
|
script(app);
|
||||||
open(app);
|
open(app);
|
||||||
|
dependence(app);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,7 @@ const cronDbFile = path.join(rootPath, 'db/crontab.db');
|
||||||
const envDbFile = path.join(rootPath, 'db/env.db');
|
const envDbFile = path.join(rootPath, 'db/env.db');
|
||||||
const appDbFile = path.join(rootPath, 'db/app.db');
|
const appDbFile = path.join(rootPath, 'db/app.db');
|
||||||
const authDbFile = path.join(rootPath, 'db/auth.db');
|
const authDbFile = path.join(rootPath, 'db/auth.db');
|
||||||
|
const dependenceDbFile = path.join(rootPath, 'db/dependence.db');
|
||||||
const versionFile = path.join(rootPath, 'src/version.ts');
|
const versionFile = path.join(rootPath, 'src/version.ts');
|
||||||
|
|
||||||
const configFound = dotenv.config({ path: confFile });
|
const configFound = dotenv.config({ path: confFile });
|
||||||
|
@ -68,6 +69,7 @@ export default {
|
||||||
envDbFile,
|
envDbFile,
|
||||||
appDbFile,
|
appDbFile,
|
||||||
authDbFile,
|
authDbFile,
|
||||||
|
dependenceDbFile,
|
||||||
configPath,
|
configPath,
|
||||||
scriptPath,
|
scriptPath,
|
||||||
samplePath,
|
samplePath,
|
||||||
|
|
45
back/data/dependence.ts
Normal file
45
back/data/dependence.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
export class Dependence {
|
||||||
|
timestamp?: string;
|
||||||
|
created?: number;
|
||||||
|
_id?: string;
|
||||||
|
status?: DependenceStatus;
|
||||||
|
type?: DependenceTypes;
|
||||||
|
name?: number;
|
||||||
|
log?: string[];
|
||||||
|
remark?: string;
|
||||||
|
|
||||||
|
constructor(options: Dependence) {
|
||||||
|
this._id = options._id;
|
||||||
|
this.created = options.created || new Date().valueOf();
|
||||||
|
this.status = options.status || DependenceStatus.installing;
|
||||||
|
this.type = options.type || DependenceTypes.nodejs;
|
||||||
|
this.timestamp = new Date().toString();
|
||||||
|
this.name = options.name;
|
||||||
|
this.log = options.log || [];
|
||||||
|
this.remark = options.remark || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DependenceStatus {
|
||||||
|
'installing',
|
||||||
|
'installed',
|
||||||
|
'installFailed',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DependenceTypes {
|
||||||
|
'nodejs',
|
||||||
|
'python3',
|
||||||
|
'linux',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum InstallDependenceCommandTypes {
|
||||||
|
'pnpm install -g',
|
||||||
|
'pip3 install',
|
||||||
|
'apk add --no-cache',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum unInstallDependenceCommandTypes {
|
||||||
|
'pnpm uninstall -g',
|
||||||
|
'pip3 uninstall',
|
||||||
|
'apk del',
|
||||||
|
}
|
16
back/data/sock.ts
Normal file
16
back/data/sock.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export class SockMessage {
|
||||||
|
message?: string;
|
||||||
|
type?: SockMessageType;
|
||||||
|
references?: string[];
|
||||||
|
|
||||||
|
constructor(options: SockMessage) {
|
||||||
|
this.type = options.type;
|
||||||
|
this.message = options.message;
|
||||||
|
this.references = options.references;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SockMessageType =
|
||||||
|
| 'ping'
|
||||||
|
| 'installDependence'
|
||||||
|
| 'updateSystemVersion';
|
|
@ -18,7 +18,7 @@ export default async ({ server }: { server: Server }) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const { token = '', tokens = {} } = JSON.parse(data);
|
const { token = '', tokens = {} } = JSON.parse(data);
|
||||||
if (headerToken === token || tokens[platform] === headerToken) {
|
if (headerToken === token || tokens[platform] === headerToken) {
|
||||||
conn.write('hanhh');
|
conn.write(JSON.stringify({ type: 'ping', message: 'hanhh' }));
|
||||||
sockService.addClient(conn);
|
sockService.addClient(conn);
|
||||||
|
|
||||||
conn.on('data', (message) => {
|
conn.on('data', (message) => {
|
||||||
|
|
245
back/services/dependence.ts
Normal file
245
back/services/dependence.ts
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import config from '../config';
|
||||||
|
import DataStore from 'nedb';
|
||||||
|
import {
|
||||||
|
Dependence,
|
||||||
|
InstallDependenceCommandTypes,
|
||||||
|
DependenceStatus,
|
||||||
|
DependenceTypes,
|
||||||
|
unInstallDependenceCommandTypes,
|
||||||
|
} from '../data/dependence';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import SockService from './sock';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class DependenceService {
|
||||||
|
private dependenceDb = new DataStore({ filename: config.dependenceDbFile });
|
||||||
|
constructor(
|
||||||
|
@Inject('logger') private logger: winston.Logger,
|
||||||
|
private sockService: SockService,
|
||||||
|
) {
|
||||||
|
this.dependenceDb.loadDatabase((err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDb(): DataStore {
|
||||||
|
return this.dependenceDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(payloads: Dependence[]): Promise<Dependence[]> {
|
||||||
|
const tabs = payloads.map((x) => {
|
||||||
|
const tab = new Dependence({ ...x, status: DependenceStatus.installing });
|
||||||
|
return tab;
|
||||||
|
});
|
||||||
|
const docs = await this.insert(tabs);
|
||||||
|
this.installOrUninstallDependencies(docs);
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async insert(payloads: Dependence[]): Promise<Dependence[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.dependenceDb.insert(payloads, (err, docs) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
} else {
|
||||||
|
resolve(docs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(
|
||||||
|
payload: Dependence & { _id: string },
|
||||||
|
): Promise<Dependence> {
|
||||||
|
const { _id, ...other } = payload;
|
||||||
|
const doc = await this.get(_id);
|
||||||
|
const tab = new Dependence({
|
||||||
|
...doc,
|
||||||
|
...other,
|
||||||
|
status: DependenceStatus.installing,
|
||||||
|
});
|
||||||
|
const newDoc = await this.updateDb(tab);
|
||||||
|
this.installOrUninstallDependencies([newDoc]);
|
||||||
|
return newDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateDb(payload: Dependence): Promise<Dependence> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.dependenceDb.update(
|
||||||
|
{ _id: payload._id },
|
||||||
|
payload,
|
||||||
|
{ returnUpdatedDocs: true },
|
||||||
|
(err, num, doc) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
} else {
|
||||||
|
resolve(doc as Dependence);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async remove(ids: string[]) {
|
||||||
|
return new Promise((resolve: any) => {
|
||||||
|
this.dependenceDb.find({ _id: { $in: ids } }).exec((err, docs) => {
|
||||||
|
this.installOrUninstallDependencies(docs, false);
|
||||||
|
this.removeDb(ids);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeDb(ids: string[]) {
|
||||||
|
return new Promise((resolve: any) => {
|
||||||
|
this.dependenceDb.remove(
|
||||||
|
{ _id: { $in: ids } },
|
||||||
|
{ multi: true },
|
||||||
|
async (err) => {
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dependencies(
|
||||||
|
{ searchValue, type }: { searchValue: string; type: string },
|
||||||
|
sort: any = { position: -1 },
|
||||||
|
query: any = {},
|
||||||
|
): Promise<Dependence[]> {
|
||||||
|
let condition = { ...query, type: DependenceTypes[type as any] };
|
||||||
|
if (searchValue) {
|
||||||
|
const reg = new RegExp(searchValue);
|
||||||
|
condition = {
|
||||||
|
...condition,
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
name: reg,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const newDocs = await this.find(condition, sort);
|
||||||
|
return newDocs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reInstall(ids: string[]): Promise<Dependence[]> {
|
||||||
|
return new Promise((resolve: any) => {
|
||||||
|
this.dependenceDb.update(
|
||||||
|
{ _id: { $in: ids } },
|
||||||
|
{ $set: { status: DependenceStatus.installing, log: [] } },
|
||||||
|
{ multi: true, returnUpdatedDocs: true },
|
||||||
|
async (err, num, docs: Dependence[]) => {
|
||||||
|
this.installOrUninstallDependencies(docs);
|
||||||
|
resolve(docs);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async find(query: any, sort: any): Promise<Dependence[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.dependenceDb
|
||||||
|
.find(query)
|
||||||
|
.sort({ ...sort })
|
||||||
|
.exec((err, docs) => {
|
||||||
|
resolve(docs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(_id: string): Promise<Dependence> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.dependenceDb.find({ _id }).exec((err, docs) => {
|
||||||
|
resolve(docs[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateLog(ids: string[], log: string): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.dependenceDb.update(
|
||||||
|
{ _id: { $in: ids } },
|
||||||
|
{ $push: { log } },
|
||||||
|
{ multi: true },
|
||||||
|
(err, num, doc) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public installOrUninstallDependencies(
|
||||||
|
dependencies: Dependence[],
|
||||||
|
isInstall: boolean = true,
|
||||||
|
) {
|
||||||
|
if (dependencies.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const depNames = dependencies.map((x) => x.name).join(' ');
|
||||||
|
const depRunCommand = (
|
||||||
|
isInstall
|
||||||
|
? InstallDependenceCommandTypes
|
||||||
|
: unInstallDependenceCommandTypes
|
||||||
|
)[dependencies[0].type as any];
|
||||||
|
const depIds = dependencies.map((x) => x._id) as string[];
|
||||||
|
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: `开始安装依赖 ${depNames}`,
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
this.updateLog(depIds, `开始安装依赖 ${depNames}\n`);
|
||||||
|
cp.stdout.on('data', (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: data.toString(),
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
isInstall && this.updateLog(depIds, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.stderr.on('data', (data) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: data.toString(),
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
isInstall && this.updateLog(depIds, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('error', (err) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: JSON.stringify(err),
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
isInstall && this.updateLog(depIds, JSON.stringify(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
cp.on('close', (code) => {
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'installDependence',
|
||||||
|
message: '安装结束',
|
||||||
|
references: depIds,
|
||||||
|
});
|
||||||
|
isInstall && this.updateLog(depIds, '安装结束');
|
||||||
|
isInstall &&
|
||||||
|
this.dependenceDb.update(
|
||||||
|
{ _id: { $in: depIds } },
|
||||||
|
{
|
||||||
|
$set: { status: DependenceStatus.installed },
|
||||||
|
$unset: { pid: true },
|
||||||
|
},
|
||||||
|
{ multi: true },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { Connection } from 'sockjs';
|
import { Connection } from 'sockjs';
|
||||||
|
import { SockMessage } from '../data/sock';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SockService {
|
export default class SockService {
|
||||||
|
@ -25,9 +26,9 @@ export default class SockService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendMessage(msg: string) {
|
public sendMessage(msg: SockMessage) {
|
||||||
this.clients.forEach((x) => {
|
this.clients.forEach((x) => {
|
||||||
x.write(msg);
|
x.write(JSON.stringify(msg));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -433,16 +433,29 @@ export default class UserService {
|
||||||
public async updateSystem() {
|
public async updateSystem() {
|
||||||
const cp = spawn('ql -l update', { shell: '/bin/bash' });
|
const cp = spawn('ql -l update', { shell: '/bin/bash' });
|
||||||
|
|
||||||
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: `开始更新系统`,
|
||||||
|
});
|
||||||
cp.stdout.on('data', (data) => {
|
cp.stdout.on('data', (data) => {
|
||||||
this.sockService.sendMessage(data.toString());
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: data.toString(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.stderr.on('data', (data) => {
|
cp.stderr.on('data', (data) => {
|
||||||
this.sockService.sendMessage(data.toString());
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: data.toString(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('error', (err) => {
|
cp.on('error', (err) => {
|
||||||
this.sockService.sendMessage(JSON.stringify(err));
|
this.sockService.sendMessage({
|
||||||
|
type: 'updateSystemVersion',
|
||||||
|
message: JSON.stringify(err),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return { code: 200 };
|
return { code: 200 };
|
||||||
|
|
|
@ -48,6 +48,12 @@ export default {
|
||||||
icon: <FormOutlined />,
|
icon: <FormOutlined />,
|
||||||
component: '@/pages/script/index',
|
component: '@/pages/script/index',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/dependence',
|
||||||
|
name: '依赖管理',
|
||||||
|
icon: <FormOutlined />,
|
||||||
|
component: '@/pages/dependence/index',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/diff',
|
path: '/diff',
|
||||||
name: '对比工具',
|
name: '对比工具',
|
||||||
|
|
|
@ -129,9 +129,14 @@ export default function (props: any) {
|
||||||
);
|
);
|
||||||
|
|
||||||
ws.current.onmessage = (e: any) => {
|
ws.current.onmessage = (e: any) => {
|
||||||
if (e.data === 'hanhh') {
|
try {
|
||||||
console.log('websocket连接成功', e);
|
const data = JSON.parse(e.data);
|
||||||
} else {
|
if (data && data.message === 'hanhh') {
|
||||||
|
console.log('websocket连接成功', e);
|
||||||
|
} else {
|
||||||
|
console.log('websocket连接失败', e);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
console.log('websocket连接失败', e);
|
console.log('websocket连接失败', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ const CronLogModal = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState<string>('启动中...');
|
const [value, setValue] = useState<string>('启动中...');
|
||||||
const [loading, setLoading] = useState<any>(true);
|
const [loading, setLoading] = useState<any>(true);
|
||||||
const [excuting, setExcuting] = useState<any>(true);
|
const [executing, setExecuting] = useState<any>(true);
|
||||||
const [isPhone, setIsPhone] = useState(false);
|
const [isPhone, setIsPhone] = useState(false);
|
||||||
const [theme, setTheme] = useState<string>('');
|
const [theme, setTheme] = useState<string>('');
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ const CronLogModal = ({
|
||||||
if (localStorage.getItem('logCron') === cron._id) {
|
if (localStorage.getItem('logCron') === cron._id) {
|
||||||
const log = data.data as string;
|
const log = data.data as string;
|
||||||
setValue(log || '暂无日志');
|
setValue(log || '暂无日志');
|
||||||
setExcuting(
|
setExecuting(
|
||||||
log && !log.includes('执行结束') && !log.includes('重启面板'),
|
log && !log.includes('执行结束') && !log.includes('重启面板'),
|
||||||
);
|
);
|
||||||
if (log && !log.includes('执行结束') && !log.includes('重启面板')) {
|
if (log && !log.includes('执行结束') && !log.includes('重启面板')) {
|
||||||
|
@ -89,8 +89,8 @@ const CronLogModal = ({
|
||||||
const titleElement = () => {
|
const titleElement = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(excuting || loading) && <Loading3QuartersOutlined spin />}
|
{(executing || loading) && <Loading3QuartersOutlined spin />}
|
||||||
{!excuting && <CheckCircleOutlined />}
|
{!executing && <CheckCircleOutlined />}
|
||||||
<span style={{ marginLeft: 5 }}>日志-{cron && cron.name}</span>{' '}
|
<span style={{ marginLeft: 5 }}>日志-{cron && cron.name}</span>{' '}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
0
src/pages/dependence/index.less
Normal file
0
src/pages/dependence/index.less
Normal file
424
src/pages/dependence/index.tsx
Normal file
424
src/pages/dependence/index.tsx
Normal file
|
@ -0,0 +1,424 @@
|
||||||
|
import React, { useCallback, useRef, useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Tooltip,
|
||||||
|
Input,
|
||||||
|
Tabs,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
SyncOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
StopOutlined,
|
||||||
|
BugOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import DependenceModal from './modal';
|
||||||
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
import './index.less';
|
||||||
|
import { getTableScroll } from '@/utils/index';
|
||||||
|
import DependenceLogModal from './logModal';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
const { Search } = Input;
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
'安装中',
|
||||||
|
'已安装',
|
||||||
|
'安装失败',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StatusColor {
|
||||||
|
'processing',
|
||||||
|
'success',
|
||||||
|
'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dependence = ({ headerStyle, isPhone, ws }: any) => {
|
||||||
|
const columns: any = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
align: 'center' as const,
|
||||||
|
width: 50,
|
||||||
|
render: (text: string, record: any, index: number) => {
|
||||||
|
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
align: 'center' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
dataIndex: 'status',
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (text: string, record: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<Space size="middle" style={{ cursor: 'text' }}>
|
||||||
|
<Tag color={StatusColor[record.status]} style={{ marginRight: 0 }}>
|
||||||
|
{Status[record.status]}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'created',
|
||||||
|
dataIndex: 'created',
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (text: string, record: any) => {
|
||||||
|
return <span>{new Date(record.created).toLocaleString()}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (text: string, record: any, index: number) => {
|
||||||
|
const isPc = !isPhone;
|
||||||
|
return (
|
||||||
|
<Space size="middle">
|
||||||
|
{record.status !== Status.安装中 && (
|
||||||
|
<>
|
||||||
|
<Tooltip title={isPc ? '重新安装' : ''}>
|
||||||
|
<a onClick={() => reInstallDependence(record, index)}>
|
||||||
|
<BugOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={isPc ? '删除' : ''}>
|
||||||
|
<a onClick={() => deleteDependence(record, index)}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Tooltip title={isPc ? '日志' : ''}>
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
setLogDependence({ ...record, timestamp: Date.now() });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileTextOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [value, setValue] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [editedDependence, setEditedDependence] = useState();
|
||||||
|
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
|
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||||
|
const [logDependence, setLogDependence] = useState<any>();
|
||||||
|
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||||
|
const [type, setType] = useState('nodejs');
|
||||||
|
|
||||||
|
const getDependencies = () => {
|
||||||
|
setLoading(true);
|
||||||
|
request
|
||||||
|
.get(
|
||||||
|
`${config.apiPrefix}dependencies?searchValue=${searchText}&type=${type}`,
|
||||||
|
)
|
||||||
|
.then((data: any) => {
|
||||||
|
setValue(data.data);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDependence = () => {
|
||||||
|
setEditedDependence(null as any);
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const editDependence = (record: any, index: number) => {
|
||||||
|
setEditedDependence(record);
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDependence = (record: any, index: number) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认删除依赖{' '}
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{record.name}
|
||||||
|
</Text>{' '}
|
||||||
|
吗
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.delete(`${config.apiPrefix}dependencies`, { data: [record._id] })
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
message.success('删除成功');
|
||||||
|
const result = [...value];
|
||||||
|
result.splice(index, 1);
|
||||||
|
setValue(result);
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const reInstallDependence = (record: any, index: number) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认重新安装',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认重新安装{' '}
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{record.name}
|
||||||
|
</Text>{' '}
|
||||||
|
吗
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}dependencies/reinstall`, {
|
||||||
|
data: [record._id],
|
||||||
|
})
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
handleDependence(data.data[0]);
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = (dependence?: any[]) => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
dependence && handleDependence(dependence);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDependence = (dependence: any) => {
|
||||||
|
const result = [...value];
|
||||||
|
if (Array.isArray(dependence)) {
|
||||||
|
result.push(...dependence);
|
||||||
|
} else {
|
||||||
|
const index = value.findIndex((x) => x._id === dependence._id);
|
||||||
|
result.splice(index, 1, {
|
||||||
|
...dependence,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setValue(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectChange = (selectedIds: any[]) => {
|
||||||
|
setSelectedRowIds(selectedIds);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
|
||||||
|
const offset = isPhone ? 40 : 0;
|
||||||
|
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowSelection = {
|
||||||
|
selectedRowIds,
|
||||||
|
onChange: onSelectChange,
|
||||||
|
};
|
||||||
|
|
||||||
|
const delDependencies = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: <>确认删除选中的变量吗</>,
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.delete(`${config.apiPrefix}dependencies`, { data: selectedRowIds })
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
message.success('批量删除成功');
|
||||||
|
setSelectedRowIds([]);
|
||||||
|
getDependencies();
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDependenceDetail = (dependence: any) => {
|
||||||
|
request
|
||||||
|
.get(`${config.apiPrefix}dependencies/${dependence._id}`)
|
||||||
|
.then((data: any) => {
|
||||||
|
const index = value.findIndex((x) => x._id === dependence._id);
|
||||||
|
const result = [...value];
|
||||||
|
result.splice(index, 1, {
|
||||||
|
...dependence,
|
||||||
|
...data.data,
|
||||||
|
});
|
||||||
|
setValue(result);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearch = (value: string) => {
|
||||||
|
setSearchText(value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDependencies();
|
||||||
|
}, [searchText, type]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const offset = isPhone ? 40 : 0;
|
||||||
|
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (logDependence) {
|
||||||
|
localStorage.setItem('logDependence', logDependence._id);
|
||||||
|
setIsLogModalVisible(true);
|
||||||
|
}
|
||||||
|
}, [logDependence]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ws.onmessage = (e: any) => {
|
||||||
|
const { type, message, references } = JSON.parse(e.data);
|
||||||
|
if (
|
||||||
|
type === 'installDependence' &&
|
||||||
|
message === '安装结束' &&
|
||||||
|
references.length > 0
|
||||||
|
) {
|
||||||
|
const result = [...value];
|
||||||
|
for (let i = 0; i < references.length; i++) {
|
||||||
|
const index = value.findIndex((x) => x._id === references[i]);
|
||||||
|
result.splice(index, 1, {
|
||||||
|
...result[index],
|
||||||
|
status: Status.已安装,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setValue(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const panelContent = () => (
|
||||||
|
<>
|
||||||
|
{selectedRowIds.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginBottom: 5, marginLeft: 8 }}
|
||||||
|
onClick={delDependencies}
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</Button>
|
||||||
|
<span style={{ marginLeft: 8 }}>
|
||||||
|
已选择
|
||||||
|
<a>{selectedRowIds?.length}</a>项
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={value}
|
||||||
|
rowKey="_id"
|
||||||
|
size="middle"
|
||||||
|
scroll={{ x: 768, y: tableScrollHeight }}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</DndProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const onTabChange = (activeKey: string) => {
|
||||||
|
setType(activeKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
className="ql-container-wrapper dependence-wrapper"
|
||||||
|
title="依赖管理"
|
||||||
|
extra={[
|
||||||
|
<Search
|
||||||
|
placeholder="请输入名称"
|
||||||
|
style={{ width: 'auto' }}
|
||||||
|
enterButton
|
||||||
|
loading={loading}
|
||||||
|
onSearch={onSearch}
|
||||||
|
/>,
|
||||||
|
<Button key="2" type="primary" onClick={() => addDependence()}>
|
||||||
|
添加依赖
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
header={{
|
||||||
|
style: headerStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="nodejs"
|
||||||
|
size="small"
|
||||||
|
tabPosition="top"
|
||||||
|
onChange={onTabChange}
|
||||||
|
>
|
||||||
|
<Tabs.TabPane tab="nodejs" key="nodejs">
|
||||||
|
{panelContent()}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="python3" key="python3">
|
||||||
|
{panelContent()}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="linux" key="linux">
|
||||||
|
{panelContent()}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
<DependenceModal
|
||||||
|
visible={isModalVisible}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
dependence={editedDependence}
|
||||||
|
defaultType={type}
|
||||||
|
/>
|
||||||
|
<DependenceLogModal
|
||||||
|
visible={isLogModalVisible}
|
||||||
|
handleCancel={() => {
|
||||||
|
setIsLogModalVisible(false);
|
||||||
|
getDependenceDetail(logDependence);
|
||||||
|
}}
|
||||||
|
ws={ws}
|
||||||
|
dependence={logDependence}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dependence;
|
122
src/pages/dependence/logModal.tsx
Normal file
122
src/pages/dependence/logModal.tsx
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Modal, message, Input, Form, Statistic, Button } from 'antd';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
import {
|
||||||
|
Loading3QuartersOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { PageLoading } from '@ant-design/pro-layout';
|
||||||
|
|
||||||
|
const DependenceLogModal = ({
|
||||||
|
dependence,
|
||||||
|
handleCancel,
|
||||||
|
visible,
|
||||||
|
ws,
|
||||||
|
}: {
|
||||||
|
dependence?: any;
|
||||||
|
visible: boolean;
|
||||||
|
handleCancel: () => void;
|
||||||
|
ws: any;
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = useState<string>('');
|
||||||
|
const [executing, setExecuting] = useState<any>(true);
|
||||||
|
const [isPhone, setIsPhone] = useState(false);
|
||||||
|
const [loading, setLoading] = useState<any>(true);
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
localStorage.removeItem('logDependence');
|
||||||
|
handleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleElement = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{executing && <Loading3QuartersOutlined spin />}
|
||||||
|
{!executing && <CheckCircleOutlined />}
|
||||||
|
<span style={{ marginLeft: 5 }}>
|
||||||
|
日志 - {dependence && dependence.name}
|
||||||
|
</span>{' '}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDependenceLog = () => {
|
||||||
|
setLoading(true);
|
||||||
|
request
|
||||||
|
.get(`${config.apiPrefix}dependencies/${dependence._id}`)
|
||||||
|
.then((data: any) => {
|
||||||
|
if (localStorage.getItem('logDependence') === dependence._id) {
|
||||||
|
const log = (data.data.log || []).join('\n') as string;
|
||||||
|
setValue(log);
|
||||||
|
setExecuting(!log.includes('安装结束'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dependence) {
|
||||||
|
getDependenceLog();
|
||||||
|
ws.onmessage = (e: any) => {
|
||||||
|
const { type, message, references } = JSON.parse(e.data);
|
||||||
|
if (
|
||||||
|
type === 'installDependence' &&
|
||||||
|
message === '安装结束' &&
|
||||||
|
references.length > 0
|
||||||
|
) {
|
||||||
|
setExecuting(false);
|
||||||
|
}
|
||||||
|
setValue(`${value} \n ${message}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [dependence]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsPhone(document.body.clientWidth < 768);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={titleElement()}
|
||||||
|
visible={visible}
|
||||||
|
centered
|
||||||
|
className="log-modal"
|
||||||
|
bodyStyle={{
|
||||||
|
overflowY: 'auto',
|
||||||
|
maxHeight: 'calc(70vh - var(--vh-offset, 0px))',
|
||||||
|
minHeight: '300px',
|
||||||
|
}}
|
||||||
|
forceRender
|
||||||
|
onOk={() => cancel()}
|
||||||
|
onCancel={() => cancel()}
|
||||||
|
footer={[
|
||||||
|
<Button type="primary" onClick={() => cancel()}>
|
||||||
|
知道了
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<PageLoading />
|
||||||
|
) : (
|
||||||
|
<pre
|
||||||
|
style={
|
||||||
|
isPhone
|
||||||
|
? {
|
||||||
|
fontFamily: 'Source Code Pro',
|
||||||
|
width: 375,
|
||||||
|
zoom: 0.83,
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DependenceLogModal;
|
139
src/pages/dependence/modal.tsx
Normal file
139
src/pages/dependence/modal.tsx
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Modal, message, Input, Form, Radio, Select } from 'antd';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
enum DependenceTypes {
|
||||||
|
'nodejs',
|
||||||
|
'python3',
|
||||||
|
'linux',
|
||||||
|
}
|
||||||
|
|
||||||
|
const DependenceModal = ({
|
||||||
|
dependence,
|
||||||
|
handleCancel,
|
||||||
|
visible,
|
||||||
|
defaultType,
|
||||||
|
}: {
|
||||||
|
dependence?: any;
|
||||||
|
visible: boolean;
|
||||||
|
handleCancel: (cks?: any[]) => void;
|
||||||
|
defaultType: string;
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleOk = async (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
|
const { name, split, type } = values;
|
||||||
|
const method = dependence ? 'put' : 'post';
|
||||||
|
let payload;
|
||||||
|
if (!dependence) {
|
||||||
|
if (split === '1') {
|
||||||
|
const symbol = name.includes('&') ? '&' : '\n';
|
||||||
|
payload = name.split(symbol).map((x: any) => {
|
||||||
|
return {
|
||||||
|
name: x,
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
payload = [{ name, type }];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload = { ...values, _id: dependence._id };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { code, data } = await request[method](
|
||||||
|
`${config.apiPrefix}dependencies`,
|
||||||
|
{
|
||||||
|
data: payload,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(dependence ? '更新依赖成功' : '添加依赖成功');
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
handleCancel(data);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.resetFields();
|
||||||
|
}, [dependence, visible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={dependence ? '编辑依赖' : '新建依赖'}
|
||||||
|
visible={visible}
|
||||||
|
forceRender
|
||||||
|
onOk={() => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then((values) => {
|
||||||
|
handleOk(values);
|
||||||
|
})
|
||||||
|
.catch((info) => {
|
||||||
|
console.log('Validate Failed:', info);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancel={() => handleCancel()}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
name="dependence_modal"
|
||||||
|
initialValues={dependence}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="type"
|
||||||
|
label="依赖类型"
|
||||||
|
initialValue={DependenceTypes[defaultType as any]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{config.dependenceTypes.map((x, i) => (
|
||||||
|
<Option value={i}>{x}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
{!dependence && (
|
||||||
|
<Form.Item
|
||||||
|
name="split"
|
||||||
|
label="自动拆分"
|
||||||
|
initialValue="0"
|
||||||
|
tooltip="多个依赖是否换行分割"
|
||||||
|
>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="1">是</Radio>
|
||||||
|
<Radio value="0">否</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="名称"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入依赖名称', whitespace: true },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
rows={4}
|
||||||
|
autoSize={true}
|
||||||
|
placeholder="请输入依赖名称"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="remark" label="备注">
|
||||||
|
<Input placeholder="请输入备注" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DependenceModal;
|
4
src/pages/env/index.tsx
vendored
4
src/pages/env/index.tsx
vendored
|
@ -368,7 +368,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
|
if (selectedRowIds.length === 0 || selectedIds.length === 0) {
|
||||||
const offset = isPhone ? 40 : 0;
|
const offset = isPhone ? 40 : 0;
|
||||||
setTableScrollHeight(getTableScroll({ extraHeight: 127 }) - offset);
|
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -438,7 +438,7 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const offset = isPhone ? 40 : 0;
|
const offset = isPhone ? 40 : 0;
|
||||||
setTableScrollHeight(getTableScroll({ extraHeight: 127 }) - offset);
|
setTableScrollHeight(getTableScroll({ extraHeight: 87 }) - offset);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
9
src/pages/env/modal.tsx
vendored
9
src/pages/env/modal.tsx
vendored
|
@ -85,7 +85,12 @@ const EnvModal = ({
|
||||||
<Input placeholder="请输入环境变量名称" />
|
<Input placeholder="请输入环境变量名称" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{!env && (
|
{!env && (
|
||||||
<Form.Item name="split" label="自动拆分" initialValue="0">
|
<Form.Item
|
||||||
|
name="split"
|
||||||
|
label="自动拆分"
|
||||||
|
initialValue="0"
|
||||||
|
tooltip="多个依赖是否换行分割"
|
||||||
|
>
|
||||||
<Radio.Group>
|
<Radio.Group>
|
||||||
<Radio value="1">是</Radio>
|
<Radio value="1">是</Radio>
|
||||||
<Radio value="0">否</Radio>
|
<Radio value="0">否</Radio>
|
||||||
|
@ -106,7 +111,7 @@ const EnvModal = ({
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="remarks" label="备注">
|
<Form.Item name="remarks" label="备注">
|
||||||
<Input />
|
<Input placeholder="请输入备注" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -188,4 +188,5 @@ export default {
|
||||||
'/log': '任务日志',
|
'/log': '任务日志',
|
||||||
'/setting': '系统设置',
|
'/setting': '系统设置',
|
||||||
},
|
},
|
||||||
|
dependenceTypes: ['nodejs', 'python3', 'linux'],
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user