feat: 独立admin的模块

This commit is contained in:
xion 2024-10-26 01:12:37 +08:00
parent 61d7004c8f
commit 947a8507e9
12 changed files with 1050 additions and 80 deletions

View File

@ -61,6 +61,7 @@
"semver": "^7.6.3", "semver": "^7.6.3",
"sequelize": "^6.37.4", "sequelize": "^6.37.4",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"sqlite3": "^5.1.7",
"strip-ansi": "^7.1.0", "strip-ansi": "^7.1.0",
"uuid": "^10.0.0", "uuid": "^10.0.0",
"zod": "^3.23.8" "zod": "^3.23.8"

855
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
import { App } from '@kevisual/router'; import { App } from '@kevisual/router';
import { sequelize } from './modules/sequelize.ts';
export { sequelize };
export const app = new App(); export const app = new App();

View File

@ -1,4 +1,4 @@
import { app } from '../app.ts'; import { app } from './app.ts';
new app.Route('admin', 'getRouteList') new app.Route('admin', 'getRouteList')
.define(async (ctx) => { .define(async (ctx) => {

View File

@ -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 { Route } from '@kevisual/router';
import { RouterCodeModel, RouterCode } from '../../models/code.ts'; import { RouterCodeModel, RouterCode } from '../models/code.ts';
export enum CodeStatus { export enum CodeStatus {
running = 'running', running = 'running',
@ -40,8 +42,8 @@ export const loadOne = async (item: RouterCodeModel) => {
const codeRunRoute = new Route(path, key, { id }); const codeRunRoute = new Route(path, key, { id });
codeRunRoute.run = fn; codeRunRoute.run = fn;
codeRunRoute.middleware = middleware; codeRunRoute.middleware = middleware;
router.removeById(id); // TODO: app.router.removeById(id); // TODO:
router.add(codeRunRoute); app.router.add(codeRunRoute);
return { return {
...item.toJSON(), ...item.toJSON(),
path, path,
@ -90,7 +92,7 @@ export const load = async function () {
const codeRunRoute = new Route(path, key, { id }); const codeRunRoute = new Route(path, key, { id });
codeRunRoute.run = fn; codeRunRoute.run = fn;
codeRunRoute.middleware = middleware; codeRunRoute.middleware = middleware;
router.add(codeRunRoute); app.router.add(codeRunRoute);
return { return {
...item.toJSON(), ...item.toJSON(),
path, path,

View File

@ -1,6 +1,6 @@
import { EventEmitter, once } from 'stream'; import { EventEmitter, once } from 'stream';
import { load, CodeManager, CodeStatus, loadOne } from './load.ts'; import { load, CodeManager, CodeStatus, loadOne } from './load.ts';
import { RouterCodeModel } from '../../models/code.ts'; import { RouterCodeModel, TableIsExist } from '../models/code.ts';
export enum LoadStatus { export enum LoadStatus {
LOADING = 'loading', LOADING = 'loading',
@ -37,7 +37,7 @@ export const stopCode = (id: string) => {
}; };
export const startCode = async (code: RouterCodeModel) => { export const startCode = async (code: RouterCodeModel) => {
const index = manager.list.findIndex((item) => item.id === code.id); 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) { if (index !== -1) {
manager.list[index] = await loadOne(code); manager.list[index] = await loadOne(code);
} else { } else {
@ -65,5 +65,22 @@ const init = async function () {
events.emit('loaded'); events.emit('loaded');
}; };
TableIsExist().then(async (res) => {
if (res) {
init(); 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();

View File

@ -1,9 +1,7 @@
// admin 需要最后运行并在route中进行过滤。 // admin 需要最后运行并在route中进行过滤。
import { Route } from '@kevisual/router'; 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 { manager, updateNewCode, removeCode, stopCode, startCode } from './dashboard/manager.ts';
import { loadOne } from './dashboard/load.ts';
import { RouterCodeModel } from '../models/code.ts';
// get manager status // get manager status
export const managerRouter = new Route('admin', 'getManagerStatus'); export const managerRouter = new Route('admin', 'getManagerStatus');
managerRouter.run = async (ctx) => { managerRouter.run = async (ctx) => {
@ -13,20 +11,21 @@ managerRouter.run = async (ctx) => {
}; };
return ctx; return ctx;
}; };
router.add(managerRouter); managerRouter.addTo(app);
// get manager list // get manager list
export const managerList = new Route('admin', 'getManagerList'); export const managerList = new Route('admin', 'getManagerList');
managerList.run = async (ctx) => { managerList.run = async (ctx) => {
ctx.body = manager.list; 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 = { ctx.body = {
list: manager.list, list: manager.list,
routerList, routerList,
}; };
return ctx; return ctx;
}; };
router.add(managerList); managerList.addTo(app);
// get manager one // get manager one
export const managerOne = new Route('admin', 'getManagerOne'); 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
View 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');
};

View File

@ -1,17 +1,34 @@
import { useConfig } from '@abearxiong/use-config'; import { useConfig } from '@abearxiong/use-config';
import { Sequelize } from 'sequelize'; 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 (!checkFileExistsSync(envisionPath)) {
fs.mkdirSync(envisionPath, { recursive: true });
if (!postgresConfig) { }
console.error('postgres config is required'); let flowPath = config.flowPath || configPath;
process.exit(1); if (!path.isAbsolute(flowPath)) {
flowPath = path.join(process.cwd(), flowPath);
}
if (!flowPath.endsWith('.sqlite')) {
flowPath = path.join(flowPath, 'flow.sqlite');
} }
// connect to db // connect to db
export const sequelize = new Sequelize({ export const sequelize = new Sequelize({
dialect: 'sqlite', dialect: 'sqlite',
storage: 'db.sqlite', storage: flowPath,
// logging: false, // logging: false,
}); });

View File

@ -1,5 +1,5 @@
import { Route } from '@kevisual/router'; import { Route } from '@kevisual/router';
import { router } from '../modules/router.ts'; import { app } from './app.ts';
import { getPackage, installPackage } from '../lib/npm.ts'; import { getPackage, installPackage } from '../lib/npm.ts';
const install = new Route('admin', 'install'); const install = new Route('admin', 'install');
@ -15,7 +15,7 @@ install.validator = {
required: true, required: true,
}, },
}; };
router.add(install); install.addTo(app);
const getNpm = new Route('admin', 'getNpm'); const getNpm = new Route('admin', 'getNpm');
getNpm.run = async (ctx) => { getNpm.run = async (ctx) => {
@ -23,4 +23,4 @@ getNpm.run = async (ctx) => {
ctx.body = data['dependencies']; ctx.body = data['dependencies'];
return ctx; return ctx;
}; };
router.add(getNpm); getNpm.addTo(app);

View File

@ -1,28 +1,30 @@
// admin router manger // admin router manger
import { CustomError, Route } from '@kevisual/router'; 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 { manager, updateNewCode, removeCode, stopCode, startCode } from './dashboard/manager.ts';
import { loadOne } from './dashboard/load.ts'; import { loadOne } from './dashboard/load.ts';
import { RouterCodeModel } from '../models/code.ts'; import { RouterCodeModel } from './models/code.ts';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { convertTsToJs as transform } from '../lib/ts2js.ts'; import { convertTsToJs as transform } from '../lib/ts2js.ts';
export const getRouterList = new Route('admin', 'getRouterList'); export const getRouterList = new Route('admin', 'getRouterList');
getRouterList.run = async (ctx) => { 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')); // ctx.body = router.getList().filter((r) => r.path.startsWith('admin'));
return ctx; return ctx;
}; };
router.add(getRouterList); getRouterList.addTo(app);
// remove router // remove router
export const removeRouter = new Route('admin', 'removeRouter'); export const removeRouter = new Route('admin', 'removeRouter');
removeRouter.run = async (ctx) => { removeRouter.run = async (ctx) => {
const { path, key } = ctx.query; 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 } }); const routerCode = await RouterCodeModel.findOne({ where: { path, key } });
if (routerCode) { if (routerCode) {
const id = routerCode.id; const id = routerCode.id;
@ -42,13 +44,14 @@ removeRouter.validator = {
required: true, required: true,
}, },
}; };
router.add(removeRouter); removeRouter.addTo(app);
// remove router by id // remove router by id
export const removeRouterById = new Route('admin', 'removeRouterById'); export const removeRouterById = new Route('admin', 'removeRouterById');
removeRouterById.run = async (ctx) => { removeRouterById.run = async (ctx) => {
const { id } = ctx.query; const { id } = ctx.query;
router.removeById(id); // router.removeById(id);
// TODO:
app.router.removeById(id);
removeCode(id); removeCode(id);
await RouterCodeModel.destroy({ where: { id } }); await RouterCodeModel.destroy({ where: { id } });
ctx.body = 'success'; ctx.body = 'success';
@ -60,13 +63,13 @@ removeRouterById.validator = {
required: true, required: true,
}, },
}; };
router.add(removeRouterById); removeRouterById.addTo(app);
// stop router by id // stop router by id
export const stopRouterById = new Route('admin', 'stopRouterById'); export const stopRouterById = new Route('admin', 'stopRouterById');
stopRouterById.run = async (ctx) => { stopRouterById.run = async (ctx) => {
const { id } = ctx.query; const { id } = ctx.query;
router.removeById(id); // TODO
app.router.removeById(id);
const routerCode = await RouterCodeModel.findByPk(id); const routerCode = await RouterCodeModel.findByPk(id);
if (routerCode) { if (routerCode) {
routerCode.active = false; routerCode.active = false;
@ -82,7 +85,7 @@ stopRouterById.validator = {
required: true, required: true,
}, },
}; };
router.add(stopRouterById); stopRouterById.addTo(app);
// start router by id // start router by id
export const startRouterById = new Route('admin', 'startRouterById'); export const startRouterById = new Route('admin', 'startRouterById');
@ -104,7 +107,7 @@ startRouterById.validator = {
required: true, required: true,
}, },
}; };
router.add(startRouterById); startRouterById.addTo(app);
// add or update router // add or update router
export const updateRouter = new Route('admin', 'updateRouter'); export const updateRouter = new Route('admin', 'updateRouter');
@ -154,13 +157,15 @@ updateRouter.run = async (ctx) => {
ctx.body = 'success'; ctx.body = 'success';
return ctx; return ctx;
}; };
router.add(updateRouter); updateRouter.addTo(app);
export const getRouterApi = new Route('admin', 'getRouterApi'); 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.description = 'get all router api list, and you can use this api to get router detail by path and key';
getRouterApi.run = async (ctx) => { getRouterApi.run = async (ctx) => {
const { origin = 'http://localhost:4000' } = ctx.query; 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) => { const apiList = routerList.map((item: any) => {
return { return {
path: item.path, path: item.path,
@ -192,4 +197,4 @@ getRouterApi.validator = {
}, },
}; };
router.add(getRouterApi); getRouterApi.addTo(app);

View File

@ -49,7 +49,7 @@ RouterCodeModel.init(
}, },
key: { key: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: true,
}, },
active: { active: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
@ -72,12 +72,12 @@ RouterCodeModel.init(
defaultValue: RouterCodeType.route, defaultValue: RouterCodeType.route,
}, },
middleware: { middleware: {
type: DataTypes.ARRAY(DataTypes.STRING), type: DataTypes.JSON,
defaultValue: [], defaultValue: [],
}, },
next: { next: {
type: DataTypes.STRING, type: DataTypes.JSON,
defaultValue: '', defaultValue: {},
}, },
data: { data: {
type: DataTypes.JSON, type: DataTypes.JSON,
@ -94,11 +94,24 @@ RouterCodeModel.init(
paranoid: true, paranoid: true,
}, },
); );
// RouterCodeModel 检测不存在,不存在则创建
RouterCodeModel.sync({ alter: true, logging: false }) RouterCodeModel.sync({ alter: true, logging: false })
.then((res) => { .then((res) => {
console.log('RouterCodeModel sync', res); console.log('RouterCodeModel sync', res);
}) })
.catch((e) => { .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');
};