feat: add dev module

This commit is contained in:
abearxiong 2024-12-06 01:50:50 +08:00
parent 8b136d1a5a
commit a1344b9220
26 changed files with 3567 additions and 145 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ coverage
.env
/logs
cache

View File

@ -1,30 +1,62 @@
{
"name": "dev-app",
"version": "0.0.1",
"version": "0.0.2",
"description": "",
"main": "index.js",
"app": {
"type": "micro-app"
"type": "system-app",
"entry": "dist/app.mjs",
"files": [
"dist"
]
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"watch": "cross-env NODE_ENV=development rollup -c -w --watch src",
"build": "rollup -c",
"dev": "concurrently \"pnpm watch\" \"nodemon --watch dist dist/app.mjs\""
},
"files": [
"dist"
],
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me>",
"license": "MIT",
"type": "module",
"packageManager": "pnpm@9.8.0+sha512.8e4c3550fb500e808dbc30bb0ce4dd1eb614e30b1c55245f211591ec2cdf9c611cabd34e1364b42f564bd54b3945ed0f49d61d1bbf2ec9bd74b866fcdc723276",
"devDependencies": {
"@kevisual/router": "0.0.6-alpha-2",
"@kevisual/types": "^0.0.1",
"@kevisual/use-config": "^1.0.4",
"@types/node": "^22.10.1",
"typescript": "^5.7.2"
},
"dependencies": {
"@types/node": "^22.10.1",
"concurrently": "^9.1.0",
"cross-env": "^7.0.3",
"fast-glob": "^3.3.2",
"nodemon": "^3.1.7",
"@kevisual/query": "0.0.7-alpha.3",
"@kevisual/router": "0.0.6-alpha-2",
"@kevisual/types": "^0.0.2",
"@kevisual/use-config": "^1.0.5",
"autoprefixer": "^10.4.20",
"@vitejs/plugin-react": "^4.3.4",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-replace": "^6.0.1",
"@rollup/plugin-typescript": "^12.1.1",
"esbuild": "^0.24.0",
"nanoid": "^5.0.9",
"sequelize": "^6.37.5",
"sqlite3": "^5.1.7"
}
"sqlite3": "^5.1.7",
"rollup": "^4.28.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-esbuild": "^6.1.1",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/typography": "^0.5.15",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^3.4.16",
"tailwindcss-animate": "^1.0.7",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.17.0",
"vite": "^6.0.3"
},
"packageManager": "pnpm@9.14.4"
}

2795
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

118
rollup.config.mjs Normal file
View File

@ -0,0 +1,118 @@
// @ts-check
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import copy from 'rollup-plugin-copy';
import { dts } from 'rollup-plugin-dts';
import json from '@rollup/plugin-json';
import * as glob from 'fast-glob';
import path from 'path';
import esbuild from 'rollup-plugin-esbuild';
import alias from '@rollup/plugin-alias';
import replace from '@rollup/plugin-replace';
import fs from 'fs';
const pkgs = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'));
const isDev = process.env.NODE_ENV === 'development';
/**
* @type {import('rollup').RollupOptions}
*/
const config = {
input: './src/index.ts',
// input: './src/micro-client.ts',
output: {
dir: './dist',
entryFileNames: 'app.mjs',
chunkFileNames: '[name]-[hash].mjs',
format: 'esm',
},
plugins: [
alias({
// only esbuild needs to be configured
entries: [
{ find: '@', replacement: path.resolve('src') }, // 配置 @ 为 src 目录
{ find: 'http', replacement: 'node:http' },
{ find: 'https', replacement: 'node:https' },
{ find: 'fs', replacement: 'node:fs' },
{ find: 'path', replacement: 'node:path' },
{ find: 'crypto', replacement: 'node:crypto' },
{ find: 'zlib', replacement: 'node:zlib' },
{ find: 'stream', replacement: 'node:stream' },
{ find: 'net', replacement: 'node:net' },
{ find: 'tty', replacement: 'node:tty' },
{ find: 'tls', replacement: 'node:tls' },
{ find: 'buffer', replacement: 'node:buffer' },
{ find: 'timers', replacement: 'node:timers' },
// { find: 'string_decoder', replacement: 'node:string_decoder' },
{ find: 'dns', replacement: 'node:dns' },
{ find: 'domain', replacement: 'node:domain' },
{ find: 'os', replacement: 'node:os' },
{ find: 'events', replacement: 'node:events' },
{ find: 'url', replacement: 'node:url' },
{ find: 'assert', replacement: 'node:assert' },
{ find: 'util', replacement: 'node:util' },
],
}),
replace({
preventAssignment: true, // 避免直接赋值
DEV_SERVER: JSON.stringify(isDev), // 替换 DEV_SERVER
}),
resolve({
preferBuiltins: true, // 强制优先使用内置模块
}),
commonjs(),
esbuild({
target: 'node22', // 目标为 Node.js 14
minify: false, // 启用代码压缩
tsconfig: 'tsconfig.json',
}),
json(),
],
external: [
'sequelize', //
'pg',
'@kevisual/router',
'ioredis',
'socket.io',
'minio',
'pino',
'@msgpack/msgpack',
'esbuild',
],
};
const configs = [
{
input: './src/scripts/init-data.ts',
output: {
dir: './dist',
entryFileNames: 'init-data.mjs',
format: 'esm',
},
plugins: [
alias({
// only esbuild needs to be configured
entries: [
{ find: '@', replacement: path.resolve('src') }, // 配置 @ 为 src 目录
],
}),
resolve(),
commonjs(),
esbuild({
target: 'node22',
minify: false,
tsconfig: 'tsconfig.json',
}),
],
external: [
'sequelize', //
'pg',
'@kevisual/router',
'ioredis',
'socket.io',
'minio',
'pino',
'@msgpack/msgpack',
],
},
];
export default [...configs, config];

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite Demo</title>
</head>
<body>
<h1>Welcome to Vite Demo</h1>
<div id="app"></div>
<script type="module" src="./src/index.ts"></script>
</body>
</html>

View File

@ -0,0 +1 @@
document.querySelector('#app')!.innerHTML = 'Hello Vite!'

View File

@ -1,5 +1,5 @@
import { App } from '@kevisual/router';
import { DevManater } from './modules/manager.ts';
export const app = new App();
app.listen(9998)
export const devManager = new DevManater();

View File

@ -1,11 +1,23 @@
import { App } from '@kevisual/router';
import { app } from './app.ts';
import './route/ssh/list.ts';
import './route/dev/index.ts';
app
.call({
path: 'ssh',
key: 'list',
})
.then((res) => {
console.log(res);
if (DEV_SERVER) {
// app
// .call({
// path: 'ssh',
// key: 'list',
// })
// .then((res) => {
// console.log(res);
// });
app.listen(9998, () => {
console.log(`server is running on http://localhost:9998`);
});
}
export const loadApp = (mainApp: App) => {
mainApp.importApp(app);
};

0
src/lib/build/index.ts Normal file
View File

View File

@ -0,0 +1,41 @@
import { build } from 'esbuild';
import { resolve } from 'path';
import { cacheFilePath } from '../config.ts';
export type Opts = {
inputCode: string;
id: string;
};
/**
* MJS
* @param opts
* @returns
*/
export async function transformToMJS(opts: Opts) {
const { inputCode } = opts;
const outputPath = resolve(cacheFilePath, `${opts.id}.mjs`);
try {
// 1. 使用 esbuild 转换代码
const result = await build({
stdin: {
contents: inputCode,
resolveDir: process.cwd(), // 当前目录为基准路径,用于解析文件路径
loader: 'ts',
},
format: 'esm', // 输出为 ESM 格式
outfile: outputPath, // 输出文件路径
// bundle: true, // 启用打包
platform: 'node', // 指定平台为 node
// external: ['node_modules/*'], // 排除所有 node_modules 下的依赖
});
console.log(`转换完成,文件已保存到: ${outputPath}`);
return { code: 200, outputPath };
} catch (e) {
console.error('transformToMJS error', e);
return {
code: 500,
message: e.message,
};
}
}

View File

@ -0,0 +1 @@
export * from './convert.ts';

3
src/modules/config.ts Normal file
View File

@ -0,0 +1,3 @@
import { useFileStore } from '@kevisual/use-config/file-store';
export const cacheFilePath = useFileStore('cache', { needExists: true });

159
src/modules/manager.ts Normal file
View File

@ -0,0 +1,159 @@
import { spawn } from 'child_process';
import { DevData, DevModel } from '../route/dev/model.ts';
import { fileIsExist } from '@kevisual/use-config';
import { transformToMJS } from './build/convert.ts';
/**
*
* @param dev
*/
export const createCodePath = async (dev: DevModel) => {
const data = dev.data;
let { type, code } = data;
if (type === 'script' && code) {
const res = await transformToMJS({ inputCode: code, id: dev.id });
if (res.code !== 200) {
data.codePath = '';
data.codeStatus = 'error';
await dev.update({ data }, { logging: false, fields: ['data'] });
throw new Error('transformToMJS error:' + res.message);
}
data.codePath = res.outputPath;
data.codeStatus = 'success';
data.status = 'active';
await DevModel.update({ data: { ...data } }, { where: { id: dev.id }, logging: false });
}
};
type Dev = {
id: string;
key: string;
pid: number; // 进程id
title: string;
description: string;
} & DevData;
// 检察app数据
export const checkAppData = async (dev: DevModel) => {
const data = dev.data;
let { type, codePath, cwd } = data;
if (type === 'script') {
let checkFileIsExist = false;
if (codePath) {
checkFileIsExist = fileIsExist(codePath);
}
if (!codePath || !checkFileIsExist) {
await createCodePath(dev);
}
if (!cwd) {
throw new Error('cwd is required');
}
} else if (type === 'shell') {
if (!cwd) {
throw new Error('cwd is required');
}
const { command, path } = data;
if (!command) {
throw new Error('command is required');
}
if (!path) {
throw new Error('path is required');
}
}
};
export const runApp = async (dev: Dev) => {
let { type, command, env, cwd, codePath, path } = dev;
let pid = 0;
if (type === 'script') {
const scriptCommand = command || 'node';
const params = [];
if (scriptCommand === 'vite' || scriptCommand === 'rollup') {
params.push('-c', codePath);
}
// node启动
const childProcess = spawn(command, params, {
env: {
...process.env,
...env,
},
shell: true,
stdio: 'inherit',
cwd,
});
childProcess.stdout?.on('data', (data) => {
console.log(dev.id, 'stdout', data.toString());
});
childProcess.stderr?.on('data', (data) => {
console.log(dev.id, 'stderr', data.toString());
});
console.log('child pid ', childProcess.pid);
pid = childProcess.pid;
} else if (type === 'shell') {
// shell启动
const childProcess = spawn(command, [path], {
env: {
...process.env,
...env,
},
stdio: 'inherit',
shell: true,
cwd,
});
pid = childProcess.pid;
}
dev.pid = pid;
};
/**
* check pid is exist
* @param pid
* @returns
*/
export const checkPid = (pid: number) => {
try {
process.kill(pid, 0);
return true;
} catch (e) {
return false;
}
};
export const stopApp = async (dev: Dev) => {
const { pid } = dev;
if (pid) {
if (checkPid(pid)) {
console.log('stopApp', dev.id, 'pid', pid);
process.kill(pid);
}
}
};
export class DevManater {
devList: Dev[] = [];
async create(dev: DevModel) {
const data = dev.data;
await checkAppData(dev);
let hasDev = this.devList.find((item) => item.id === dev.id || item.key === dev.key);
if (hasDev) {
await stopApp(hasDev);
this.devList = this.devList.filter((item) => item.id !== dev.id && item.key !== dev.key);
}
const _dev = {
id: dev.id,
key: dev.key,
title: dev.title,
description: dev.description,
pid: 0,
...data,
};
await runApp(_dev);
this.devList.push(_dev);
return _dev;
}
async stop(id: string) {
const dev = this.devList.find((item) => item.id === id);
if (dev) {
await stopApp(dev);
this.devList = this.devList.filter((item) => item.id !== id);
}
return dev;
}
async getList() {
return this.devList;
}
}

5
src/modules/md5.ts Normal file
View File

@ -0,0 +1,5 @@
import { createHash } from 'crypto';
export function md5(input: string): string {
return createHash('md5').update(input).digest('hex');
}

7
src/modules/memory.ts Normal file
View File

@ -0,0 +1,7 @@
import { Sequelize } from 'sequelize';
export const memory = new Sequelize({
dialect: 'sqlite',
storage: ':memory:',
logging: false,
});

105
src/route/dev/dev-list.ts Normal file
View File

@ -0,0 +1,105 @@
// 获取文件的
import { app } from '@/app.ts';
import { DevModel } from './model.ts';
import { md5 } from '@/modules/md5.ts';
import { createCodePath } from '@/modules/manager.ts';
app
.route({
path: 'dev-app',
key: 'list',
description: '获取开发配置列表',
})
.define(async (ctx) => {
const list = await DevModel.findAll({
logging: false,
});
ctx.body = list;
})
.addTo(app);
app
.route({
path: 'dev-app',
key: 'update',
description: '更新开发配置',
})
.define(async (ctx) => {
const data = ctx.query.data || {};
const id = data.id;
let dev: DevModel;
let hashChange = false || !!ctx.query.force;
if (id) {
dev = await DevModel.findByPk(id);
if (!dev) {
ctx.throw(404, 'dev not found');
return;
}
const updateData = {
...dev.data,
...data?.data,
};
console.log('updateData', updateData);
if (updateData?.code) {
const codeHash = md5(updateData.code);
if (codeHash !== dev.data.codeHash) {
updateData.codeHash = codeHash;
updateData.codePath = '';
hashChange = true;
}
}
await dev.update(
{
title: dev.title,
description: dev.description,
data: updateData,
},
{
logging: false,
fields: ['title', 'key', 'description', 'data'],
},
);
} else {
const updateData = {
...data?.data,
};
if (updateData?.code) {
const codeHash = md5(updateData.code);
updateData.codeHash = codeHash;
updateData.codePath = '';
hashChange = true;
}
dev = await DevModel.create({
...updateData,
});
}
if (hashChange && dev.data?.status === 'active') {
await createCodePath(dev);
}
ctx.body = dev;
})
.addTo(app);
app
.route({
path: 'dev-app',
key: 'delete',
description: '删除开发配置',
})
.define(async (ctx) => {
const id = ctx.query.id;
if (!id) {
ctx.throw(400, 'id is required');
return;
}
const dev = await DevModel.findByPk(id);
if (!dev) {
ctx.throw(404, 'dev not found');
return;
}
await dev.destroy({
logging: false,
});
ctx.body = 'ok';
})
.addTo(app);

2
src/route/dev/index.ts Normal file
View File

@ -0,0 +1,2 @@
import './dev-list.ts';
import './operate.ts';

View File

@ -1,15 +1,18 @@
import { DataTypes, Model, Op } from 'sequelize';
import { sequelize } from '@/modules/sequelize.ts';
type DevData = {
export type DevData = {
cwd?: string; // 当前工作目录
command: string; // 启动命令
type: 'node' | 'shell' | 'script'; // 启动类型
type: 'shell' | 'script'; // 启动类型
env?: Record<string, string>; // 环境变量
keepalive?: boolean; // 是否保持运行, 让用户知道是不是保持运行的
code?: string; // 代码
status?: 'active' | 'inactive'; // 状态
path?: string; // esbuild 打包路径,用于 node 启动
codeHash?: string; // 代码hash
codePath?: string; // 代码路径, 转成 code 的路径
codeStatus?: 'success' | 'error'; // 转换状态
status: 'active' | 'inactive'; // 状态, 是否生成codePath
path?: string; // esbuild 打包路径,用于 node 启动, vite 配置 esbuild 配置rollup配置路径等
};
export type Dev = Partial<InstanceType<typeof DevModel>>;
@ -19,6 +22,7 @@ export type Dev = Partial<InstanceType<typeof DevModel>>;
export class DevModel extends Model {
declare id: string;
declare title: string;
declare key: string;
declare description: string;
declare data: DevData;
}
@ -34,6 +38,11 @@ DevModel.init(
allowNull: false,
defaultValue: '',
},
key: {
type: DataTypes.TEXT,
allowNull: false,
defaultValue: '',
},
description: {
type: DataTypes.TEXT,
allowNull: true,
@ -48,13 +57,12 @@ DevModel.init(
sequelize,
paranoid: true,
modelName: 'local_dev',
freezeTableName: true, // 禁用表名复数化
},
);
DevModel.sync({
await DevModel.sync({
alter: true,
logging: false,
}).catch((e) => {
console.error(e);
console.error('DevModel sync error', e);
});

115
src/route/dev/operate.ts Normal file
View File

@ -0,0 +1,115 @@
import { app, devManager } from '@/app.ts';
import { DevModel } from './model.ts';
import { transformToMJS } from '@/modules/build/index.ts';
app
.route({
path: 'dev-app',
key: 'createConfig',
description: '创建开发的配置项比如vite的执行配置。',
})
.define(async (ctx) => {
const id = ctx.query.id;
if (!id) {
ctx.throw(400, 'id is required');
return;
}
const dev = await DevModel.findByPk(id);
if (!dev) {
ctx.throw(404, 'dev not found');
return;
}
const data = dev.data;
if (!data) {
ctx.throw(400, 'data is required');
return;
}
const { type, path, code, codePath, command, env } = data;
if (type !== 'script') {
ctx.throw(400, 'type is not script');
return;
}
const res = await transformToMJS({ inputCode: code, id: id });
if (res.code !== 200) {
ctx.throw(res.code, res.message);
return;
}
await dev.update(
{
data: {
...data,
codePath: res.outputPath,
codeStatus: 'success',
},
},
{
logging: false,
fields: ['data'],
},
);
ctx.body = dev;
})
.addTo(app);
app
.route({
path: 'dev-app',
key: 'runningList',
})
.define(async (ctx) => {
const list = await devManager.getList();
ctx.body = list;
})
.addTo(app);
app
.route({
path: 'dev-app',
key: 'run',
})
.define(async (ctx) => {
const id = ctx.query.id;
const key = ctx.query.appKey;
if (!id && !key) {
ctx.throw(400, 'id or appKey is required');
return;
}
let dev: DevModel;
if (id) {
dev = await DevModel.findByPk(id, {
logging: false,
});
}
if (!dev) {
dev = await DevModel.findOne({
where: {
key,
},
logging: false,
});
}
if (!dev) {
ctx.throw(404, 'dev not found');
return;
}
const devInfo = await devManager.create(dev);
ctx.body = devInfo;
})
.addTo(app);
app
.route({
path: 'dev-app',
key: 'stop',
})
.define(async (ctx) => {
const id = ctx.query.id;
if (!id) {
ctx.throw(400, 'id is required');
return;
}
const dev = await devManager.stop(id);
ctx.body = dev;
})
.addTo(app);

View File

@ -9,7 +9,11 @@ const init = async () => {
sshManager.createServer(ssh.remote, ssh.configs);
});
};
try {
if (!DEV_SERVER) {
setTimeout(init, 1000);
}
} catch (e) {}
app
.route({
path: 'ssh',

View File

@ -11,7 +11,6 @@ export type SSH = Partial<InstanceType<typeof SSHModel>>;
export class SSHModel extends Model {
declare id: string;
// declare title: string;
declare description: string;
declare configs: SSHModelData[];
declare remote: string;
@ -23,11 +22,6 @@ SSHModel.init(
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
// title: {
// type: DataTypes.TEXT,
// allowNull: false,
// defaultValue: '',
// },
description: {
type: DataTypes.TEXT,
allowNull: true,
@ -47,11 +41,10 @@ SSHModel.init(
sequelize,
paranoid: true,
modelName: 'local_ssh',
freezeTableName: true, // 禁用表名复数化
},
);
SSHModel.sync({
await SSHModel.sync({
alter: true,
logging: false,
}).catch((e) => {

View File

@ -0,0 +1,9 @@
import { defineConfig } from 'vite';
export default defineConfig({
root: '/Users/xion/kevisual/dev-app/simple/vite-demo',
plugins: [],
define: {
DEV_SERVER: JSON.stringify(process.env.NODE_ENV === 'development'),
},
});

View File

@ -0,0 +1,79 @@
import { Query } from '@kevisual/query/query';
import fs from 'fs';
import path from 'path';
const __dirname = path.dirname(new URL(import.meta.url).pathname);
const query = new Query({
url: 'http://localhost:9998/api/router',
});
const getList = async () => {
const res = await query.post({
path: 'dev-app',
key: 'list',
});
const list = res.data;
console.log(JSON.stringify(list, null, 2));
};
// await getList();
const addOne = async () => {
const res = await query.post({
path: 'dev-app',
key: 'update',
data: {
title: 'vite test',
description: 'test',
// key: 'vite-demo',
data: {
code: fs.readFileSync(path.resolve(__dirname, './configs/vite-demo.ts'), 'utf-8'),
type: 'script',
cwd: '/Users/xion/kevisual/dev-app/simple/vite-demo',
status: 'active',
command: 'vite',
},
},
});
console.log(res);
};
// await addOne();
const updateOne = async () => {
const updateData = {
id: '6e83c9b1-f16d-4a67-ac53-480aaafcf4fb',
data: {
code: fs.readFileSync(path.resolve(__dirname, './configs/vite-demo.ts'), 'utf-8'),
type: 'script',
cwd: '/Users/xion/kevisual/dev-app/simple/vite-demo',
status: 'active',
command: 'vite',
},
};
const res = await query.post({
path: 'dev-app',
key: 'update',
data: updateData,
force: true,
});
console.log(res);
};
// await updateOne();
const runOne = async (id: string) => {
const res = await query.post({
path: 'dev-app',
key: 'run',
id: id,
});
console.log(res);
};
await runOne('6e83c9b1-f16d-4a67-ac53-480aaafcf4fb');
const deleteOne = async (id: string) => {
const res = await query.post({
path: 'dev-app',
key: 'delete',
id,
});
console.log(res);
};

View File

@ -1,70 +1,16 @@
import { app } from '../app.ts';
import '../index.ts';
const sleep = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
import { data } from './init-data.ts';
const main = async () => {
await sleep(3000);
// console.log('add nisar')
app.call({
path: 'ssh',
key: 'update',
payload: {
exec: true,
data: {
remote: 'nisar',
description: 'diana的项目ssl l关联',
configs: [
{
localPort: 3000,
remoteHost: 'localhost',
remotePort: 3000,
description: 'openweb ui',
status: 'active',
},
{
localPort: 5244,
remoteHost: 'localhost',
remotePort: 5244,
description: 'alist',
status: 'inactive',
},
{
localPort: 3003,
remoteHost: 'localhost',
remotePort: 3003,
description: 'onai api',
status: 'inactive',
},
{
localPort: 3002,
remoteHost: 'localhost',
remotePort: 3004,
description: 'codeflow ui',
status: 'inactive',
},
{
localPort: 8000,
remoteHost: 'localhost',
remotePort: 8000,
description: 'parsex python api',
status: 'inactive',
},
{
localPort: 9092,
remoteHost: 'localhost',
remotePort: 9092,
description: 'kafka',
status: 'inactive',
},
{
localPort: 9200,
remoteHost: 'localhost',
remotePort: 9200,
description: 'elasticsearch',
status: 'inactive',
},
],
},
data: data,
},
});
};

74
src/scripts/init-data.ts Normal file
View File

@ -0,0 +1,74 @@
import { SSHModel } from '@/route/ssh/model.ts';
export const data = {
remote: 'nisar',
description: 'diana的项目ssl -l关联',
configs: [
{
localPort: 3000,
remoteHost: 'localhost',
remotePort: 3000,
description: 'openweb ui',
status: 'active',
},
{
localPort: 5244,
remoteHost: 'localhost',
remotePort: 5244,
description: 'alist',
status: 'inactive',
},
{
localPort: 3003,
remoteHost: 'localhost',
remotePort: 3003,
description: 'onai py llm api',
status: 'active',
},
{
localPort: 3004,
remoteHost: 'localhost',
remotePort: 3004,
description: 'onai ui',
status: 'active',
},
{
localPort: 4002,
remoteHost: 'localhost',
remotePort: 4002,
description: 'codeflow api',
status: 'active',
},
{
localPort: 8000,
remoteHost: 'localhost',
remotePort: 8000,
description: 'parsex python api',
status: 'active',
},
{
localPort: 9092,
remoteHost: 'localhost',
remotePort: 9092,
description: 'kafka',
status: 'inactive',
},
{
localPort: 9200,
remoteHost: 'localhost',
remotePort: 9200,
description: 'elasticsearch',
status: 'active',
},
],
};
const main = async () => {
const { remote, description, configs } = data;
let ssh = await SSHModel.findOne({ where: { remote } });
if (!ssh) {
ssh = await SSHModel.create({ description, configs, remote });
}
await ssh.update({ description, configs }, { where: { remote } });
};
main();

View File

@ -10,6 +10,7 @@
"baseUrl": "./",
"typeRoots": [
"node_modules/@types",
"node_modules/@kevisual/types"
],
"declaration": true,
"noEmit": false,