mirror of
				https://github.com/whyour/qinglong.git
				synced 2025-11-01 01:16:07 +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
	 hanhh
						hanhh