feat: 独立admin的模块
This commit is contained in:
		| @@ -61,6 +61,7 @@ | ||||
|     "semver": "^7.6.3", | ||||
|     "sequelize": "^6.37.4", | ||||
|     "socket.io": "^4.8.1", | ||||
|     "sqlite3": "^5.1.7", | ||||
|     "strip-ansi": "^7.1.0", | ||||
|     "uuid": "^10.0.0", | ||||
|     "zod": "^3.23.8" | ||||
|   | ||||
							
								
								
									
										855
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										855
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,3 +1,5 @@ | ||||
| import { App } from '@kevisual/router'; | ||||
| import { sequelize } from './modules/sequelize.ts'; | ||||
|  | ||||
| export { sequelize }; | ||||
| export const app = new App(); | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { app } from '../app.ts'; | ||||
| import { app } from './app.ts'; | ||||
|  | ||||
| new app.Route('admin', 'getRouteList') | ||||
|   .define(async (ctx) => { | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| import { router } from '../../modules/router.ts'; | ||||
| // import { router } from '../../modules/router.ts'; | ||||
| import { app } from '../app.ts'; | ||||
|  | ||||
| import { Route } from '@kevisual/router'; | ||||
| import { RouterCodeModel, RouterCode } from '../../models/code.ts'; | ||||
| import { RouterCodeModel, RouterCode } from '../models/code.ts'; | ||||
|  | ||||
| export enum CodeStatus { | ||||
|   running = 'running', | ||||
| @@ -40,8 +42,8 @@ export const loadOne = async (item: RouterCodeModel) => { | ||||
|     const codeRunRoute = new Route(path, key, { id }); | ||||
|     codeRunRoute.run = fn; | ||||
|     codeRunRoute.middleware = middleware; | ||||
|     router.removeById(id); // TODO: | ||||
|     router.add(codeRunRoute); | ||||
|     app.router.removeById(id); // TODO: | ||||
|     app.router.add(codeRunRoute); | ||||
|     return { | ||||
|       ...item.toJSON(), | ||||
|       path, | ||||
| @@ -90,7 +92,7 @@ export const load = async function () { | ||||
|       const codeRunRoute = new Route(path, key, { id }); | ||||
|       codeRunRoute.run = fn; | ||||
|       codeRunRoute.middleware = middleware; | ||||
|       router.add(codeRunRoute); | ||||
|       app.router.add(codeRunRoute); | ||||
|       return { | ||||
|         ...item.toJSON(), | ||||
|         path, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { EventEmitter, once } from 'stream'; | ||||
| import { load, CodeManager, CodeStatus, loadOne } from './load.ts'; | ||||
| import { RouterCodeModel } from '../../models/code.ts'; | ||||
| import { RouterCodeModel, TableIsExist } from '../models/code.ts'; | ||||
|  | ||||
| export enum LoadStatus { | ||||
|   LOADING = 'loading', | ||||
| @@ -37,7 +37,7 @@ export const stopCode = (id: string) => { | ||||
| }; | ||||
| export const startCode = async (code: RouterCodeModel) => { | ||||
|   const index = manager.list.findIndex((item) => item.id === code.id); | ||||
|   console.log('index', index, code.toJSON()) | ||||
|   console.log('index', index, code.toJSON()); | ||||
|   if (index !== -1) { | ||||
|     manager.list[index] = await loadOne(code); | ||||
|   } else { | ||||
| @@ -65,5 +65,22 @@ const init = async function () { | ||||
|  | ||||
|   events.emit('loaded'); | ||||
| }; | ||||
|  | ||||
| init(); | ||||
| TableIsExist().then(async (res) => { | ||||
|   if (res) { | ||||
|     init(); | ||||
|   } else { | ||||
|     console.log('TableIsExist not exist, waiting create'); | ||||
|     // 3s后再次检测 | ||||
|     setTimeout(() => { | ||||
|       TableIsExist().then(async (res) => { | ||||
|         if (res) { | ||||
|           init(); | ||||
|         } else { | ||||
|           console.error('TableIsExist not exist, create error'); | ||||
|           process.exit(1); | ||||
|         } | ||||
|       }); | ||||
|     }, 3000); | ||||
|   } | ||||
| }); | ||||
| // init(); | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| // admin 需要最后运行,并在route中进行过滤。 | ||||
| import { Route } from '@kevisual/router'; | ||||
| import { router } from '../modules/router.ts'; | ||||
| import { app } from './app.ts'; | ||||
| import { manager, updateNewCode, removeCode, stopCode, startCode } from './dashboard/manager.ts'; | ||||
| import { loadOne } from './dashboard/load.ts'; | ||||
| import { RouterCodeModel } from '../models/code.ts'; | ||||
| // get manager status | ||||
| export const managerRouter = new Route('admin', 'getManagerStatus'); | ||||
| managerRouter.run = async (ctx) => { | ||||
| @@ -13,20 +11,21 @@ managerRouter.run = async (ctx) => { | ||||
|   }; | ||||
|   return ctx; | ||||
| }; | ||||
| router.add(managerRouter); | ||||
| managerRouter.addTo(app); | ||||
|  | ||||
| // get manager list | ||||
| export const managerList = new Route('admin', 'getManagerList'); | ||||
| managerList.run = async (ctx) => { | ||||
|   ctx.body = manager.list; | ||||
|   const routerList = router.getList().filter((r) => !r.path.startsWith('admin')); | ||||
|   // TODO: | ||||
|   const routerList = app.router.getList().filter((r) => !r.path.startsWith('admin')); | ||||
|   ctx.body = { | ||||
|     list: manager.list, | ||||
|     routerList, | ||||
|   }; | ||||
|   return ctx; | ||||
| }; | ||||
| router.add(managerList); | ||||
| managerList.addTo(app); | ||||
|  | ||||
| // get manager one | ||||
| export const managerOne = new Route('admin', 'getManagerOne'); | ||||
| @@ -52,4 +51,4 @@ managerOne.validator = { | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| router.add(managerOne); | ||||
| managerOne.addTo(app); | ||||
|   | ||||
							
								
								
									
										119
									
								
								src/admin/models/code.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/admin/models/code.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| import { sequelize } from '../modules/sequelize.ts'; | ||||
| import { DataTypes, Model } from 'sequelize'; | ||||
|  | ||||
| export type RouterCode = { | ||||
|   id: string; | ||||
|   path: string; | ||||
|   key: string; | ||||
|   active: boolean; | ||||
|   project: string; | ||||
|   code: string; | ||||
|   exec: string; | ||||
|   type: RouterCodeType; | ||||
|   middleware: string[]; | ||||
|   next: string; | ||||
|   data: any; | ||||
|   validator: any; | ||||
| }; | ||||
|  | ||||
| export enum RouterCodeType { | ||||
|   route = 'route', | ||||
|   middleware = 'middleware', | ||||
| } | ||||
|  | ||||
| export class RouterCodeModel extends Model { | ||||
|   declare id: string; | ||||
|   declare path: string; | ||||
|   declare key: string; | ||||
|   declare active: boolean; | ||||
|   declare project: string; | ||||
|   declare code: string; | ||||
|   declare exec: string; | ||||
|   declare type: RouterCodeType; | ||||
|   declare middleware: string[]; | ||||
|   declare next: string; // 如果是中间件,不存在 | ||||
|   declare data: any; // 内容 | ||||
|   declare validator: any; | ||||
| } | ||||
| RouterCodeModel.init( | ||||
|   { | ||||
|     id: { | ||||
|       type: DataTypes.UUID, | ||||
|       primaryKey: true, | ||||
|       defaultValue: DataTypes.UUIDV4, | ||||
|       comment: 'code id', | ||||
|     }, | ||||
|     path: { | ||||
|       type: DataTypes.STRING, | ||||
|       allowNull: false, | ||||
|     }, | ||||
|     key: { | ||||
|       type: DataTypes.STRING, | ||||
|       allowNull: true, | ||||
|     }, | ||||
|     active: { | ||||
|       type: DataTypes.BOOLEAN, | ||||
|       defaultValue: false, | ||||
|     }, | ||||
|     project: { | ||||
|       type: DataTypes.STRING, | ||||
|       defaultValue: 'default', | ||||
|     }, | ||||
|     code: { | ||||
|       type: DataTypes.TEXT, | ||||
|       defaultValue: '', | ||||
|     }, | ||||
|     exec: { | ||||
|       type: DataTypes.TEXT, // 对代码进行编译后的代码 | ||||
|       defaultValue: '', | ||||
|     }, | ||||
|     type: { | ||||
|       type: DataTypes.ENUM(RouterCodeType.route, RouterCodeType.middleware), | ||||
|       defaultValue: RouterCodeType.route, | ||||
|     }, | ||||
|     middleware: { | ||||
|       type: DataTypes.JSON, | ||||
|       defaultValue: [], | ||||
|     }, | ||||
|     next: { | ||||
|       type: DataTypes.JSON, | ||||
|       defaultValue: {}, | ||||
|     }, | ||||
|     data: { | ||||
|       type: DataTypes.JSON, | ||||
|       defaultValue: {}, | ||||
|     }, | ||||
|     validator: { | ||||
|       type: DataTypes.JSON, | ||||
|       defaultValue: {}, | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     sequelize, | ||||
|     tableName: 'cf_router_code', // container flow router code | ||||
|     paranoid: true, | ||||
|   }, | ||||
| ); | ||||
| // RouterCodeModel 检测不存在,不存在则创建 | ||||
| RouterCodeModel.sync({ alter: true, logging: false }) | ||||
|   .then((res) => { | ||||
|     console.log('RouterCodeModel sync', res); | ||||
|   }) | ||||
|   .catch((e) => { | ||||
|     console.error('RouterCodeModel sync', e.message); | ||||
|     RouterCodeModel.sync({ force: true }) | ||||
|       .then((res) => { | ||||
|         console.log('RouterCodeModel force sync', res); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         console.error('RouterCodeModel force sync error'); | ||||
|       }); | ||||
|   }); | ||||
|  | ||||
| export const TableIsExist = async () => { | ||||
|   // 判断cf_router_code表是否存在 | ||||
|   const tableIsExist = await sequelize.getQueryInterface().showAllTables({ | ||||
|     logging: false, | ||||
|   }); | ||||
|   return tableIsExist.includes('cf_router_code'); | ||||
| }; | ||||
| @@ -1,17 +1,34 @@ | ||||
| import { useConfig } from '@abearxiong/use-config'; | ||||
| import { Sequelize } from 'sequelize'; | ||||
| import path from 'path'; | ||||
| import os from 'os'; | ||||
| import fs from 'fs'; | ||||
|  | ||||
| const config = useConfig(); | ||||
| const checkFileExistsSync = (filePath: string) => { | ||||
|   try { | ||||
|     fs.accessSync(filePath, fs.constants.F_OK); | ||||
|   } catch (err) { | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| }; | ||||
| const config = useConfig<{ flowPath: string }>(); | ||||
| export const envisionPath = path.join(os.homedir(), '.config', 'envision'); | ||||
| const configPath = path.join(os.homedir(), '.config', 'envision', 'flow.sqlite'); | ||||
|  | ||||
| const postgresConfig = config.postgres; | ||||
|  | ||||
| if (!postgresConfig) { | ||||
|   console.error('postgres config is required'); | ||||
|   process.exit(1); | ||||
| if (!checkFileExistsSync(envisionPath)) { | ||||
|   fs.mkdirSync(envisionPath, { recursive: true }); | ||||
| } | ||||
| let flowPath = config.flowPath || configPath; | ||||
| if (!path.isAbsolute(flowPath)) { | ||||
|   flowPath = path.join(process.cwd(), flowPath); | ||||
| } | ||||
| if (!flowPath.endsWith('.sqlite')) { | ||||
|   flowPath = path.join(flowPath, 'flow.sqlite'); | ||||
| } | ||||
| // connect to db | ||||
| export const sequelize = new Sequelize({ | ||||
|   dialect: 'sqlite', | ||||
|   storage: 'db.sqlite', | ||||
|   storage: flowPath, | ||||
|   // logging: false, | ||||
| }); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { Route } from '@kevisual/router'; | ||||
| import { router } from '../modules/router.ts'; | ||||
| import { app } from './app.ts'; | ||||
| import { getPackage, installPackage } from '../lib/npm.ts'; | ||||
|  | ||||
| const install = new Route('admin', 'install'); | ||||
| @@ -15,7 +15,7 @@ install.validator = { | ||||
|     required: true, | ||||
|   }, | ||||
| }; | ||||
| router.add(install); | ||||
| install.addTo(app); | ||||
|  | ||||
| const getNpm = new Route('admin', 'getNpm'); | ||||
| getNpm.run = async (ctx) => { | ||||
| @@ -23,4 +23,4 @@ getNpm.run = async (ctx) => { | ||||
|   ctx.body = data['dependencies']; | ||||
|   return ctx; | ||||
| }; | ||||
| router.add(getNpm); | ||||
| getNpm.addTo(app); | ||||
|   | ||||
| @@ -1,28 +1,30 @@ | ||||
| // admin router manger | ||||
|  | ||||
| import { CustomError, Route } from '@kevisual/router'; | ||||
| import { router } from '../modules/router.ts'; | ||||
| import { app } from './app.ts'; | ||||
| import { manager, updateNewCode, removeCode, stopCode, startCode } from './dashboard/manager.ts'; | ||||
| import { loadOne } from './dashboard/load.ts'; | ||||
| import { RouterCodeModel } from '../models/code.ts'; | ||||
| import { RouterCodeModel } from './models/code.ts'; | ||||
| import { nanoid } from 'nanoid'; | ||||
| import { convertTsToJs as transform } from '../lib/ts2js.ts'; | ||||
|  | ||||
| export const getRouterList = new Route('admin', 'getRouterList'); | ||||
|  | ||||
| getRouterList.run = async (ctx) => { | ||||
|   ctx.body = router.getList().filter((r) => !r.path.startsWith('admin')); | ||||
|   // TODO: | ||||
|   ctx.body = app.router.getList().filter((r) => !r.path.startsWith('admin')); | ||||
|   // ctx.body = router.getList().filter((r) => r.path.startsWith('admin')); | ||||
|   return ctx; | ||||
| }; | ||||
|  | ||||
| router.add(getRouterList); | ||||
| getRouterList.addTo(app); | ||||
|  | ||||
| // remove router | ||||
| export const removeRouter = new Route('admin', 'removeRouter'); | ||||
| removeRouter.run = async (ctx) => { | ||||
|   const { path, key } = ctx.query; | ||||
|   router.remove({ path, key }); | ||||
|   // TODO: | ||||
|   // router.remove({ path, key }); | ||||
|   app.router.remove({ path, key }); | ||||
|   const routerCode = await RouterCodeModel.findOne({ where: { path, key } }); | ||||
|   if (routerCode) { | ||||
|     const id = routerCode.id; | ||||
| @@ -42,13 +44,14 @@ removeRouter.validator = { | ||||
|     required: true, | ||||
|   }, | ||||
| }; | ||||
| router.add(removeRouter); | ||||
|  | ||||
| removeRouter.addTo(app); | ||||
| // remove router by id | ||||
| export const removeRouterById = new Route('admin', 'removeRouterById'); | ||||
| removeRouterById.run = async (ctx) => { | ||||
|   const { id } = ctx.query; | ||||
|   router.removeById(id); | ||||
|   // router.removeById(id); | ||||
|   // TODO: | ||||
|   app.router.removeById(id); | ||||
|   removeCode(id); | ||||
|   await RouterCodeModel.destroy({ where: { id } }); | ||||
|   ctx.body = 'success'; | ||||
| @@ -60,13 +63,13 @@ removeRouterById.validator = { | ||||
|     required: true, | ||||
|   }, | ||||
| }; | ||||
| router.add(removeRouterById); | ||||
|  | ||||
| removeRouterById.addTo(app); | ||||
| // stop router by id | ||||
| export const stopRouterById = new Route('admin', 'stopRouterById'); | ||||
| stopRouterById.run = async (ctx) => { | ||||
|   const { id } = ctx.query; | ||||
|   router.removeById(id); | ||||
|   // TODO | ||||
|   app.router.removeById(id); | ||||
|   const routerCode = await RouterCodeModel.findByPk(id); | ||||
|   if (routerCode) { | ||||
|     routerCode.active = false; | ||||
| @@ -82,7 +85,7 @@ stopRouterById.validator = { | ||||
|     required: true, | ||||
|   }, | ||||
| }; | ||||
| router.add(stopRouterById); | ||||
| stopRouterById.addTo(app); | ||||
|  | ||||
| // start router by id | ||||
| export const startRouterById = new Route('admin', 'startRouterById'); | ||||
| @@ -104,7 +107,7 @@ startRouterById.validator = { | ||||
|     required: true, | ||||
|   }, | ||||
| }; | ||||
| router.add(startRouterById); | ||||
| startRouterById.addTo(app); | ||||
|  | ||||
| // add or update router | ||||
| export const updateRouter = new Route('admin', 'updateRouter'); | ||||
| @@ -154,13 +157,15 @@ updateRouter.run = async (ctx) => { | ||||
|   ctx.body = 'success'; | ||||
|   return ctx; | ||||
| }; | ||||
| router.add(updateRouter); | ||||
| updateRouter.addTo(app); | ||||
|  | ||||
| export const getRouterApi = new Route('admin', 'getRouterApi'); | ||||
| getRouterApi.description = 'get all router api list, and you can use this api to get router detail by path and key'; | ||||
| getRouterApi.run = async (ctx) => { | ||||
|   const { origin = 'http://localhost:4000' } = ctx.query; | ||||
|   const routerList = router.getList(); | ||||
|   // const routerList = router.getList(); | ||||
|   // TODO: | ||||
|   const routerList = app.router.getList(); | ||||
|   const apiList = routerList.map((item: any) => { | ||||
|     return { | ||||
|       path: item.path, | ||||
| @@ -192,4 +197,4 @@ getRouterApi.validator = { | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| router.add(getRouterApi); | ||||
| getRouterApi.addTo(app); | ||||
|   | ||||
| @@ -49,7 +49,7 @@ RouterCodeModel.init( | ||||
|     }, | ||||
|     key: { | ||||
|       type: DataTypes.STRING, | ||||
|       allowNull: false, | ||||
|       allowNull: true, | ||||
|     }, | ||||
|     active: { | ||||
|       type: DataTypes.BOOLEAN, | ||||
| @@ -72,12 +72,12 @@ RouterCodeModel.init( | ||||
|       defaultValue: RouterCodeType.route, | ||||
|     }, | ||||
|     middleware: { | ||||
|       type: DataTypes.ARRAY(DataTypes.STRING), | ||||
|       type: DataTypes.JSON, | ||||
|       defaultValue: [], | ||||
|     }, | ||||
|     next: { | ||||
|       type: DataTypes.STRING, | ||||
|       defaultValue: '', | ||||
|       type: DataTypes.JSON, | ||||
|       defaultValue: {}, | ||||
|     }, | ||||
|     data: { | ||||
|       type: DataTypes.JSON, | ||||
| @@ -94,11 +94,24 @@ RouterCodeModel.init( | ||||
|     paranoid: true, | ||||
|   }, | ||||
| ); | ||||
| // RouterCodeModel 检测不存在,不存在则创建 | ||||
| RouterCodeModel.sync({ alter: true, logging: false }) | ||||
|   .then((res) => { | ||||
|     console.log('RouterCodeModel sync', res); | ||||
|   }) | ||||
|   .catch((e) => { | ||||
|     console.error('RouterCodeModel sync', e); | ||||
|     console.error('RouterCodeModel sync', e.message); | ||||
|     RouterCodeModel.sync({ force: true }) | ||||
|       .then((res) => { | ||||
|         console.log('RouterCodeModel force sync', res); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         console.error('RouterCodeModel force sync error'); | ||||
|       }); | ||||
| // RouterCodeModel.sync({force: true}); | ||||
|   }); | ||||
|  | ||||
| export const TableIsExist = async () => { | ||||
|   // 判断cf_router_code表是否存在 | ||||
|   const tableIsExist = await sequelize.getQueryInterface().showAllTables(); | ||||
|   return tableIsExist.includes('cf_router_code'); | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user