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'
+ }
+})