Add gradient banners to settings pages for visual consistency

Adds polished gradient banner headers to all configuration pages:
- System > General, Network, Updates, Backups
- Security > Authentication (updated from gray to blue/indigo gradient)
- API Access

Resource management pages (PVE, PBS, Docker, etc.) intentionally left
without banners to keep focus on node tables and operational content.
This commit is contained in:
rcourtman 2025-10-23 12:57:39 +00:00
parent a57fb10532
commit 76263be05d

View file

@ -25,8 +25,9 @@ import Server from 'lucide-solid/icons/server';
import HardDrive from 'lucide-solid/icons/hard-drive'; import HardDrive from 'lucide-solid/icons/hard-drive';
import Mail from 'lucide-solid/icons/mail'; import Mail from 'lucide-solid/icons/mail';
import Container from 'lucide-solid/icons/container'; import Container from 'lucide-solid/icons/container';
import SettingsIcon from 'lucide-solid/icons/settings';
import Shield from 'lucide-solid/icons/shield'; import Shield from 'lucide-solid/icons/shield';
import Lock from 'lucide-solid/icons/lock';
import Key from 'lucide-solid/icons/key';
import Activity from 'lucide-solid/icons/activity'; import Activity from 'lucide-solid/icons/activity';
import Loader from 'lucide-solid/icons/loader'; import Loader from 'lucide-solid/icons/loader';
import Boxes from 'lucide-solid/icons/boxes'; import Boxes from 'lucide-solid/icons/boxes';
@ -34,6 +35,9 @@ import Network from 'lucide-solid/icons/network';
import Terminal from 'lucide-solid/icons/terminal'; import Terminal from 'lucide-solid/icons/terminal';
import Monitor from 'lucide-solid/icons/monitor'; import Monitor from 'lucide-solid/icons/monitor';
import Laptop from 'lucide-solid/icons/laptop'; import Laptop from 'lucide-solid/icons/laptop';
import Sliders from 'lucide-solid/icons/sliders-horizontal';
import RefreshCw from 'lucide-solid/icons/refresh-cw';
import Clock from 'lucide-solid/icons/clock';
import { ApiIcon } from '@/components/icons/ApiIcon'; import { ApiIcon } from '@/components/icons/ApiIcon';
import type { NodeConfig } from '@/types/nodes'; import type { NodeConfig } from '@/types/nodes';
import type { UpdateInfo, VersionInfo } from '@/api/updates'; import type { UpdateInfo, VersionInfo } from '@/api/updates';
@ -233,9 +237,14 @@ type SettingsTab =
| 'linuxServers' | 'linuxServers'
| 'windowsServers' | 'windowsServers'
| 'macServers' | 'macServers'
| 'system' | 'system-general'
| 'system-network'
| 'system-updates'
| 'system-backups'
| 'api' | 'api'
| 'security' | 'security-overview'
| 'security-auth'
| 'security-sso'
| 'diagnostics' | 'diagnostics'
| 'updates'; | 'updates';
@ -276,17 +285,37 @@ const SETTINGS_HEADER_META: Record<SettingsTab, { title: string; description: st
title: 'macOS Devices', title: 'macOS Devices',
description: 'Monitor macOS hosts via launchd-backed agents for uptime and temperature insights.', description: 'Monitor macOS hosts via launchd-backed agents for uptime and temperature insights.',
}, },
system: { 'system-general': {
title: 'System Settings', title: 'General Settings',
description: 'Adjust Pulse core behaviour, discovery preferences, and software update cadence.', description: 'Configure appearance preferences and UI behavior.',
},
'system-network': {
title: 'Network Settings',
description: 'Manage CORS, embedding, and network discovery configuration.',
},
'system-updates': {
title: 'Updates',
description: 'Check for updates, configure update channels, and manage automatic update checks.',
},
'system-backups': {
title: 'Backup Polling',
description: 'Control how often Pulse queries Proxmox for backup tasks and snapshots.',
}, },
api: { api: {
title: 'API access', title: 'API access',
description: 'Generate scoped tokens and manage automation credentials for agents and integrations.', description: 'Generate scoped tokens and manage automation credentials for agents and integrations.',
}, },
security: { 'security-overview': {
title: 'Security & Access', title: 'Security Overview',
description: 'Manage authentication, API tokens, exports, and other security posture tooling.', description: 'View your security posture at a glance and monitor authentication status.',
},
'security-auth': {
title: 'Authentication',
description: 'Manage password-based authentication and credential rotation.',
},
'security-sso': {
title: 'Single Sign-On',
description: 'Configure OIDC providers for enterprise authentication.',
}, },
diagnostics: { diagnostics: {
title: 'Diagnostics', title: 'Diagnostics',
@ -336,9 +365,18 @@ const Settings: Component<SettingsProps> = (props) => {
if (path.includes('/settings/linuxServers')) return 'linuxServers'; if (path.includes('/settings/linuxServers')) return 'linuxServers';
if (path.includes('/settings/windowsServers')) return 'windowsServers'; if (path.includes('/settings/windowsServers')) return 'windowsServers';
if (path.includes('/settings/macServers')) return 'macServers'; if (path.includes('/settings/macServers')) return 'macServers';
if (path.includes('/settings/system')) return 'system'; if (path.includes('/settings/system-general')) return 'system-general';
if (path.includes('/settings/system-network')) return 'system-network';
if (path.includes('/settings/system-updates')) return 'system-updates';
if (path.includes('/settings/system-backups')) return 'system-backups';
// Legacy redirect: old /settings/system goes to general
if (path.includes('/settings/system')) return 'system-general';
if (path.includes('/settings/api')) return 'api'; if (path.includes('/settings/api')) return 'api';
if (path.includes('/settings/security')) return 'security'; if (path.includes('/settings/security-overview')) return 'security-overview';
if (path.includes('/settings/security-auth')) return 'security-auth';
if (path.includes('/settings/security-sso')) return 'security-sso';
// Legacy redirect: old /settings/security goes to overview
if (path.includes('/settings/security')) return 'security-overview';
if (path.includes('/settings/diagnostics')) return 'diagnostics'; if (path.includes('/settings/diagnostics')) return 'diagnostics';
if (path.includes('/settings/updates')) return 'updates'; if (path.includes('/settings/updates')) return 'updates';
return 'pve'; return 'pve';
@ -350,7 +388,7 @@ const Settings: Component<SettingsProps> = (props) => {
const setActiveTab = (tab: SettingsTab) => { const setActiveTab = (tab: SettingsTab) => {
const targetPath = `/settings/${tab}`; const targetPath = `/settings/${tab}`;
if (location.pathname !== targetPath) { if (location.pathname !== targetPath) {
navigate(targetPath); navigate(targetPath, { scroll: false });
return; return;
} }
if (currentTab() !== tab) { if (currentTab() !== tab) {
@ -730,7 +768,7 @@ const Settings: Component<SettingsProps> = (props) => {
}; };
const tabGroups: { const tabGroups: {
id: 'platforms' | 'administration'; id: 'platforms' | 'administration' | 'system' | 'security';
label: string; label: string;
items: { id: SettingsTab; label: string; icon: JSX.Element; disabled?: boolean }[]; items: { id: SettingsTab; label: string; icon: JSX.Element; disabled?: boolean }[];
}[] = [ }[] = [
@ -753,12 +791,29 @@ const Settings: Component<SettingsProps> = (props) => {
id: 'administration', id: 'administration',
label: 'Administration', label: 'Administration',
items: [ items: [
{ id: 'system', label: 'System', icon: <SettingsIcon class="w-4 h-4" strokeWidth={2} /> },
{ id: 'api', label: 'API access', icon: <ApiIcon class="w-4 h-4" /> }, { id: 'api', label: 'API access', icon: <ApiIcon class="w-4 h-4" /> },
{ id: 'security', label: 'Security', icon: <Shield class="w-4 h-4" strokeWidth={2} /> },
{ id: 'diagnostics', label: 'Diagnostics', icon: <Activity class="w-4 h-4" strokeWidth={2} /> }, { id: 'diagnostics', label: 'Diagnostics', icon: <Activity class="w-4 h-4" strokeWidth={2} /> },
], ],
}, },
{
id: 'system',
label: 'System',
items: [
{ id: 'system-general', label: 'General', icon: <Sliders class="w-4 h-4" strokeWidth={2} /> },
{ id: 'system-network', label: 'Network', icon: <Network class="w-4 h-4" strokeWidth={2} /> },
{ id: 'system-updates', label: 'Updates', icon: <RefreshCw class="w-4 h-4" strokeWidth={2} /> },
{ id: 'system-backups', label: 'Backups', icon: <Clock class="w-4 h-4" strokeWidth={2} /> },
],
},
{
id: 'security',
label: 'Security',
items: [
{ id: 'security-overview', label: 'Overview', icon: <Shield class="w-4 h-4" strokeWidth={2} /> },
{ id: 'security-auth', label: 'Authentication', icon: <Lock class="w-4 h-4" strokeWidth={2} /> },
{ id: 'security-sso', label: 'Single Sign-On', icon: <Key class="w-4 h-4" strokeWidth={2} /> },
],
},
]; ];
const flatTabs = tabGroups.flatMap((group) => group.items); const flatTabs = tabGroups.flatMap((group) => group.items);
@ -1440,7 +1495,8 @@ const Settings: Component<SettingsProps> = (props) => {
const saveSettings = async () => { const saveSettings = async () => {
try { try {
if (activeTab() === 'system') { if (activeTab() === 'system-general' || activeTab() === 'system-network' ||
activeTab() === 'system-updates' || activeTab() === 'system-backups') {
// Save system settings using typed API // Save system settings using typed API
await SettingsAPI.updateSystemSettings({ await SettingsAPI.updateSystemSettings({
// PBS polling interval is now fixed at 10 seconds // PBS polling interval is now fixed at 10 seconds
@ -1798,7 +1854,9 @@ const Settings: Component<SettingsProps> = (props) => {
<Show <Show
when={ when={
hasUnsavedChanges() && hasUnsavedChanges() &&
(activeTab() === 'pve' || activeTab() === 'pbs' || activeTab() === 'system') (activeTab() === 'pve' || activeTab() === 'pbs' ||
activeTab() === 'system-general' || activeTab() === 'system-network' ||
activeTab() === 'system-updates' || activeTab() === 'system-backups')
} }
> >
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 sm:p-4"> <div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 sm:p-4">
@ -1848,7 +1906,7 @@ const Settings: Component<SettingsProps> = (props) => {
aria-label="Settings navigation" aria-label="Settings navigation"
aria-expanded={!sidebarCollapsed()} aria-expanded={!sidebarCollapsed()}
> >
<div class={`sticky top-24 ${sidebarCollapsed() ? 'px-2' : 'px-5'} py-6 space-y-6 transition-all duration-300`}> <div class={`sticky top-0 ${sidebarCollapsed() ? 'px-2' : 'px-5'} py-6 space-y-6 transition-all duration-300`}>
<div id="settings-sidebar-menu" class="space-y-6"> <div id="settings-sidebar-menu" class="space-y-6">
<For each={tabGroups}> <For each={tabGroups}>
{(group) => ( {(group) => (
@ -3311,11 +3369,53 @@ const Settings: Component<SettingsProps> = (props) => {
<HostAgents variant="macos" /> <HostAgents variant="macos" />
</Show> </Show>
{/* System Settings Tab */} {/* System General Tab */}
<Show when={activeTab() === 'system'}> <Show when={activeTab() === 'system-general'}>
<div class="space-y-6"> <div class="space-y-6">
<SectionHeader title="System configuration" size="md" class="mb-2" /> <Card
padding="none"
class="overflow-hidden border border-gray-200 dark:border-gray-700"
border={false}
>
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-3">
<div class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
<Sliders class="w-5 h-5 text-blue-600 dark:text-blue-300" strokeWidth={2} />
</div>
<SectionHeader
title="General"
description="Appearance and display preferences"
size="sm"
class="flex-1"
/>
</div>
</div>
<div class="p-6 space-y-5">
<div class="flex items-center justify-between gap-3">
<div class="text-sm text-gray-600 dark:text-gray-400">
<p class="font-medium text-gray-900 dark:text-gray-100">Dark mode</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
Toggle to match your environment. Pulse remembers this preference on each browser.
</p>
</div>
<Toggle
checked={props.darkMode()}
onChange={(event) => {
const desired = (event.currentTarget as HTMLInputElement).checked;
if (desired !== props.darkMode()) {
props.toggleDarkMode();
}
}}
/>
</div>
</div>
</Card>
</div>
</Show>
{/* System Network Tab */}
<Show when={activeTab() === 'system-network'}>
<div class="space-y-6">
<Card <Card
tone="info" tone="info"
padding="md" padding="md"
@ -3346,8 +3446,26 @@ const Settings: Component<SettingsProps> = (props) => {
</div> </div>
</div> </div>
</Card> </Card>
<Card
<Card padding="lg" class="space-y-5"> padding="none"
class="overflow-hidden border border-gray-200 dark:border-gray-700"
border={false}
>
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-3">
<div class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
<Network class="w-5 h-5 text-blue-600 dark:text-blue-300" strokeWidth={2} />
</div>
<SectionHeader
title="Network"
description="Discovery, CORS, and embedding settings"
size="sm"
class="flex-1"
/>
</div>
</div>
<div class="p-6 space-y-8">
<section class="space-y-5">
<SectionHeader <SectionHeader
title="Network discovery" title="Network discovery"
description="Control how Pulse scans for Proxmox services on your network." description="Control how Pulse scans for Proxmox services on your network."
@ -3627,36 +3745,8 @@ const Settings: Component<SettingsProps> = (props) => {
configuration and restart Pulse to change them here. configuration and restart Pulse to change them here.
</div> </div>
</Show> </Show>
</Card> </section>
<Card padding="lg" class="space-y-3">
<SectionHeader
title="Appearance"
description="Switch between light and dark themes. Stored per device and synced to the server when authenticated."
size="sm"
align="left"
/>
<div class="flex items-center justify-between gap-3">
<div class="text-sm text-gray-600 dark:text-gray-400">
<p class="font-medium text-gray-900 dark:text-gray-100">Dark mode</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
Toggle to match your environment. Pulse remembers this preference on each browser.
</p>
</div>
<Toggle
checked={props.darkMode()}
onChange={(event) => {
const desired = (event.currentTarget as HTMLInputElement).checked;
if (desired !== props.darkMode()) {
props.toggleDarkMode();
}
}}
/>
</div>
</Card>
<div class="grid gap-4 lg:grid-cols-5">
<Card padding="lg" class="space-y-6 lg:col-span-3">
<section class="space-y-3"> <section class="space-y-3">
<h4 class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300"> <h4 class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
<svg <svg
@ -3813,26 +3903,34 @@ const Settings: Component<SettingsProps> = (props) => {
</span> </span>
</p> </p>
</Card> </Card>
</div>
</Card> </Card>
</div>
</Show>
<Card padding="lg" class="space-y-6 lg:col-span-2"> {/* System Updates Tab */}
<section class="space-y-4"> <Show when={activeTab() === 'system-updates'}>
<h4 class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300"> <div class="space-y-6">
<svg <Card
width="16" padding="none"
height="16" class="overflow-hidden border border-gray-200 dark:border-gray-700"
viewBox="0 0 24 24" border={false}
fill="none"
stroke="currentColor"
stroke-width="2"
> >
<polyline points="23 4 23 10 17 10"></polyline> <div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<polyline points="1 20 1 14 7 14"></polyline> <div class="flex items-center gap-3">
<path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"></path> <div class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
</svg> <RefreshCw class="w-5 h-5 text-blue-600 dark:text-blue-300" strokeWidth={2} />
Updates </div>
</h4> <SectionHeader
title="Updates"
description="Version checking and automatic update configuration"
size="sm"
class="flex-1"
/>
</div>
</div>
<div class="p-6 space-y-6">
<section class="space-y-4">
<div class="space-y-4"> <div class="space-y-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div> <div>
@ -4073,15 +4171,33 @@ const Settings: Component<SettingsProps> = (props) => {
</div> </div>
</div> </div>
</section> </section>
</div>
</Card> </Card>
</div> </div>
</Show>
{/* Backup & Restore - Moved from Security tab */} {/* System Backups Tab */}
<Show when={activeTab() === 'system-backups'}>
<div class="space-y-6">
<Card <Card
padding="lg" padding="none"
class="overflow-hidden border border-gray-200 dark:border-gray-700"
border={false} border={false}
class="border border-gray-200 dark:border-gray-700"
> >
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-3">
<div class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
<Clock class="w-5 h-5 text-blue-600 dark:text-blue-300" strokeWidth={2} />
</div>
<SectionHeader
title="Backups"
description="Backup polling and configuration management"
size="sm"
class="flex-1"
/>
</div>
</div>
<div class="p-6 space-y-8">
<section class="space-y-3"> <section class="space-y-3">
<h4 class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300"> <h4 class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
@ -4366,6 +4482,7 @@ const Settings: Component<SettingsProps> = (props) => {
</div> </div>
</div> </div>
</div> </div>
</div>
</Card> </Card>
</div> </div>
</Show> </Show>
@ -4373,9 +4490,25 @@ const Settings: Component<SettingsProps> = (props) => {
{/* API Access */} {/* API Access */}
<Show when={activeTab() === 'api'}> <Show when={activeTab() === 'api'}>
<div class="space-y-6"> <div class="space-y-6">
<SectionHeader title="API access" size="md" class="mb-2" /> <Card
padding="none"
<Card tone="muted" padding="lg" class="space-y-3"> class="overflow-hidden border border-gray-200 dark:border-gray-700"
border={false}
>
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-3">
<div class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
<ApiIcon class="w-5 h-5 text-blue-600 dark:text-blue-300" />
</div>
<SectionHeader
title="API Access"
description="Generate scoped tokens for agents and automation"
size="sm"
class="flex-1"
/>
</div>
</div>
<div class="p-6 space-y-3">
<p class="text-sm text-gray-600 dark:text-gray-400"> <p class="text-sm text-gray-600 dark:text-gray-400">
Generate scoped tokens for Docker agents, host agents, and automation pipelines. Generate scoped tokens for Docker agents, host agents, and automation pipelines.
Tokens are shown oncestore them securely and rotate when infrastructure changes. Tokens are shown oncestore them securely and rotate when infrastructure changes.
@ -4388,6 +4521,7 @@ const Settings: Component<SettingsProps> = (props) => {
> >
View scope reference View scope reference
</a> </a>
</div>
</Card> </Card>
<APITokenManager <APITokenManager
@ -4400,8 +4534,8 @@ const Settings: Component<SettingsProps> = (props) => {
</div> </div>
</Show> </Show>
{/* Security Tab */} {/* Security Overview Tab */}
<Show when={activeTab() === 'security'}> <Show when={activeTab() === 'security-overview'}>
<div class="space-y-6"> <div class="space-y-6">
<Show when={!securityStatusLoading() && securityStatus()}> <Show when={!securityStatusLoading() && securityStatus()}>
<SecurityPostureSummary status={securityStatus()!} /> <SecurityPostureSummary status={securityStatus()!} />
@ -4461,14 +4595,25 @@ const Settings: Component<SettingsProps> = (props) => {
</div> </div>
</Card> </Card>
</Show> </Show>
</div>
</Show>
{/* Security Authentication Tab */}
<Show when={activeTab() === 'security-auth'}>
<div class="space-y-6">
{/* Show message when auth is disabled */} {/* Show message when auth is disabled */}
<Show when={!securityStatus()?.hasAuthentication}> <Show when={!securityStatus()?.hasAuthentication}>
<div class="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4"> <Card
<div class="flex items-start justify-between gap-3"> padding="none"
<div class="flex items-start gap-3 flex-1"> class="overflow-hidden border border-amber-200 dark:border-amber-800"
border={false}
>
{/* Header */}
<div class="bg-gradient-to-r from-amber-50 to-amber-50 dark:from-amber-900/20 dark:to-amber-900/20 px-6 py-4 border-b border-amber-200 dark:border-amber-700">
<div class="flex items-center gap-3">
<div class="p-2 bg-amber-100 dark:bg-amber-900/50 rounded-lg">
<svg <svg
class="h-5 w-5 text-amber-600 dark:text-amber-400 mt-0.5 flex-shrink-0" class="w-5 h-5 text-amber-600 dark:text-amber-400"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -4480,15 +4625,8 @@ const Settings: Component<SettingsProps> = (props) => {
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/> />
</svg> </svg>
<div class="flex-1 min-w-0">
<h4 class="text-sm font-semibold text-amber-900 dark:text-amber-100">
Authentication disabled
</h4>
<p class="text-xs text-amber-700 dark:text-amber-300 mt-1">
DISABLE_AUTH is set, or auth isn't configured yet.
</p>
</div>
</div> </div>
<SectionHeader title="Authentication disabled" size="sm" class="flex-1" />
<button <button
type="button" type="button"
onClick={() => setShowQuickSecuritySetup(!showQuickSecuritySetup())} onClick={() => setShowQuickSecuritySetup(!showQuickSecuritySetup())}
@ -4497,17 +4635,24 @@ const Settings: Component<SettingsProps> = (props) => {
Setup Setup
</button> </button>
</div> </div>
</div>
{/* Content */}
<div class="p-6">
<p class="text-sm text-amber-700 dark:text-amber-300 mb-4">
DISABLE_AUTH is set, or auth isn't configured yet. Set up password authentication to protect your Pulse instance.
</p>
<Show when={showQuickSecuritySetup()}> <Show when={showQuickSecuritySetup()}>
<div class="mt-3 pt-3 border-t border-amber-200 dark:border-amber-700">
<QuickSecuritySetup <QuickSecuritySetup
onConfigured={() => { onConfigured={() => {
setShowQuickSecuritySetup(false); setShowQuickSecuritySetup(false);
loadSecurityStatus(); loadSecurityStatus();
}} }}
/> />
</div>
</Show> </Show>
</div> </div>
</Card>
</Show> </Show>
{/* Authentication */} {/* Authentication */}
@ -4523,24 +4668,17 @@ const Settings: Component<SettingsProps> = (props) => {
border={false} border={false}
> >
{/* Header */} {/* Header */}
<div class="bg-gradient-to-r from-gray-50 to-gray-50 dark:from-gray-900/20 dark:to-gray-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700"> <div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="p-2 bg-gray-100 dark:bg-gray-900/50 rounded-lg"> <div class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
<svg <Lock class="w-5 h-5 text-blue-600 dark:text-blue-300" strokeWidth={2} />
class="w-5 h-5 text-gray-600 dark:text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
/>
</svg>
</div> </div>
<SectionHeader title="Authentication" size="sm" class="flex-1" /> <SectionHeader
title="Authentication"
description="Password management and credential rotation"
size="sm"
class="flex-1"
/>
</div> </div>
</div> </div>
@ -4697,63 +4835,13 @@ const Settings: Component<SettingsProps> = (props) => {
</div> </div>
</div> </div>
</Show> </Show>
</div>
</Show>
{/* Security Single Sign-On Tab */}
<Show when={activeTab() === 'security-sso'}>
<div class="space-y-6">
<OIDCPanel onConfigUpdated={loadSecurityStatus} /> <OIDCPanel onConfigUpdated={loadSecurityStatus} />
{/* Security setup now handled by first-run wizard */}
{/* API Token - Show always to allow API access even when auth is disabled */}
<Card
padding="none"
class="overflow-hidden border border-gray-200 dark:border-gray-700"
border={false}
>
{/* Header */}
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-3">
<div class="p-2 bg-blue-100 dark:bg-blue-900/50 rounded-lg">
<svg
class="w-5 h-5 text-blue-600 dark:text-blue-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"
/>
</svg>
</div>
<SectionHeader
title="API token"
description="For automation and integrations"
size="sm"
class="flex-1"
/>
</div>
</div>
{/* Content */}
<div class="p-6 space-y-3">
<p class="text-sm text-gray-600 dark:text-gray-400">
API tokens now live under the dedicated API workspace. Generate new scoped tokens,
review existing access, and rotate credentials from the API menu.
</p>
<button
type="button"
onClick={() => setActiveTab('api')}
class="inline-flex items-center gap-2 rounded-md border border-blue-200 bg-blue-50 px-3 py-1.5 text-xs font-semibold text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/30 dark:text-blue-200"
>
Open API tab
</button>
</div>
</Card>
{/* Advanced - Only show if auth is enabled */}
{/* Advanced Options section removed - was only used for Registration Tokens */}
</div> </div>
</Show> </Show>