diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..0cbfcfe --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,12 @@ +__pycache__ +*.pyc +*.log +__pycache__/ +.venv +.idea +test +dist/* +dist/ +.DS_Store +/node_modules/ +/www/ diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..da63f57 --- /dev/null +++ b/web/README.md @@ -0,0 +1,51 @@ +# Vue 3 + Vite 项目模板 + +这是一个基于 Vue 3 和 Vite 的项目模板,旨在帮助开发者快速搭建和开发现代前端应用。 + +## 技术栈 + +- **Vue 3**: 使用 Vue 3 的组合式 API 和 ` + + diff --git a/web/src/api.js b/web/src/api.js new file mode 100644 index 0000000..cad884b --- /dev/null +++ b/web/src/api.js @@ -0,0 +1,39 @@ +// ===================== API 封装 ===================== +// GM 应用内通过 $gm.request 调用后端 RPC(socket 传输) +const ORG = 'user_TGPJp0KLpNsa5TAo' +const APP = 'ddhv' + +export function call(method, params = {}) { + return new Promise((resolve, reject) => { + const doCall = () => { + $gm.request.post(`/api/call/${ORG}/${APP}/${method}`, { + version: '1.0.0', + transport: 'socket', + params, + }).then(res => { + // 后端统一返回 {code, data, msg} + const body = res && res.data !== undefined ? res.data : res + if (body && body.code === 200) { + resolve(body.data) + } else { + reject(new Error((body && body.msg) || '请求失败')) + } + }).catch(err => { + reject(err) + }) + } + if (window.$gm && $gm.request) { + doCall() + } else { + // 开发环境无 SDK 时降级(便于浏览器直接调试) + reject(new Error('GM SDK 未初始化(请在面板内打开应用)')) + } + }) +} + +// 消息提示(兼容 SDK 缺失环境) +export const toast = { + success(msg) { if (window.$gm && $gm.message) $gm.message.success(msg); else alert('[OK] ' + msg) }, + error(msg) { if (window.$gm && $gm.message) $gm.message.error(msg); else alert('[失败] ' + msg) }, + warning(msg) { if (window.$gm && $gm.message) $gm.message.warning(msg); else alert('[警告] ' + msg) }, +} diff --git a/web/src/assets/icon.png b/web/src/assets/icon.png new file mode 100644 index 0000000..1bc03bc Binary files /dev/null and b/web/src/assets/icon.png differ diff --git a/web/src/icons.js b/web/src/icons.js new file mode 100644 index 0000000..b6b2532 --- /dev/null +++ b/web/src/icons.js @@ -0,0 +1,54 @@ +// ===================== SVG 图标库 ===================== +// Feather 风格线性图标(stroke=currentColor,随文字颜色变化) +// 用法: + +import { h } from 'vue' + +export const paths = { + // 通用 + zap: '', + memory: '', + rocket: '', + settings: '', + shield: '', + fileText: '', + cpu: '', + hardDrive: '', + box: '', + lock: '', + search: '', + refresh: '', + check: '', + x: '', + camera: '', + rotate: '', + globe: '', + gauge: '', + activity: '', + power: '', + external: '', +} + +export const Icon = { + name: 'Icon', + props: { + name: { type: String, required: true }, + size: { type: [Number, String], default: 16 }, + }, + render() { + return h('svg', { + class: 'icon', + width: this.size, + height: this.size, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': 2, + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + innerHTML: paths[this.name] || '', + }) + }, +} + +export default Icon diff --git a/web/src/main.js b/web/src/main.js new file mode 100644 index 0000000..cb54c4b --- /dev/null +++ b/web/src/main.js @@ -0,0 +1,6 @@ +import 'gm-app-sdk'; +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/web/src/params.js b/web/src/params.js new file mode 100644 index 0000000..7fdf013 --- /dev/null +++ b/web/src/params.js @@ -0,0 +1,190 @@ +// ===================== 内核参数知识库 ===================== +// 每个参数:中文名、说明、风险等级(risk: safe/warn/danger)、推荐值、单位/格式提示 +// 用于可视化展示与编辑引导 + +export const PARAM_GROUPS = [ + { + key: 'network', + name: '网络', + icon: 'globe', + params: { + 'net.ipv4.tcp_congestion_control': { + name: 'TCP 拥塞控制算法', + desc: '控制 TCP 在高延迟/丢包下的发送策略。BBR 由 Google 开发,对高带宽长距离链路提升显著', + risk: 'safe', recommend: 'bbr', example: 'cubic / bbr', + }, + 'net.core.default_qdisc': { + name: '默认队列规则', + desc: '与 BBR 配套使用的排队规则,fq(fair queue) 可平滑突发流量、降低延迟', + risk: 'safe', recommend: 'fq', example: 'pfifo_fast / fq', + }, + 'net.ipv4.tcp_fastopen': { + name: 'TCP Fast Open', + desc: '允许在 TCP 握手期间携带数据,减少一次 RTT。3 = 客户端+服务端都启用', + risk: 'warn', recommend: '3', example: '0=关 / 1=客户端 / 2=服务端 / 3=全开', + }, + 'net.ipv4.tcp_slow_start_after_idle': { + name: '空闲后慢启动', + desc: '连接空闲后是否重置拥塞窗口。设为 0 可让长连接保持高吞吐,对 Web 服务友好', + risk: 'safe', recommend: '0', example: '0 / 1', + }, + 'net.ipv4.tcp_mtu_probing': { + name: 'MTU 探测', + desc: '探测路径 MTU,避免分片。1 = 在 DF 标志下探测', + risk: 'safe', recommend: '1', example: '0 / 1', + }, + 'net.core.somaxconn': { + name: '最大连接队列', + desc: 'socket 监听队列上限,高并发服务建议调大(配合应用 backlog 配置)', + risk: 'safe', recommend: '1024~4096', example: '128~65535', + }, + 'net.ipv4.tcp_max_syn_backlog': { + name: 'SYN 半连接队列', + desc: '未完成三次握手的连接队列长度,抗 SYN 洪水、高并发连接有用', + risk: 'safe', recommend: '4096~8192', example: '128~65535', + }, + 'net.ipv4.tcp_syncookies': { + name: 'SYN Cookies', + desc: 'SYN 洪水防护,正常情况下保持 1 开启即可', + risk: 'safe', recommend: '1', example: '0 / 1', + }, + 'net.ipv4.tcp_tw_reuse': { + name: 'TIME_WAIT 复用', + desc: '允许复用 TIME_WAIT 状态的连接。[NAT 环境下可能引发连接异常,需谨慎]', + risk: 'danger', recommend: '1(谨慎)', example: '0 / 1', + }, + 'net.ipv4.ip_local_port_range': { + name: '本地端口范围', + desc: '发起连接可用的本地端口区间,范围越大并发出站连接越多', + risk: 'warn', recommend: '1024 65535', example: '1024 65535', + }, + 'net.core.rmem_max': { + name: '最大接收缓冲区', + desc: '所有协议的最大接收缓冲。过大浪费内存,需与内存匹配', + risk: 'danger', recommend: '16777216(16MB)', example: '整数(字节)', + }, + 'net.core.wmem_max': { + name: '最大发送缓冲区', + desc: '所有协议的最大发送缓冲。过大浪费内存', + risk: 'danger', recommend: '16777216(16MB)', example: '整数(字节)', + }, + 'net.ipv4.tcp_rmem': { + name: 'TCP 接收缓冲范围', + desc: 'TCP 接收缓冲的动态范围(最小值 默认值 最大值),不当设置影响吞吐', + risk: 'danger', recommend: '4096 87380 16777216', example: '三个整数', + }, + 'net.ipv4.tcp_wmem': { + name: 'TCP 发送缓冲范围', + desc: 'TCP 发送缓冲的动态范围,不当设置影响吞吐', + risk: 'danger', recommend: '4096 16384 16777216', example: '三个整数', + }, + }, + }, + { + key: 'memory', + name: '内存', + icon: 'memory', + params: { + 'vm.swappiness': { + name: 'Swappiness 交换倾向', + desc: '内核使用 swap 的积极程度(0-100)。服务器建议 10,数据库 0-1,桌面 60。调优核心参数', + risk: 'danger', recommend: '10(服务器) / 0-1(数据库)', example: '0~100', + }, + 'vm.vfs_cache_pressure': { + name: '缓存回收压力', + desc: '内核回收目录/文件缓存(dentry/inode)的倾向。值越低缓存保留越久,但内存不足时可能 OOM', + risk: 'danger', recommend: '50~100', example: '0~1000', + }, + 'vm.dirty_ratio': { + name: '脏页比例(全局)', + desc: '进程写入时脏页占内存比例上限,超过后强制写回。调高提升写吞吐但崩溃丢数据风险增大', + risk: 'warn', recommend: '20~30', example: '0~100(百分比)', + }, + 'vm.dirty_background_ratio': { + name: '脏页后台写回阈值', + desc: '脏页达到该比例时后台开始写回。建议为 dirty_ratio 的一半左右', + risk: 'warn', recommend: '10~15', example: '0~100(百分比)', + }, + 'vm.overcommit_memory': { + name: '内存过载策略', + desc: '0=启发式 1=总是允许 2=禁止过载。数据库/大内存应用建议评估后再改', + risk: 'danger', recommend: '0(默认)', example: '0 / 1 / 2', + }, + }, + }, + { + key: 'fs', + name: '文件系统', + icon: 'hardDrive', + params: { + 'fs.file-max': { + name: '系统最大文件句柄', + desc: '全系统可打开的文件描述符总数上限。高并发服务需要调大', + risk: 'safe', recommend: '65535~1048576', example: '整数', + }, + 'fs.inotify.max_user_watches': { + name: 'inotify 监听上限', + desc: '单个用户可创建的 inotify 监听数量。文件监听类应用(如 Node 热重载)不够会报错', + risk: 'safe', recommend: '524288', example: '整数', + }, + 'fs.aio-max-nr': { + name: '异步 IO 上限', + desc: '系统级异步 IO 请求数上限,数据库/高 IO 应用需要', + risk: 'safe', recommend: '1048576', example: '整数', + }, + }, + }, + { + key: 'kernel', + name: '内核', + icon: 'settings', + params: { + 'kernel.pid_max': { + name: 'PID 上限', + desc: '系统最大进程数。进程多的服务器(容器/常驻任务)建议调大', + risk: 'safe', recommend: '4194304(64位)', example: '整数', + }, + 'kernel.threads-max': { + name: '线程上限', + desc: '系统最大线程数', + risk: 'safe', recommend: '跟随 pid_max', example: '整数', + }, + 'kernel.core_uses_pid': { + name: 'core 文件带 PID', + desc: 'core dump 文件名附加 PID,方便多进程崩溃定位', + risk: 'safe', recommend: '1', example: '0 / 1', + }, + 'kernel.sysrq': { + name: 'SysRq 魔术键', + desc: '紧急系统操作快捷键开关。服务器建议 1,云主机可能被限制', + risk: 'warn', recommend: '1', example: '0 / 1', + }, + }, + }, +] + +// 拍平所有参数(供搜索) +export const ALL_PARAMS = (() => { + const map = {} + for (const g of PARAM_GROUPS) { + for (const [key, info] of Object.entries(g.params)) { + map[key] = { ...info, group: g.name, groupKey: g.key } + } + } + return map +})() + +export function searchParams(keyword) { + const kw = (keyword || '').trim().toLowerCase() + if (!kw) return Object.entries(ALL_PARAMS) + return Object.entries(ALL_PARAMS).filter(([key, info]) => + key.toLowerCase().includes(kw) || info.name.includes(kw) || info.desc.includes(kw) + ) +} + +// 风险徽章 +export const RISK_META = { + safe: { label: '安全', color: '#34d399' }, + warn: { label: '谨慎', color: '#f59e0b' }, + danger: { label: '危险', color: '#ef4444' }, +} diff --git a/web/src/style.css b/web/src/style.css new file mode 100644 index 0000000..30e4e77 --- /dev/null +++ b/web/src/style.css @@ -0,0 +1,3 @@ +:root { + +} diff --git a/web/vite.config.js b/web/vite.config.js new file mode 100644 index 0000000..7221347 --- /dev/null +++ b/web/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], + base: './', + server: { + host: '0.0.0.0', + allowedHosts: true, + }, + build: { + outDir: 'www' + } +})