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",
"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

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
import { App } from '@kevisual/router';
import { sequelize } from './modules/sequelize.ts';
export { sequelize };
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')
.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 { 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,

View File

@ -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();

View File

@ -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
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 { 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,
});

View File

@ -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);

View File

@ -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);

View File

@ -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');
};