forked from EduCraft/curriculum-project-hub
fix(hub): lock provider id during BYOK rotate + scroll/focus feedback
Clicking 轮换 on a BYOK provider row prefilled the 供应方 ID below but gave
no signal — no scroll, no focus, no toast — so it looked dead ("点不进去").
The 供应方 ID input stayed editable too: changing it silently created a new
connection (version 1) instead of rotating the one clicked.
- startRotate now locks the id (readonly), scrolls the form into view,
focuses 接口地址, and toasts "已填入供应方 X…".
- Heading switches between 轮换凭据 · <id> and 新建 / 轮换 BYOK 凭据;
an inline hint explains how to create a different provider (清空).
- save() distinguishes create vs rotate via the returned activeVersion
(version 1 → 已创建 BYOK 连接, else 凭据已轮换).
- Add toastInfo to $lib/toast for the info-level affordance.
Backend unchanged: list/rotate/reject-member/reject-platform-overwrite tests
already pass; this is a UI feedback + id-lock fix.
This commit is contained in:
@@ -32,3 +32,6 @@ export function toastSuccess(message: string): void {
|
|||||||
export function toastError(message: string): void {
|
export function toastError(message: string): void {
|
||||||
pushToast(message, 'error', 5000);
|
pushToast(message, 'error', 5000);
|
||||||
}
|
}
|
||||||
|
export function toastInfo(message: string): void {
|
||||||
|
pushToast(message, 'info');
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { tick } from 'svelte';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { api, type ProviderConnectionRow } from '$lib/api';
|
import { api, type ProviderConnectionRow } from '$lib/api';
|
||||||
import { session } from '$lib/session';
|
import { session } from '$lib/session';
|
||||||
@@ -8,7 +9,7 @@
|
|||||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||||
import LoadingState from '$lib/components/LoadingState.svelte';
|
import LoadingState from '$lib/components/LoadingState.svelte';
|
||||||
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
|
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
|
||||||
import { toastError, toastSuccess } from '$lib/toast';
|
import { toastError, toastInfo, toastSuccess } from '$lib/toast';
|
||||||
|
|
||||||
const org = $derived(resolveOrg($session.me, page.url.search));
|
const org = $derived(resolveOrg($session.me, page.url.search));
|
||||||
const slug = $derived(org?.slug ?? '');
|
const slug = $derived(org?.slug ?? '');
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
|
|
||||||
let providerId = $state('');
|
let providerId = $state('');
|
||||||
|
let rotatingId = $state<string | null>(null);
|
||||||
let baseUrl = $state('');
|
let baseUrl = $state('');
|
||||||
let authToken = $state('');
|
let authToken = $state('');
|
||||||
let anthropicApiKey = $state('');
|
let anthropicApiKey = $state('');
|
||||||
@@ -36,14 +38,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startRotate(row: ProviderConnectionRow) {
|
async function startRotate(row: ProviderConnectionRow) {
|
||||||
providerId = row.providerId;
|
providerId = row.providerId;
|
||||||
|
rotatingId = row.providerId;
|
||||||
baseUrl = '';
|
baseUrl = '';
|
||||||
authToken = '';
|
authToken = '';
|
||||||
anthropicApiKey = '';
|
anthropicApiKey = '';
|
||||||
|
await tick();
|
||||||
|
const el = document.getElementById('base-url');
|
||||||
|
el?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
el?.focus();
|
||||||
|
toastInfo(`已填入供应方 ${row.providerId},请在下方填写新接口地址与访问令牌`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
|
rotatingId = null;
|
||||||
providerId = '';
|
providerId = '';
|
||||||
baseUrl = '';
|
baseUrl = '';
|
||||||
authToken = '';
|
authToken = '';
|
||||||
@@ -70,8 +79,8 @@
|
|||||||
const key = anthropicApiKey.trim();
|
const key = anthropicApiKey.trim();
|
||||||
if (key !== '') body.anthropicApiKey = key;
|
if (key !== '') body.anthropicApiKey = key;
|
||||||
try {
|
try {
|
||||||
await api.rotateProviderConnection(slug, id, body);
|
const saved = await api.rotateProviderConnection(slug, id, body);
|
||||||
toastSuccess('凭据已轮换');
|
toastSuccess(saved.activeVersion === 1 ? '已创建 BYOK 连接' : '凭据已轮换');
|
||||||
resetForm();
|
resetForm();
|
||||||
await load();
|
await load();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -139,14 +148,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="saas-card-pad">
|
<div class="saas-card-pad">
|
||||||
<h3 class="saas-section-title mb-1">轮换 BYOK 凭据</h3>
|
<h3 class="saas-section-title mb-1">{rotatingId ? `轮换凭据 · ${rotatingId}` : '新建 / 轮换 BYOK 凭据'}</h3>
|
||||||
<p class="saas-muted mb-4">
|
<p class="saas-muted mb-4">
|
||||||
密钥仅写入新版本,旧版本归档;保存时需重新填写接口地址与访问令牌。平台托管连接不在此处管理。
|
密钥仅写入新版本,旧版本归档;保存时需重新填写接口地址与访问令牌。平台托管连接不在此处管理。
|
||||||
</p>
|
</p>
|
||||||
<div class="grid gap-5">
|
<div class="grid gap-5">
|
||||||
<div>
|
<div>
|
||||||
<Label.Root class="saas-label" for="provider-id">供应方 ID</Label.Root>
|
<Label.Root class="saas-label" for="provider-id">供应方 ID</Label.Root>
|
||||||
<input id="provider-id" class="saas-input font-mono text-sm" bind:value={providerId} placeholder="openrouter" />
|
<input id="provider-id" class="saas-input font-mono text-sm" bind:value={providerId} placeholder="openrouter" readonly={rotatingId !== null} />
|
||||||
|
{#if rotatingId}
|
||||||
|
<p class="mt-1 text-xs text-surface-500">轮换中:ID 已锁定。如需新建其他供应方,点"清空"后填写新 ID。</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label.Root class="saas-label" for="base-url">接口地址</Label.Root>
|
<Label.Root class="saas-label" for="base-url">接口地址</Label.Root>
|
||||||
@@ -169,4 +181,4 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
Reference in New Issue
Block a user