项目:nas-console | 版本:v1.0 | 生成日期:2026-10-05 | 依据:源码分析
nas-console 是一个部署在家庭 NAS 上的服务管理台,对外呈现为「个人主页 + 运维控制台」的双重形态。它把站长散落在 NAS 与 VPS 上的十几个自托管服务(后台管理系统、云盘、商城、文档站、数据库中间件等)收拢到一个入口,解决三个核心痛点:
项目单体仓库内同时包含前端(src/,React + Vite + TypeScript)与后端探针(server/,Node.js 原生 http + SQLite),构建产物 dist/ 由探针进程同端口静态托管,最终以**单容器单端口(5950)**形态部署。整个项目面向的用户只有两类:
/console/* 管理区,使用总览、访问分析、服务管理、设置四个页面,并负责维护探针侧的 config.json;/、项目详情 /projects/:id 与全屏状态页 /status,只读脱敏快照,不接触任何内部信息。| 功能域 | 功能点 | 关键入口/文件 | 面向用户 |
|---|---|---|---|
| 公开门户 | Bento 网格个人主页(名片/实时状态/NAS 主机/精选项目/快捷入口) | src/pages/Home.tsx | 访客 |
| 公开门户 | 项目详情页(封面横幅/故事/亮点/可用率/截图/时间线) | src/pages/ProjectDetail.tsx | 访客 |
| 公开门户 | 全屏状态页(分组清单+状态徽章+45 天可用率条+证书天数) | src/components/StatusPage.tsx | 访客 |
| 健康监控 | 服务端探针 7×24 巡检(http/tcp,默认 30s 间隔,8s 超时) | server/src/probe.ts | 站长 |
| 健康监控 | 健康历史(SQLite:原始记录 90 天 + 按天聚合永久 + 翻转事件) | server/src/store.ts | 站长 |
| 健康监控 | SSE 实时推送(status 单次结果 / batch 30s 全量兜底 / event 翻转) | server/src/sse.ts | 站长 |
| 健康监控 | 浏览器端探测降级(探针不可达时 no-cors fetch + localStorage 累积) | src/api/health.ts、src/api/health-history.ts | 站长 |
| 健康监控 | TLS 证书剩余天数采集与徽章展示(<30 天橙、<15 天红) | server/src/probe.ts、src/components/StatusPage.tsx | 双方 |
| 告警 | Bark / Telegram / Webhook 三渠道推送,防抖+抑制+安静时段 | server/src/alerts.ts | 站长 |
| 访问分析 | Umami 数据面板(单站点或全站聚合,24h/7d/30d/90d 区间) | src/pages/Analytics.tsx、src/api/umami.ts | 站长 |
| 访问分析 | 总览页 Umami 摘要卡(近 24h PV/实时在线/趋势/浏览器分布) | src/pages/Dashboard.tsx | 站长 |
| 主机运维 | 1Panel 集成:CPU/内存/负载指标、容器列表与一键重启 | server/src/1panel.ts、src/components/HostMetricsCard.tsx | 站长 |
| 服务管理 | 服务清单表(搜索/分组/排序/分页)+ 详情抽屉(可用率/延迟/事件/容器) | src/pages/Services.tsx、src/components/ServiceDetailDrawer.tsx | 站长 |
| 服务管理 | 清单服务端化:内置+自定义+隐藏标记全量 upsert 到探针 config.json | src/api/use-probe-sync.ts、server/src/config.ts | 站长 |
| 门户运营 | 项目档案编辑:封面上传(≤5MB)、故事、亮点、时间线、截图集 | src/components/ProfileModal.tsx、server/src/server.ts(upload 端点) | 站长 |
| 鉴权 | 单用户登录(HMAC 签名 Cookie 会话 30 天,失败 5 次锁 10 分钟) | server/src/auth.ts、src/components/LoginPage.tsx | 双方 |
| 公开数据 | 脱敏快照 /api/public/status:仅白名单分组下发 url/tags/error/profile | server/src/server.ts(publicStatus 函数) | 访客 |
| 主题体验 | 浅色=品牌晕染 / 深色=暗色霓虹,跟随系统,⌘K 命令面板 | src/theme/、src/components/Spotlight.tsx | 双方 |
| 层 | 技术 | 版本 | 选型理由(源码/注释可证) |
|---|---|---|---|
| 前端框架 | React | ^19.1.1 | antd v6 原生支持 React 19;vite.config.ts 以 resolve.dedupe 统一 React 实例避免双份 |
| UI 组件库 | antd | ^6.0.0 | React 19 原生兼容;ConfigProvider 统一主色 #4c6ef5 与深浅色算法(src/App.tsx) |
| 路由 | react-router-dom | ^7.14.2 | BrowserRouter + 嵌套路由实现公开/管理区分区与 RequireAuth 登录门 |
| 图表 | recharts(内置封装) | ^3.0.0 | 项目自带 src/charts/ 封装层(Area/Bar/Line/Donut/Sparkline),配色为本地扁平色板,Sparkline 为手写 SVG 零依赖 |
| 图标 | react-icons / @ant-design/icons | ^5.4.0 | ServiceIcon 以 14 个 lu 系列图标映射服务类型 |
| 工具库 | dayjs | ^1.11.11 | 日期格式化与按天聚合分桶,前后端同构使用 |
| 构建 | Vite + TypeScript | ^5.4.0 / 5.5.2 strict | npm run build = tsc --noEmit -p tsconfig.build.json && vite build,类型检查前置于打包 |
| 后端运行时 | Node.js(原生 node:http) | 22(bookworm-slim) | 零 Web 框架:路由/静态托管/JSON 解析/SSE 全部手写(server/src/server.ts),部署面最小、同源免 CORS |
| 存储 | better-sqlite3 | ^11.10.0 | 同步 API 对单实例个人规模「绰绰有余」(server/src/store.ts 注释);WAL 模式;bookworm(glibc) 可命中预编译二进制,免现场编译 |
| 容器 | Docker / docker compose | — | 两个 Dockerfile(全量构建 vs NAS 轻量构建)+ 两份 compose(桥接 vs host 网络)覆盖两种部署路径 |
| 鉴权 | 自研 HMAC 签名 Cookie | — | 「无需额外依赖」(README 0.3.1 节):node:crypto HMAC-SHA256 + HttpOnly Cookie,会话密钥落盘 data/session-secret |
技术栈整体取向非常明确:能不引依赖就不引。前端没有 Redux/Zustand/React Query(用 Context + localStorage + useSyncExternalStore 模块级 store 替代),后端没有 Express/Fastify/Koa(原生 http 手写路由表),存储没有 MySQL/Redis(SQLite 单文件 WAL)。这与其「单 NAS、单用户、个人规模」的定位完全匹配,也让整个系统在 NAS 这种低功耗设备上足够轻。
选型理由值得逐项展开说明。其一,antd v6 而非 Mantine:从 src/theme/feedback.tsx 的注释(「必须挂在 antd <App> 内:把上下文实例绑定给静态桥,等价原 notifications/modals 包」)与 src/components/Field.tsx 的注释(「替代 Mantine 风格输入组件的 label 属性」)可以还原出一段演进史——项目早期使用 Mantine,后来整体迁移到 antd v6,迁移层被刻意保留成很薄的兼容桥,新代码可以直接使用 antd 原生 API。其二,recharts v3 而非 ECharts:图表需求只有面积/柱/折线/环形/迷你走势五种,src/charts/ 以不到千行封装出统一主题(chart-theme.ts 的扁平色板 + 深浅色轴/提示框配色),其中 Sparkline 完全手写 SVG(sparkline-path.ts 实现 Fritsch–Carlson 单调三次插值,避免平滑过冲),零运行时依赖。其三,better-sqlite3 而无 ORM:同步 API 在单进程低并发下反而省去异步开销,建表/迁移直接写在 Store 构造函数里,个人规模完全够用且排障直观(一个 probe.db 文件即全部状态)。
nas-console/
├── index.html # SPA 壳:zh-CN、PWA manifest、防白闪背景样式
├── package.json # 前端工程清单(v0.3.0,scripts: dev/build/preview)
├── vite.config.ts # dev 代理 /api、/portal-covers、/sse → 127.0.0.1:5951
├── tsconfig.json # 前端 TS 配置(strict、react-jsx、bundler 解析)
├── tsconfig.build.json # 构建用(仅 include src,排除测试文件)
├── config.example.json # 探针配置样例:services/alerts/auth 三段
├── auth.admin.json # 本地生成的管理员凭据暂存(含明文,.gitignore 已排除,⚠️ 见 06 篇安全注记)
├── Dockerfile # 全量构建(前端+探针),构建上下文 = 上级 PROJECT 目录
├── Dockerfile.nas # NAS 专用轻量构建(仅探针,dist 由宿主机挂载)
├── compose.yml # 桥接网络部署(端口映射 5950:5950,单文件挂载 config.json)
├── docker-compose.nas.yml # host 网络部署(dist 只读挂载,config 目录挂载避免 rename EBUSY)
├── docs/
│ └── v03-probe-design.md # v0.3 探针设计稿(架构决策/里程碑/边界)
├── scripts/
│ └── make-icons.mjs # PWA 图标生成(纯 Node zlib 手写 PNG 编码,零依赖)
├── public/
│ ├── manifest.webmanifest # PWA 清单(standalone 安装形态)
│ └── icons/ # icon-180/192/512.png
└── src/ # ───────── 前端(React + Vite)─────────
├── main.tsx # 入口:StrictMode + createRoot
├── App.tsx # Provider 编排 + 全部路由表 + RequireAuth 登录门
├── app.css # 深浅色 CSS 变量、Bento 网格、斑马纹等全局样式
├── api/ # 数据访问层(一个外部系统一个文件)
│ ├── auth.ts # 登录会话客户端(checkSession/login/logout)
│ ├── probe.ts # 探针 API 客户端(latest/history/events/SSE/清单同步)
│ ├── health.ts # 浏览器端 no-cors 探测(降级路径)
│ ├── health-history.ts # 健康历史模块级 store(localStorage + useSyncExternalStore)
│ ├── use-health.ts # 双模式健康检查 Hook(probe/browser 自动二选一)
│ ├── use-probe-sync.ts # 60s 周期清单同步 Hook(AppLayout 挂载)
│ ├── umami.ts # Umami API 客户端(浏览器直连,token 缓存 sessionStorage)
│ ├── host.ts # 1Panel 代理 API 客户端(/api/host/*、/api/containers/*)
│ └── public-status.ts # 公开快照客户端(首页/详情/状态页共用)
├── config/
│ ├── services.ts # 内置服务清单(19 项、4 预设分组)+ merge/collect 工具
│ └── identity.ts # 首页名片文案(编译期常量,改后需重新构建)
├── settings/
│ └── ConsoleSettingsContext.tsx # 用户设置 Context(localStorage 持久化)
├── theme/
│ ├── ColorSchemeProvider.tsx # 深浅色解析(auto 跟随系统)+ html data 属性
│ ├── feedback.tsx # antd App 静态桥(message/notification/modal)
│ └── index.ts
├── hooks/
│ ├── useInterval.ts # 声明式 setInterval 封装
│ └── useLocalStorage.ts # localStorage 持久化的 useState
├── layouts/
│ ├── AppLayout.tsx # 管理区布局(Header/Sider/Outlet + ⌘K + 主题切换)
│ └── PageContainer.tsx # 页面容器(标题+副标题+extra 操作区)
├── pages/ # 6 个路由级页面
│ ├── Home.tsx # / 公开首页
│ ├── ProjectDetail.tsx # /projects/:id 公开项目详情
│ ├── Dashboard.tsx # /console 总览
│ ├── Analytics.tsx # /console/analytics 访问分析
│ ├── Services.tsx # /console/services 服务管理
│ └── Settings.tsx # /console/settings 设置
├── components/ # 跨页面组件(登录页、状态页、徽章、抽屉、命令面板等)
│ ├── home/ # 首页 Bento 卡片组(Identity/Status/Nas/Project/QuickLinks/Footer)
│ └── *.tsx # LoginPage / StatusPage / ServiceDetailDrawer / HostMetricsCard /
│ # ServiceManagerCard / ProfileModal / Spotlight / UptimeBars /
│ # HealthBadge / StatTile / ServiceIcon / Field
└── charts/ # 内置图表库(recharts 封装 + 手写 SVG Sparkline)
└── server/ # ───────── 后端探针(Node 原生 http)─────────
├── package.json # nas-console-probe(build/start/dev 三脚本)
├── tsconfig.json # NodeNext ESM、严格族选项全开
├── config.json # 运行配置(gitignore;服务清单权威副本,19 项实测)
├── scripts/
│ └── hash-password.mjs # 生成 auth.passwordHash(salt$HMAC-SHA256)
├── data/ # 运行数据卷:probe.db / session-secret / covers/
└── src/
├── index.ts # 进程入口:环境变量解析、装配、优雅停机(SIGINT/SIGTERM)
├── server.ts # HTTP 服务:路由分发、鉴权门、静态托管、公开快照组装
├── probe.ts # ProbeEngine 巡检引擎(http/tcp 检查器)
├── store.ts # Store:SQLite WAL(checks/daily/events 三表)
├── sse.ts # SseHub:SSE 客户端管理与广播(25s 心跳)
├── alerts.ts # AlertManager:防抖/抑制/安静时段 + 三渠道投递
├── auth.ts # Auth:登录/会话校验/限流 + authGuard 中间件
├── config.ts # 配置读写(tmp+rename 原子写)/清单 upsert/profile 净化
├── 1panel.ts # PanelClient:1Panel v2 API 签名客户端
└── types.ts # 全部共享类型(ServiceConfig/CheckConfig/CheckResult…)本地开发按「先后端、后前端」顺序(摘自 README「开发」节,均为源码可验证的命令):
# 1) 前端依赖
npm install
# 2) 构建并启动探针(dev 约定端口 5951,vite 已配置 /api、/sse 代理)
cd server && npm install && npm run build
PORT=5951 node dist/index.js
cd ..
# 3) 前端开发服务器(探针在线则页面进入探针模式,否则自动降级浏览器巡检)
npm run dev # http://127.0.0.1:5173
# 4) 生产构建(tsc 类型检查 + vite 打包 → dist/)
npm run build首次启动前需要准备探针配置:cp config.example.json config.json(样例含空的 services/alerts/auth 三段)。若需启用登录鉴权,用 cd server && read -s P && node scripts/hash-password.mjs admin "$P" 生成 auth 段(输出 JSON 直接合并进 config.json 后重启),避免密码进入 shell 历史。启动后打开控制台,前端会把「内置+自定义」清单经 PUT /api/services upsert 给探针并落盘 config.json;手工调优探测参数(tcp 端口、关键词、tlsSkipVerify、intervalSec)则直接编辑 config.json 后重启进程。
Docker 部署有两条路径(详见 06-部署运行.md):全量构建用 Dockerfile + compose.yml(构建上下文须为上级 PROJECT 目录);NAS 常规更新用 Dockerfile.nas + docker-compose.nas.yml(本机构建好 dist 上传后 docker compose up -d --build,host 网络直连本机 1Panel)。
上手过程中有三个容易踩的点需要提前知晓。第一,dev 与生产的端口约定不同:本地探针跑在 5951(与 vite 代理对齐),容器内固定 5950(与 frp 链路对齐),混淆会导致页面进入浏览器降级模式而非探针模式。第二,前端身份文案是编译期常量:src/config/identity.ts 头注释明确「⚠️ 以下为占位示例,部署前替换为真实信息」,且「修改=改代码发版」——它不走任何运行时配置。第三,健康检查的模式在页面加载时锁定:use-health.ts 只在挂载时探测一次探针可达性,运行期不自动切换,因此先启动页面再启动探针时,需要刷新页面才能进入探针模式。
从模块协作的角度看,前端九个 src/api/ 文件恰好构成一张「外部系统 → 客户端」的映射表:探针(probe.ts)、登录会话(auth.ts)、Umami(umami.ts)、1Panel 代理(host.ts)、公开快照(public-status.ts),外加三块自有领域逻辑——浏览器降级探测(health.ts)、历史存储(health-history.ts)与两个组合 Hook(use-health.ts、use-probe-sync.ts)。后端十个 server/src/ 文件则按「入口 → 门卫 → 领域」排布:index.ts 装配、server.ts 分发、auth.ts 守门,probe.ts/store.ts/sse.ts/alerts.ts 四件套构成监控领域核,config.ts 与 1panel.ts 分别对内管理配置、对外对接面板。两侧仅通过 HTTP/SSE 一条通道通信,没有共享代码包,契约靠 server/src/types.ts 与前端 src/api/*.ts 里的镜像类型手工对齐(这一取舍及其风险在 04 篇第 6 节讨论)。
不同角色的接手者可以按需取用本套文档:想快速跑起来,读本节加 06-部署运行.md 第 2–3 节即可;想改页面或加功能,按 02-页面设计.md 的组件树定位文件,再查 03-接口设计.md 确认数据从哪来;想理解「为什么这样设计」,01-系统架构.md 第 5 节的五个架构决策与 docs/v03-probe-design.md 原始设计稿对照阅读最有价值;排查数据问题时,04-数据模型.md 的三处持久化与 05-核心流程.md 的五张时序/流程图给出了从浏览器事件到 SQLite 落盘的完整链路。
项目经历了三个可辨识的阶段,理解它有助于读懂代码中的「双模式」设计:
docs/v03-probe-design.md 第 1 节)。这条路径今天仍完整保留为降级模式(src/api/health.ts、src/api/health-history.ts),而非被删除——这是本项目「演进不拆迁」风格的最好例证。docs/v03-probe-design.md 第 2 节的「关键决策」)。设计稿同时圈定了边界(第 9 节「不做的」):不做多实例高可用、不接 Prometheus/InfluxDB、不做分布式探针——这些「不做」同样是架构信息。/status 状态页、TLS 证书监控、1Panel 集成与项目档案上传(README 各节与 server/src/server.ts 对应端点)。项目从「给自己用的监控台」长成了「对外的个人门户 + 对内的运维台」双形态,公开面与鉴权面的字段级隔离(01 篇决策五)正是这一阶段的核心命题。从 git 历史之外的证据也能看到这种演进的痕迹:package.json 描述写的是「服务健康监控 + Umami 访问分析」,而实际功能已远超此描述;Dockerfile 顶部关于 react-ui 的注释对应更早的工具链形态(当前 package.json 已无 react-ui 依赖,该 COPY 步骤疑似历史遗留,见 06 篇);charts/、theme/ 里的若干「替代原 XX 包」注释记录了 Mantine → antd 的整体迁移。接手者阅读代码时遇到「为什么有两套做同样事情的路径」,几乎都可以在「v0.2 保留为降级」或「迁移兼容层」这两个答案中找到解释。
最后说明本篇与其余六篇的分工。本篇回答「这是什么、用什么造的、东西在哪」;01-系统架构.md 回答「整体如何组织、为什么这样组织」;02-页面设计.md 与 03-接口设计.md 分别是前端与接口的详细图纸;04-数据模型.md 盘点所有持久化与运行时数据的形状;05-核心流程.md 把最重要的五条端到端链路拆成时序图与分步说明;06-部署运行.md 覆盖从本地开发到两种容器部署与日常运维的全部操作面。七篇之间通过篇名互相引用,结论一律标注源码依据,无法从源码确认的事项统一以「⚠️ 待确认」标记而非臆测。
本文所有结论均来自对 nas-console 源码的直接阅读;涉及版本号以 package.json(0.3.0)与 README 标注(v0.3/0.3.1)为准。