This commit is contained in:
decolua
2026-05-12 09:54:58 +07:00
parent c3d91b019b
commit 067b7c5292
4 changed files with 50 additions and 47 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "9router-app",
"version": "0.4.30",
"version": "0.4.31",
"description": "9Router web dashboard",
"private": true,
"scripts": {
+20 -4
View File
@@ -28,6 +28,7 @@ export default function ProfilePage() {
const [oidcTestLoading, setOidcTestLoading] = useState(false);
const [oidcTestStatus, setOidcTestStatus] = useState({ type: "", message: "" });
const [oidcRedirectUri, setOidcRedirectUri] = useState("/api/auth/oidc/callback");
const [oidcExpanded, setOidcExpanded] = useState(false);
const importFileRef = useRef(null);
const [proxyForm, setProxyForm] = useState({
outboundProxyEnabled: false,
@@ -51,6 +52,7 @@ export default function ProfilePage() {
oidcLoginLabel: data?.oidcLoginLabel || "Sign in with OIDC",
});
setOidcClientSecret("");
if (data?.authMode === "oidc" || data?.authMode === "both") setOidcExpanded(true);
setProxyForm({
outboundProxyEnabled: data?.outboundProxyEnabled === true,
outboundProxyUrl: data?.outboundProxyUrl || "",
@@ -675,13 +677,26 @@ export default function ProfilePage() {
{/* OIDC */}
<Card>
<div className="flex items-center gap-3 mb-4">
<button
type="button"
onClick={() => setOidcExpanded((v) => !v)}
className="w-full flex items-center gap-3 text-left"
>
<div className="p-2 rounded-lg bg-indigo-500/10 text-indigo-500 shrink-0">
<span className="material-symbols-outlined text-[20px]">lock_open</span>
</div>
<h3 className="text-base sm:text-lg font-semibold">OIDC Dashboard Login</h3>
</div>
<div className="flex flex-col gap-4">
<div className="flex-1 min-w-0">
<h3 className="text-base sm:text-lg font-semibold">OIDC Dashboard Login</h3>
<p className="text-xs text-text-muted">
{settings.authMode === "oidc" ? "OIDC active" : settings.authMode === "both" ? "Password + OIDC active" : "Optional SSO via Authentik/Keycloak/Google"}
</p>
</div>
<span className="material-symbols-outlined text-text-muted shrink-0">
{oidcExpanded ? "expand_less" : "expand_more"}
</span>
</button>
{oidcExpanded && (
<div className="flex flex-col gap-4 mt-4">
<p className="text-xs sm:text-sm text-text-muted">
Use Authentik or any OIDC provider to sign in to the dashboard. You can enable password-only, OIDC-only, or both for the dashboard; model API access still uses API keys.
</p>
@@ -820,6 +835,7 @@ export default function ProfilePage() {
</p>
)}
</div>
)}
</Card>
{/* Routing Preferences */}
+25 -36
View File
@@ -252,46 +252,35 @@ export default function ProvidersPage() {
}
};
const compatibleProviders = sortItemsByPriority(
providerNodes
.filter((node) => node.type === "openai-compatible")
.map((node) => ({
id: node.id,
name: node.name || "OpenAI Compatible",
color: "#10A37F",
textIcon: "OC",
apiType: node.apiType,
}))
.filter((p) => matchSearch(p.name)),
"apikey",
);
const compatibleProviders = providerNodes
.filter((node) => node.type === "openai-compatible")
.map((node) => ({
id: node.id,
name: node.name || "OpenAI Compatible",
color: "#10A37F",
textIcon: "OC",
apiType: node.apiType,
}))
.filter((p) => matchSearch(p.name));
const anthropicCompatibleProviders = sortItemsByPriority(
providerNodes
.filter((node) => node.type === "anthropic-compatible")
.map((node) => ({
id: node.id,
name: node.name || "Anthropic Compatible",
color: "#D97757",
textIcon: "AC",
}))
.filter((p) => matchSearch(p.name)),
"apikey",
);
const anthropicCompatibleProviders = providerNodes
.filter((node) => node.type === "anthropic-compatible")
.map((node) => ({
id: node.id,
name: node.name || "Anthropic Compatible",
color: "#D97757",
textIcon: "AC",
}))
.filter((p) => matchSearch(p.name));
const oauthEntries = sortByPriority(
Object.entries(OAUTH_PROVIDERS).filter(([, info]) => matchSearch(info.name)),
"oauth",
const oauthEntries = Object.entries(OAUTH_PROVIDERS).filter(([, info]) =>
matchSearch(info.name),
);
const freeEntries = sortByPriority(
Object.entries(FREE_PROVIDERS).filter(([, info]) => matchSearch(info.name)),
"oauth",
const freeEntries = Object.entries(FREE_PROVIDERS).filter(([, info]) =>
matchSearch(info.name),
);
const freeTierEntries = sortByPriority(
Object.entries(FREE_TIER_PROVIDERS).filter(([, info]) =>
matchSearch(info.name),
),
"apikey",
const freeTierEntries = Object.entries(FREE_TIER_PROVIDERS).filter(
([, info]) => matchSearch(info.name),
);
const apikeyEntries = sortByPriority(
Object.entries(APIKEY_PROVIDERS).filter(
+4 -6
View File
@@ -294,15 +294,13 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
{/* Right actions */}
<div className="flex items-center gap-1 shrink-0">
{displayName && (
{displayName && loginMethod === "OIDC" && (
<div className="hidden sm:flex items-center max-w-[220px] px-3 py-1.5 rounded-full border border-border bg-surface/70 text-xs text-text-muted truncate">
<span className="material-symbols-outlined text-[14px] mr-1.5 text-primary">person</span>
<span className="truncate">{displayName}</span>
{loginMethod && (
<span className="ml-2 shrink-0 rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary">
{loginMethod}
</span>
)}
<span className="ml-2 shrink-0 rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary">
OIDC
</span>
</div>
)}
<HeaderSearch />