# fn-dir-mapper **Repository Path**: chenshaogui/fn-dir-mapper ## Basic Information - **Project Name**: fn-dir-mapper - **Description**: 函数目录映射可视化工具,自动解析代码仓库中的函数定义与目录结构,生成模块依赖关系图谱。支持多种编程语言,输出可视化图表帮助开发者快速理解项目架构、梳理调用链路、识别循环依赖,适用于代码重构、新成员熟悉项目等场景。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-02 - **Last Updated**: 2026-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # FnDirMapper(飞牛目录映射器) 专为飞牛 fnOS 打造的轻量级目录软链接(Symlink)映射管理工具。跨存储空间 / 应用目录 建软链(如 `/vol1/1000/docker/xxx → /vol2/1000/xxx`)不再需要 SSH 敲 `ln -s`, 浏览器里点几下即可完成,并对历史映射做持久记录与有效性校验。 ## 功能 - 🔍 创建前实时预检:输入路径即给出结论,不靠提交后报错——目标是否存在 / 是否为空目录 / 子项数量与体积、链接位置是空闲、文件、非空目录还是已有链接,均逐项标注 - 📁 可视化目录浏览器:面包屑 + 存储空间快捷入口 + 上一级,可切换「只看目录 / 显示文件」, 显示每个目录的项数与空目录提示、链接指向与失效标记,并可在当前目录**新建文件夹** - ➕ 一键创建软链接:`ln -s 目标 源` 的图形化等价;「一键同名映射到其它存储空间」自动推导源路径; 链接位置的上级目录不存在时可勾选**自动创建缺失的上级目录**(新目录继承上级属主与权限, 避免 root 建完目录后应用写不了);同名冲突保护,真实文件与非空目录永不覆盖 - ✂️ 解除软链接:单条解除、勾选后批量解除;只 `unlink` 链接本身,永不递归删除真实数据 - 📋 映射记录管理:状态徽标(有效 / 目标失效 / 已被真实目录替代 / 目标已变更 / 链接不存在)、 关键字搜索、状态筛选、异常优先排序、单条重新校验、备注编辑 - 🗄️ 存储空间概览:顶栏展示各 `/volN` 可用容量与使用率进度条 - 🧭 「收编」SSH 手工创建的链接:浏览时直接登记进映射记录统一管理与校验 - 📱 手机端适配:≤ 860px 表格转卡片,≤ 620px 重排为拇指友好布局(顶栏存储空间横向滑动、 按钮铺满、弹窗全屏 / 确认框底部抽屉、长路径换行不截断、避开刘海与 Home 条) - 🌙 自适应界面:跟随系统的浅色 / 深色主题 ## 架构(对齐官方 Native 案例) - 后端:零依赖 Node(`server/`),fnOS 下直接监听统一网关 unix socket `app.sock`, 本地开发退回 TCP 5612 - 前端:原生 HTML/JS(`web/`),由同一服务托管,相对路径调 API,网关前缀无关 - 运行时:manifest 声明 `install_dep_apps=nodejs_v22`,不内置 node,fpk 体积 < 1MB - 权限:`config/privilege` 使用 `run-as: root`——跨卷跨属主创建 symlink 必须特权; 安全兜底:统一网关先校验 fnOS 登录态,后端强制「源必须位于存储空间(`/volN`)、目标必须存在、 拒绝 `..`、双向递归防护、只 unlink 链接 / 只 rmdir 空目录」;页脚展示服务运行身份(uid) - 记录文件:`TRIM_PKGETC/links.json`(fnOS 上为 `/var/apps/fn-dir-mapper/etc`,卸载随应用清除) ## 目录结构 ```text fn-dir-mapper/ ├── package/ # fnOS 应用包(fnpack build --directory package) │ ├── manifest │ ├── config/ # privilege / resource │ ├── cmd/ # 生命周期脚本(main 负责 start|stop|status) │ ├── app/ui/ # 桌面入口配置 + 图标 │ └── app/{server,web}# 由 scripts/sync-package.mjs 生成(已 gitignore) ├── server/ # 后端源码(零依赖) ├── web/ # 前端源码(原生 JS,含生成的图标) ├── scripts/ # 图标生成 / sync / fnpack 打包 / 冒烟测试 / 界面静态校验 └── package.json # npm scripts 入口 ``` ## 后端接口 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | `/api/health` | 服务信息(含运行身份 uid / gid / 用户名) | | GET | `/api/volumes` | 存储空间列表与容量(`/volN` + statfs) | | GET | `/api/browse` | 目录浏览(`path`、`onlyDirs`),返回项数 / 链接数 / 断链数统计 | | GET | `/api/stat` | 单个路径详情:是否存在 / 类型 / **是否为空** / 子项统计与体积 | | GET | `/api/preflight` | 创建前预检:`errors` / `warnings` / `infos` / `needForce` / `needMkdir` / `parentPath` / `suggestedSource` | | GET | `/api/links` | 映射记录列表(`check=1` 同时校验) | | GET | `/api/scan` | 扫描目录下已存在的软链接(含未登记的) | | POST | `/api/link` | 创建软链接(`source`、`target`、`note`、`force`、`makeParent`) | | POST | `/api/mkdir` | 新建目录(逐级创建,属主与权限继承最近的已有目录) | | DELETE | `/api/link` | 解除链接(`recordOnly=true` 时仅删记录) | | POST | `/api/link/record` | 收编手工链接 | | POST | `/api/link/note` | 更新备注 | | POST | `/api/check` | 有效性校验(带 `sources` 则只查指定记录) | 预检(`/api/preflight`)与创建(`/api/link`)共用同一套判定函数,界面结论不会与实际行为不一致。 存储空间路径按 `/^\/vol\d*(\/|$)/` 判定:飞牛的存储空间是**根目录下的 `/vol1`、`/vol2` …**, 并不是 `/vol` 的子目录,所以不能按 `/vol/` 前缀判断(否则 `/vol1/…` 会被误拒)。 入口地址不带尾斜杠时(网关给的就是 `/app/{appname}`)服务会 302 补上斜杠:否则页面里的 相对资源 `styles.css` / `app.js` 会被解析到上一级而 404,表现为“无样式 + 点击无响应”; 前端另有 `` 与 `apiBase()` 双重兜底。 ## 界面与移动端适配 断点分两层:`≤ 860px` 把两列输入合并为一列、映射表格转成卡片(勾选框 / 映射 / 状态一行, 操作按钮独占下一行);`≤ 620px` 针对手机:顶栏存储空间改成横向滑动单行、主操作按钮铺满、 目录选择器全屏、确认框改底部抽屉、长路径换行完整展示。另有几条触屏专属规则: - 输入控件 `font-size: 16px`——iOS Safari 对小于 16px 的输入框会在聚焦时放大整个页面; - `@media (hover: none)` 收敛 hover 态——触屏点完样式会“粘”在悬停色上不还原; - `?` 提示用 `data-tip` 气泡而不是 `title`——触屏不会触发 title,否则说明在手机上永远看不到; - 弹窗容器需 `min-height: 0`——grid item 默认 `min-height:auto` 会被内容撑破, 目录选择器底部那一排按钮会被顶出屏幕外(已用 390px 视口实测)。 列宽写在 `table.links .c-*` 类上而不是内联 `style`:内联样式盖不住,窄屏的卡片重排会失效。 ## 已知限制(真机实测) **飞牛文件管理器把软链接显示成「文件」,手机 App 里还点不开。**实测 `/vol1/1000/测试 → /vol2/1000/ssd980/docker/compose/kasm_telegram` 在 PC 端「文件管理」里 类型列显示的是链接名、大小 46 B,恰好等于目标路径字符串的长度;加尾斜杠重建后是 47 B、 表现完全一致。说明它是按 `lstat` 判类型、不做跟随,**不是链接建错了**(尾斜杠方案已被实测证伪)。 - 影响范围:只影响在飞牛文件管理器 / 手机 App 里把它当文件夹浏览; 应用(docker 等)读写穿透软链接由内核完成,不受影响。 - 本应用的「浏览…」入口按 `stat`(跟随)判类型,所以能正常进入映射出来的目录。 - `namei -l` 里 `/vol1`、`/vol2/1000/ssd980` 等级目录显示为 `d---------`,但 PC 端能正常列出 其内容,说明飞牛的实际访问控制不靠这 9 位(走 ACL),它不是“打不开”的原因,别再往这个方向查。 - 想在文件管理器里当真实文件夹用,只有 `mount --bind` 一条路(需 root、重启不保留、 误操作风险与删链接不同类),与本应用「只建/删软链接、不碰挂载点」的安全边界冲突,故未实现。 - 手机 App 里要看这些数据,直接切到目标所在的存储空间浏览即可。 ## 本地开发(Windows) ```powershell npm start # 打开 http://127.0.0.1:5612/app/fn-dir-mapper/(不带尾斜杠会 302 自动补上) # Windows 本机没有 /vol 目录,调试时可放开源路径限制(此时也支持 D:/xxx 盘符绝对路径): # $env:FNM_ALLOW_ANY_SOURCE="1"; npm start npm run check:ui # 界面静态校验(8 项):HTML id / el 控件声明 / 接口路由 / CSS 类名 / 移动端适配规则 npm run icons # 重新生成应用图标(仅在调整图标后需要) ``` ## 冒烟测试 服务端跑起来后执行(76 项用例:创建 / 断链校验 / 收编 / 预检 / 目录是否为空 / 自动建目录 / 安全拒绝)。 测试目录 `D:/fnm-test` 里的 fixture 由脚本自建并自清,可反复执行,不需要人工预置: ```powershell $env:FNM_ALLOW_ANY_SOURCE="1"; npm start # 一个终端 $env:SMOKE_ROOT="D:/fnm-test"; npm run smoke # 另一个终端 ``` 关键安全用例:真实文件与非空目录**永远不会**被覆盖或删除(即使勾选了替换), 只有软链接和空目录可被接管;解除链接只 `unlink` 链接本身; 递归防护双向生效(源在目标内部、目标在源内部都会被拒)。 ## 打包 ```powershell npm run sync # server/ web/ → package/app/(server.js 重命名为 server.cjs) npm run build # 调用官方 fnpack 生成 dist/fn-dir-mapper_.fpk ``` fnpack 二进制默认自动下载;若本地已有可直接复用(避免联网): ```powershell $env:FNPACK="D:\myProject\scrcpy-web-rtc\fnos-app-native\.tools\fnpack.exe"; npm run build ``` 图标由脚本生成(fnpack 硬性要求这些文件存在): ```powershell npm run icons # scripts/gen-icons.mjs:自绘位图 + 自写 PNG 编码,产出下列文件 ``` - `package/ICON.PNG`(64)、`package/ICON_256.PNG`(256) - `package/app/ui/images/icon_{64,128,256}.png`(桌面入口)、`web/images/`(页面内 logo / favicon) > 图标语义:左侧描边文件夹(软链接别名)→ 箭头 → 右侧实心文件夹(真实目录), > 右上角绿点表示映射有效。改图形请改 `scripts/gen-icons.mjs` 里的形状参数与配色, > 不要手改 PNG。 ## 安装测试清单(对齐官方文档) - [ ] 应用中心本地安装 fpk,能正常安装 / 启动 / 停止,状态显示正确 - [ ] 桌面卡片打开 `/app/fn-dir-mapper` 页面正常 - [ ] 创建 `/vol1/… → /vol2/…` 链接,SSH `ls -l` 验证 - [ ] 删除目标目录后点「校验有效性」,应显示「目标失效」 - [ ] 解除链接后目标数据完好 - [ ] 卸载后重新安装,服务正常拉起 - [ ] 应用中心「停用 / 启用」(走 config_callback 重启)生效 - [ ] 预检提示:选空目录作目标时提示“目标为空”;选非空目录作链接位置时被拦下 - [ ] 只填「目标目录」时,目标框下不再出现“路径必须是以 / 开头”(缺失参数不等于非法路径,错误按字段归属) - [ ] 链接位置的上层目录还不存在时,勾选可自动创建,且 `ls -l` 确认新目录属主不是 root - [ ] 目录浏览器里「新建文件夹」可用;报错信息中的路径与实际访问的一致 - [ ] 手机上(≤ 620px):顶栏可横滑、按钮好点、目录选择器底部四个按钮都在屏内、长路径完整可见 - [ ] 手机上批量勾选解除、搜索与状态筛选可正常操作 ## 参考 - https://developer.fnnas.com/docs/examples/native/ - https://developer.fnnas.com/docs/core-concepts/privilege/ - https://developer.fnnas.com/docs/core-concepts/resource/ - https://developer.fnnas.com/docs/core-concepts/framework/