mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-23 06:46:09 +08:00
添加openapi模块
This commit is contained in:
parent
7739cef7b8
commit
1e58254f4c
|
@ -5,6 +5,7 @@ import config from './config';
|
||||||
import log from './log';
|
import log from './log';
|
||||||
import cron from './cron';
|
import cron from './cron';
|
||||||
import script from './script';
|
import script from './script';
|
||||||
|
import open from './open';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
|
@ -14,6 +15,7 @@ export default () => {
|
||||||
log(app);
|
log(app);
|
||||||
cron(app);
|
cron(app);
|
||||||
script(app);
|
script(app);
|
||||||
|
open(app);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
126
back/api/open.ts
Normal file
126
back/api/open.ts
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
import OpenService from '../services/open';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { celebrate, Joi } from 'celebrate';
|
||||||
|
const route = Router();
|
||||||
|
|
||||||
|
export default (app: Router) => {
|
||||||
|
app.use('/', route);
|
||||||
|
route.get(
|
||||||
|
'/apps',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const data = await openService.list();
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
'/apps',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
scopes: Joi.array().items(Joi.string().required()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const data = await openService.create(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/apps',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
scopes: Joi.array().items(Joi.string()),
|
||||||
|
_id: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const data = await openService.update(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
'/apps',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.string().required()),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const data = await openService.remove(req.body);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/apps/:id/reset-secret',
|
||||||
|
celebrate({
|
||||||
|
params: Joi.object({
|
||||||
|
id: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const data = await openService.resetSecret(req.params.id);
|
||||||
|
return res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/auth/token',
|
||||||
|
celebrate({
|
||||||
|
query: {
|
||||||
|
client_id: Joi.string().required(),
|
||||||
|
client_secret: Joi.string().required(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const logger: Logger = Container.get('logger');
|
||||||
|
try {
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const result = await openService.authToken(req.query as any);
|
||||||
|
return res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('🔥 error: %o', e);
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
|
@ -23,6 +23,8 @@ const configString = 'config sample crontab shareCode diy';
|
||||||
const dbPath = path.join(rootPath, 'db/');
|
const dbPath = path.join(rootPath, 'db/');
|
||||||
const cronDbFile = path.join(rootPath, 'db/crontab.db');
|
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 configFound = dotenv.config({ path: confFile });
|
const configFound = dotenv.config({ path: confFile });
|
||||||
|
|
||||||
if (envFound.error) {
|
if (envFound.error) {
|
||||||
|
@ -57,6 +59,7 @@ export default {
|
||||||
dbPath,
|
dbPath,
|
||||||
cronDbFile,
|
cronDbFile,
|
||||||
envDbFile,
|
envDbFile,
|
||||||
|
appDbFile,
|
||||||
configPath,
|
configPath,
|
||||||
scriptPath,
|
scriptPath,
|
||||||
samplePath,
|
samplePath,
|
||||||
|
@ -67,10 +70,5 @@ export default {
|
||||||
'crontab.list',
|
'crontab.list',
|
||||||
'env.sh',
|
'env.sh',
|
||||||
],
|
],
|
||||||
writePathList: [
|
writePathList: ['/ql/scripts/', '/ql/config/', '/ql/jbot/', '/ql/bak/'],
|
||||||
'/ql/scripts/',
|
|
||||||
'/ql/config/',
|
|
||||||
'/ql/jbot/',
|
|
||||||
'/ql/bak/',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -86,7 +86,7 @@ export function createRandomString(min: number, max: number): string {
|
||||||
'Y',
|
'Y',
|
||||||
'Z',
|
'Z',
|
||||||
];
|
];
|
||||||
const special = ['-', '_', '#'];
|
const special = ['-', '_'];
|
||||||
const config = num.concat(english).concat(ENGLISH).concat(special);
|
const config = num.concat(english).concat(ENGLISH).concat(special);
|
||||||
|
|
||||||
const arr = [];
|
const arr = [];
|
||||||
|
|
31
back/data/open.ts
Normal file
31
back/data/open.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
export class App {
|
||||||
|
name: string;
|
||||||
|
scopes: AppScope[];
|
||||||
|
client_id: string;
|
||||||
|
client_secret: string;
|
||||||
|
tokens?: AppToken[];
|
||||||
|
_id?: string;
|
||||||
|
|
||||||
|
constructor(options: App) {
|
||||||
|
this.name = options.name;
|
||||||
|
this.scopes = options.scopes;
|
||||||
|
this.client_id = options.client_id;
|
||||||
|
this.client_secret = options.client_secret;
|
||||||
|
this._id = options._id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppToken {
|
||||||
|
value: string;
|
||||||
|
type: 'Bearer';
|
||||||
|
expiration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AppScope = 'envs' | 'crons' | 'configs' | 'scripts' | 'logs';
|
||||||
|
|
||||||
|
export enum CrontabStatus {
|
||||||
|
'running',
|
||||||
|
'idle',
|
||||||
|
'disabled',
|
||||||
|
'queued',
|
||||||
|
}
|
|
@ -6,6 +6,9 @@ import config from '../config';
|
||||||
import jwt from 'express-jwt';
|
import jwt from 'express-jwt';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { getToken } from '../config/util';
|
import { getToken } from '../config/util';
|
||||||
|
import Container from 'typedi';
|
||||||
|
import OpenService from '../services/open';
|
||||||
|
import rewrite from 'express-urlrewrite';
|
||||||
|
|
||||||
export default ({ app }: { app: Application }) => {
|
export default ({ app }: { app: Application }) => {
|
||||||
app.enable('trust proxy');
|
app.enable('trust proxy');
|
||||||
|
@ -15,19 +18,42 @@ export default ({ app }: { app: Application }) => {
|
||||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||||
app.use(
|
app.use(
|
||||||
jwt({ secret: config.secret as string, algorithms: ['HS384'] }).unless({
|
jwt({ secret: config.secret as string, algorithms: ['HS384'] }).unless({
|
||||||
path: ['/api/login', '/api/crons/status'],
|
path: ['/api/login', '/api/crons/status', /^\/open\//],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
app.use((req, res, next) => {
|
|
||||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
app.use(async (req, res, next) => {
|
||||||
const headerToken = getToken(req);
|
const headerToken = getToken(req);
|
||||||
|
if (req.path.startsWith('/open/')) {
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
const doc = await openService.findTokenByValue(headerToken);
|
||||||
|
if (doc && doc.tokens.length > 0) {
|
||||||
|
const currentToken = doc.tokens.find((x) => x.value === headerToken);
|
||||||
|
const key =
|
||||||
|
req.path.match(/\/open\/([a-z]+)\/*/) &&
|
||||||
|
req.path.match(/\/open\/([a-z]+)\/*/)[1];
|
||||||
|
if (
|
||||||
|
doc.scopes.includes(key as any) &&
|
||||||
|
currentToken &&
|
||||||
|
currentToken.expiration >= Math.round(Date.now() / 1000)
|
||||||
|
) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
||||||
if (data) {
|
if (data) {
|
||||||
const { token } = JSON.parse(data);
|
const { token } = JSON.parse(data);
|
||||||
if (token && headerToken === token) {
|
if (token && headerToken === token) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!headerToken && req.path && req.path === '/api/login') {
|
if (
|
||||||
|
!headerToken &&
|
||||||
|
req.path &&
|
||||||
|
(req.path === '/api/login' || req.path === '/open/auth/token')
|
||||||
|
) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
const remoteAddress = req.socket.remoteAddress;
|
const remoteAddress = req.socket.remoteAddress;
|
||||||
|
@ -37,10 +63,13 @@ export default ({ app }: { app: Application }) => {
|
||||||
) {
|
) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const err: any = new Error('UnauthorizedError');
|
const err: any = new Error('UnauthorizedError');
|
||||||
err['status'] = 401;
|
err.status = 401;
|
||||||
next(err);
|
next(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.use(rewrite('/open/*', '/api/$1'));
|
||||||
app.use(config.api.prefix, routes());
|
app.use(config.api.prefix, routes());
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
|
171
back/services/open.ts
Normal file
171
back/services/open.ts
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createRandomString } from '../config/util';
|
||||||
|
import config from '../config';
|
||||||
|
import DataStore from 'nedb';
|
||||||
|
import { App } from '../data/open';
|
||||||
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class OpenService {
|
||||||
|
private appDb = new DataStore({ filename: config.appDbFile });
|
||||||
|
constructor(@Inject('logger') private logger: winston.Logger) {
|
||||||
|
this.appDb.loadDatabase((err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDb(): DataStore {
|
||||||
|
return this.appDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findTokenByValue(token: string): Promise<App> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.appDb.find(
|
||||||
|
{ tokens: { $elemMatch: { value: token } } },
|
||||||
|
(err, docs) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
} else {
|
||||||
|
resolve(docs[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(payload: App): Promise<App> {
|
||||||
|
const tab = new App({ ...payload });
|
||||||
|
tab.client_id = createRandomString(12, 12);
|
||||||
|
tab.client_secret = createRandomString(24, 24);
|
||||||
|
const docs = await this.insert([tab]);
|
||||||
|
return { ...docs[0], tokens: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async insert(payloads: App[]): Promise<App[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.appDb.insert(payloads, (err, docs) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
} else {
|
||||||
|
resolve(docs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(payload: App): Promise<App> {
|
||||||
|
const { _id, client_id, client_secret, tokens, ...other } = payload;
|
||||||
|
const doc = await this.get(_id);
|
||||||
|
const tab = new App({ ...doc, ...other });
|
||||||
|
const newDoc = await this.updateDb(tab);
|
||||||
|
return { ...newDoc, tokens: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateDb(payload: App): Promise<App> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.appDb.update(
|
||||||
|
{ _id: payload._id },
|
||||||
|
payload,
|
||||||
|
{ returnUpdatedDocs: true },
|
||||||
|
(err, num, doc, up) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
} else {
|
||||||
|
resolve(doc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async remove(ids: string[]) {
|
||||||
|
return new Promise((resolve: any) => {
|
||||||
|
this.appDb.remove({ _id: { $in: ids } }, { multi: true }, async (err) => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resetSecret(_id: string): Promise<App> {
|
||||||
|
const doc = await this.get(_id);
|
||||||
|
const tab = new App({ ...doc });
|
||||||
|
tab.client_secret = createRandomString(24, 24);
|
||||||
|
tab.tokens = [];
|
||||||
|
const newDoc = await this.updateDb(tab);
|
||||||
|
return newDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(
|
||||||
|
searchText: string = '',
|
||||||
|
sort: any = {},
|
||||||
|
query: any = {},
|
||||||
|
): Promise<App[]> {
|
||||||
|
let condition = { ...query };
|
||||||
|
if (searchText) {
|
||||||
|
const reg = new RegExp(searchText);
|
||||||
|
condition = {
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
value: reg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: reg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
remarks: reg,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const newDocs = await this.find(condition, sort);
|
||||||
|
return newDocs.map((x) => ({ ...x, tokens: [] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async find(query: any, sort: any): Promise<App[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.appDb
|
||||||
|
.find(query)
|
||||||
|
.sort({ ...sort })
|
||||||
|
.exec((err, docs) => {
|
||||||
|
resolve(docs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(_id: string): Promise<App> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.appDb.find({ _id }).exec((err, docs) => {
|
||||||
|
resolve(docs[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async authToken({ client_id, client_secret }): Promise<any> {
|
||||||
|
const token = uuidV4();
|
||||||
|
const expiration = Math.round(Date.now() / 1000) + 2592000; // 2592000 30天
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.appDb.find({ client_id, client_secret }).exec((err, docs) => {
|
||||||
|
if (docs && docs[0]) {
|
||||||
|
this.appDb.update(
|
||||||
|
{ client_id, client_secret },
|
||||||
|
{ $push: { tokens: { value: token, expiration } } },
|
||||||
|
{},
|
||||||
|
(err, num, doc) => {
|
||||||
|
resolve({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
token_type: 'Bearer',
|
||||||
|
expiration,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resolve({ code: 400, message: 'client_id或client_seret有误' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-jwt": "^6.0.0",
|
"express-jwt": "^6.0.0",
|
||||||
|
"express-urlrewrite": "^1.4.0",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
"p-queue": "6.6.2",
|
"p-queue": "6.6.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"typedi": "^0.8.0",
|
"typedi": "^0.8.0",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -69,10 +71,10 @@
|
||||||
"react": "17.x",
|
"react": "17.x",
|
||||||
"react-codemirror2": "^7.2.1",
|
"react-codemirror2": "^7.2.1",
|
||||||
"react-diff-viewer": "^3.1.1",
|
"react-diff-viewer": "^3.1.1",
|
||||||
"react-split-pane": "^0.1.92",
|
|
||||||
"react-dnd": "^14.0.2",
|
"react-dnd": "^14.0.2",
|
||||||
"react-dnd-html5-backend": "^14.0.0",
|
"react-dnd-html5-backend": "^14.0.0",
|
||||||
"react-dom": "17.x",
|
"react-dom": "17.x",
|
||||||
|
"react-split-pane": "^0.1.92",
|
||||||
"ts-node": "^9.0.0",
|
"ts-node": "^9.0.0",
|
||||||
"typescript": "^4.1.2",
|
"typescript": "^4.1.2",
|
||||||
"umi": "^3.3.9",
|
"umi": "^3.3.9",
|
||||||
|
|
|
@ -17,7 +17,6 @@ specifiers:
|
||||||
'@types/react-dom': ^17.0.0
|
'@types/react-dom': ^17.0.0
|
||||||
'@umijs/plugin-antd': ^0.9.1
|
'@umijs/plugin-antd': ^0.9.1
|
||||||
'@umijs/test': ^3.3.9
|
'@umijs/test': ^3.3.9
|
||||||
axios: ^0.21.1
|
|
||||||
body-parser: ^1.19.0
|
body-parser: ^1.19.0
|
||||||
celebrate: ^13.0.3
|
celebrate: ^13.0.3
|
||||||
codemirror: ^5.62.2
|
codemirror: ^5.62.2
|
||||||
|
@ -28,6 +27,7 @@ specifiers:
|
||||||
dotenv: ^8.2.0
|
dotenv: ^8.2.0
|
||||||
express: ^4.17.1
|
express: ^4.17.1
|
||||||
express-jwt: ^6.0.0
|
express-jwt: ^6.0.0
|
||||||
|
express-urlrewrite: ^1.4.0
|
||||||
got: ^11.8.2
|
got: ^11.8.2
|
||||||
jsonwebtoken: ^8.5.1
|
jsonwebtoken: ^8.5.1
|
||||||
lint-staged: ^10.0.7
|
lint-staged: ^10.0.7
|
||||||
|
@ -52,13 +52,13 @@ specifiers:
|
||||||
typescript: ^4.1.2
|
typescript: ^4.1.2
|
||||||
umi: ^3.3.9
|
umi: ^3.3.9
|
||||||
umi-request: ^1.3.5
|
umi-request: ^1.3.5
|
||||||
|
uuid: ^8.3.2
|
||||||
vh-check: ^2.0.5
|
vh-check: ^2.0.5
|
||||||
webpack: ^5.28.0
|
webpack: ^5.28.0
|
||||||
winston: ^3.3.3
|
winston: ^3.3.3
|
||||||
yorkie: ^2.0.0
|
yorkie: ^2.0.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 0.21.1
|
|
||||||
body-parser: 1.19.0
|
body-parser: 1.19.0
|
||||||
celebrate: 13.0.4
|
celebrate: 13.0.4
|
||||||
cors: 2.8.5
|
cors: 2.8.5
|
||||||
|
@ -66,6 +66,7 @@ dependencies:
|
||||||
dotenv: 8.6.0
|
dotenv: 8.6.0
|
||||||
express: 4.17.1
|
express: 4.17.1
|
||||||
express-jwt: 6.0.0
|
express-jwt: 6.0.0
|
||||||
|
express-urlrewrite: 1.4.0
|
||||||
got: 11.8.2
|
got: 11.8.2
|
||||||
jsonwebtoken: 8.5.1
|
jsonwebtoken: 8.5.1
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
|
@ -75,6 +76,7 @@ dependencies:
|
||||||
p-queue: 6.6.2
|
p-queue: 6.6.2
|
||||||
reflect-metadata: 0.1.13
|
reflect-metadata: 0.1.13
|
||||||
typedi: 0.8.0
|
typedi: 0.8.0
|
||||||
|
uuid: 8.3.2
|
||||||
winston: 3.3.3
|
winston: 3.3.3
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
@ -2008,14 +2010,6 @@ packages:
|
||||||
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
|
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/axios/0.21.1:
|
|
||||||
resolution: {integrity: sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==}
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: 1.14.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/babel-core/7.0.0-bridge.0_@babel+core@7.12.10:
|
/babel-core/7.0.0-bridge.0_@babel+core@7.12.10:
|
||||||
resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
|
resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -3122,6 +3116,18 @@ packages:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/debug/4.3.2:
|
||||||
|
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/decamelize/1.2.0:
|
/decamelize/1.2.0:
|
||||||
resolution: {integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=}
|
resolution: {integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -3643,6 +3649,15 @@ packages:
|
||||||
resolution: {integrity: sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=}
|
resolution: {integrity: sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/express-urlrewrite/1.4.0:
|
||||||
|
resolution: {integrity: sha512-PI5h8JuzoweS26vFizwQl6UTF25CAHSggNv0J25Dn/IKZscJHWZzPrI5z2Y2jgOzIaw2qh8l6+/jUcig23Z2SA==}
|
||||||
|
dependencies:
|
||||||
|
debug: 4.3.2
|
||||||
|
path-to-regexp: 1.8.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/express/4.17.1:
|
/express/4.17.1:
|
||||||
resolution: {integrity: sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==}
|
resolution: {integrity: sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
|
@ -3838,16 +3853,6 @@ packages:
|
||||||
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/follow-redirects/1.14.2:
|
|
||||||
resolution: {integrity: sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==}
|
|
||||||
engines: {node: '>=4.0'}
|
|
||||||
peerDependencies:
|
|
||||||
debug: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
debug:
|
|
||||||
optional: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/for-each/0.3.3:
|
/for-each/0.3.3:
|
||||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4687,7 +4692,6 @@ packages:
|
||||||
|
|
||||||
/isarray/0.0.1:
|
/isarray/0.0.1:
|
||||||
resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}
|
resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/isarray/1.0.0:
|
/isarray/1.0.0:
|
||||||
resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=}
|
resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=}
|
||||||
|
@ -6003,7 +6007,6 @@ packages:
|
||||||
|
|
||||||
/ms/2.1.2:
|
/ms/2.1.2:
|
||||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ms/2.1.3:
|
/ms/2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
@ -6518,7 +6521,6 @@ packages:
|
||||||
resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
|
resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
isarray: 0.0.1
|
isarray: 0.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/path-to-regexp/2.4.0:
|
/path-to-regexp/2.4.0:
|
||||||
resolution: {integrity: sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==}
|
resolution: {integrity: sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==}
|
||||||
|
@ -9506,8 +9508,6 @@ packages:
|
||||||
/uuid/8.3.2:
|
/uuid/8.3.2:
|
||||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/v8-compile-cache/2.3.0:
|
/v8-compile-cache/2.3.0:
|
||||||
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
||||||
|
|
80
src/pages/setting/appModal.tsx
Normal file
80
src/pages/setting/appModal.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Modal, message, Input, Form, Select } from 'antd';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
|
||||||
|
const AppModal = ({
|
||||||
|
app,
|
||||||
|
handleCancel,
|
||||||
|
visible,
|
||||||
|
}: {
|
||||||
|
app?: any;
|
||||||
|
visible: boolean;
|
||||||
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleOk = async (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
|
const method = app ? 'put' : 'post';
|
||||||
|
const payload = { ...values };
|
||||||
|
if (app) {
|
||||||
|
payload._id = app._id;
|
||||||
|
}
|
||||||
|
const { code, data } = await request[method](`${config.apiPrefix}apps`, {
|
||||||
|
data: payload,
|
||||||
|
});
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(app ? '更新应用成功' : '添加应用成功');
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
handleCancel(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.resetFields();
|
||||||
|
}, [app, visible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={app ? '编辑应用' : '新建应用'}
|
||||||
|
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="form_app_modal"
|
||||||
|
initialValues={app}
|
||||||
|
>
|
||||||
|
<Form.Item name="name" label="名称">
|
||||||
|
<Input placeholder="请输入应用名称" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="scopes" label="权限" rules={[{ required: true }]}>
|
||||||
|
<Select mode="multiple" allowClear style={{ width: '100%' }}>
|
||||||
|
{config.scopes.map((x) => {
|
||||||
|
return <Select.Option value={x.value}>{x.name}</Select.Option>;
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppModal;
|
|
@ -1,5 +1,18 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button, Input, Form, Radio, Tabs } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Form,
|
||||||
|
Radio,
|
||||||
|
Tabs,
|
||||||
|
Table,
|
||||||
|
Tooltip,
|
||||||
|
Space,
|
||||||
|
Tag,
|
||||||
|
Modal,
|
||||||
|
message,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
|
@ -10,19 +23,87 @@ import {
|
||||||
setFetchMethod,
|
setFetchMethod,
|
||||||
} from 'darkreader';
|
} from 'darkreader';
|
||||||
import { history } from 'umi';
|
import { history } from 'umi';
|
||||||
import { useCtx } from '@/utils/hooks';
|
import AppModal from './appModal';
|
||||||
|
import {
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
const optionsWithDisabled = [
|
const optionsWithDisabled = [
|
||||||
{ label: '亮色', value: 'light' },
|
{ label: '亮色', value: 'light' },
|
||||||
{ label: '暗色', value: 'dark' },
|
{ label: '暗色', value: 'dark' },
|
||||||
{ label: '跟随系统', value: 'auto' },
|
{ label: '跟随系统', value: 'auto' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Password = ({ headerStyle, isPhone }: any) => {
|
const Setting = ({ headerStyle, isPhone }: any) => {
|
||||||
const [value, setValue] = useState('');
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
align: 'center' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Client ID',
|
||||||
|
dataIndex: 'client_id',
|
||||||
|
key: 'client_id',
|
||||||
|
align: 'center' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Client Secret',
|
||||||
|
dataIndex: 'client_secret',
|
||||||
|
key: 'client_secret',
|
||||||
|
align: 'center' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '权限',
|
||||||
|
dataIndex: 'scopes',
|
||||||
|
key: 'scopes',
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (text: string, record: any) => {
|
||||||
|
return record.scopes.map((scope: any) => {
|
||||||
|
return <Tag key={scope}>{(config.scopesMap as any)[scope]}</Tag>;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
align: 'center' as const,
|
||||||
|
render: (text: string, record: any, index: number) => {
|
||||||
|
const isPc = !isPhone;
|
||||||
|
return (
|
||||||
|
<Space size="middle" style={{ paddingLeft: 8 }}>
|
||||||
|
<Tooltip title={isPc ? '编辑' : ''}>
|
||||||
|
<a onClick={() => editApp(record, index)}>
|
||||||
|
<EditOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={isPc ? '重置secret' : ''}>
|
||||||
|
<a onClick={() => resetSecret(record, index)}>
|
||||||
|
<ReloadOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={isPc ? '删除' : ''}>
|
||||||
|
<a onClick={() => deleteApp(record, index)}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const defaultDarken = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
const defaultDarken = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
||||||
const [theme, setTheme] = useState(defaultDarken);
|
const [theme, setTheme] = useState(defaultDarken);
|
||||||
|
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [editedApp, setEditedApp] = useState();
|
||||||
|
const [tabActiveKey, setTabActiveKey] = useState('person');
|
||||||
|
|
||||||
const handleOk = (values: any) => {
|
const handleOk = (values: any) => {
|
||||||
request
|
request
|
||||||
|
@ -46,12 +127,116 @@ const Password = ({ headerStyle, isPhone }: any) => {
|
||||||
localStorage.setItem('qinglong_dark_theme', e.target.value);
|
localStorage.setItem('qinglong_dark_theme', e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const importJob = () => {
|
const getApps = () => {
|
||||||
request.get(`${config.apiPrefix}crons/import`).then((data: any) => {
|
setLoading(true);
|
||||||
console.log(data);
|
request
|
||||||
|
.get(`${config.apiPrefix}apps`)
|
||||||
|
.then((data: any) => {
|
||||||
|
setDataSource(data.data);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addApp = () => {
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const editApp = (record: any, index: number) => {
|
||||||
|
setEditedApp(record);
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteApp = (record: any, index: number) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认删除应用{' '}
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{record.name}
|
||||||
|
</Text>{' '}
|
||||||
|
吗
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.delete(`${config.apiPrefix}apps`, { data: [record._id] })
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
message.success('删除成功');
|
||||||
|
const result = [...dataSource];
|
||||||
|
result.splice(index, 1);
|
||||||
|
setDataSource(result);
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetSecret = (record: any, index: number) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认重置',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
确认重置应用{' '}
|
||||||
|
<Text style={{ wordBreak: 'break-all' }} type="warning">
|
||||||
|
{record.name}
|
||||||
|
</Text>{' '}
|
||||||
|
的Secret吗
|
||||||
|
<br />
|
||||||
|
<Text type="secondary">重置Secret会让当前应用所有token失效</Text>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk() {
|
||||||
|
request
|
||||||
|
.put(`${config.apiPrefix}apps/${record._id}/reset-secret`)
|
||||||
|
.then((data: any) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
message.success('重置成功');
|
||||||
|
handleApp(data.data);
|
||||||
|
} else {
|
||||||
|
message.error(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log('Cancel');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = (app?: any) => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
if (app) {
|
||||||
|
handleApp(app);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApp = (app: any) => {
|
||||||
|
const index = dataSource.findIndex((x) => x._id === app._id);
|
||||||
|
const result = [...dataSource];
|
||||||
|
if (index === -1) {
|
||||||
|
result.push(app);
|
||||||
|
} else {
|
||||||
|
result.splice(index, 1, {
|
||||||
|
...app,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setDataSource(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabChange = (activeKey: string) => {
|
||||||
|
setTabActiveKey(activeKey);
|
||||||
|
if (activeKey === 'app') {
|
||||||
|
getApps();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFetchMethod(window.fetch);
|
setFetchMethod(window.fetch);
|
||||||
if (theme === 'dark') {
|
if (theme === 'dark') {
|
||||||
|
@ -70,8 +255,22 @@ const Password = ({ headerStyle, isPhone }: any) => {
|
||||||
header={{
|
header={{
|
||||||
style: headerStyle,
|
style: headerStyle,
|
||||||
}}
|
}}
|
||||||
|
extra={
|
||||||
|
tabActiveKey === 'app'
|
||||||
|
? [
|
||||||
|
<Button key="2" type="primary" onClick={() => addApp()}>
|
||||||
|
添加应用
|
||||||
|
</Button>,
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Tabs defaultActiveKey="person" size="small" tabPosition="top">
|
<Tabs
|
||||||
|
defaultActiveKey="person"
|
||||||
|
size="small"
|
||||||
|
tabPosition="top"
|
||||||
|
onChange={tabChange}
|
||||||
|
>
|
||||||
<Tabs.TabPane tab="个人设置" key="person">
|
<Tabs.TabPane tab="个人设置" key="person">
|
||||||
<Form onFinish={handleOk} layout="vertical">
|
<Form onFinish={handleOk} layout="vertical">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
@ -97,6 +296,17 @@ const Password = ({ headerStyle, isPhone }: any) => {
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="应用设置" key="app">
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={dataSource}
|
||||||
|
rowKey="_id"
|
||||||
|
size="middle"
|
||||||
|
scroll={{ x: 768 }}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="其他设置" key="theme">
|
<Tabs.TabPane tab="其他设置" key="theme">
|
||||||
<Form layout="vertical">
|
<Form layout="vertical">
|
||||||
<Form.Item label="主题设置" name="theme" initialValue={theme}>
|
<Form.Item label="主题设置" name="theme" initialValue={theme}>
|
||||||
|
@ -111,8 +321,13 @@ const Password = ({ headerStyle, isPhone }: any) => {
|
||||||
</Form>
|
</Form>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
<AppModal
|
||||||
|
visible={isModalVisible}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
app={editedApp}
|
||||||
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Password;
|
export default Setting;
|
||||||
|
|
|
@ -34,4 +34,33 @@ export default {
|
||||||
],
|
],
|
||||||
defaultLanguage: 'en',
|
defaultLanguage: 'en',
|
||||||
},
|
},
|
||||||
|
scopes: [
|
||||||
|
{
|
||||||
|
name: '定时任务',
|
||||||
|
value: 'crons',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '环境变量',
|
||||||
|
value: 'envs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '配置文件',
|
||||||
|
value: 'configs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '脚本管理',
|
||||||
|
value: 'scripts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '任务日志',
|
||||||
|
value: 'logs',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scopesMap: {
|
||||||
|
crons: '定时任务',
|
||||||
|
envs: '环境变量',
|
||||||
|
configs: '配置文件',
|
||||||
|
scripts: '脚本管理',
|
||||||
|
logs: '任务日志',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user