forked from EduCraft/curriculum-project-hub
fix(admin-web): break /admin/login returnTo redirect loop in dev
vite dev only proxied /api and /auth, so /admin/login hit the SPA root layout, re-ran loadSession, got 401, and redirected back to /admin/login with an ever-nesting returnTo. Proxy /admin/login to the backend (which owns it before the SPA fallback) and guard redirectToLogin against re-entering /admin/login. Replace the only emoji-as-icon (the back-arrow on project detail) with the Icon component (new arrow-left glyph). Add scripts/dev-bootstrap.ts for seeding a local Silo (stub probes) so npm run dev can start on a fresh dev DB.
This commit is contained in:
@@ -11,11 +11,12 @@
|
|||||||
| 'projects'
|
| 'projects'
|
||||||
| 'provider'
|
| 'provider'
|
||||||
| 'feishu'
|
| 'feishu'
|
||||||
| 'menu'
|
| 'menu'
|
||||||
| 'logout'
|
| 'logout'
|
||||||
| 'org'
|
| 'org'
|
||||||
| 'chevron'
|
| 'chevron'
|
||||||
| 'folder'
|
| 'arrow-left'
|
||||||
|
| 'folder'
|
||||||
| 'file'
|
| 'file'
|
||||||
| 'check';
|
| 'check';
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -100,6 +101,14 @@
|
|||||||
<svg class={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
<svg class={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||||
</svg>
|
</svg>
|
||||||
|
{:else if name === 'arrow-left'}
|
||||||
|
<svg class={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
{:else if name === 'folder'}
|
{:else if name === 'folder'}
|
||||||
<svg class={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
<svg class={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export function resolveLoginOrgSlug(): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function redirectToLogin(): void {
|
export function redirectToLogin(): void {
|
||||||
|
if (window.location.pathname === '/admin/login') return;
|
||||||
const ret = encodeURIComponent(
|
const ret = encodeURIComponent(
|
||||||
window.location.pathname + window.location.search + window.location.hash,
|
window.location.pathname + window.location.search + window.location.hash,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
|
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
|
||||||
import EmptyState from '$lib/components/EmptyState.svelte';
|
import EmptyState from '$lib/components/EmptyState.svelte';
|
||||||
import SelectField from '$lib/components/SelectField.svelte';
|
import SelectField from '$lib/components/SelectField.svelte';
|
||||||
|
import Icon from '$lib/components/Icon.svelte';
|
||||||
import { toastError, toastSuccess } from '$lib/toast';
|
import { toastError, toastSuccess } from '$lib/toast';
|
||||||
|
|
||||||
const slug = $derived(page.params.slug ?? '');
|
const slug = $derived(page.params.slug ?? '');
|
||||||
@@ -158,7 +159,8 @@
|
|||||||
href={`/admin/org/${slug}/projects`}
|
href={`/admin/org/${slug}/projects`}
|
||||||
class="inline-flex items-center gap-1 text-sm text-surface-700 hover:text-primary-600"
|
class="inline-flex items-center gap-1 text-sm text-surface-700 hover:text-primary-600"
|
||||||
>
|
>
|
||||||
← 返回项目列表
|
<Icon name="arrow-left" class="h-4 w-4" />
|
||||||
|
返回项目列表
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ export default defineConfig({
|
|||||||
proxy: {
|
proxy: {
|
||||||
'/api': 'http://127.0.0.1:8788',
|
'/api': 'http://127.0.0.1:8788',
|
||||||
'/auth': 'http://127.0.0.1:8788',
|
'/auth': 'http://127.0.0.1:8788',
|
||||||
|
// Backend owns /admin/login (registered before the SPA fallback in
|
||||||
|
// src/admin/static.ts). Proxy it in dev so the SPA doesn't re-render
|
||||||
|
// its layout on that path and 401-redirect into a returnTo loop.
|
||||||
|
'/admin/login': 'http://127.0.0.1:8788',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { config } from "dotenv";
|
||||||
|
config();
|
||||||
|
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import { bootstrapAlphaSilo } from "../src/deployment/bootstrap-silo.js";
|
||||||
|
import { loadLocalSecretKeyring, LocalSecretEnvelope } from "../src/security/secretEnvelope.js";
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const keyringFile = process.env["HUB_SECRET_KEYRING_FILE"];
|
||||||
|
if (!keyringFile) throw new Error("HUB_SECRET_KEYRING_FILE is required");
|
||||||
|
const secrets = new LocalSecretEnvelope(await loadLocalSecretKeyring());
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
try {
|
||||||
|
const result = await bootstrapAlphaSilo(
|
||||||
|
prisma,
|
||||||
|
secrets,
|
||||||
|
{
|
||||||
|
organization: {
|
||||||
|
id: "org_default",
|
||||||
|
slug: "default",
|
||||||
|
name: "Default Organization",
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
openId: process.env["FEISHU_BOT_OPEN_ID"] ?? "dev-owner",
|
||||||
|
displayName: "Dev Owner",
|
||||||
|
},
|
||||||
|
feishu: {
|
||||||
|
appId: process.env["FEISHU_APP_ID"] ?? "dev-app",
|
||||||
|
appSecret: process.env["FEISHU_APP_SECRET"] ?? "dev-secret",
|
||||||
|
botOpenId: process.env["FEISHU_BOT_OPEN_ID"] ?? "dev-bot",
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
providerId: "openrouter",
|
||||||
|
baseUrl: process.env["ANTHROPIC_BASE_URL"] ?? "https://openrouter.ai/api",
|
||||||
|
authToken: process.env["ANTHROPIC_AUTH_TOKEN"] ?? "dev-token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
feishu: async () => undefined,
|
||||||
|
provider: async () => undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log("bootstrap ok:", JSON.stringify(result, null, 2));
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error: unknown) => {
|
||||||
|
console.error("[dev-bootstrap] failed:", error instanceof Error ? error.message : String(error));
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user