Fix
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "9router-app",
|
"name": "9router-app",
|
||||||
"version": "0.4.30",
|
"version": "0.4.31",
|
||||||
"description": "9Router web dashboard",
|
"description": "9Router web dashboard",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default function ProfilePage() {
|
|||||||
const [oidcTestLoading, setOidcTestLoading] = useState(false);
|
const [oidcTestLoading, setOidcTestLoading] = useState(false);
|
||||||
const [oidcTestStatus, setOidcTestStatus] = useState({ type: "", message: "" });
|
const [oidcTestStatus, setOidcTestStatus] = useState({ type: "", message: "" });
|
||||||
const [oidcRedirectUri, setOidcRedirectUri] = useState("/api/auth/oidc/callback");
|
const [oidcRedirectUri, setOidcRedirectUri] = useState("/api/auth/oidc/callback");
|
||||||
|
const [oidcExpanded, setOidcExpanded] = useState(false);
|
||||||
const importFileRef = useRef(null);
|
const importFileRef = useRef(null);
|
||||||
const [proxyForm, setProxyForm] = useState({
|
const [proxyForm, setProxyForm] = useState({
|
||||||
outboundProxyEnabled: false,
|
outboundProxyEnabled: false,
|
||||||
@@ -51,6 +52,7 @@ export default function ProfilePage() {
|
|||||||
oidcLoginLabel: data?.oidcLoginLabel || "Sign in with OIDC",
|
oidcLoginLabel: data?.oidcLoginLabel || "Sign in with OIDC",
|
||||||
});
|
});
|
||||||
setOidcClientSecret("");
|
setOidcClientSecret("");
|
||||||
|
if (data?.authMode === "oidc" || data?.authMode === "both") setOidcExpanded(true);
|
||||||
setProxyForm({
|
setProxyForm({
|
||||||
outboundProxyEnabled: data?.outboundProxyEnabled === true,
|
outboundProxyEnabled: data?.outboundProxyEnabled === true,
|
||||||
outboundProxyUrl: data?.outboundProxyUrl || "",
|
outboundProxyUrl: data?.outboundProxyUrl || "",
|
||||||
@@ -675,13 +677,26 @@ export default function ProfilePage() {
|
|||||||
|
|
||||||
{/* OIDC */}
|
{/* OIDC */}
|
||||||
<Card>
|
<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">
|
<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>
|
<span className="material-symbols-outlined text-[20px]">lock_open</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="text-base sm:text-lg font-semibold">OIDC Dashboard Login</h3>
|
<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>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<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">
|
<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.
|
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>
|
</p>
|
||||||
@@ -820,6 +835,7 @@ export default function ProfilePage() {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Routing Preferences */}
|
{/* Routing Preferences */}
|
||||||
|
|||||||
@@ -252,8 +252,7 @@ export default function ProvidersPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const compatibleProviders = sortItemsByPriority(
|
const compatibleProviders = providerNodes
|
||||||
providerNodes
|
|
||||||
.filter((node) => node.type === "openai-compatible")
|
.filter((node) => node.type === "openai-compatible")
|
||||||
.map((node) => ({
|
.map((node) => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
@@ -262,12 +261,9 @@ export default function ProvidersPage() {
|
|||||||
textIcon: "OC",
|
textIcon: "OC",
|
||||||
apiType: node.apiType,
|
apiType: node.apiType,
|
||||||
}))
|
}))
|
||||||
.filter((p) => matchSearch(p.name)),
|
.filter((p) => matchSearch(p.name));
|
||||||
"apikey",
|
|
||||||
);
|
|
||||||
|
|
||||||
const anthropicCompatibleProviders = sortItemsByPriority(
|
const anthropicCompatibleProviders = providerNodes
|
||||||
providerNodes
|
|
||||||
.filter((node) => node.type === "anthropic-compatible")
|
.filter((node) => node.type === "anthropic-compatible")
|
||||||
.map((node) => ({
|
.map((node) => ({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
@@ -275,23 +271,16 @@ export default function ProvidersPage() {
|
|||||||
color: "#D97757",
|
color: "#D97757",
|
||||||
textIcon: "AC",
|
textIcon: "AC",
|
||||||
}))
|
}))
|
||||||
.filter((p) => matchSearch(p.name)),
|
.filter((p) => matchSearch(p.name));
|
||||||
"apikey",
|
|
||||||
);
|
|
||||||
|
|
||||||
const oauthEntries = sortByPriority(
|
const oauthEntries = Object.entries(OAUTH_PROVIDERS).filter(([, info]) =>
|
||||||
Object.entries(OAUTH_PROVIDERS).filter(([, info]) => matchSearch(info.name)),
|
|
||||||
"oauth",
|
|
||||||
);
|
|
||||||
const freeEntries = sortByPriority(
|
|
||||||
Object.entries(FREE_PROVIDERS).filter(([, info]) => matchSearch(info.name)),
|
|
||||||
"oauth",
|
|
||||||
);
|
|
||||||
const freeTierEntries = sortByPriority(
|
|
||||||
Object.entries(FREE_TIER_PROVIDERS).filter(([, info]) =>
|
|
||||||
matchSearch(info.name),
|
matchSearch(info.name),
|
||||||
),
|
);
|
||||||
"apikey",
|
const freeEntries = Object.entries(FREE_PROVIDERS).filter(([, info]) =>
|
||||||
|
matchSearch(info.name),
|
||||||
|
);
|
||||||
|
const freeTierEntries = Object.entries(FREE_TIER_PROVIDERS).filter(
|
||||||
|
([, info]) => matchSearch(info.name),
|
||||||
);
|
);
|
||||||
const apikeyEntries = sortByPriority(
|
const apikeyEntries = sortByPriority(
|
||||||
Object.entries(APIKEY_PROVIDERS).filter(
|
Object.entries(APIKEY_PROVIDERS).filter(
|
||||||
|
|||||||
@@ -294,15 +294,13 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
|
|||||||
|
|
||||||
{/* Right actions */}
|
{/* Right actions */}
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<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">
|
<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="material-symbols-outlined text-[14px] mr-1.5 text-primary">person</span>
|
||||||
<span className="truncate">{displayName}</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">
|
<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}
|
OIDC
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<HeaderSearch />
|
<HeaderSearch />
|
||||||
|
|||||||
Reference in New Issue
Block a user