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:
parent
a57fb10532
commit
76263be05d
1 changed files with 287 additions and 199 deletions
|
|
@ -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,37 +3745,9 @@ 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">
|
<section 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">
|
|
||||||
<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
|
||||||
width="16"
|
width="16"
|
||||||
|
|
@ -3813,26 +3903,34 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</Card>
|
</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">
|
<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="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>
|
||||||
</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
|
<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,21 +4490,38 @@ 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"
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
border={false}
|
||||||
Generate scoped tokens for Docker agents, host agents, and automation pipelines.
|
>
|
||||||
Tokens are shown once—store them securely and rotate when infrastructure changes.
|
<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">
|
||||||
</p>
|
<div class="flex items-center gap-3">
|
||||||
<a
|
<div class="p-2 bg-blue-100 dark:bg-blue-900/40 rounded-lg">
|
||||||
href="https://github.com/rcourtman/Pulse/blob/main/docs/CONFIGURATION.md#token-scopes"
|
<ApiIcon class="w-5 h-5 text-blue-600 dark:text-blue-300" />
|
||||||
target="_blank"
|
</div>
|
||||||
rel="noreferrer"
|
<SectionHeader
|
||||||
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"
|
title="API Access"
|
||||||
>
|
description="Generate scoped tokens for agents and automation"
|
||||||
View scope reference
|
size="sm"
|
||||||
</a>
|
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 once—store 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>
|
</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,53 +4595,64 @@ 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"
|
||||||
<svg
|
border={false}
|
||||||
class="h-5 w-5 text-amber-600 dark:text-amber-400 mt-0.5 flex-shrink-0"
|
>
|
||||||
fill="none"
|
{/* Header */}
|
||||||
viewBox="0 0 24 24"
|
<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">
|
||||||
stroke="currentColor"
|
<div class="flex items-center gap-3">
|
||||||
>
|
<div class="p-2 bg-amber-100 dark:bg-amber-900/50 rounded-lg">
|
||||||
<path
|
<svg
|
||||||
stroke-linecap="round"
|
class="w-5 h-5 text-amber-600 dark:text-amber-400"
|
||||||
stroke-linejoin="round"
|
fill="none"
|
||||||
stroke-width="2"
|
viewBox="0 0 24 24"
|
||||||
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"
|
stroke="currentColor"
|
||||||
/>
|
>
|
||||||
</svg>
|
<path
|
||||||
<div class="flex-1 min-w-0">
|
stroke-linecap="round"
|
||||||
<h4 class="text-sm font-semibold text-amber-900 dark:text-amber-100">
|
stroke-linejoin="round"
|
||||||
Authentication disabled
|
stroke-width="2"
|
||||||
</h4>
|
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"
|
||||||
<p class="text-xs text-amber-700 dark:text-amber-300 mt-1">
|
/>
|
||||||
DISABLE_AUTH is set, or auth isn't configured yet.
|
</svg>
|
||||||
</p>
|
|
||||||
</div>
|
</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>
|
</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>
|
</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
|
<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>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue