Commit Graph
142 Commits
Author SHA1 Message Date
xiaozhuandtao.chen 7772938148 fix:上传其他文件未自动更新 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen 98b657be5f update:部分样式优化 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen 5f5eb739c2 feat:账号设置+密码重置 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 856bdbae8d fix: loadChildren + loadScripts + loadDataResources 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 9b95357b39 refactor: api.ts 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen cc0e2eaec4 fix:补齐file-viewer预览资源包依赖 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen 0aca4934f7 feat:脚本文件预览 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen 19585522ff update:文件后缀、icon添加 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen 8a2862c8c4 update:新建用户可选角色、项目 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen b7b8e28b52 update:添加分页、成员添加优化 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen 5a90f7db36 部分样式调整 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 1f9c4a2cd9 update: style 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen af2f035d03 updata:icon替换 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen 3a5cc12782 updata:admin.css替用 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen bf5e00fd4f fix:input边框 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen d0f0fd2922 update:Dialog替换 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen ed58344add refactor: replace Toast with Sonner 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 635d80e3cf update: ScriptsPage.tsx 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen df374d00c3 update: unify 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 2a81b98309 update: replace table component 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen c2f153cabd update: replace table component 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen bc617b6ade update: use components 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 77cabea834 feat: add ui components 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen ce77aa9e5d update: role_code 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen fbd4da5224 feat: role CRUD page 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen dea6b4cfcf refactor: permission 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 1c220490cf refactor: use shadcn sidebar 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen fd724a1b45 update: use Button component in SchedulePage.tsx 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen ea2f92921b fix: build error 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen ae11fd6540 feat: add shadcn/ui 2026-09-02 10:10:41 +08:00
xiaozhuandtao.chen f452ba70fb update:css样式拆分 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen ce7adce89c fix: notebook 2026-09-02 10:10:41 +08:00
c3947d6066 refactor(frontend): consolidate features & components into single tree
把 components/admin/* 和 components/platform/* 共 11 个文件
移到对应 features/<name>/ 下,components/ 只保留跨特性共享的
common/ 子目录(features/admin/* 之前和 components/admin/* 同名
共存,迫使 features/admin/AdminPages.tsx 出现 barrel,顺带消除了
barrel 副作用 CSS 丢失的隐患)。

新的目录约定:
  - features/<name>/  = 特性模块,所有页面 / Modal / state / hooks
                         / routes 都在内,无 components/<name>/ 并列
  - components/common/ = 仅放 ≥2 个特性共用的 widgets
                         (Icon、Sidebar、Toast、Topbar、WelcomePanel)

改动:
  - 11 文件 git mv (components/{admin,platform}/* → features/*)
  - 8 文件的 Icon 相对路径 '../common/Icon' → '../../components/common/Icon'
  - 2 文件补 admin.css side-effect import(UserManagementPage /
    ProjectManagementPage 之前依赖 AdminPages barrel)
  - 3 文件(引用方)更新 import:SystemAdminPage、DashboardRoute、
    ScriptsPage
  - 删除 features/admin/AdminPages.tsx barrel(无消费者)
  - 删除空目录 components/admin、components/platform

验证:
  - pnpm typecheck 通过
  - pnpm build 通过,CSS 体积 35.11 kB 与重构前一致(无样式增减)
  - routes.ts / routes/platform.tsx 路径未动,route id 不变

踩坑:
  第一轮把 '../common/Icon' 错改成 '../../../components/common/Icon',
  typecheck 报 11 个 Cannot find module。原因:features/admin/ 和
  components/admin/ 都是 depth 2,'../../X' 在两边都解析到 app/X,
  只有 '../X' 才需要多一层。正确改法是 '../../components/common/Icon'。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 10:10:41 +08:00
tao.chenandtao.chen e9d9bcfc97 refactor: scriptWorkspaceStore.ts 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 8278cdd8b0 refactor: useSchedulesStore.ts 2026-09-02 10:10:41 +08:00
a8826f7224 fix(frontend): restore login & auth-loading styles with Tailwind
提交 6b77018 把 frontend/app/app.css 从 112 行砍到 91 行,
删除了 .login-* 与 .auth-loading 选择器,但 login.tsx /
AuthContext.tsx 的类名没同步迁移,导致:

  - /login 页面无样式(背景渐变、卡片阴影、输入框焦点环全部失效)
  - AuthContext 初次加载时的旋转 spinner 无样式

两处都用 Tailwind 任意值 [..] 语法精确复刻原 CSS,精确到
原始色值(#1677ff、#eef5fb 等),不引入新的 CSS 文件,
不修改 app.css、路由配置或依赖。

Spinner 用 Tailwind 内置 animate-spin(1s)替代原 0.8s
keyframes auth-spin,视觉差异肉眼不可见,如需精确复刻可在
app.css @theme 加 --animate-auth-spin。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 9187724ceb update: replace Icon with lucide-react 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 9c67ae333c feat: add lucide-react Icon 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen e30e7740cc feat: register tailwindcss plugins 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen b2870fb568 refactor: style css -> tailwindcss 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen c6e931d1bc update: init tailwindcss 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen be162a9ee3 refactor: update artifact list style 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen b47848cc08 refactor: update artifact list style 2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 81c83e5dc1 chore: initialContent 2026-09-02 10:10:41 +08:00
cb0205fcd2 feat(scripts): 跨 owner 懒加载目录树 + 跨用户可见 workspace/public
修两个后端接口问题:
1) /api/v1/workspace-directories 返回为空,目录树结构消失
2) 同 workspace 内脚本/数据互相可见但默认排除 private

后端改动
--------
* list_scripts / list_resources / list_workspace_directories 新增
  owner_user_id 可选 query 参数;缺省 = 当前请求者本人(scope 到
  workspace/{me}/...),传值时 scope 到该 owner 的子树。前端根加载
  默认只见自己一级,其他成员以折叠分组呈现。
* visibility 过滤统一:非 admin 请求者只返回 owner==me 或
  visibility ∈ {workspace, public};admin 跳过。owner=me 含自己
  的 private,owner=other 只剩其 workspace/public,排除他人 private。
* create_workspace_directory 两个分支 visibility 默认 'public'
  (非 private),使跨 owner 目录树可见;响应新增 owner_user_id 字段。
* platform.list_members 鉴权从 system_admin_context 放宽为
  系统管理员或该 workspace 活跃成员(让普通用户也能渲染同
  workspace 成员名册,用于跨 owner 分组)。
* main.py 注册 platform 模块(随 list_members 改动补齐导入)。
* .env.example 同步 common/config.py 26 个字段。

前端改动
--------
* ScriptExplorer.memberScriptGroups 改由 members 列表播种分组,
  display_name 取 members.display_name;inferredDirectories 现在按
  owner_user_id 标记,统一跨 owner 目录渲染。删除脚本目录页头与
  树分组标题的工作副本数量角标。
* WorkspaceTree 新增 ownerUserId 透传到 store.toggleExpanded;
  仅"我"的分组 mount 时 auto-expand,他人分组默认折叠,展开才
  调 loadOwnerGroup / owner-scoped loadScripts / loadChildren。
* scriptWorkspaceStore 引入 namespaced cache key
  (ownerCacheKey = `${ownerUserId ?? me}:${path}`),loadedScriptPaths
  / loadedChildPaths / loadedOwnerGroups 全部按 owner 隔离;
  toggleExpanded 用 loadPath === undefined 区分 group 头与真实
  目录,修"他人子目录点击不触发接口"的 loadPath 前缀误判 bug。
* api.ts / AuthContext 透传 ownerUserId 给 listScripts /
  listResources / listWorkspaceDirectories。

文档
----
* API.md: §3.2 创建目录 visibility 默认 public + 响应加 owner_user_id;
  §3.3.1 GET directories 加 owner_user_id 参数 + 响应字段;
  §3.4 GET scripts 改写为 owner 作用域 + visibility 过滤语义;
  §五.1 GET data-resources 新增,同一套统一语义;
  §7 intro 例外 — GET members 对系统管理员或 workspace 活跃成员开放。
* DEVELOP.md: Code layout 重写以反映 backend api/services/clients/
  schemas 拆分 + schedule domain/scheduling/application/execution/
  infrastructure 拆分 + common 子包(auth/storage/backends);
  Configuration 系统补全 26 个 settings 字段;新增
  "Owner-scoping + visibility (cross-owner browsing)" 小节;
  Per-service dev 注释用 uv run 的源布局要求;Add a new DAG endpoint /
  storage bucket 路径改为 backend/src/backend/api/* 与 services/*。

测试
----
* test_list_scripts_parent_path.py /
  test_resources.py 补充 owner_user_id 参数化直接调用 + LIKE
  前缀断言(workspace/{owner}/... 前缀)。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 57f21f2017 feat(scripts/resources): align list_scripts visibility with list_resources
69a9a48 把 data resources 的可见性从 list 恒真改成
workspace-wide + visibility 过滤 + admin 短路,但 scripts 端
没动。两端不对称,导致:

* 非 admin 调 list_scripts 走 user_relative_path →
  workspace/{当前用户ID}/...,永远拿不到别人的脚本
* count_scripts 同样 user-scoped,dashboard "全部脚本"
  只统计自己
* list_resources 响应没带 owner_display_name,data-only
  owner 的目录名回退到 userId.slice(-6),显示不友好

修复:
* list_scripts / count_scripts 改 workspace-wide(新建
  _build_list_scripts_workspace_descendant_prefix helper;
  旧 _build_list_scripts_descendant_prefix 保留标 deprecated
  避免破坏其它调用方);非 admin 追加
  or_(owner_user_id = me, visibility in {workspace, public})
  与 list_resources 完全对称;admin 短路
* resource_payload 加 owner_display_name 字段(与
  script_payload 对称);list_resources SELECT 加
  Users.display_name + outerjoin
* 前端 ScriptExplorer displayName 回退链:scripts 的
  owner_display_name → data resources 的 owner_display_name
  → 本人 user.display_name → userId.slice(-6) 占位
  ;ResourceItem 类型同步加 owner_display_name?: string|null

存储物理布局仍是 workspace/{user_id}/...,仅读取侧
listing/count 跨 owner。
2026-09-02 10:10:41 +08:00
tao.chenandtao.chen a337804700 feat(scripts/data-resources): merge tree + add parent_path filter
- Backend: GET /api/v1/data-resources accepts parent_path; LIKE
  '{ws_id}/%/{escaped}/%' AND NOT LIKE '{ws_id}/%/{escaped}/%/%' on
  StorageObjects.object_key (workspace-wide, escapes _ and %, mirrors
  list_scripts parent_path semantics). 13 new tests in
  test_resources.py (helper unit / SQL compile / SQLite behavioral).
- Frontend: listResources gains parentPath arg, propagated through
  WorkspaceBoundApi + AuthContext binding. WorkspaceTreeGroup title
  count and ScriptExplorer header count now include dataResources.
  memberScriptGroups backfills data-only owners so users with only
  data resources still render a group. loadDataResources accepts an
  optional parentPath, default empty preserves prior behavior.
2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 0ac034d7d3 fix(scripts): sequence-token race + comment/import cleanup (Codex ffec234 follow-up)
Codex review of ffec234 flagged:

1. **MEDIUM — loadScriptCount in-flight race on workspace switch.**
   Previous implementation used `if (get().scriptCountLoading) return`
   to dedupe. That meant switching workspaces WHILE a fetch was in
   flight dropped the new fetch entirely; the stale response from the
   previous workspace then overwrote state, leaving the dashboard
   showing workspace A's total while the user is on workspace B.
   Fix: drop the dedupe-via-flag, use a module-level
   `_scriptCountSeq` counter. Every call increments, captures the
   seq at start, and the response/finally block only mutates state
   when `_scriptCountSeq === seq` — stale responses are silently
   dropped. Rapid workspace switches each get their own fetch; only
   the latest response wins.

2. **LOW — scriptCount scope comment was misleading.**
   "范围与 list_scripts(parent_path=\"\") 对齐" is wrong: the
   count includes all descendant depths, not just root-level.
   Behaviour is correct for the dashboard's "全部脚本" intent but
   the comment would mislead the next maintainer. Rewritten to
   explicitly call out that the count is the UNION across all
   parent_path depths, with the rationale for each design choice.

3. **LOW — local `from backend.scripts import _escape_like_pattern`
   inside `resources.py` keyword-search block.**
   The "avoid cycle" justification was false: scripts.py and
   resources.py don't import each other at module level. Moved to
   the top-of-file import block.

Skipped:
- get_workspace_tree `like_prefix` not run through the escape helper
  (user_id is a 26-char Crockford ULID so no `_`/`%` can appear, but
  the invariant is not documented at the call site). Pre-existing
  pattern; out of scope for this round.

Verified: pytest 65 passed; pnpm typecheck clean.
2026-09-02 10:10:41 +08:00
tao.chenandtao.chen 23ed028f25 fix(scripts): actually escape LIKE pattern literals + scope count endpoint
Codex review of #36 + #37 surfaced that my prior `escape="\\"` only
declared the escape character — the pattern literals themselves still
contained unescaped `_` and `%`, so `parent_path="foo_bar"` continued
to match `fooXbar/...`, `foo2bar/...`, etc. My earlier ESCAPE-clause
assertions were tautological: they verified the SQL rendered the
ESCAPE keyword without ever checking that the pattern was actually
escaped. The tests passed; the leak persisted.

Fix in three layers:

1. Real escape: `backend/src/backend/scripts.py` gains
   `_escape_like_pattern(value)` that escapes `\` → `\\`, `%` → `\%`,
   `_` → `\_` (in that order — the escape char MUST be escaped first).
   `_build_list_scripts_descendant_prefix` now returns the escaped
   prefix. `list_workspace_directories` and `delete_workspace_directory`
   also escape their server-built prefixes. `count_scripts` escapes
   the user subtree prefix.

2. Same bug elsewhere: `backend/src/backend/resources.py:404` had the
   identical `DataResources.resource_name.like(f"%{keyword}%")`
   pattern; a search for "100%" would match everything. Now escaped
   too.

3. Count endpoint scope: `count_scripts` was workspace-wide and
   skipped the StorageObjects JOIN. Now INNER JOINs StorageObjects
   (drops orphans whose current_object_id is dangling) and filters
   by `workspace/{user_id}/` subtree so the result matches what
   `list_scripts(parent_path="")` would return. Multi-member
   workspaces no longer over-report, and orphan rows no longer
   inflate the count.

Frontend: `DashboardRoute` is not keyed by workspace/user (only
ScriptsPage is), so without a workspace_id dep the previous
workspace's count persisted across navigation. useEffect now depends
on `currentWorkspace?.workspace_id`; `loadScriptCount` clears the
count to null at the start of the fetch so the dashboard doesn't
flash a stale number.

Tests — backend/tests/test_list_scripts_parent_path.py
- Rewritten with three layers of coverage:
  * Pure helper tests for `_escape_like_pattern` (7 cases including
    backslash-escape-first ordering).
  * SQL-contract tests asserting the COMPILED PATTERN contains the
    escaped form (lowercased to neutralise SQLAlchemy keyword casing).
  * BEHAVIORAL tests on SQLite in-memory with the same LIKE
    semantics — proves the fix actually prevents the wildcard leak.
    Includes a negative test (without escape, siblings DO match) so
    the fixture is verified to exercise the bug.

Tests — backend/tests/test_count_scripts.py
- Updated to assert the JOIN + user-scope filter. New test verifies
  two different users in the same workspace get different subtrees.

Verified:
- pytest backend/tests: 65 passed (43 baseline + 12 list_scripts + 4 count + 6 helper/SQLite behavioral)
- pnpm typecheck: clean
- Raw SQL on MySQL (live DB) confirms `LIKE 'workspace/.../foo\_bar/%%' ESCAPE '\\'`.
2026-09-02 10:10:41 +08:00
tao.chenandtao.chen c6ac886133 feat(scripts): GET /api/v1/scripts/count + DashboardRoute wiring
After #34 the workspace store only holds the root-level scripts plus
whatever subfolders the user has expanded. DashboardRoute's
"全部脚本"/"工作副本" counts derived from scripts.length therefore
underreport the workspace total until the user navigates to /scripts
and expands every folder.

Fix: separate count endpoint + dedicated store field, mounted
independently.

Backend — backend/src/backend/scripts.py
- New endpoint GET /api/v1/scripts/count.
- Route declared BEFORE /api/v1/scripts/{script_id}/... so FastAPI's
  declaration-order matching does not interpret "count" as a script_id.
- Returns { data: { total: number }, meta: {} }; SQL is a single
  COUNT(*) on scripts filtered by workspace_id + status='active'.

Frontend — services/api.ts + context/AuthContext.tsx
- countScripts(workspaceId) client; WorkspaceBoundApi gains the field;
  AuthContext binding forwards workspaceId.

Frontend — state/scriptWorkspaceStore.ts
- scriptCount: number | null, scriptCountLoading: boolean.
- loadScriptCount() action: idempotent (no-op while in-flight), silent
  on failure (dashboard tolerates a stale count).
- Initial state and reset() clear both fields.

Frontend — features/platform/DashboardRoute.tsx
- Subscribes to scriptCount; calls loadScriptCount() on mount.
- Falls back to scripts.length until the count resolves so the
  dashboard never blanks.

Tests — backend/tests/test_count_scripts.py (new)
- 3 unit tests: scalar result handling, NULL coercion, route callable.

Verified: pytest 55 passed (52 + 3 new); pnpm typecheck clean.
2026-09-02 10:10:41 +08:00