291189fb77
fix(sanitizer): 折叠嵌套 if 修复 clippy::collapsible_if
...
865871f 引入的 checkbox type 白名单逻辑用了嵌套 if,触发 rust 1.96
的 clippy::collapsible_if,导致 make clippy 直接失败。按 clippy 建议
合并为单个 if 链,行为不变。
2026-07-02 16:36:05 +08:00
59359b18ce
refactor(hooks): 新增 use_paginated 并迁移 posts/trash 分页加载
...
按 Dioxus 0.7 custom hooks 范式,把后台分页列表页反复出现的
use_effect + spawn + (loading/items/total/error) 三件套收口到
use_paginated 组合式 hook。统一非 wasm cfg 处理(SSR 不发请求、
loading 立即置 false),调用方只关心 fetch 闭包与页码依赖。
迁移范围(按降级预案,只做最规整的两处):
- pages/admin/posts.rs:顺手补 error signal(原先吞掉 Err),向
trash 看齐,消除一致性债。
- pages/admin/trash.rs:直接替换,error 已有。
保留手写的形态(强行套 hook 反而更复杂):
- comments.rs:依赖 (page, status) 双 signal,status 是额外维度,
套不进 (page, per_page) 模型;改 hook 会为单一调用点变复杂。
- system.rs 三个 tab:load_once + use_future 轮询形态,与 hook 的
单次加载模型不契合。
- dashboard.rs:多源聚合(3 个并发 spawn 分别 set 不同 signal)。
list_posts / list_deleted_posts 是 server function,两端都生成
(wasm 端 client stub、server 端真实实现),故去掉原 cfg import。
2026-07-02 16:30:47 +08:00
759bbab7ee
refactor(utils): comment_storage 从 hooks/ 迁至 utils/
...
该文件全是普通函数(save_author / load_pending_comments 等),没有任何
use_ 开头的 Hook,放在 hooks/ 下名不副实。内部只剩结构体与 localStorage
读写(时间/HTML 工具已在上一 commit 拆出),迁到 utils/ 后文件名与内容
自洽,hooks/ 目录回归只放真正的渲染期 Hook。
机械替换 4 个组件文件的 use 路径(hooks::comment_storage → utils::comment_storage)。
2026-07-02 16:18:52 +08:00
dcdd72a8c2
refactor(utils): 拆分 comment_storage 的时间/HTML 工具函数
...
消除 server 端对 hooks 模块的跨层引用:
- escape_html(+ 两份重复实现:comment_storage 用 '、helpers 用 ')
统一到 utils/html.rs,单引号采用 HTML5 标准 '。
- now_millis / relative_label_from_millis / format_relative_time_iso 及其
单元测试迁到 utils/time.rs,与既有 sleep_ms 同构(wasm32/非 wasm 双版本)。
调用方迁移:
- api/comments/helpers.rs:删掉本地 escape_html,format_relative_time 改引
utils::time;其 escape_html 测试随之删除(utils/html.rs 已覆盖)。
- api/comments/create.rs:两处 helpers::escape_html 改 utils::html。
- api/markdown.rs:escape_heading_text 改 utils::html。
- components/comments/pending_item.rs:format_relative_time_iso 改从
utils::time 引入;render_pending_content 留在 comment_storage(语义紧贴
待审核评论)。
- hooks/comment_storage.rs:is_expired 的 now_millis 改引 utils::time。
comment_storage.rs 现在只剩结构体与 localStorage 读写,文件名即将自洽。
2026-07-02 16:15:23 +08:00
f00c2bf963
refactor(hooks): 新增 use_event_listener 通用事件监听 hook
...
把 theme.rs 与 footer.rs 两处手写的 "use_hook 持有 Closure+target、
use_effect 注册 add_event_listener、use_drop 移除" 样板收口到一个
通用 hook,按 Dioxus 0.7 custom hooks 范式封装。
hook 设计:
- target 通过 acquire 闭包在 use_effect 首次运行时获取(此时 DOM 一定
可用),调用方无需再自己包 use_effect。
- 泛型 AsRef<EventTarget> 统一 Window / Element / MediaQueryList。
- 非 wasm32 整体编译为 noop,调用方用 #[cfg] 隔离 web_sys 调用即可。
footer 的 "注册后首次同步" 业务逻辑保留在 hook 之外(语义不属于
事件监听)。theme 的 handler 改为重新 match_media 读取 matches,
功能等价(低频事件,开销可忽略)。
2026-07-02 16:02:51 +08:00
5a00ef31f3
docs(agents): update agents for JavaScript
2026-07-02 15:45:42 +08:00
0772951867
fix(editor): 修复升级后空项 Enter 不退出列表(根因:畸形文档)
...
bug 表现:- [ ] 123 升级成任务列表后,空项按 Enter 不退出,而是继续新建空项。
根因有两处,都在 appendTransaction 构造新节点时:
1. stripPrefix 作用域错误:原代码 cut 作用在 listItem.content(把段落节点
当原子),cut(N) 会切进段落的标签边界,产生畸形文档——段落文本前缀残留
(如' 未完成'多出空格)、nodeSize 与实际不符。修正为作用在段落内部的
content 上(用 para.type.create 重建段落),并用 Fragment.replaceChild
替换 listItem 的第一个子节点。
2. 光标位置少算一层:+1 进 taskList 内容,+offset 到命中 taskItem,+1 进
taskItem 内容(段落节点位置),还需 +1 进段落内容(文本),原代码漏了
这最后一个 +1,导致光标落在文本中间而非末尾,splitListItem 在错误位置
分裂(把'123'拆成'12'+'3')。
畸形文档 + 光标错位 → splitListItem 在错误位置分裂,产生的'空'taskItem
实际不空,二次 Enter 走分裂路径而非退出路径。
新增回归测试:段落文本无残留 + 完整 Enter 链路(升级→Enter 空项→Enter 退出)。
2026-07-02 15:27:37 +08:00
53b53d3169
fix(editor): appendTransaction 加重入防护 + Enter 行为测试
...
用户反馈:升级成 taskList 后,空项按 Enter 未退出列表(与斜杠命令创建的
行为不一致)。
单测用 splitListItem 命令验证:升级后的 taskList,有内容项 Enter 分裂出空
taskItem,空项 Enter 正确退出列表(doc 末尾产生 paragraph)。行为与斜杠
命令创建的一致。说明 appendTransaction 产生的节点结构合法。
追加防护:给 appendTransaction 返回的 transaction 打 pluginKey 标记,
重入时跳过,避免 Enter 等操作的文档变化被本插件二次干扰。
happy-dom 无法可靠模拟真实 keydown 链路(派发 KeyboardEvent 时光标映射
不准),Enter 的 bug 需真实浏览器复现。
2026-07-02 14:59:53 +08:00
873ccb1a80
fix(editor): task list label 内 checkbox 居中
...
label 默认是 inline 元素,内部 checkbox(inline-block)受父级行高影响会产生
基线间距,导致 checkbox 在 label 盒子内偏上,即便 li 用了 align-items: center
也只对齐了 label 盒子而非 checkbox 本身。
给 label 加 display: flex + align-items: center + line-height: 0,消除行高
撑出的基线空间,让 checkbox 严格居于 label 盒子中心。
2026-07-02 14:42:52 +08:00
4e96243407
style(editor): 任务列表 checkbox 与文本垂直中线对齐
...
与前台 input.css 同步,编辑器内 task list 同样改为 align-items: center。
- li: flex-start → center,实现真正的中线对齐
- label: 去掉 margin-top: 0.3em(center 已处理)
- 新增 li > div > p { margin: 0 }:task list 项内段落清掉默认 .6em 0
上下 margin,否则 margin 撑高 div 导致 checkbox 与文本错位、项间距过大
2026-07-02 14:38:59 +08:00
17006f8d0c
style(post): 任务列表 checkbox 与文本改为垂直中线对齐
...
flex-start + 固定 margin 是近似居中,在不同字体/行高下会偏。改用
align-items: center 实现真正的中线对齐(checkbox 中线与文本中线重合),
去掉不再需要的 0.35rem 顶部偏移。
单行任务项场景下 center 更合适;多行时 checkbox 会垂直居中于整个文本块。
2026-07-02 14:32:43 +08:00
250fe653c1
fix(editor): 修复任务列表升级后光标被甩到下一行
...
appendTransaction 用 replaceWith 整段替换 bulletList→taskList,ProseMirror 的
选区映射无法把原 listItem 内的光标正确映射到新 taskItem 内,默认落到替换
区域之后(下一行)。
显式用 TextSelection.near 把光标设到命中的 taskItem 段落文本末尾:
按命中项在 newItems 中的索引 + 段落文本长度算出目标 pos。
新增光标位置断言(selection.from === 3,即 doc>taskList>taskItem>paragraph 内)。
2026-07-02 14:25:57 +08:00
62bd281684
fix(admin/posts): 重建结果消息改为独立行,修复悬浮溢出表格的错位
...
RebuildCacheBar 的「已重建 X 篇文章」结果消息原用 absolute top-full mt-2
悬挂在按钮行下方,但 mt-2(8px) + text-sm(20px) = 28px 大于外层 space-y-6
的 24px 间距,消息底边向下溢出 4px 侵入表格容器上沿;横向 left-0 又使消息
落在右上角按钮组正下方、覆盖状态/日期/操作列,语义位置错位。
将 rebuilding / rebuild_result 状态上提到 PostsPage:
- 结果消息在 header 与表格之间独立成行,进入文档流,吃 space-y-6 正常间距,
既不撑高 header 触发 items-center 重排(曾用 absolute 规避的旧问题),
也不脱离流溢进表格(absolute 引入的新问题)。
- RebuildCacheBar 改为接收父级 Signal props,只渲染按钮行本身。
2026-07-02 14:19:06 +08:00
4a27236859
fix(editor): TaskInputRule 改用 appendTransaction(逐字符输入才生效)
...
input rule 方案对逐字符输入无效:BulletList 在用户打 - (第2字符)时就抢先
触发,把行变成 bulletList>listItem,之后 [ ] 在 listItem 内无法被任何
input rule 升级。提高 priority 也无济于事——BulletList 在更短文本上抢先。
改用 appendTransaction 监听文档变化:BulletList 触发后,用户在 listItem 里
打出 [ ] / [x] 前缀时,把该 bulletList 整体替换成 taskList,对应 listItem
转成带 checked 的 taskItem 并删除已识别的文本前缀。
- 测试模拟逐字符时序:先 - (applyInputRules 触发 BulletList),再 [ ]
(appendTransaction 升级),5 个用例覆盖未勾选/已勾选/大写/不误判/星号
- * + - 三种 marker 的 bulletList 都会升级(BulletList 对三者创建同种节点)
2026-07-02 14:16:34 +08:00
1f8147f323
chore(make): dev 改用 make css 一次性构建 Tailwind
2026-07-02 14:07:57 +08:00
4a76397af8
feat(editor): 支持 - [ ] 手动输入创建任务列表
...
Tiptap 官方 TaskItem 的 input rule 只匹配 [ ] 不含 marker,且 StarterKit
的 BulletList input rule /^\s*([-+*])\s$/ 在用户打 - 时就抢先触发,把这行
变成无序列表;后续的 [ ] 沦为字面文本,getMarkdown 序列化时转义成 \[ \],
前台无法渲染成 checkbox。
- 新增 TaskInputRule 扩展,priority 1000 抢在 BulletList(默认 100)之前,
正则 /^(\s*-\s)\[( |x|X)\]\s$/ 匹配 - [ ] / - [x],
wrappingInputRule 包裹成 taskList > taskItem(findWrapping 自动补齐两层)
- 仅识别减号 marker(* + 仍走 BulletList),Enter 续行复用 TaskItem 内置
splitListItem,不重复实现
- 新增 task-input-rule 测试(真实 Editor + happy-dom):覆盖未勾选/已勾选/
大写 X/不误判普通列表/星号 marker
2026-07-02 13:56:11 +08:00
865871f45c
fix(markdown): 修复任务列表 checkbox 在前台被 sanitizer 剥离
...
编辑器端已挂 TaskList/TaskItem,但前台渲染走 pulldown-cmark,其输出的
<input type="checkbox"> 不在 sanitizer 白名单内,被 remove_and_keep_content
剥离,导致任务列表在前台只剩裸 <li> 文本。
- sanitizer: 白名单放开 input,但强制 type="checkbox" 属性值校验
- attrs_to_remove 增加 input.type 值校验,非 checkbox 一律删除属性
- element_handler 末尾兜底:缺 type 或非 checkbox 的 input 整标签移除
(void 元素无内容,remove() 不丢正文,封堵 type=image/text 等 XSS 滥用)
- 仅 clean_html 放开,clean_comment_html 保持不放开(评论无需任务列表)
- input.css 补 .md-content 任务列表样式(pulldown-cmark 裸 li>input 结构,
用 :has() 识别,不支持 :has() 的浏览器降级为普通列表仍可读)
- 新增 sanitizer 5 个测试(白名单/XSS 边界)+ markdown 1 个端到端测试
2026-07-02 13:22:37 +08:00
a10003c90d
feat(theme): 切换按钮点击立即换图标并播放进入动画
...
将 theme.set 从 450ms 延迟改为立即执行——图标在点击瞬间切换,不再
等待圆形 VT 动画结束。原作者担心重渲染打断 VT 实属多余:VT 伪元素
快照在 __startThemeTransition 内已同步拍好,后续真实 DOM 变化不影响。
- 移除 click_gen 防抖机制与 spawn_local 延迟回调(不再需要)。
- SVG 加 key 属性,theme 变化时 Dioxus 重新挂载 SVG,触发 CSS 进入动画。
- input.css 新增 @keyframes theme-icon-enter(缩放 0.5→1 + 旋转 -60°→0°
+ 淡入,0.3s ease-out),仅在 .theme-toggle svg 上生效。
- prefers-reduced-motion 下禁用该动画,遵循项目既有约定。
2026-07-02 12:00:00 +08:00
9a4e6c3e6e
docs(agents): 校正构建/测试/迁移说明,对齐当前代码库
...
- 移除不存在的 ./migrate.sh:迁移实际由 src/db/migrate.rs 在启动期自动运行,
目标库由 db/pool.rs::ensure_database_exists 自举创建;补充新增迁移需在
MIGRATIONS 数组登记(编译期校验磁盘与数组一致)
- npm → pnpm:4 个 libs 均使用 pnpm-lock.yaml,Makefile 全程 pnpm
- make test / make build 覆盖 4 个 libs(补充 codemirror-editor)
- 补充 restore-webp 兜底步骤的成因(dx 0.7.9 重编码 .webp)
- 补全 make clippy(-D warnings)、make fix、make clean 真实行为
- 模块树补齐 context/router/ssr_cache/sysinfo_sampler 及 *_bridge
- 4 个前端子项目合并为对比表,保留 wasm-bindgen 接线等关键细节
- 保留 Workflow 提交规范
2026-07-02 11:40:47 +08:00
eacc79ed83
feat(theme): 新增跟随系统主题模式与三态循环切换
...
Theme 枚举从 {Light, Dark} 扩展为 {Light, Dark, System},新增
ResolvedTheme 表示实际生效明暗。
- System 模式移除 localStorage 持久化,首屏防闪烁脚本因此自动回退到
prefers-color-scheme 分支,首屏零闪烁。
- use_theme_provider 提供 Theme + ResolvedTheme 双 context;resolved 是
theme 与 system_dark 的派生 memo。WASM 端注册 matchMedia change 监听,
系统偏好变化时实时同步 .dark class 与下游(CodeMirror 等)。
- ThemeToggle 由二态 toggle 改为三态循环(Light→Dark→System→Light),
图标按当前模式切换(太阳/月亮/显示器);仅当实际明暗翻转时才触发
圆形展开动画,避免 System 与同向 Light/Dark 间无意义动画。
- system.rs CodeMirror 传参改读 resolved,使 System 模式下系统偏好变化
时编辑器主题自动跟随。
2026-07-02 11:35:28 +08:00
9a218db478
refactor(assets): 编辑器背景图缩小至 w-24 并贴近右下角
...
原图无宽度约束,按原始尺寸渲染占满编辑器;现固定 w-24(与评论区
一致)并用 bottom-2 right-2 定位到右下角。一并提交新增的线条小狗
素材图与图标资源。
2026-07-02 11:22:34 +08:00
48a21564c4
refactor(assets): 背景图固定 opacity-10,移除动态透明度逻辑
...
动态判断不生效,统一固定为 10% 透明度,移除聚焦/有内容的条件类。
2026-07-02 11:17:41 +08:00
5fd883d7a1
feat(comments): 评论区有内容时自动调淡背景图透明度
...
与 tiptap 编辑器保持一致:无内容时 60%,有内容或聚焦时淡出到
10%。content_md 由 oninput 实时维护,输入即触发重渲染。
2026-07-02 11:14:58 +08:00
fe22d6dad8
feat(editor): tiptap 编辑器有内容时自动调淡背景图透明度
...
无内容时背景图保持 60% 透明度;当编辑器内有内容(非纯空白)时,
背景图淡出到 10%,与聚焦时行为一致。content signal 由 onUpdate
回调实时维护,输入即触发重渲染,无需额外状态。
2026-07-02 11:11:04 +08:00
48107d8e18
refactor(assets): 评论区与后台编辑器背景图换用 xiantiaoxiaogou_input_bg
2026-07-02 10:58:52 +08:00
79425ca678
feat(assets): 添加线条小狗透明背景图
...
用 rembg (silueta 模型) 对 xiantiaoxiaogou_input.webp 去除粉色背景,
输出无损 WebP (RGBA, 736x587)。alpha 通道经像素抽样验证:
背景全透明, 小狗主体白色填充完整, 边缘平滑无锯齿/光晕/粉色残留。
2026-07-02 10:52:09 +08:00
fba88639a9
fix(build): dx build 后用源文件还原 webp 产物
...
dx build 0.7.9 会把 public/ 下的 .webp 重编码成 VP8L 无损静图:
动画帧被完全丢弃(动图变静图),静图因 VP8L 无损反而膨胀 7-8 倍。
这与 Dioxus 文档承诺的「public/ 原样拷贝」不符,疑似 0.7.9 的 bug。
新增 restore-webp target:dx build 完成后遍历所有产物目录
(release/debug),用源 public/ 的同名 .webp 覆盖回去。
仅在 build / build-linux 末尾调用,build-freebsd 不受影响
(用 cargo 直出,部署直接用源 public/)。
上游修复后可移除该 target 及两处调用。
2026-07-01 18:22:45 +08:00
9cf0e605d4
fix(admin): 修复服务器/数据库状态自动刷新失效 Bug
...
use_future 旧实现在同步闭包体内捕获 status/loading/error signal,
导致这些 signal 每次 .set() 后都触发 use_future 重建新 loop,
旧 loop 同时继续运行,造成并发 loop 指数增长(请求爆炸),
实际效果等同于自动刷新完全失效或行为异常。
修复:采用 Dioxus 0.7 官方推荐模式——
- use_future 闭包直接返回 async move {},不在同步层读任何 signal
- loop 内每次迭代读 refresh_interval/refresh_ms 的实时值
- 手动模式(interval = 0)改为 200ms yield + continue,
使切换到自动模式时最多延迟 200ms 生效,避免忙等
- sleep 结束后二次检查 interval,防止 sleep 期间用户切回手动时
仍然发起一次无意义请求
同时修复 DbStatusTab 和 ServerStatusTab 两处。
2026-07-01 17:09:39 +08:00
404f85b732
refactor(web): 用 document::Link 注入 favicon,移除手写 index.html
...
Dioxus 0.7 提供 document::Link 组件,可在 Rust 代码里声明 <link rel="icon">
注入 <head>,与已有的 document::Stylesheet / document::Title 同机制。
之前的方案手写 index.html(复制 dx CLI 默认 shell + 加 favicon 链接),
脆弱:嵌入了 toast 基础设施 JS/CSS,升级 dioxus-cli 后需手动同步。
改用 document::Link 后,favicon 由 SSR 运行时注入,无需维护静态模板。
2026-07-01 17:00:58 +08:00
5ecaf86879
feat(web): 设置网站 favicon
...
Dioxus.toml 的 [web.app] 无 favicon 配置项,dx CLI 默认 shell 也不注入
<link rel="icon">。新增 index.html 自定义 shell(复制自 dx 默认模板,含
toast 基础设施),加上 favicon 链接。<title> 留空由 [web.app] title 注入,
避免重复。favicon.ico 放 public/ 根目录随静态资源分发。
2026-07-01 17:00:58 +08:00
afa6bf892e
feat(register): 输入框支持回车键提交表单
...
注册页 4 个输入框(用户名/邮箱/密码/确认密码)原先 onkeydown 为 None,
无法用回车提交。改为监听 Enter 触发 on_submit,与登录页行为一致。
2026-07-01 17:00:58 +08:00
78f3ff4b2f
fix(admin): 登出按钮补 cursor-pointer
...
登出按钮缺少 cursor-pointer,鼠标悬停时仍显示文本光标而非手型,与
其他可点击元素不一致。
2026-07-01 17:00:58 +08:00
3faff98dc8
fix(layout): 后台导航栏与正文宽度对齐
...
Header 的导航宽度写死 max-w-3xl(前台阅读宽度),后台直接复用导致
导航栏(768px)比正文(max-w-5xl=1024px)窄 256px,左边缘错位。
给 Header 加 max_width prop(默认 max-w-3xl 保持前台行为不变),后台
两处调用点传 max-w-5xl,与 admin_layout 的 main 同宽对齐。
2026-07-01 17:00:58 +08:00
7de97d8222
fix(write): 编辑模式回显空白由 effect 竞态导致
...
编辑文章时编辑器空白。根因:edit_post 由 None→Some 时,回填 effect
(写 content signal)与初始化 effect(读 content signal)在同一 tick
触发,无保证先后顺序。初始化 effect 经常先跑,读到 content()= 跳过
setMarkdown,且 editor_content_set=true 永久阻止重试。
修复:初始化 effect 直接从 edit_post(真值源)取 content_md 回填,不再
依赖 content signal——后者是派生值,与回填 effect 存在竞态。两个 effect
都已在 edit_post 上订阅,回填时读取它没有竞态。
2026-07-01 17:00:58 +08:00
a4331bd813
feat(build): add FreeBSD x86_64 cross-compile support
...
方案 A(clang + lld + 本地 sysroot)。rustc 为 freebsd target 链接的系统库
(libc/libexecinfo/libgcc_s/libthr)及 sysinfo 依赖的 freebsd 专有库
(libkvm/libmemstat/libprocstat/libdevstat)需 FreeBSD 运行时,无法纯靠
clang+lld 无 sysroot 完成;故从 FreeBSD 15.1 base.txz 解出 crt 对象 + 系统库
到 .freebsd-sysroot/(gitignored,machine-local)。
- .cargo/config.toml: freebsd target 配置作为注释模板(sysroot 路径机器相关,
不硬编码进仓库);linker 经 rustflags 的 -C linker=clang 设置,使 Makefile
能用 CARGO_TARGET_*_RUSTFLAGS 环境变量整体注入。
- Makefile: 新增 freebsd-sysroot(幂等下载+解压 base.txz)与 build-freebsd
(release server 二进制交叉编译)target。server 用 cargo 直出而非 dx CLI,
因 dx 对该 target 的支持未经验证。
- .gitignore: 忽略 .freebsd-sysroot/。
- AGENTS.md: 补充构建命令说明。
实测 release 二进制 16MB,stripped ELF for FreeBSD 15.1,动态依赖均为
FreeBSD base 自带库(libc.so.7 libthr.so.3 libkvm.so.7 等)。
2026-07-01 13:40:00 +08:00
096345d2fe
chore: add docker target to Makefile
2026-07-01 10:28:26 +08:00
23b0256e47
Merge branch 'refactor/unify-tabs-into-filtertabs'
...
CI / check (push) Failing after 5m4s
CI / build (push) Has been skipped
统一全局 tab 视觉:删除冗余 Tabs 组件,/admin/system 改用既有 FilterTabs。
- refactor(admin): system.rs tab 栏从 Tabs 切到 FilterTabs(Props 相同,仅改组件名)
- refactor(ui): 删除无调用方的 Tabs 组件(消除 DRY 违反)
结果:/admin/system 与 /admin/comments 的 tab 栏动画/样式完全一致
(平滑滑动指示条 + 选中文字 text-paper-primary)。
2026-06-30 18:19:37 +08:00
90734446b5
refactor(ui): remove redundant Tabs component
...
Tabs 现已无调用方(/admin/system 已改用 FilterTabs),删除以消除 DRY 违反。
全局只保留 FilterTabs 一个 tab 组件,动画/样式统一。
2026-06-30 18:12:03 +08:00
26d37f7271
refactor(admin): use FilterTabs in /admin/system
...
system.rs 的 tab 栏从 Tabs 改用既有 FilterTabs,视觉与评论页完全一致
(平滑滑动指示条 + 选中文字 text-paper-primary)。Props 形状相同,仅改组件名;
SystemTab::as_str/from_str 桥接与枚举 match 穷举逻辑不变。
2026-06-30 18:05:05 +08:00
5426458f4a
fix(docs): resolve rustdoc broken links and dead_code warnings
...
CI / check (push) Failing after 5m18s
CI / build (push) Has been skipped
doc 构建报 8 个 broken_intra_doc_links + dx 构建报 7 个 dead_code 警告。
两者根源相同:被引用的常量/类型/模块要么是 server-only(消费方都在
#[cfg(feature="server")] 块里),要么是跨 cfg 引用(host doc 构建看不到
wasm32-only 类型)。
dead_code:给 sql_console 的 MAX_ROWS/ABSOLUTELY_FORBIDDEN/GuardResult 与
backup 的 BACKUP_DIR/FILENAME_RE/BACKUP_SIGNATURE 加 #[cfg(feature="server")]
gate——这些都是安全护栏常量,消费方全在 server fn 体里,WASM 构建剥掉 fn 体后
常量即成死代码。sysinfo_sampler 的 WASM 桩 read_snapshot 加 allow(dead_code)
(与 codemirror_bridge 既有处理一致)。
rustdoc 链接:跨模块引用改全路径(crate::xxx),跨 cfg 引用(EditorHandle 在
wasm32 mod 内、host doc 构建不可见)转义为纯文本。
验证:cargo doc(0 warning)+ cargo check(default)+ wasm32 check 全部干净。
2026-06-30 17:42:52 +08:00
95fb8d8698
Merge branch 'feature/admin-database-system'
...
CI / check (push) Failing after 5m21s
CI / build (push) Has been skipped
抽取 /admin/system 页面级导航 Tab 公共组件并迁移调用方:
- feat(database): SystemTab::as_str/from_str 字符串桥接 + 单测
- feat(ui): Tabs 组件(与 FilterTabs 并列,页面导航语义,无动画)
- refactor(admin): System 组件改用 Tabs,移除内联 tab 与临时 allow
2026-06-30 14:56:20 +08:00
bf91d18c4f
refactor(admin): use shared Tabs component in /admin/system
...
移除 System 组件内联的 tab 按钮,改调公共 Tabs 组件。
active_tab signal 经 SystemTab::as_str/from_str 与 Tabs 的 String API 桥接,
match 穷举面板选择逻辑不变。视觉/行为零变化,只消除重复。
同时移除 Task 1/2 加的临时 #[allow(dead_code)](as_str/from_str/Tabs 现已有调用方)。
2026-06-30 14:29:05 +08:00
ff7d9e1e46
feat(ui): add Tabs component for page-level navigation
...
与 FilterTabs(列表筛选 + 滑动动画)并列,职责区分:
- FilterTabs:同质数据过滤(全部/待审/已通过)
- Tabs:切换到结构不同的面板(数据库/服务器/SQL 控制台)
选中态用按钮自身 border-b-2 下划线(无动画)。
API 与 FilterTabs 对齐(String value + EventHandler),便于调用方迁移。
2026-06-30 14:16:36 +08:00
ef75631889
feat(database): add SystemTab::as_str/from_str string bridge
...
为后续用基于 String 的 Tabs 公共组件替换内联 tab 做准备。
as_str/from_str 是纯函数双向映射,带 roundtrip/稳定 key/拒绝未知输入三类单测。
2026-06-30 14:13:54 +08:00
0b6a076d78
feat(database): use real COUNT(*) for small tables, fall back to estimate for large
...
row_estimate came from pg_class.reltuples, which is -1 for tables that
have never been ANALYZEd (the common case on a fresh/small DB), so the
admin table list showed -1 for every table — meaningless.
Replace row_estimate with row_count + row_count_estimated:
- total_size < 100MB: SELECT count(*) (real value, cost negligible on
small tables; identifier quoted via PG escaping to stay injection-safe)
- total_size >= 100MB: fall back to reltuples estimate, flagged so the UI
can prefix with ~
Frontend now shows real counts (1/2/14...) for small tables and ~N for
large ones; header copy updated to reflect the mixed source.
2026-06-30 13:57:37 +08:00
36168f26d7
fix(database): cast extract(epoch)::double precision in active-connections query
...
extract(epoch FROM now() - query_start) returns numeric (decimal) in
PostgreSQL, but the result was read as f64 via r.get(4). tokio-postgres
has no FromSql<f64> impl for the numeric type, so get_db_status panicked
at runtime with 'error deserializing column 4' (HTTP 500).
Cast to ::double precision (float8) to match query_duration_secs: Option<f64>.
Audited the remaining column mappings in status.rs to rule out further
type mismatches — all sound: reltuples::bigint->i64, native bigint size
columns->i64, last_vacuum/last_analyze timestamptz->DateTime<Utc>
(chrono feature enabled), schema_migrations.version TEXT->String,
count(*)::int->i32 (fixed previously).
2026-06-30 13:51:48 +08:00
116286f80f
fix(database): cast count(*)::int in get_db_status to match i32 field
...
count(*) returns bigint (int8) in PostgreSQL, but the result was read as
i32 via conn_row.get(0). tokio-postgres's FromSql<i32> rejects an int8
column, so get_db_status panicked at runtime with
'error deserializing column 0' (HTTP 500).
Cast count(*)::int to int4, matching the adjacent setting::int column
and the total_connections/max_connections i32 struct fields. No other
queries in the file are affected: reltuples is already ::bigint -> i64,
size columns are native bigint -> i64, and system_status.rs reads its
count(*) as i64 correctly.
2026-06-30 13:47:24 +08:00
ecf3c73715
fix(database): make src/api/database compile under WASM (web-only) build
...
The /admin/system feature was never verified against the WASM frontend
target (dx serve builds src/main.rs with --features web and no server).
The entire src/api/database/ tree unconditionally pulled in server-only
crates (axum, sqlparser, dashmap, tokio, tokio_postgres, futures, regex)
and server-gated helpers (get_current_admin_user, parse_session_token,
get_user_by_token), so the frontend build failed with 43 errors.
Root cause + fixes (mirror the established settings.rs pattern):
- Gate server-only imports behind #[cfg(feature = server)] in
status/system_status/sql_console/schema/tasks/backup. The #[server] macro
strips call sites but NOT use statements, so unresolved imports remained.
- Ungate in main.rs: SystemSnapshot is a shared serde
type referenced by ServerStatus.host on both targets; only the sampler
task/SNAPSHOT static are internally server-gated.
- Gate (pure Axum handler, zero WASM consumers) and the
download_backup Axum handler in backup.rs.
- Move restore_backup's validation preamble (regex/backup_path/fs) inside
the #[cfg(feature = server)] block; gate std::path/chrono::Utc imports.
- Add [[bin]] required-features=[server] for generate_highlight_css so the
syntect-dependent build tool is skipped in web-only builds.
Genuine WASM bugs surfaced once the server bodies stopped masking them:
- system.rs: Closure import path (dioxus::prelude::wasm_bindgen ->
wasm_bindgen, matching write.rs); setTimeout expects i32 not u32;
editor_handle() -> editor_handle.read() (Signal is not Fn in 0.7).
- codemirror_bridge.rs: set_schema extern took &SqlSchema but SqlSchema is
a serde type with no IntoWasmAbi. Changed both set_schema signatures to
&JsValue; call site now serializes via serde-wasm-bindgen::to_value.
- system.rs: signals .set() inside spawn/use_future/onclick closures need
bindings (incl. the *_f rebinding copies); added cfg_attr
allow(unused_mut) on the 4 tab components, matching write.rs convention.
Verified: cargo check (default/server) clean; cargo check --features web
--target wasm32-unknown-unknown clean (0 errors); dx check clean; 411
cargo tests pass.
2026-06-30 13:41:23 +08:00
37d7ec49eb
fix(database): harden SQL console guards + review fixes
...
最终 code review 发现的安全/正确性修复:
- C1 (critical): execute_one 改用 stmt.to_string() 重序列化的单条语句 SQL
而非原始整段 SQL,保证执行的语句与护栏检查的 AST 一致;杜绝 allow_multi
时整段 SQL 被重复执行、读写分类与实际执行解耦。
- C2 (critical): check_guards 在 AST 层结构性禁止 DROP SCHEMA(ObjectType
匹配),不再仅靠字符串预检——防 SQL 注释/空白绕过。
- I2: 备份/恢复在 DATABASE_URL 为空时提前失败任务,而非传空串给 pg_dump/psql。
- I4: 回退备份进度计算用 u32 避免 >255 表时截断/溢出。
- I5: SQL 导出用真实表名(table 模式)而非硬编码 "export"。
- M7: 移除 SqlResult 中未使用的 total_estimate 字段。
2026-06-29 19:24:06 +08:00
6e94db0c2b
feat(admin): add backup/restore tab (dual-mode + task progress polling)
...
新增 dashmap 依赖。create_backup 探测 pg_dump 优先(含 schema,签名头前置)、
不可用回退纯 SQL(COPY TO STDOUT 逐表,按表精确进度)。restore_backup 校验
签名头(仅本系统备份)+ 路径穿越防护 + 二次确认,探测 psql 执行。
list_backups/delete_backup 管理备份,GET /api/database/backups/{name} 下载
(admin 鉴权 + 白名单)。tasks.rs DashMap 进度表(1h 惰性 GC),前端每 1.5s
轮询 get_task_progress 显示进度条/stage,完成后刷新列表。
backups/ gitignored 不直接暴露。AGENTS.md 同步新增 CodeMirror 子项目与
数据库管理章节。
2026-06-29 19:16:11 +08:00