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 Mail from 'lucide-solid/icons/mail';
import Container from 'lucide-solid/icons/container';
import SettingsIcon from 'lucide-solid/icons/settings';
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 Loader from 'lucide-solid/icons/loader';
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 Monitor from 'lucide-solid/icons/monitor';
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 type { NodeConfig } from '@/types/nodes';
import type { UpdateInfo, VersionInfo } from '@/api/updates';
@ -233,9 +237,14 @@ type SettingsTab =
| 'linuxServers'
| 'windowsServers'
| 'macServers'
| 'system'
| 'system-general'
| 'system-network'
| 'system-updates'
| 'system-backups'
| 'api'
| 'security'
| 'security-overview'
| 'security-auth'
| 'security-sso'
| 'diagnostics'
| 'updates';
@ -276,17 +285,37 @@ const SETTINGS_HEADER_META: Record<SettingsTab, { title: string; description: st
title: 'macOS Devices',
description: 'Monitor macOS hosts via launchd-backed agents for uptime and temperature insights.',
},
system: {
title: 'System Settings',
description: 'Adjust Pulse core behaviour, discovery preferences, and software update cadence.',
'system-general': {
title: 'General Settings',
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: {
title: 'API access',
description: 'Generate scoped tokens and manage automation credentials for agents and integrations.',
},
security: {
title: 'Security & Access',
description: 'Manage authentication, API tokens, exports, and other security posture tooling.',
'security-overview': {
title: 'Security Overview',
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: {
title: 'Diagnostics',
@ -336,9 +365,18 @@ const Settings: Component<SettingsProps> = (props) => {
if (path.includes('/settings/linuxServers')) return 'linuxServers';
if (path.includes('/settings/windowsServers')) return 'windowsServers';
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/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/updates')) return 'updates';
return 'pve';
@ -350,7 +388,7 @@ const Settings: Component<SettingsProps> = (props) => {
const setActiveTab = (tab: SettingsTab) => {
const targetPath = `/settings/${tab}`;
if (location.pathname !== targetPath) {
navigate(targetPath);
navigate(targetPath, { scroll: false });
return;
}
if (currentTab() !== tab) {
@ -730,7 +768,7 @@ const Settings: Component<SettingsProps> = (props) => {
};
const tabGroups: {
id: 'platforms' | 'administration';
id: 'platforms' | 'administration' | 'system' | 'security';
label: string;
items: { id: SettingsTab; label: string; icon: JSX.Element; disabled?: boolean }[];
}[] = [
@ -753,12 +791,29 @@ const Settings: Component<SettingsProps> = (props) => {
id: 'administration',
label: 'Administration',
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: '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: '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);
@ -1440,7 +1495,8 @@ const Settings: Component<SettingsProps> = (props) => {
const saveSettings = async () => {
try {
if (activeTab() === 'system') {
if (activeTab() === 'system-general' || activeTab() === 'system-network' ||
activeTab() === 'system-updates' || activeTab() === 'system-backups') {
// Save system settings using typed API
await SettingsAPI.updateSystemSettings({
// PBS polling interval is now fixed at 10 seconds
@ -1798,7 +1854,9 @@ const Settings: Component<SettingsProps> = (props) => {
<Show
when={
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">
@ -1848,7 +1906,7 @@ const Settings: Component<SettingsProps> = (props) => {
aria-label="Settings navigation"
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">
<For each={tabGroups}>
{(group) => (
@ -3311,11 +3369,53 @@ const Settings: Component<SettingsProps> = (props) => {
<HostAgents variant="macos" />
</Show>
{/* System Settings Tab */}
<Show when={activeTab() === 'system'}>
{/* System General Tab */}
<Show when={activeTab() === 'system-general'}>
<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
tone="info"
padding="md"
@ -3346,8 +3446,26 @@ const Settings: Component<SettingsProps> = (props) => {
</div>
</div>
</Card>
<Card padding="lg" class="space-y-5">
<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">
<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
title="Network discovery"
description="Control how Pulse scans for Proxmox services on your network."
@ -3627,37 +3745,9 @@ const Settings: Component<SettingsProps> = (props) => {
configuration and restart Pulse to change them here.
</div>
</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">
<svg
width="16"
@ -3813,26 +3903,34 @@ const Settings: Component<SettingsProps> = (props) => {
</span>
</p>
</Card>
</Card>
</div>
</Card>
</div>
</Show>
<Card padding="lg" class="space-y-6 lg:col-span-2">
{/* System Updates Tab */}
<Show when={activeTab() === 'system-updates'}>
<div class="space-y-6">
<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">
<RefreshCw class="w-5 h-5 text-blue-600 dark:text-blue-300" strokeWidth={2} />
</div>
<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">
<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"
stroke-width="2"
>
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"></path>
</svg>
Updates
</h4>
<div class="space-y-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
@ -4073,15 +4171,33 @@ const Settings: Component<SettingsProps> = (props) => {
</div>
</div>
</section>
</Card>
</div>
</div>
</Card>
</div>
</Show>
{/* Backup & Restore - Moved from Security tab */}
{/* System Backups Tab */}
<Show when={activeTab() === 'system-backups'}>
<div class="space-y-6">
<Card
padding="lg"
padding="none"
class="overflow-hidden border border-gray-200 dark:border-gray-700"
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">
<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">
@ -4366,6 +4482,7 @@ const Settings: Component<SettingsProps> = (props) => {
</div>
</div>
</div>
</div>
</Card>
</div>
</Show>
@ -4373,21 +4490,38 @@ const Settings: Component<SettingsProps> = (props) => {
{/* API Access */}
<Show when={activeTab() === 'api'}>
<div class="space-y-6">
<SectionHeader title="API access" size="md" class="mb-2" />
<Card tone="muted" padding="lg" class="space-y-3">
<p class="text-sm text-gray-600 dark:text-gray-400">
Generate scoped tokens for Docker agents, host agents, and automation pipelines.
Tokens are shown oncestore them securely and rotate when infrastructure changes.
</p>
<a
href="https://github.com/rcourtman/Pulse/blob/main/docs/CONFIGURATION.md#token-scopes"
target="_blank"
rel="noreferrer"
class="inline-flex w-fit 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"
>
View scope reference
</a>
<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">
<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">
Generate scoped tokens for Docker agents, host agents, and automation pipelines.
Tokens are shown oncestore them securely and rotate when infrastructure changes.
</p>
<a
href="https://github.com/rcourtman/Pulse/blob/main/docs/CONFIGURATION.md#token-scopes"
target="_blank"
rel="noreferrer"
class="inline-flex w-fit 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"
>
View scope reference
</a>
</div>
</Card>
<APITokenManager
@ -4400,8 +4534,8 @@ const Settings: Component<SettingsProps> = (props) => {
</div>
</Show>
{/* Security Tab */}
<Show when={activeTab() === 'security'}>
{/* Security Overview Tab */}
<Show when={activeTab() === 'security-overview'}>
<div class="space-y-6">
<Show when={!securityStatusLoading() && securityStatus()}>
<SecurityPostureSummary status={securityStatus()!} />
@ -4461,53 +4595,64 @@ const Settings: Component<SettingsProps> = (props) => {
</div>
</Card>
</Show>
</div>
</Show>
{/* Security Authentication Tab */}
<Show when={activeTab() === 'security-auth'}>
<div class="space-y-6">
{/* Show message when auth is disabled */}
<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">
<div class="flex items-start justify-between gap-3">
<div class="flex items-start gap-3 flex-1">
<svg
class="h-5 w-5 text-amber-600 dark:text-amber-400 mt-0.5 flex-shrink-0"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
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>
<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>
<Card
padding="none"
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
class="w-5 h-5 text-amber-600 dark:text-amber-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
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>
</div>
<SectionHeader title="Authentication disabled" size="sm" class="flex-1" />
<button
type="button"
onClick={() => setShowQuickSecuritySetup(!showQuickSecuritySetup())}
class="px-3 py-1.5 text-xs font-medium rounded-lg border border-amber-300 text-amber-800 bg-amber-100/50 hover:bg-amber-100 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900/30 dark:hover:bg-amber-900/40 whitespace-nowrap"
>
Setup
</button>
</div>
<button
type="button"
onClick={() => setShowQuickSecuritySetup(!showQuickSecuritySetup())}
class="px-3 py-1.5 text-xs font-medium rounded-lg border border-amber-300 text-amber-800 bg-amber-100/50 hover:bg-amber-100 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900/30 dark:hover:bg-amber-900/40 whitespace-nowrap"
>
Setup
</button>
</div>
<Show when={showQuickSecuritySetup()}>
<div class="mt-3 pt-3 border-t border-amber-200 dark:border-amber-700">
{/* 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()}>
<QuickSecuritySetup
onConfigured={() => {
setShowQuickSecuritySetup(false);
loadSecurityStatus();
}}
/>
</div>
</Show>
</div>
</Show>
</div>
</Card>
</Show>
{/* Authentication */}
@ -4523,24 +4668,17 @@ const Settings: Component<SettingsProps> = (props) => {
border={false}
>
{/* 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="p-2 bg-gray-100 dark:bg-gray-900/50 rounded-lg">
<svg
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 class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
<Lock class="w-5 h-5 text-blue-600 dark:text-blue-300" strokeWidth={2} />
</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>
@ -4697,63 +4835,13 @@ const Settings: Component<SettingsProps> = (props) => {
</div>
</div>
</Show>
</div>
</Show>
{/* Security Single Sign-On Tab */}
<Show when={activeTab() === 'security-sso'}>
<div class="space-y-6">
<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>
</Show>