Add OIDC login flow to UI and fix dark mode datetime picker icons
All checks were successful
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 50s
CI / deploy (push) Successful in 31s

- Add "Sign in with SSO" button on login page (shown when OIDC is configured)
- Add /oidc/callback route to exchange authorization code for JWT tokens
- Add loginWithOidcCode action to auth store
- Treat issuer URI as complete discovery URL (no auto-append of .well-known)
- Update admin page placeholder to show full discovery URL format
- Fix datetime picker calendar icon visibility in dark mode (color-scheme)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-14 14:19:06 +01:00
parent b024f83c26
commit 84f4c505a2
8 changed files with 208 additions and 4 deletions

View File

@@ -1,12 +1,31 @@
import { type FormEvent, useState } from 'react';
import { type FormEvent, useEffect, useState } from 'react';
import { Navigate } from 'react-router';
import { useAuthStore } from './auth-store';
import { config } from '../config';
import styles from './LoginPage.module.css';
interface OidcInfo {
clientId: string;
authorizationEndpoint: string;
}
export function LoginPage() {
const { isAuthenticated, login, loading, error } = useAuthStore();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [oidc, setOidc] = useState<OidcInfo | null>(null);
const [oidcLoading, setOidcLoading] = useState(false);
useEffect(() => {
fetch(`${config.apiBaseUrl}/auth/oidc/config`)
.then((res) => (res.ok ? res.json() : null))
.then((data) => {
if (data?.authorizationEndpoint && data?.clientId) {
setOidc({ clientId: data.clientId, authorizationEndpoint: data.authorizationEndpoint });
}
})
.catch(() => {});
}, []);
if (isAuthenticated) return <Navigate to="/" replace />;
@@ -15,6 +34,19 @@ export function LoginPage() {
login(username, password);
};
const handleOidcLogin = () => {
if (!oidc) return;
setOidcLoading(true);
const redirectUri = `${window.location.origin}/oidc/callback`;
const params = new URLSearchParams({
response_type: 'code',
client_id: oidc.clientId,
redirect_uri: redirectUri,
scope: 'openid email profile',
});
window.location.href = `${oidc.authorizationEndpoint}?${params}`;
};
return (
<div className={styles.page}>
<form className={styles.card} onSubmit={handleSubmit}>
@@ -27,6 +59,22 @@ export function LoginPage() {
</div>
<div className={styles.subtitle}>Sign in to access the observability dashboard</div>
{oidc && (
<>
<button
className={styles.ssoButton}
type="button"
onClick={handleOidcLogin}
disabled={oidcLoading}
>
{oidcLoading ? 'Redirecting...' : 'Sign in with SSO'}
</button>
<div className={styles.divider}>
<span className={styles.dividerText}>or</span>
</div>
</>
)}
<div className={styles.field}>
<label className={styles.label}>Username</label>
<input