From 875f45d70fd84d2ca86902b38b04db006b099366 Mon Sep 17 00:00:00 2001 From: MasterChiefCN <49179444+MasterChiefCN@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:06:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(storage):=20=E4=BF=AE=E5=A4=8D=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E4=BA=91=20COS=20=E8=BF=9E=E6=8E=A5=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=20(#362)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/package.json | 2 +- web/src/lib/oss-settings.ts | 40 +++++++++++++------ .../admin/settings/storage-settings-page.tsx | 26 ++++++------ web/test/oss-settings.test.ts | 28 ++++++++++++- 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/web/package.json b/web/package.json index 458adcfb2..b8bb28ee2 100644 --- a/web/package.json +++ b/web/package.json @@ -7,7 +7,7 @@ "build:bridge": "bun ../canvas-agent/scripts/build-comfy-bridge.ts", "dev": "vite --host 0.0.0.0 --port 3000", "build": "bun run build:bridge && tsc --noEmit && vite build", - "pretest": "bun test test/admin-ui-regressions.test.ts test/password-reset.test.ts", + "pretest": "bun test test/admin-ui-regressions.test.ts test/password-reset.test.ts test/oss-settings.test.ts", "typecheck": "tsc --noEmit", "test:canvas": "bun test test/canvas-media-performance.test.ts test/canvas-node-title-interaction.test.ts", "test": "bun test test/api-request.test.ts test/video-api-contracts.test.ts test/image-api-contracts.test.ts test/channel-model-catalog.test.ts test/model-protocols.test.ts test/model-capabilities.test.ts test/agent-prompt-cache.test.ts test/canvas-agent-context.test.ts test/canvas-batch-connection.test.ts test/canvas-character-reference.test.ts test/canvas-document.test.ts test/canvas-drawing-engine.test.ts test/canvas-emotion-edit-plan.test.ts test/canvas-extension-nodes.test.ts test/canvas-folder.test.ts test/canvas-generation-layout.test.ts test/canvas-generation-submission.test.ts test/canvas-image-batch-retry.test.ts test/canvas-keyboard.test.ts test/canvas-node-generation-mentions.test.ts test/canvas-node-search.test.ts test/canvas-leafer-viewport.test.ts test/canvas-media-download.test.ts test/canvas-local-runtime.test.ts test/canvas-model-policy.test.ts test/canvas-resource-mention-editor.test.ts test/canvas-resource-references.test.ts test/canvas-storyboard-assets.test.ts test/canvas-storyboard-references.test.ts test/canvas-node-config-reset.test.ts test/canvas-node-registry.test.ts test/canvas-video-frame.test.ts test/canvas-video-regeneration.test.ts test/canvas-video-timeline-segments.test.ts test/create-canvas-handoff.test.ts test/create-library-button.test.ts test/creation-references.test.ts test/director-transform-semantics.test.ts test/director-bone-precedence.test.ts test/director-gesture-transaction.test.ts test/director-transaction-lifecycle.test.ts test/director-session-authority.test.ts test/director-placement.test.ts test/director-resources.test.ts test/director-recovery.test.ts test/director-node-preview.test.ts test/generation-asset-repository.node.test.ts test/generation-storage-consistency.test.ts test/generation-task-durability.node.test.ts test/generation-task-local.test.ts test/generation-task-materializer.test.ts test/task-media-fallback.test.ts test/user-scoped-generation-persistence.test.ts test/local-channel-permission.test.ts test/local-channel-runtime-projection.node.test.mjs test/local-channel-task-center.node.test.mjs test/local-channel-ui.node.test.mjs test/local-cli-settings.test.ts test/local-dreamina-cli.test.ts test/local-dreamina-generation.test.ts test/local-dreamina-model-catalog.test.ts test/local-runtime-bootstrap.test.ts test/local-runtime-connection.test.ts test/local-runtime.test.ts test/client-id.test.ts test/grok-image-prompt-limit.test.ts test/image-resolution-tiers.test.ts test/gemini-image.test.ts test/libtv-import.test.ts test/timeline-to-ffmpeg.test.ts test/canvas-video-segment-args.test.ts test/tapnow-import.test.ts test/prompt-optimizer-drawer.test.ts test/director-save-coordinator.test.ts test/director-save-wiring.test.ts test/director-diagnostics.test.ts test/director-repro-runtime.test.ts test/director-diagnostics-recorder.test.ts test/director-keyframe-editing.test.ts test/director-shortcuts.test.ts test/director-keyframe-delete-wiring.test.ts test/director-templates.test.ts test/director-modes.test.ts test/director-template-mode-wiring.test.ts test/director-view-modes.test.ts && bun test test/local-dreamina-cross-runtime.test.ts", diff --git a/web/src/lib/oss-settings.ts b/web/src/lib/oss-settings.ts index 7053d5710..8008adf6e 100644 --- a/web/src/lib/oss-settings.ts +++ b/web/src/lib/oss-settings.ts @@ -18,6 +18,8 @@ export type OSSConnectionTestInput = { pathStyle?: boolean; }; +export type OSSConnectionTestDraft = Omit, "provider"> & Pick; + export type OSSConnectionTestResult = { ok: boolean; message?: string; @@ -61,19 +63,7 @@ const S3_PRESET_HINTS: Record) { return Object.keys(changedValues).some((key) => CONNECTION_FIELDS.has(key)); } + +export function normalizeOSSConnectionTestInput(input: OSSConnectionTestDraft): OSSConnectionTestInput { + return { + provider: input.provider, + s3Preset: input.s3Preset || "custom", + region: trimConnectionValue(input.region), + endpoint: trimConnectionURL(input.endpoint), + cdnBaseUrl: trimConnectionURL(input.cdnBaseUrl), + bucket: trimConnectionValue(input.bucket), + accessKeyId: trimConnectionValue(input.accessKeyId), + accessKeySecret: trimConnectionValue(input.accessKeySecret), + sessionToken: trimConnectionValue(input.sessionToken), + pathPrefix: (trimConnectionValue(input.pathPrefix) || DEFAULT_OSS_PATH_PREFIX).replace(/^\/+|\/+$/g, ""), + pathStyle: input.pathStyle === true, + }; +} + +function trimConnectionValue(value?: string) { + return (value || "").trim(); +} + +function trimConnectionURL(value?: string) { + return trimConnectionValue(value).replace(/\/+$/, ""); +} diff --git a/web/src/pages/admin/settings/storage-settings-page.tsx b/web/src/pages/admin/settings/storage-settings-page.tsx index 94513e9f4..4fa983b4d 100644 --- a/web/src/pages/admin/settings/storage-settings-page.tsx +++ b/web/src/pages/admin/settings/storage-settings-page.tsx @@ -3,7 +3,7 @@ import { AlertTriangle, BadgeCheck, Check, Cloud, Database, Globe2, HardDrive, K import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { useBlocker } from "react-router"; -import { changesRequireOSSRetest, DEFAULT_OSS_PATH_PREFIX, getS3PresetHints, S3_PRESET_OPTIONS, type OSSConnectionTestResult, type S3Preset } from "@/lib/oss-settings"; +import { changesRequireOSSRetest, DEFAULT_OSS_PATH_PREFIX, getS3PresetHints, normalizeOSSConnectionTestInput, S3_PRESET_OPTIONS, type OSSConnectionTestResult, type S3Preset } from "@/lib/oss-settings"; import { cn } from "@/lib/utils"; import { getAdminOSSSetting, testAdminOSSConnection, updateAdminOSSSetting, type AdminOSSSetting } from "@/services/api/auth"; import { AdminPageFrame } from "../components/admin-shell"; @@ -770,20 +770,20 @@ function trimTrailingSlash(value: string) { return value.trim().replace(/\/+$/, ""); } -function connectionInput(values: OSSFormValues) { - return { - provider: values.mode === "local" ? ("aliyun" as const) : values.mode, +function connectionInput(values: Partial) { + return normalizeOSSConnectionTestInput({ + provider: !values.mode || values.mode === "local" ? ("aliyun" as const) : values.mode, s3Preset: values.s3Preset, - region: values.region.trim(), - endpoint: trimTrailingSlash(values.endpoint), - cdnBaseUrl: trimTrailingSlash(values.cdnBaseUrl), - bucket: values.bucket.trim(), - accessKeyId: values.accessKeyId.trim(), - accessKeySecret: values.accessKeySecret.trim(), - sessionToken: values.sessionToken.trim(), - pathPrefix: (values.pathPrefix.trim() || DEFAULT_OSS_PATH_PREFIX).replace(/^\/+|\/+$/g, ""), + region: values.region, + endpoint: values.endpoint, + cdnBaseUrl: values.cdnBaseUrl, + bucket: values.bucket, + accessKeyId: values.accessKeyId, + accessKeySecret: values.accessKeySecret, + sessionToken: values.sessionToken, + pathPrefix: values.pathPrefix, pathStyle: values.pathStyle === true, - }; + }); } function ConnectionTestStatus({ result, stale }: { result: OSSConnectionTestResult | null; stale: boolean }) { diff --git a/web/test/oss-settings.test.ts b/web/test/oss-settings.test.ts index e2127e2a9..6e3d3bc18 100644 --- a/web/test/oss-settings.test.ts +++ b/web/test/oss-settings.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { changesRequireOSSRetest, DEFAULT_OSS_PATH_PREFIX, getS3PresetHints } from "../src/lib/oss-settings"; +import { changesRequireOSSRetest, DEFAULT_OSS_PATH_PREFIX, getS3PresetHints, normalizeOSSConnectionTestInput } from "../src/lib/oss-settings"; describe("OSS settings helpers", () => { test("provides editable S3 endpoint hints for known presets", () => { @@ -17,4 +17,30 @@ describe("OSS settings helpers", () => { test("uses the product path prefix by default", () => { expect(DEFAULT_OSS_PATH_PREFIX).toBe("open-ai-canvas"); }); + + test("normalizes a Tencent COS test draft when S3-only fields are not mounted", () => { + const input = normalizeOSSConnectionTestInput({ + provider: "tencent", + region: " ap-guangzhou ", + endpoint: " https://cos.ap-guangzhou.myqcloud.com/ ", + bucket: " example-1250000000 ", + accessKeyId: " secret-id ", + accessKeySecret: " secret-key ", + pathPrefix: " /canvas/ ", + }); + + expect(input).toMatchObject({ + provider: "tencent", + region: "ap-guangzhou", + endpoint: "https://cos.ap-guangzhou.myqcloud.com", + cdnBaseUrl: "", + bucket: "example-1250000000", + accessKeyId: "secret-id", + accessKeySecret: "secret-key", + sessionToken: "", + pathPrefix: "canvas", + s3Preset: "custom", + pathStyle: false, + }); + }); });