Skip to content

Refactor remote policies to bind storage targets#380

Merged
AptS-1547 merged 5 commits into
masterfrom
feat/issue-370
Jul 5, 2026
Merged

Refactor remote policies to bind storage targets#380
AptS-1547 merged 5 commits into
masterfrom
feat/issue-370

Conversation

@AptS-1547

@AptS-1547 AptS-1547 commented Jul 4, 2026

Copy link
Copy Markdown
Member

Summary

  • bind remote storage policies to explicit remote storage targets
  • add quick remote storage target creation from the policy dialog
  • remove target-level max_file_size and carry policy max size through signed remote requests

Validation

  • cargo fmt --all -- --check
  • cargo check
  • cargo test --lib remote_protocol
  • cargo test --lib master_binding_service
  • cargo test --lib remote_storage_target_service
  • cargo test --test test_migrations remote_storage_target_max_file_size_migration_removes_target_level_limit
  • cargo test --features openapi --test generate_openapi
  • cd frontend-panel && bun run generate-api
  • cd frontend-panel && bun run check
  • cd frontend-panel && bun run test -- remoteStorageTargetDialogShared RemoteNodeRemoteStorageTargetSection AdminRemoteNodesPage adminService AdminPoliciesPage
  • git diff --check

Closed #370

Summary by CodeRabbit

  • 新功能
    • 策略创建与编辑支持选择/快速创建远程存储目标,并将选定的远程目标键纳入保存与提交校验。
    • 策略向导/表单支持在远程节点下展示目标列表(含加载/错误/空状态),并支持在部分只读场景下仅查看/折叠展开。
  • 优化
    • 移除了远程存储目标相关的文件大小字段与展示;表单与列表更精简,并补齐相关提示文案。
  • 数据库与后端
    • 增加策略到远程目标键的归属字段与索引;删除目标级文件大小字段;补齐缺失目标与必选校验的错误处理。
    • 预签名与远程请求将按选定目标键区分并参与校验逻辑。

AptS-1547 added 3 commits July 5, 2026 00:52
- Add `remote_storage_target_key` field to storage policy form, entity, and API DTOs (`CreatePolicyReq`, `PatchPolicyReq`, `TestPolicyParamsReq`, `ExecuteDraftStoragePolicyActionReq`)
- Add DB migration to add `remote_storage_target_key` column and `idx_storage_policies_remote_target` index to `storage_policies` table
- Validate target key on create/update: verify the target exists on the remote node, is applied, and has no errors; reject non-blank keys on non-remote policies
- Auto-select default target when a remote node is chosen and no valid target key is already set; clear target key when the node changes
- Add `resolve_target_by_key` service function; route internal/presigned requests through the policy-specified target instead of always falling back to the binding default
- Propagate `target_key` query parameter through `RemoteStorageClient` to all object, capacity, compose, and list paths
- Add `RemoteNodeField` secondary select for remote storage target in the policy dialog; show loading, empty, error, and hint states; reset selection on node change
- Rename `createRemoteNodeError` to `createRemoteTargetError` throughout the wizard and edit form to cover both node and target validation
- Add i18n keys for target field labels, placeholder, loading, empty, error, and hint in `en` and `zh` locales
- Add integration test `test_remote_policy_uses_selected_remote_storage_target_key` verifying writes land on the selected target and not the binding default
Add inline remote storage target management to policy creation and edit flows:
- Display read-only collapsible target list in policy dialogs when remote node is selected
- Support quick target creation directly from policy forms without leaving the dialog
- Remove max_file_size field from target creation form (moved to backend defaults)
- Remove revision display from target cards (internal detail)
- Add new i18n keys for policy-scoped target management UI
- Thread remoteStorageTargetDriverDescriptors and onCreateRemoteStorageTarget through dialog component tree
- Refactor RemoteNodeRemoteStorageTargetSection to support read-only mode with optional creation
- Add AnimatedCollapsible wrapper for target list in read-only mode with viewport height constraint
- Update test fixtures and mocks to cover new creation flow and driver descriptor loading
- Extend AdminPoliciesPage state to load driver descriptors and handle target creation for selected node
… level

- Drop `max_file_size` column from `remote_storage_targets` table via new migration `m20260705_000001_drop_remote_storage_target_max_file_size` with rollback support
- Remove `max_file_size` field from `RemoteStorageTarget` entity, API models, and all create/update request structs
- Read `max_file_size` from a signed query parameter (`max_file_size`) on incoming requests instead of the target record, sourcing the value from the storage policy
- Propagate `policy_max_file_size` through `resolve_authorized_ingress` for internal, presigned PUT, and presigned GET request paths
- Attach `max_file_size` query param to PUT, streaming PUT, presigned PUT, and compose request URLs via `append_policy_max_file_size` in `RemoteStorageClient`
- Rename `with_storage_target_key` to `with_policy_context` and add `max_file_size` parameter; clamp negative values to zero
- Remove `max_file_size` driver field descriptor from Local and S3 connectors and delete the `remote_storage_target_number_field` helper
- Strip `max_file_size` from frontend form data, payload builders, and all related tests
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 33842bb7-63f4-409a-9197-0183aa152603

📥 Commits

Reviewing files that changed from the base of the PR and between 054ab0c and 6686dd6.

📒 Files selected for processing (4)
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx
  • frontend-panel/src/pages/admin/AdminPoliciesPage.test.tsx
  • src/storage/remote_protocol/tests.rs
  • tests/test_migrations.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/storage/remote_protocol/tests.rs
  • frontend-panel/src/pages/admin/AdminPoliciesPage.test.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx

📝 Walkthrough

Walkthrough

本次改动把远程存储目标从目标级 max_file_size 迁移到策略级 remote_storage_target_key,并贯穿更新了迁移、后端服务、远程协议、连接器、前端策略向导与远程节点页;同时补齐了相关错误码、文案和测试。

Changes

后端:迁移、服务、协议与测试

Layer / File(s) Summary
迁移与错误码
migration/src/..., tests/test_migrations.rs, src/api/api_error_code.rs, src/storage/remote_protocol/errors.rs, frontend-panel/src/types/api-helpers.ts
新增策略目标键迁移、删除目标级 max_file_size 的迁移,并注册到迁移序列;错误码与迁移回放测试同步补齐。
实体、DTO 与路由
src/entities/..., src/api/dto/admin.rs, src/api/routes/admin/policies.rs, src/services/task_service/...
策略实体、远程目标实体、管理员请求 DTO 和路由转换都新增或移除了对应字段,清理快照与测试固件同步更新。
策略服务与授权解析
src/services/policy_service/*.rs, src/services/master_binding_service.rs, src/api/routes/internal_storage.rs
策略创建/更新开始校验并落库 remote_storage_target_key,授权链路改为解析目标键与策略级 max_file_size
远程目标服务与远程协议
src/services/remote_storage_target_service/*.rs, src/storage/remote_protocol/*.rs, src/storage/drivers/remote/*.rs
远程目标服务移除 max_file_size,新增按 key 解析;远程协议客户端、模型与错误映射改为携带目标键与策略上下文。
连接器与后端驱动
src/storage/connectors/*.rs, src/storage/drivers/*, src/storage/registry.rs
连接器输入开始透传目标键,驱动和注册测试补齐新字段,相关测试从 max_file_size 切换到 is_default 或目标键。
后端回归与集成测试
tests/test_remote_storage.rs, tests/test_policies.rs, tests/test_s3.rs, 其余 tests/*
补齐新字段的测试构造与断言,并新增按目标键选择、创建、更新和迁移回放覆盖。

Estimated code review effort: 5 (Critical) | ~120 minutes

前端:策略选择、创建和只读展示

Layer / File(s) Summary
生成类型、共享表单与文案
frontend-panel/src/services/api.generated.ts, frontend-panel/src/i18n/locales/*, frontend-panel/src/components/admin/storage-policy-dialog/remoteStorageTargetDialogShared.ts
API 生成类型新增 remote_storage_target_key,多处 max_file_size 字段移除,策略与错误文案补齐远程存储目标相关文本。
共享表单、payload 与校验
frontend-panel/src/components/admin/storage-policy-dialog/*.ts, *.test.tsx
表单状态、payload 构建和变更检测新增 remote_storage_target_key;远程目标表单移除 max_file_size
策略对话框与向导
frontend-panel/src/components/admin/StoragePolicyDialog.tsx, storage-policy-dialog/*.tsx, admin-policies-page/PolicyDialogs.tsx
策略对话框与创建/编辑向导接入远程目标列表、驱动描述符和快速创建回调,并在远程节点绑定分支渲染目标下拉。
AdminPoliciesPage 目标加载与提交
frontend-panel/src/pages/admin/AdminPoliciesPage.tsx(.test.tsx)
页面侧新增远程目标/驱动描述符状态、加载与创建逻辑,并把 remote_storage_target_key 纳入创建与提交校验。
远程节点页只读折叠
frontend-panel/src/components/admin/admin-remote-nodes-page/*.tsx(.test.tsx)
远程节点页支持只读折叠展示,隐藏编辑/删除操作,同时移除 max_file_sizerevision 相关展示。

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related PRs

Poem

远端落点换了名,旧的 max_file_size 退场,
remote_storage_target_key 接过钥匙,照样发光。
迁移、服务、UI,一路都被你改通,
看完这串链路,猫都得点头:像样。

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了把远程策略绑定到存储目标这一主变更。
Description check ✅ Passed 描述包含了摘要和验证步骤,虽未完全沿用模板标题,但核心信息齐全。
Linked Issues check ✅ Passed 代码已把远程策略改为显式选择或创建 target,并移除了目标级 max_file_size,符合 #370 的主目标。
Out of Scope Changes check ✅ Passed 未见明显与远程策略重构无关的代码改动,额外测试和迁移都服务于同一主目标。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-370

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/storage/remote_protocol/tests.rs (1)

775-780: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

tests/test_remote_storage.rs 里的旧字段也要一起删掉
RemoteCreateLocalStorageTargetRequest 现在只剩 name / base_path / is_default,但 tests/test_remote_storage.rs 里还有多处 max_file_size 构造没清。这个文件会直接编译不过,把这些旧字段一并去掉。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/remote_protocol/tests.rs` around lines 775 - 780,
`RemoteCreateLocalStorageTargetRequest` no longer has `max_file_size`, so update
the affected test setup in `tests/test_remote_storage.rs` to use only `name`,
`base_path`, and `is_default`. Remove every leftover `max_file_size`
initialization in the request constructors and any assertions tied to it so the
test module compiles cleanly with the current
`RemoteCreateLocalStorageTargetRequest` shape.
src/storage/connectors/tests.rs (1)

77-93: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

夹具补字段没问题,但 reject_unexpected_remote_storage_target_key 这条分支还没用例。直接在 src/storage/connectors/tests.rs 基于 draft_connection 加个非 remote driver + 非空 remote_storage_target_key 的断言,确认会回 PolicyRemoteNodeUnexpected

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/connectors/tests.rs` around lines 77 - 93, Add a test case in
tests.rs using draft_connection to cover
reject_unexpected_remote_storage_target_key: build a non-remote driver
connection with a non-empty remote_storage_target_key and assert validation
returns PolicyRemoteNodeUnexpected. Reuse draft_connection and the
StorageConnectorConnectionInput / DriverType setup so the new case sits
alongside the existing connector policy tests and exercises the unexpected
remote_storage_target_key branch directly.

Source: Coding guidelines

frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx (1)

224-307: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

非只读分支的创建按钮没吃到 canCreateTargets 这道闸。

只读分支(259-264行)老老实实检查了 !canCreateTargets,非只读分支(298-302行)却漏了——按钮亮着能点,点了也是白点,因为 startCreate() 内部会因为 canCreateTargets 为 false 直接返回。现在没人踩坑是因为 RemoteNodeEditForm 目前必传 onCreateTarget,但这变量是你自己这次 diff 新加的,加了就该两处都用上,不然就是埋了颗迟早要绊人的钉子。

🔧 补上这道闸
 					disabled={
 						loading ||
 						Boolean(errorMessage) ||
-						firstSupportedDriverType == null
+						firstSupportedDriverType == null ||
+						!canCreateTargets
 					}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx`
around lines 224 - 307, The non-read-only create button in
RemoteNodeRemoteStorageTargetSection is missing the canCreateTargets guard, so
it can appear enabled even when startCreate will immediately return; update the
create button disabled logic in the activeDraftMode == null branch to match the
readOnly branch and include canCreateTargets alongside loading, errorMessage,
and firstSupportedDriverType checks, using the existing startCreate and
canCreateTargets symbols to locate the fix.
frontend-panel/src/components/admin/StoragePolicyDialog.tsx (1)

296-310: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

把远程节点缺失和远程目标缺失拆开,别让编辑态一打开就报红
createRemoteTargetError 现在把“没选 remote node”和“没选 remote target”两种语义硬塞进一个变量里;更要命的是编辑模式没有 createStepTouched 门槛,只要历史策略的 remote_storage_target_key 为空,RemoteNodeField 就会直接亮错。拆成两个错误,或者给编辑态补一层明确的触发条件。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend-panel/src/components/admin/StoragePolicyDialog.tsx` around lines 296
- 310, The remote node missing state and remote storage target missing state are
conflated in the StoragePolicyDialog error logic, causing edit mode to show an
error immediately when historical data lacks remote_storage_target_key. Update
the createRemoteNodeError and createRemoteTargetError handling in
StoragePolicyDialog so each validation has its own explicit condition, and
ensure the RemoteNodeField only shows the target error after an appropriate user
interaction trigger in both create and edit modes.
🧹 Nitpick comments (5)
migration/src/m20260704_000002_add_remote_storage_target_key_to_storage_policies.rs (1)

45-64: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

down() 少了对称的幂等守卫

up() 特意用 has_column 判断是否已存在该列才 add_column,摆明是防着重复执行或状态不一致的情况。可 down()drop_column 直接无脑执行,没有对应的 has_column 判断兜底。同一个 PR 里的 m20260705_000001_drop_remote_storage_target_max_file_size.rs 两侧(up/down)都做了对称守卫,这里风格不统一,回滚重放时容易在列已经被删过的情况下直接炸掉。

小子,既然你都想到 up 要防重放了,down 也别偷懒。

🔧 建议补齐对称守卫
     async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
         manager
             .drop_index(
                 Index::drop()
                     .name("idx_storage_policies_remote_target")
                     .table(StoragePolicies::Table)
                     .if_exists()
                     .to_owned(),
             )
             .await?;

-        manager
-            .alter_table(
-                Table::alter()
-                    .table(StoragePolicies::Table)
-                    .drop_column(StoragePolicies::RemoteStorageTargetKey)
-                    .to_owned(),
-            )
-            .await
+        if manager
+            .has_column(
+                StoragePolicies::Table.to_string(),
+                StoragePolicies::RemoteStorageTargetKey.to_string(),
+            )
+            .await?
+        {
+            manager
+                .alter_table(
+                    Table::alter()
+                        .table(StoragePolicies::Table)
+                        .drop_column(StoragePolicies::RemoteStorageTargetKey)
+                        .to_owned(),
+                )
+                .await?;
+        }
+        Ok(())
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@migration/src/m20260704_000002_add_remote_storage_target_key_to_storage_policies.rs`
around lines 45 - 64, The down() implementation in the migration is missing a
symmetric idempotency guard for dropping the remote storage target key column.
Update the down path in the migration struct that defines up()/down() so it
checks has_column before calling alter_table(...).drop_column(...) on
StoragePolicies::RemoteStorageTargetKey, matching the defensive pattern already
used in up() and the sibling migration’s up/down symmetry.
src/services/remote_storage_target_service/tests.rs (1)

756-808: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

resolve_target_by_key 没有专门的单测

这个文件里 resolve_effective_target 的四种状态(required / default_missing / error / pending)都测得挺全,但 target.rs 新增的 resolve_target_by_key——也就是策略显式指定 target_key 时真正决定文件落到哪个远程目标的函数——在这里一个直接测试都没有,只能靠间接推断它复用了 build_resolved_target 就当它没问题。

它可是这次重构的核心路由逻辑,建了错目标可不是小事。补一组类似 resolve_effective_target_reports_required_default_and_pending_states 的用例(key 不存在 / target 有 last_error / target 处于 pending revision),成本不高,但能防住以后有人手滑改坏路由。

别指望我天天替你盯着这种坑,你自己补上。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/remote_storage_target_service/tests.rs` around lines 756 - 808,
Add direct unit coverage for resolve_target_by_key in the target resolution
tests, since it currently has no explicit assertions and is only indirectly
exercised through build_resolved_target. Create cases around the existing
resolve_target_by_key flow for a missing target_key, a target with last_error
set, and a target in pending revision state (using the same setup patterns as
resolve_effective_target_reports_required_default_and_pending_states) so the
core routing logic is verified directly.
migration/src/m20260705_000001_drop_remote_storage_target_max_file_size.rs (1)

39-52: 🧹 Nitpick | 🔵 Trivial

顺嘴提醒:回滚不是真的"恢复"

down() 补列时用的是 default(0),这只是让 schema 形状复原,原来每个 target 的真实 max_file_size 数值在 up() 执行时已经被物理删除,回滚不会拿回原值。生产环境如果真跑到这步回滚,记得提前有备份或者接受这些值全部归零。这是删列迁移的通病,不是这段代码写错了。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@migration/src/m20260705_000001_drop_remote_storage_target_max_file_size.rs`
around lines 39 - 52, The `down()` path in
`drop_remote_storage_target_max_file_size` currently recreates
`RemoteStorageTargets::MaxFileSize` with `default(0)`, but this only restores
the schema shape and cannot recover the deleted per-row values from `up()`. Add
an explicit note in the migration or `down()` implementation near
`Table::alter()`/`ColumnDef::new(RemoteStorageTargets::MaxFileSize)` clarifying
that rollback will reset the column to zero and does not restore original data,
so operators know to rely on backups if they need the prior values.
src/services/remote_storage_target_service/target.rs (1)

40-58: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

给 target 缺失单独分码 resolve_target_by_key 找不到 target_key 时还在回 ManagedIngressRequired,和“binding 没配任何 target”混在一起。补一个专门的 target-not-found 码,并在这里切过去。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/remote_storage_target_service/target.rs` around lines 40 - 58,
The resolve_target_by_key flow currently maps a missing target_key to
ManagedIngressRequired, which conflates it with a different configuration
failure. Add a dedicated target-not-found ApiErrorCode and update
resolve_target_by_key to use that code in the ok_or_else branch after
remote_storage_target_repo::find_by_binding_and_target_key, while keeping
build_resolved_target unchanged for the success path.
frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx (1)

338-373: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

只读/非只读两段列表渲染基本是复制粘贴。

除了外面裹没裹 AnimatedCollapsiblereadOnly prop 有没有传,两段 RemoteNodeRemoteStorageTargetsList 的其余 props 一模一样。抽个公共 props 对象出来,以后同步维护能少挨一次骂。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx`
around lines 338 - 373, The read-only and non-read-only branches in
RemoteNodeRemoteStorageTargetSection are duplicating the same
RemoteNodeRemoteStorageTargetsList props, so refactor the shared props into a
single local object and reuse it in both branches. Keep the branch-specific
differences only where needed: AnimatedCollapsible and the readOnly prop in the
read-only path, while preserving the existing handlers and target/loading/error
props via the shared object.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend-panel/src/pages/admin/AdminPoliciesPage.tsx`:
- Around line 708-733: The remote node ID validation path in
createRemoteStorageTargetForPolicy is swallowing failures by only showing a
toast and returning, unlike the catch branch that rethrows. Make this failure
path behave like a real error so
RemoteNodeRemoteStorageTargetSection.handleSubmit can await and prevent draft
reset, by replacing the silent return with an exception flow after toast.error
while keeping the existing adminRemoteNodeService.createStorageTarget and
loadRemoteStorageTargetsForPolicy behavior unchanged.

In `@src/services/master_binding_service.rs`:
- Around line 375-393: The fallback in AuthorizedMasterBinding construction
treats a missing policy_max_file_size as 0, which downstream logic in
validate_ingress_object_size and client.rs interprets as unlimited. Update the
master_binding_service path that builds AuthorizedMasterBinding to preserve a
bounded default during rolling upgrades, or explicitly reject requests when
policy_max_file_size is absent, so older clients cannot bypass size validation.
Use the policy_max_file_size field and the target resolution flow in
resolve_target_by_key/resolve_effective_target to locate the change.

In `@src/services/policy_service/policies.rs`:
- Around line 398-405: Move the remote storage target validation out of the
write transaction in update so validate_remote_storage_policy_target and its
list_storage_targets network call run before begin, avoiding holding the
transaction open on slow remote I/O. Also add a guard in update (using
remote_node_id and remote_storage_target_key) so unchanged targets are not
revalidated; when the target hasn’t changed, reuse the existing
final_remote_storage_target_key instead of calling
validate_remote_storage_policy_target again.
- Around line 638-709: `validate_remote_storage_policy_target` is currently
re-validating the existing `remote_storage_target_key` even when it was not
changed, which blocks unrelated `update` operations. Adjust the call flow so
health checks for `last_error` and `applied_revision` are only enforced when
`remote_storage_target_key` is explicitly changed; if the key is unchanged,
preserve and reuse the existing value without failing the update. Use the
`update` path and `validate_remote_storage_policy_target` to locate the logic
and add the unchanged-key bypass there.

In `@tests/test_migrations.rs`:
- Around line 12-13: The migration test coverage in test_migrations.rs only
registers and validates
m20260705_000001_drop_remote_storage_target_max_file_size, so add the missing
round-trip test coverage for
m20260704_000002_add_remote_storage_target_key_to_storage_policies as well.
Update the migration registration/assertion list and extend the up/down column
checks in the migration test helper to verify
storage_policies.remote_storage_target_key is added on up and removed on down,
using the existing migration test symbols as the place to wire it in.

---

Outside diff comments:
In
`@frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx`:
- Around line 224-307: The non-read-only create button in
RemoteNodeRemoteStorageTargetSection is missing the canCreateTargets guard, so
it can appear enabled even when startCreate will immediately return; update the
create button disabled logic in the activeDraftMode == null branch to match the
readOnly branch and include canCreateTargets alongside loading, errorMessage,
and firstSupportedDriverType checks, using the existing startCreate and
canCreateTargets symbols to locate the fix.

In `@frontend-panel/src/components/admin/StoragePolicyDialog.tsx`:
- Around line 296-310: The remote node missing state and remote storage target
missing state are conflated in the StoragePolicyDialog error logic, causing edit
mode to show an error immediately when historical data lacks
remote_storage_target_key. Update the createRemoteNodeError and
createRemoteTargetError handling in StoragePolicyDialog so each validation has
its own explicit condition, and ensure the RemoteNodeField only shows the target
error after an appropriate user interaction trigger in both create and edit
modes.

In `@src/storage/connectors/tests.rs`:
- Around line 77-93: Add a test case in tests.rs using draft_connection to cover
reject_unexpected_remote_storage_target_key: build a non-remote driver
connection with a non-empty remote_storage_target_key and assert validation
returns PolicyRemoteNodeUnexpected. Reuse draft_connection and the
StorageConnectorConnectionInput / DriverType setup so the new case sits
alongside the existing connector policy tests and exercises the unexpected
remote_storage_target_key branch directly.

In `@src/storage/remote_protocol/tests.rs`:
- Around line 775-780: `RemoteCreateLocalStorageTargetRequest` no longer has
`max_file_size`, so update the affected test setup in
`tests/test_remote_storage.rs` to use only `name`, `base_path`, and
`is_default`. Remove every leftover `max_file_size` initialization in the
request constructors and any assertions tied to it so the test module compiles
cleanly with the current `RemoteCreateLocalStorageTargetRequest` shape.

---

Nitpick comments:
In
`@frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx`:
- Around line 338-373: The read-only and non-read-only branches in
RemoteNodeRemoteStorageTargetSection are duplicating the same
RemoteNodeRemoteStorageTargetsList props, so refactor the shared props into a
single local object and reuse it in both branches. Keep the branch-specific
differences only where needed: AnimatedCollapsible and the readOnly prop in the
read-only path, while preserving the existing handlers and target/loading/error
props via the shared object.

In
`@migration/src/m20260704_000002_add_remote_storage_target_key_to_storage_policies.rs`:
- Around line 45-64: The down() implementation in the migration is missing a
symmetric idempotency guard for dropping the remote storage target key column.
Update the down path in the migration struct that defines up()/down() so it
checks has_column before calling alter_table(...).drop_column(...) on
StoragePolicies::RemoteStorageTargetKey, matching the defensive pattern already
used in up() and the sibling migration’s up/down symmetry.

In `@migration/src/m20260705_000001_drop_remote_storage_target_max_file_size.rs`:
- Around line 39-52: The `down()` path in
`drop_remote_storage_target_max_file_size` currently recreates
`RemoteStorageTargets::MaxFileSize` with `default(0)`, but this only restores
the schema shape and cannot recover the deleted per-row values from `up()`. Add
an explicit note in the migration or `down()` implementation near
`Table::alter()`/`ColumnDef::new(RemoteStorageTargets::MaxFileSize)` clarifying
that rollback will reset the column to zero and does not restore original data,
so operators know to rely on backups if they need the prior values.

In `@src/services/remote_storage_target_service/target.rs`:
- Around line 40-58: The resolve_target_by_key flow currently maps a missing
target_key to ManagedIngressRequired, which conflates it with a different
configuration failure. Add a dedicated target-not-found ApiErrorCode and update
resolve_target_by_key to use that code in the ok_or_else branch after
remote_storage_target_repo::find_by_binding_and_target_key, while keeping
build_resolved_target unchanged for the success path.

In `@src/services/remote_storage_target_service/tests.rs`:
- Around line 756-808: Add direct unit coverage for resolve_target_by_key in the
target resolution tests, since it currently has no explicit assertions and is
only indirectly exercised through build_resolved_target. Create cases around the
existing resolve_target_by_key flow for a missing target_key, a target with
last_error set, and a target in pending revision state (using the same setup
patterns as
resolve_effective_target_reports_required_default_and_pending_states) so the
core routing logic is verified directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 371948c4-4cdd-44e8-b4cd-dfa2f4841e00

📥 Commits

Reviewing files that changed from the base of the PR and between 0ba34fc and 067926e.

📒 Files selected for processing (67)
  • frontend-panel/src/components/admin/StoragePolicyDialog.tsx
  • frontend-panel/src/components/admin/admin-policies-page/PolicyDialogs.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeEditForm.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetForm.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.test.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetsList.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/remoteNodeRemoteStorageTargetPresentation.test.ts
  • frontend-panel/src/components/admin/remoteStorageTargetDialogShared.test.ts
  • frontend-panel/src/components/admin/remoteStorageTargetDialogShared.ts
  • frontend-panel/src/components/admin/storage-policy-dialog/StoragePolicyCreateWizard.tsx
  • frontend-panel/src/components/admin/storage-policy-dialog/StoragePolicyEditForm.tsx
  • frontend-panel/src/components/admin/storage-policy-dialog/StoragePolicyRemoteFields.tsx
  • frontend-panel/src/components/admin/storage-policy-dialog/connectionNormalization.ts
  • frontend-panel/src/components/admin/storage-policy-dialog/formTypes.ts
  • frontend-panel/src/components/admin/storage-policy-dialog/payloadBuilders.ts
  • frontend-panel/src/components/admin/storagePolicyDialogShared.test.ts
  • frontend-panel/src/i18n/locales/en/admin/policies.json
  • frontend-panel/src/i18n/locales/en/admin/remote-nodes.json
  • frontend-panel/src/i18n/locales/zh/admin/policies.json
  • frontend-panel/src/i18n/locales/zh/admin/remote-nodes.json
  • frontend-panel/src/pages/admin/AdminPoliciesPage.test.tsx
  • frontend-panel/src/pages/admin/AdminPoliciesPage.tsx
  • frontend-panel/src/pages/admin/AdminRemoteNodesPage.test.tsx
  • frontend-panel/src/services/adminService.test.ts
  • frontend-panel/src/services/api.generated.ts
  • migration/src/lib.rs
  • migration/src/m20260704_000002_add_remote_storage_target_key_to_storage_policies.rs
  • migration/src/m20260705_000001_drop_remote_storage_target_max_file_size.rs
  • src/api/dto/admin.rs
  • src/api/routes/admin/policies.rs
  • src/entities/remote_storage_target.rs
  • src/entities/storage_policy.rs
  • src/services/master_binding_service.rs
  • src/services/media_metadata_service/tests.rs
  • src/services/policy_service/mod.rs
  • src/services/policy_service/models.rs
  • src/services/policy_service/policies.rs
  • src/services/remote_storage_target_service/driver.rs
  • src/services/remote_storage_target_service/local_profiles.rs
  • src/services/remote_storage_target_service/mod.rs
  • src/services/remote_storage_target_service/models.rs
  • src/services/remote_storage_target_service/normalization.rs
  • src/services/remote_storage_target_service/target.rs
  • src/services/remote_storage_target_service/tests.rs
  • src/services/task_service/storage_policy_cleanup.rs
  • src/services/task_service/types.rs
  • src/services/workspace_storage_service/multipart/streaming_direct.rs
  • src/storage/connectors/common.rs
  • src/storage/connectors/mod.rs
  • src/storage/connectors/models.rs
  • src/storage/connectors/tests.rs
  • src/storage/drivers/azure_blob/mod.rs
  • src/storage/drivers/local/tests.rs
  • src/storage/drivers/remote/mod.rs
  • src/storage/drivers/remote/tests.rs
  • src/storage/drivers/s3/tests.rs
  • src/storage/drivers/s3_compatible.rs
  • src/storage/drivers/tencent_cos/signing.rs
  • src/storage/drivers/tencent_cos/tests.rs
  • src/storage/registry.rs
  • src/storage/remote_protocol/client.rs
  • src/storage/remote_protocol/mod.rs
  • src/storage/remote_protocol/models.rs
  • src/storage/remote_protocol/tests.rs
  • tests/test_migrations.rs
  • tests/test_remote_storage.rs
💤 Files with no reviewable changes (12)
  • src/services/remote_storage_target_service/models.rs
  • frontend-panel/src/i18n/locales/zh/admin/remote-nodes.json
  • frontend-panel/src/components/admin/admin-remote-nodes-page/remoteNodeRemoteStorageTargetPresentation.test.ts
  • src/services/remote_storage_target_service/local_profiles.rs
  • src/entities/remote_storage_target.rs
  • frontend-panel/src/i18n/locales/en/admin/remote-nodes.json
  • frontend-panel/src/components/admin/remoteStorageTargetDialogShared.ts
  • frontend-panel/src/pages/admin/AdminRemoteNodesPage.test.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetForm.tsx
  • src/storage/remote_protocol/models.rs
  • frontend-panel/src/services/adminService.test.ts
  • src/services/remote_storage_target_service/normalization.rs

Comment thread frontend-panel/src/pages/admin/AdminPoliciesPage.tsx
Comment thread src/services/master_binding_service.rs
Comment thread src/services/policy_service/policies.rs Outdated
Comment thread src/services/policy_service/policies.rs
Comment thread tests/test_migrations.rs
- Add `remote_storage_target_key` column to `storage_policies` table with migration round-trip test
- Introduce `index_helpers` module with cross-database helpers for drop, rename, and existence checks on indexes
- Fix MySQL migration path by renaming indexes in-place instead of drop/recreate after table rename
- Guard `down()` migration steps against idempotent column and index removal
- Add `PolicyRemoteStorageTargetRequired` and `RemoteStorageTargetNotFound` error codes to API and i18n (en/zh)
- Skip remote targetvalidation on policy update when node and target binding are unchanged
- Split `authorize_internal_request` into read and write variants; make `max_file_size` required on write paths
- Always append `max_file_size` query param in remote protocol client presigned URLs
- Propagate `RemoteStorageTargetNotFound` through remote error kind mapping and status error builder
- Add tests for target key validation, unchanged binding update, stale response races, and quick-create flow in policy wizard
- Remove `max_file_size` field from `RemoteCreateLocalStorageTargetRequest`
@AptS-1547 AptS-1547 self-assigned this Jul 4, 2026
@AptS-1547 AptS-1547 added Enhancement New feature or request Priority: Medium Medium priority issue Rust Pull requests that update Rust code TypeScript Pull requests that update JavaScript code labels Jul 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/storage/remote_protocol/tests.rs (1)

856-862: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

断言精度倒退:contains 太松了。

原来 ends_with("/compose") 改成 contains("/compose") 之后,只要路径任何位置出现 "/compose" 子串(哪怕是 query 参数值或者对象 key 里的巧合字符串)都会被判定通过,起不到验证路由正确性的作用了。既然只是因为新增了 query 参数导致路径不再以 "/compose" 结尾,应该先按 ? 切掉 query 部分再做 ends_with 判断,而不是直接放宽成子串匹配。

🔧 建议修复
     assert!(requests.iter().any(|request| {
+        let path = request
+            .path_and_query
+            .split('?')
+            .next()
+            .unwrap_or(request.path_and_query.as_str());
         request.method == "POST"
-            && request.path_and_query.contains("/compose")
+            && path.ends_with("/compose")
             && serde_json::from_slice::<serde_json::Value>(&request.body)
                 .expect("compose request body should be JSON")["expected_size"]
                 == 6
     }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/remote_protocol/tests.rs` around lines 856 - 862, The request
assertion in the remote protocol test has become too permissive by using the
path_and_query contains("/compose") check, which weakens route validation.
Update the test around the request matching logic to strip off any query string
from request.path_and_query and then verify the path ends with "/compose",
keeping the JSON expected_size assertion unchanged. Use the existing
request.iter().any closure and path_and_query field as the locating symbols when
tightening the match.
tests/test_migrations.rs (1)

268-307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

测试只验证了列,没验证配套索引的往返。

storage_policy_remote_storage_target_key_migration_round_trips_column 只断言了 remote_storage_target_key 列的存在性,但 m20260704_000002up/down 同时也创建/删除了 idx_storage_policies_remote_target 索引(remote_node_id + remote_storage_target_key)。建议顺手加个索引存在性断言,覆盖面更完整。

新增两段迁移集成测试:storage_policy_remote_storage_target_key_migration_round_trips_column:验证 up 后 storage_policies 新增列 remote_storage_target_key、down 后移除、再次 up 后恢复。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_migrations.rs` around lines 268 - 307, The migration round-trip
test currently checks only the `remote_storage_target_key` column, but
`m20260704_000002` also manages the `idx_storage_policies_remote_target` index.
Update `storage_policy_remote_storage_target_key_migration_round_trips_column`
in `tests/test_migrations.rs` to also assert the index exists after
`CurrentMigrator::up`, is removed after `CurrentMigrator::down`, and is restored
after reapplying, using the existing helpers and `sqlite_table_indexes`-style
lookup if available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_migrations.rs`:
- Around line 268-307: add coverage for the MySQL-specific migration path in the
migration tests: the existing storage policy remote target key round-trip test
in test_migrations.rs only exercises sqlite::memory() and never hits
rename_mysql_index_if_exists in
m20260704_000001_rename_managed_ingress_profiles_to_remote_storage_targets. Add
a test that runs with ASTER_TEST_DATABASE_BACKEND=mysql, or refactor the
migration suite to run this case per backend, so the MySQL branch in
CurrentMigrator is actually validated.

---

Nitpick comments:
In `@src/storage/remote_protocol/tests.rs`:
- Around line 856-862: The request assertion in the remote protocol test has
become too permissive by using the path_and_query contains("/compose") check,
which weakens route validation. Update the test around the request matching
logic to strip off any query string from request.path_and_query and then verify
the path ends with "/compose", keeping the JSON expected_size assertion
unchanged. Use the existing request.iter().any closure and path_and_query field
as the locating symbols when tightening the match.

In `@tests/test_migrations.rs`:
- Around line 268-307: The migration round-trip test currently checks only the
`remote_storage_target_key` column, but `m20260704_000002` also manages the
`idx_storage_policies_remote_target` index. Update
`storage_policy_remote_storage_target_key_migration_round_trips_column` in
`tests/test_migrations.rs` to also assert the index exists after
`CurrentMigrator::up`, is removed after `CurrentMigrator::down`, and is restored
after reapplying, using the existing helpers and `sqlite_table_indexes`-style
lookup if available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c06b37ed-eb6d-402d-a8ec-e7d31d96b08a

📥 Commits

Reviewing files that changed from the base of the PR and between 067926e and 054ab0c.

📒 Files selected for processing (35)
  • .gitignore
  • frontend-panel/src/components/admin/StoragePolicyDialog.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.test.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx
  • frontend-panel/src/components/admin/storage-policy-dialog/StoragePolicyRemoteFields.test.tsx
  • frontend-panel/src/i18n/index.test.ts
  • frontend-panel/src/i18n/locales/en/errors/managed-ingress.json
  • frontend-panel/src/i18n/locales/en/errors/storage.json
  • frontend-panel/src/i18n/locales/zh/errors/managed-ingress.json
  • frontend-panel/src/i18n/locales/zh/errors/storage.json
  • frontend-panel/src/pages/admin/AdminPoliciesPage.test.tsx
  • frontend-panel/src/pages/admin/AdminPoliciesPage.tsx
  • frontend-panel/src/services/api.generated.ts
  • frontend-panel/src/types/api-helpers.ts
  • migration/src/index_helpers.rs
  • migration/src/lib.rs
  • migration/src/m20260704_000001_rename_managed_ingress_profiles_to_remote_storage_targets.rs
  • migration/src/m20260704_000002_add_remote_storage_target_key_to_storage_policies.rs
  • migration/src/m20260705_000001_drop_remote_storage_target_max_file_size.rs
  • src/api/api_error_code.rs
  • src/api/routes/internal_storage.rs
  • src/services/master_binding_service.rs
  • src/services/policy_service/policies.rs
  • src/services/remote_storage_target_service/target.rs
  • src/services/remote_storage_target_service/tests.rs
  • src/storage/connectors/tests.rs
  • src/storage/remote_protocol/client.rs
  • src/storage/remote_protocol/errors.rs
  • src/storage/remote_protocol/tests.rs
  • tests/test_azure_blob.rs
  • tests/test_local_driver_security.rs
  • tests/test_migrations.rs
  • tests/test_policies.rs
  • tests/test_remote_storage.rs
  • tests/test_s3.rs
✅ Files skipped from review due to trivial changes (7)
  • frontend-panel/src/i18n/locales/zh/errors/managed-ingress.json
  • .gitignore
  • frontend-panel/src/i18n/locales/en/errors/managed-ingress.json
  • tests/test_s3.rs
  • frontend-panel/src/i18n/locales/zh/errors/storage.json
  • frontend-panel/src/types/api-helpers.ts
  • frontend-panel/src/services/api.generated.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • migration/src/m20260705_000001_drop_remote_storage_target_max_file_size.rs
  • src/storage/connectors/tests.rs
  • migration/src/lib.rs
  • migration/src/m20260704_000002_add_remote_storage_target_key_to_storage_policies.rs
  • src/services/remote_storage_target_service/target.rs
  • src/storage/remote_protocol/client.rs
  • frontend-panel/src/components/admin/StoragePolicyDialog.tsx
  • src/services/remote_storage_target_service/tests.rs
  • src/services/policy_service/policies.rs
  • frontend-panel/src/pages/admin/AdminPoliciesPage.tsx
  • frontend-panel/src/components/admin/admin-remote-nodes-page/RemoteNodeRemoteStorageTargetSection.tsx

Comment thread tests/test_migrations.rs
…ests

- Keep quick-create draft open on storage target creation failure in RemoteNodeRemoteStorageTargetSection by catching errors silently and letting parent handlers surface API errors
- Add frontend test verifying draft form remains visible with original values when createStorageTarget rejects
- Refactor sqlite_index_exists into sqlite_table_index_exists accepting a table name parameter; add mysql_table_index_exists helper using information_schema.statistics
- Extend storage_policy_remote_storage_target_key migration test to assert idx_storage_policies_remote_target index is created, removed on rollback, and restored on reapply
- Add mysql_remote_storage_target_rename_migration_round_trips_indexes test covering full up/down/up cycle for renamed indexes on remote_storage_targets vs managed_ingress_profiles tables
- Fix remote_client_object_profile_and_compose_paths_roundtrip assertion to strip query string before checking /compose path suffix
- Register RENAME_MANAGED_INGRESS_PROFILES_MIGRATION constant and steps_to_roll_back_rename_managed_ingress_profiles helper in test_migrations.rs
@AptS-1547 AptS-1547 merged commit 49fa7bd into master Jul 5, 2026
4 of 7 checks passed
@AptS-1547 AptS-1547 deleted the feat/issue-370 branch July 5, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request Priority: Medium Medium priority issue Rust Pull requests that update Rust code TypeScript Pull requests that update JavaScript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0.3: Rework remote storage target and policy ownership

1 participant