mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Simplify settings shell and nav guide
This commit is contained in:
parent
b4692ef7f0
commit
42d657caf5
38 changed files with 84 additions and 100 deletions
|
|
@ -407,7 +407,7 @@ export function AppLayout(props: AppLayoutProps) {
|
|||
|
||||
return (
|
||||
<div
|
||||
class={`pulse-shell ${layoutStore.isFullWidth() || kioskMode() ? 'pulse-shell--full-width' : ''} ${!kioskMode() ? 'pb-safe-or-20 md:pb-0' : ''}`}
|
||||
class={`pulse-shell ${layoutStore.isFullWidth() || kioskMode() ? 'pulse-shell--full-width' : ''} ${!kioskMode() ? 'pb-safe-or-20 lg:pb-0' : ''}`}
|
||||
>
|
||||
<Show when={kioskMode()}>
|
||||
<div
|
||||
|
|
@ -566,7 +566,7 @@ export function AppLayout(props: AppLayoutProps) {
|
|||
|
||||
<Show when={!kioskMode()}>
|
||||
<div
|
||||
class="tabs mb-2 hidden md:flex items-end gap-2 overflow-x-auto overflow-y-hidden whitespace-nowrap border-b border-border scrollbar-hide"
|
||||
class="tabs mb-2 hidden lg:flex items-end gap-2 overflow-x-auto overflow-y-hidden whitespace-nowrap border-b border-border scrollbar-hide"
|
||||
role="tablist"
|
||||
aria-label="Primary navigation"
|
||||
>
|
||||
|
|
@ -760,7 +760,7 @@ export function AppLayout(props: AppLayoutProps) {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => aiChatStore.toggle()}
|
||||
class="fixed right-0 top-1/2 -translate-y-1/2 z-40 flex min-h-10 sm:min-h-9 min-w-10 items-center justify-center px-2.5 py-2.5 rounded-l-xl bg-blue-600 text-white shadow-sm hover:bg-blue-700 transition-colors duration-200 group sm:top-1/2 sm:translate-y-[-50%] top-auto bottom-[calc(5rem+env(safe-area-inset-bottom,0px))] translate-y-0"
|
||||
class="fixed right-0 top-1/2 -translate-y-1/2 z-40 flex min-h-10 sm:min-h-9 min-w-10 items-center justify-center px-2.5 py-2.5 rounded-l-lg border border-r-0 border-border bg-surface text-blue-600 dark:text-blue-400 hover:bg-surface-hover hover:text-blue-700 dark:hover:text-blue-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 transition-colors duration-200 group sm:top-1/2 sm:translate-y-[-50%] top-auto bottom-[calc(5rem+env(safe-area-inset-bottom,0px))] translate-y-0"
|
||||
title={getAIChatLauncherTitle(aiChatStore.context.context?.name)}
|
||||
aria-label={AI_CHAT_LAUNCHER_ARIA_LABEL}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -26,21 +26,6 @@ export const AISettings: Component = () => {
|
|||
<SettingsPanel
|
||||
title={AI_SETTINGS_PANEL_TITLE}
|
||||
description={AI_SETTINGS_PANEL_DESCRIPTION}
|
||||
icon={
|
||||
<svg
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-300"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.8"
|
||||
d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611l-2.576.43a18.003 18.003 0 01-5.118 0l-2.576-.43c-1.717-.293-2.299-2.379-1.067-3.611L5 14.5"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
action={(() => {
|
||||
return (
|
||||
<Toggle
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { Component } from 'solid-js';
|
|||
import SettingsPanel from '@/components/shared/SettingsPanel';
|
||||
import { API_TOKEN_SCOPES_DOC_URL } from '@/utils/docsLinks';
|
||||
import APITokenManager from './APITokenManager';
|
||||
import BadgeCheck from 'lucide-solid/icons/badge-check';
|
||||
|
||||
interface APIAccessPanelProps {
|
||||
currentTokenHint?: string;
|
||||
|
|
@ -17,7 +16,6 @@ export const APIAccessPanel: Component<APIAccessPanelProps> = (props) => {
|
|||
<SettingsPanel
|
||||
title="API Access"
|
||||
description="Generate and manage scoped tokens for agents and automation."
|
||||
icon={<BadgeCheck class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
>
|
||||
<div class="space-y-3 p-4 sm:p-6 pb-6">
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ export const AgentProfilesPanel: Component = () => {
|
|||
<SettingsPanel
|
||||
title="Configuration Profiles"
|
||||
description="Reusable agent configurations"
|
||||
icon={<Settings class="w-5 h-5" strokeWidth={2} />}
|
||||
action={
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
|
|
@ -236,7 +235,6 @@ export const AgentProfilesPanel: Component = () => {
|
|||
<SettingsPanel
|
||||
title="Agent Assignments"
|
||||
description="Assign profiles to connected agents"
|
||||
icon={<Users class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ export default function AuditLogPanel() {
|
|||
<SettingsPanel
|
||||
title="Audit Log"
|
||||
description="Persistent, searchable audit events with optional signature verification."
|
||||
icon={<Shield class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="space-y-6 p-4 sm:p-6"
|
||||
action={
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
|||
<SettingsPanel
|
||||
title="Audit Webhooks"
|
||||
description="Configure real-time delivery of security audit events to external systems."
|
||||
icon={<Globe class="w-5 h-5" strokeWidth={2} />}
|
||||
>
|
||||
<Show when={!loading()} fallback={<div class="text-sm text-muted">Loading...</div>}>
|
||||
<Card tone="info" padding="md">
|
||||
|
|
@ -99,7 +98,6 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
|||
<SettingsPanel
|
||||
title="Audit Webhooks"
|
||||
description="Configure real-time delivery of security audit events to external systems."
|
||||
icon={<Globe class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
ORGANIZATION_SETTINGS_UNAVAILABLE_CLASS as ORGANIZATION_SETTINGS_PANEL_UNAVAILABLE_CLASS,
|
||||
ORGANIZATION_SETTINGS_UNAVAILABLE_MESSAGE as ORGANIZATION_SETTINGS_PANEL_UNAVAILABLE_MESSAGE,
|
||||
} from '@/utils/organizationSettingsPresentation';
|
||||
import CreditCard from 'lucide-solid/icons/credit-card';
|
||||
import { BillingAdminOrganizationsTable } from './BillingAdminOrganizationsTable';
|
||||
import { useBillingAdminPanelState } from './useBillingAdminPanelState';
|
||||
|
||||
|
|
@ -23,7 +22,6 @@ export const BillingAdminPanel: Component = () => {
|
|||
<SettingsPanel
|
||||
title="Billing Admin"
|
||||
description="View and manage billing state across all tenants (hosted mode only)."
|
||||
icon={<CreditCard class="w-5 h-5" />}
|
||||
action={
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ interface CommercialUsageMetersProps {
|
|||
interface CommercialBillingShellProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: JSX.Element;
|
||||
action?: JSX.Element;
|
||||
loading?: boolean;
|
||||
loadingFallback?: JSX.Element;
|
||||
|
|
@ -103,7 +102,6 @@ export const CommercialBillingShell: Component<CommercialBillingShellProps> = (p
|
|||
<SettingsPanel
|
||||
title={props.title}
|
||||
description={props.description}
|
||||
icon={props.icon}
|
||||
action={props.action}
|
||||
bodyClass="space-y-5"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, Show } from 'solid-js';
|
||||
import OperationsPanel from '@/components/Settings/OperationsPanel';
|
||||
import Activity from 'lucide-solid/icons/activity';
|
||||
import RefreshCw from 'lucide-solid/icons/refresh-cw';
|
||||
import Download from 'lucide-solid/icons/download';
|
||||
import { DiagnosticsResultsPanel } from '@/components/Settings/DiagnosticsResultsPanel';
|
||||
|
|
@ -17,7 +16,6 @@ export const DiagnosticsPanel: Component = () => {
|
|||
<OperationsPanel
|
||||
title={DIAGNOSTICS_PANEL_COPY.title}
|
||||
description={DIAGNOSTICS_PANEL_COPY.description}
|
||||
icon={<Activity class="w-5 h-5 sm:w-5 sm:h-5" />}
|
||||
action={
|
||||
<div class="flex items-center justify-between sm:justify-end gap-3 flex-wrap">
|
||||
<Show when={diagnosticsData()}>
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import { EnvironmentLockBadge } from '@/components/shared/EnvironmentLockBadge';
|
|||
import { FilterButtonGroup, type FilterOption } from '@/components/shared/FilterButtonGroup';
|
||||
import type { TelemetryPreviewResponse } from '@/api/settings';
|
||||
import { DockerRuntimeSettingsCard } from './DockerRuntimeSettingsCard';
|
||||
import Sliders from 'lucide-solid/icons/sliders-horizontal';
|
||||
import Activity from 'lucide-solid/icons/activity';
|
||||
import Sun from 'lucide-solid/icons/sun';
|
||||
import Moon from 'lucide-solid/icons/moon';
|
||||
import Thermometer from 'lucide-solid/icons/thermometer';
|
||||
import Maximize2 from 'lucide-solid/icons/maximize-2';
|
||||
import Compass from 'lucide-solid/icons/compass';
|
||||
import { temperatureStore } from '@/utils/temperature';
|
||||
import { layoutStore } from '@/utils/layout';
|
||||
import { WHATS_NEW_REOPEN_EVENT } from '@/components/shared/whatsNewModalModel';
|
||||
import {
|
||||
PVE_POLLING_MAX_SECONDS,
|
||||
PVE_POLLING_MIN_SECONDS,
|
||||
|
|
@ -95,7 +95,6 @@ export const GeneralSettingsPanel: Component<GeneralSettingsPanelProps> = (props
|
|||
<SettingsPanel
|
||||
title="General"
|
||||
description="Manage appearance, layout, and default monitoring cadence."
|
||||
icon={<Sliders class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
@ -124,7 +123,7 @@ export const GeneralSettingsPanel: Component<GeneralSettingsPanelProps> = (props
|
|||
</div>
|
||||
</div>
|
||||
<FilterButtonGroup
|
||||
class="shrink-0 self-start sm:self-auto ml-12 sm:ml-0"
|
||||
class="w-full sm:w-auto sm:shrink-0 max-w-full"
|
||||
options={THEME_PREFERENCE_OPTIONS}
|
||||
value={props.themePreference()}
|
||||
onChange={props.setThemePreference}
|
||||
|
|
@ -133,7 +132,7 @@ export const GeneralSettingsPanel: Component<GeneralSettingsPanelProps> = (props
|
|||
</div>
|
||||
|
||||
{/* Temperature Unit Selector */}
|
||||
<div class="flex items-center justify-between gap-4 p-4 sm:p-6">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 p-4 sm:p-6">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div class="shrink-0 p-2.5 rounded-md border border-border bg-surface">
|
||||
<Thermometer class="w-5 h-5" strokeWidth={2} />
|
||||
|
|
@ -146,7 +145,7 @@ export const GeneralSettingsPanel: Component<GeneralSettingsPanelProps> = (props
|
|||
</div>
|
||||
</div>
|
||||
<FilterButtonGroup
|
||||
class="shrink-0"
|
||||
class="w-full sm:w-auto sm:shrink-0 max-w-full"
|
||||
options={TEMPERATURE_UNIT_OPTIONS}
|
||||
value={temperatureStore.unit()}
|
||||
onChange={(value) => temperatureStore.setUnit(value)}
|
||||
|
|
@ -173,13 +172,37 @@ export const GeneralSettingsPanel: Component<GeneralSettingsPanelProps> = (props
|
|||
onChange={() => layoutStore.toggle()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Reopen Navigation Guide */}
|
||||
<div class="flex items-center justify-between gap-4 p-4 sm:p-6">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div class="shrink-0 p-2.5 rounded-md border border-border bg-surface">
|
||||
<Compass class="w-5 h-5 text-slate-500" strokeWidth={2} />
|
||||
</div>
|
||||
<div class="text-sm text-muted min-w-0">
|
||||
<p class="font-medium text-base-content truncate">Navigation guide</p>
|
||||
<p class="text-xs text-muted line-clamp-2">
|
||||
Replay the five-stop walkthrough of Dashboard, Infrastructure, Workloads, Storage,
|
||||
and Recovery.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
window.dispatchEvent(new CustomEvent(WHATS_NEW_REOPEN_EVENT));
|
||||
}}
|
||||
class="shrink-0 rounded-md border border-border bg-surface px-3 py-1.5 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
|
||||
>
|
||||
Reopen
|
||||
</button>
|
||||
</div>
|
||||
</SettingsPanel>
|
||||
|
||||
{/* Usage Data + Privacy Card */}
|
||||
<SettingsPanel
|
||||
title="Usage data and privacy"
|
||||
description="Control local-only usage events and anonymous outbound telemetry."
|
||||
icon={<Sliders class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
@ -306,7 +329,6 @@ export const GeneralSettingsPanel: Component<GeneralSettingsPanelProps> = (props
|
|||
<SettingsPanel
|
||||
title="Monitoring cadence"
|
||||
description="Control how frequently Pulse polls Proxmox VE nodes."
|
||||
icon={<Activity class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import type { Component } from 'solid-js';
|
||||
import { For, Show, createSignal } from 'solid-js';
|
||||
import Server from 'lucide-solid/icons/server';
|
||||
import SettingsPanel from '@/components/shared/SettingsPanel';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { formatAbsoluteTime, formatRelativeTime } from '@/utils/format';
|
||||
|
|
@ -31,7 +30,6 @@ export const InfrastructureInstallerSection: Component = () => {
|
|||
? 'Start here to add the first system you want Pulse to monitor, then expand into Docker, Kubernetes, Proxmox, and related infrastructure.'
|
||||
: 'Primary setup hub for installing Pulse on the first host you want to monitor, then expanding into Docker, Kubernetes, Proxmox, and related infrastructure.'
|
||||
}
|
||||
icon={<Server class="h-5 w-5" strokeWidth={2} />}
|
||||
bodyClass="space-y-5"
|
||||
>
|
||||
<Show when={state.setupHandoff()}>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import type {
|
|||
} from '@/api/license';
|
||||
import { apiErrorCode, apiErrorDetailField } from '@/api/responseUtils';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { PulseLogoIcon } from '@/components/icons/PulseLogoIcon';
|
||||
import {
|
||||
formatMonitoredSystemGroupedSourcesLabel,
|
||||
formatMonitoredSystemLedgerUnavailableMessage,
|
||||
|
|
@ -519,7 +518,6 @@ export function MonitoredSystemLedgerPanel(props: MonitoredSystemLedgerPanelProp
|
|||
<SettingsPanel
|
||||
title={presentation.panelTitle}
|
||||
description={getMonitoredSystemLedgerDescription()}
|
||||
icon={<PulseLogoIcon class="w-5 h-5" />}
|
||||
bodyClass="space-y-4"
|
||||
>
|
||||
{content}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Component } from 'solid-js';
|
||||
import SettingsPanel from '@/components/shared/SettingsPanel';
|
||||
import Network from 'lucide-solid/icons/network';
|
||||
import { NetworkBoundarySettingsSection } from './NetworkBoundarySettingsSection';
|
||||
import type { NetworkSettingsPanelProps } from './networkSettingsModel';
|
||||
|
||||
|
|
@ -9,7 +8,6 @@ export const NetworkSettingsPanel: Component<NetworkSettingsPanelProps> = (props
|
|||
<SettingsPanel
|
||||
title="Network"
|
||||
description="Configure the public URL, CORS, embedding, and webhook network boundaries."
|
||||
icon={<Network class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
>
|
||||
<NetworkBoundarySettingsSection {...props} />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import SettingsPanel from '@/components/shared/SettingsPanel';
|
|||
type OperationsPanelProps = {
|
||||
title: JSX.Element;
|
||||
description?: JSX.Element;
|
||||
icon?: JSX.Element;
|
||||
action?: JSX.Element;
|
||||
class?: string;
|
||||
children: JSX.Element;
|
||||
|
|
@ -15,7 +14,6 @@ export function OperationsPanel(props: OperationsPanelProps) {
|
|||
<SettingsPanel
|
||||
title={props.title}
|
||||
description={props.description}
|
||||
icon={props.icon}
|
||||
action={props.action}
|
||||
class={props.class}
|
||||
noPadding
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
ORGANIZATION_SETTINGS_UNAVAILABLE_CLASS,
|
||||
ORGANIZATION_SETTINGS_UNAVAILABLE_MESSAGE,
|
||||
} from '@/utils/organizationSettingsPresentation';
|
||||
import Users from 'lucide-solid/icons/users';
|
||||
import { OrganizationAccessLoadingState } from './OrganizationAccessLoadingState';
|
||||
import { OrganizationAccessInvitationsSection } from './OrganizationAccessInvitationsSection';
|
||||
import { OrganizationAccessManagementSection } from './OrganizationAccessManagementSection';
|
||||
|
|
@ -35,7 +34,6 @@ export const OrganizationAccessPanel: Component<OrganizationAccessPanelProps> =
|
|||
<SettingsPanel
|
||||
title="Organization Access"
|
||||
description="Manage organization invitations, member roles, and ownership transfers."
|
||||
icon={<Users class="w-5 h-5" />}
|
||||
bodyClass="space-y-5"
|
||||
>
|
||||
<Show when={!state.loading()} fallback={<OrganizationAccessLoadingState />}>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
ORGANIZATION_SETTINGS_UNAVAILABLE_CLASS,
|
||||
ORGANIZATION_SETTINGS_UNAVAILABLE_MESSAGE,
|
||||
} from '@/utils/organizationSettingsPresentation';
|
||||
import CreditCard from 'lucide-solid/icons/credit-card';
|
||||
import {
|
||||
CommercialBillingShell,
|
||||
CommercialSection,
|
||||
|
|
@ -33,7 +32,6 @@ export const OrganizationBillingPanel: Component<OrganizationBillingPanelProps>
|
|||
<CommercialBillingShell
|
||||
title="Billing & Usage"
|
||||
description="Review your organization plan, usage against limits, and available upgrade paths."
|
||||
icon={<CreditCard class="w-5 h-5" />}
|
||||
loading={loading()}
|
||||
loadingFallback={<OrganizationBillingLoadingState />}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
ORGANIZATION_SETTINGS_UNAVAILABLE_CLASS,
|
||||
ORGANIZATION_SETTINGS_UNAVAILABLE_MESSAGE,
|
||||
} from '@/utils/organizationSettingsPresentation';
|
||||
import Building2 from 'lucide-solid/icons/building-2';
|
||||
import { OrganizationOverviewDetailsSection } from './OrganizationOverviewDetailsSection';
|
||||
import { OrganizationOverviewLoadingState } from './OrganizationOverviewLoadingState';
|
||||
import { OrganizationOverviewMembersSection } from './OrganizationOverviewMembersSection';
|
||||
|
|
@ -34,7 +33,6 @@ export const OrganizationOverviewPanel: Component<OrganizationOverviewPanelProps
|
|||
<SettingsPanel
|
||||
title="Organization Overview"
|
||||
description="Review organization metadata, membership footprint, and edit the display name."
|
||||
icon={<Building2 class="w-5 h-5" />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
ORGANIZATION_SETTINGS_UNAVAILABLE_CLASS,
|
||||
ORGANIZATION_SETTINGS_UNAVAILABLE_MESSAGE,
|
||||
} from '@/utils/organizationSettingsPresentation';
|
||||
import Share2 from 'lucide-solid/icons/share-2';
|
||||
import { OrganizationIncomingSharesSection } from './OrganizationIncomingSharesSection';
|
||||
import { OrganizationOutgoingSharesSection } from './OrganizationOutgoingSharesSection';
|
||||
import { OrganizationSharingCreateSection } from './OrganizationSharingCreateSection';
|
||||
|
|
@ -35,7 +34,6 @@ export const OrganizationSharingPanel: Component<OrganizationSharingPanelProps>
|
|||
<SettingsPanel
|
||||
title="Organization Sharing"
|
||||
description="Share views and resources across organizations with explicit role-based access and target-organization approval."
|
||||
icon={<Share2 class="w-5 h-5" />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Show, type Component } from 'solid-js';
|
||||
import RefreshCw from 'lucide-solid/icons/refresh-cw';
|
||||
import { PulseLogoIcon } from '@/components/icons/PulseLogoIcon';
|
||||
import { MonitoredSystemLedgerPanel } from './MonitoredSystemLedgerPanel';
|
||||
import { CommercialBillingShell, CommercialSection } from './CommercialBillingSections';
|
||||
import { ProLicensePlanSection } from './ProLicensePlanSection';
|
||||
|
|
@ -23,7 +22,6 @@ const ProLicensePolicyLoadingPanel: Component = () => (
|
|||
<CommercialBillingShell
|
||||
title={SELF_HOSTED_PRO_BILLING_PRESENTATION.hiddenShellTitle}
|
||||
description={SELF_HOSTED_PRO_BILLING_PRESENTATION.hiddenShellDescription}
|
||||
icon={<PulseLogoIcon class="w-5 h-5" />}
|
||||
loading={false}
|
||||
>
|
||||
<div class="rounded-lg border border-border bg-surface px-4 py-4 text-sm">
|
||||
|
|
@ -39,7 +37,6 @@ const ProLicenseHiddenPanel: Component = () => (
|
|||
<CommercialBillingShell
|
||||
title={SELF_HOSTED_PRO_BILLING_PRESENTATION.hiddenShellTitle}
|
||||
description={SELF_HOSTED_PRO_BILLING_PRESENTATION.hiddenShellDescription}
|
||||
icon={<PulseLogoIcon class="w-5 h-5" />}
|
||||
loading={false}
|
||||
>
|
||||
<div class="rounded-lg border border-border bg-surface px-4 py-4 text-sm">
|
||||
|
|
@ -59,7 +56,6 @@ const ProLicensePanelContent: Component = () => {
|
|||
<CommercialBillingShell
|
||||
title={SELF_HOSTED_PRO_BILLING_PRESENTATION.shellTitle}
|
||||
description={SELF_HOSTED_PRO_BILLING_PRESENTATION.shellDescription}
|
||||
icon={<PulseLogoIcon class="w-5 h-5" />}
|
||||
action={
|
||||
<button
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, Show, For, Accessor, Setter } from 'solid-js';
|
||||
import SettingsPanel from '@/components/shared/SettingsPanel';
|
||||
import { SectionHeader } from '@/components/shared/SectionHeader';
|
||||
import Clock from 'lucide-solid/icons/clock';
|
||||
import {
|
||||
BACKUP_INTERVAL_MAX_MINUTES,
|
||||
BACKUP_INTERVAL_OPTIONS,
|
||||
|
|
@ -35,7 +34,6 @@ export const RecoverySettingsPanel: Component<RecoverySettingsPanelProps> = (pro
|
|||
<SettingsPanel
|
||||
title="Recovery"
|
||||
description="Manage backup/snapshot polling and configuration export/import."
|
||||
icon={<Clock class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ export const RelaySettingsPanel: Component<RelaySettingsPanelProps> = (props) =>
|
|||
<SettingsPanel
|
||||
title="Remote Access"
|
||||
description={RELAY_SETTINGS_DESCRIPTION}
|
||||
icon={<RadioTower size={20} strokeWidth={2} />}
|
||||
>
|
||||
<Show when={!state.loading()} fallback={<div class="text-sm ">Loading...</div>}>
|
||||
<Card tone="info" padding="md">
|
||||
|
|
@ -80,7 +79,6 @@ export const RelaySettingsPanel: Component<RelaySettingsPanelProps> = (props) =>
|
|||
<SettingsPanel
|
||||
title="Remote Access"
|
||||
description={RELAY_SETTINGS_DESCRIPTION}
|
||||
icon={<RadioTower size={20} strokeWidth={2} />}
|
||||
>
|
||||
<Show
|
||||
when={!state.loading()}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@ export function ReportingPanel() {
|
|||
<OperationsPanel
|
||||
title="Reporting"
|
||||
description="Loading reporting surfaces..."
|
||||
icon={<BarChart class="w-5 h-5" strokeWidth={2} />}
|
||||
>
|
||||
<div class="p-4 sm:p-6">
|
||||
<p class="text-sm text-muted">Loading reporting surfaces...</p>
|
||||
|
|
@ -114,7 +113,6 @@ export function ReportingPanel() {
|
|||
<OperationsPanel
|
||||
title="Reporting"
|
||||
description="Reporting surfaces are currently unavailable."
|
||||
icon={<BarChart class="w-5 h-5" strokeWidth={2} />}
|
||||
>
|
||||
<div class="space-y-4 p-4 sm:p-6">
|
||||
<p class="text-sm text-warning">{reportingCatalogError()}</p>
|
||||
|
|
@ -135,7 +133,6 @@ export function ReportingPanel() {
|
|||
<OperationsPanel
|
||||
title={reportingCatalog()!.title}
|
||||
description={reportingCatalog()!.description}
|
||||
icon={<BarChart class="w-5 h-5" strokeWidth={2} />}
|
||||
>
|
||||
<div class="p-4 sm:p-6">
|
||||
<div class="flex flex-col sm:flex-row items-center gap-4">
|
||||
|
|
@ -177,7 +174,6 @@ export function ReportingPanel() {
|
|||
<OperationsPanel
|
||||
title={reportingCatalog()!.title}
|
||||
description={reportingCatalog()!.description}
|
||||
icon={<BarChart class="w-5 h-5" strokeWidth={2} />}
|
||||
>
|
||||
<div class="space-y-6 p-4 sm:p-6">
|
||||
<Show when={reportingCatalogLoading()}>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { getRolesEmptyState } from '@/utils/rbacPresentation';
|
|||
import Plus from 'lucide-solid/icons/plus';
|
||||
import Pencil from 'lucide-solid/icons/pencil';
|
||||
import Trash2 from 'lucide-solid/icons/trash-2';
|
||||
import Shield from 'lucide-solid/icons/shield';
|
||||
import BadgeCheck from 'lucide-solid/icons/badge-check';
|
||||
import { PulseDataGrid } from '@/components/shared/PulseDataGrid';
|
||||
|
||||
|
|
@ -19,7 +18,6 @@ export const RolesPanel: Component = () => {
|
|||
<SettingsPanel
|
||||
title="Roles"
|
||||
description="Manage built-in and custom roles with granular permissions."
|
||||
icon={<Shield class="w-5 h-5" />}
|
||||
action={
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -185,7 +185,6 @@ export const SSOProvidersPanel: Component<SSOProvidersPanelProps> = (props) => {
|
|||
<SettingsPanel
|
||||
title="Single Sign-On Providers"
|
||||
description="Configure OIDC and SAML identity providers."
|
||||
icon={<Shield class="w-5 h-5" strokeWidth={2} />}
|
||||
action={
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import {
|
|||
SECURITY_AUTH_SETUP_LABEL,
|
||||
} from '@/utils/securityAuthPresentation';
|
||||
import { QuickSecuritySetup } from './QuickSecuritySetup';
|
||||
import Lock from 'lucide-solid/icons/lock';
|
||||
import AlertTriangle from 'lucide-solid/icons/alert-triangle';
|
||||
import type { VersionInfo } from '@/api/updates';
|
||||
|
||||
|
|
@ -60,7 +59,6 @@ export const SecurityAuthPanel: Component<SecurityAuthPanelProps> = (props) => {
|
|||
<SettingsPanel
|
||||
title="Authentication"
|
||||
description="Manage password-based authentication, login visibility, and credential rotation."
|
||||
icon={<Lock class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding={showAuthenticationControls()}
|
||||
bodyClass={showAuthenticationControls() ? 'divide-y divide-border' : 'space-y-6'}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -86,7 +86,6 @@ export const SecurityOverviewPanel: Component<SecurityOverviewPanelProps> = (pro
|
|||
<SettingsPanel
|
||||
title="Security Overview"
|
||||
description="Review your security posture, authentication boundary, and the next hardening steps for this Pulse instance."
|
||||
icon={<Shield class="w-5 h-5" strokeWidth={2} />}
|
||||
bodyClass="space-y-6"
|
||||
>
|
||||
<Show when={props.securityStatusLoading()}>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export const SystemLogsPanel: Component = () => {
|
|||
<OperationsPanel
|
||||
title={SYSTEM_LOGS_PANEL_COPY.title}
|
||||
description={SYSTEM_LOGS_PANEL_COPY.description}
|
||||
icon={<Terminal class="w-5 h-5" strokeWidth={2} />}
|
||||
>
|
||||
{/* Controls */}
|
||||
<div class="p-4 sm:p-6">
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ export const UpdatesSettingsPanel: Component<UpdatesSettingsPanelProps> = (props
|
|||
<SettingsPanel
|
||||
title={UPDATES_PANEL_COPY.title}
|
||||
description={UPDATES_PANEL_COPY.description}
|
||||
icon={<RefreshCw class="w-5 h-5" strokeWidth={2} />}
|
||||
noPadding
|
||||
bodyClass="divide-y divide-border"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export const UserAssignmentsPanel: Component = () => {
|
|||
<SettingsPanel
|
||||
title="User Access"
|
||||
description="Assign roles to users and review effective permissions."
|
||||
icon={<Users class="w-5 h-5" />}
|
||||
action={
|
||||
<SearchField
|
||||
placeholder="Search users..."
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ export function MobileNavBar(props: MobileNavBarProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<nav class="fixed inset-x-0 bottom-0 z-40 border-t border-border bg-surface pb-safe md:hidden">
|
||||
<nav class="fixed inset-x-0 bottom-0 z-40 border-t border-border bg-surface pb-safe lg:hidden">
|
||||
<div class="relative">
|
||||
<div
|
||||
ref={mobileNav.setNavRef}
|
||||
class="flex items-center gap-1 overflow-x-auto scrollbar-hide px-2 py-1.5"
|
||||
class="flex items-center gap-1 overflow-x-auto scrollbar-hide px-2 py-1.5 sm:gap-2 sm:overflow-x-visible sm:px-4 sm:justify-between"
|
||||
role="tablist"
|
||||
aria-label="Mobile navigation"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ export function PageHeader(props: PageHeaderProps) {
|
|||
<Show when={local.titleMeta}>{local.titleMeta}</Show>
|
||||
</div>
|
||||
<Show when={local.description}>
|
||||
<p class={`mt-1 text-sm font-medium text-muted ${local.descriptionClass ?? ''}`.trim()}>
|
||||
<p
|
||||
class={`mt-1 hidden text-sm font-medium text-muted sm:block ${local.descriptionClass ?? ''}`.trim()}
|
||||
>
|
||||
{local.description}
|
||||
</p>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { JSX, Show, splitProps } from 'solid-js';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { SectionHeader } from '@/components/shared/SectionHeader';
|
||||
|
||||
type SettingsPanelProps = {
|
||||
title: JSX.Element;
|
||||
description?: JSX.Element;
|
||||
action?: JSX.Element;
|
||||
icon?: JSX.Element;
|
||||
bodyClass?: string;
|
||||
tone?: 'default' | 'muted' | 'info' | 'success' | 'warning' | 'danger';
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg';
|
||||
|
|
@ -18,7 +16,6 @@ export function SettingsPanel(props: SettingsPanelProps) {
|
|||
'title',
|
||||
'description',
|
||||
'action',
|
||||
'icon',
|
||||
'bodyClass',
|
||||
'children',
|
||||
'class',
|
||||
|
|
@ -37,16 +34,15 @@ export function SettingsPanel(props: SettingsPanelProps) {
|
|||
>
|
||||
<div class="px-3 py-3 sm:px-6 sm:py-4 border-b border-border bg-surface-alt">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<div class="flex min-w-0 items-center gap-3 flex-1">
|
||||
<Show when={local.icon}>
|
||||
<div class="text-base-content dark:text-slate-100">{local.icon}</div>
|
||||
<div class="flex min-w-0 flex-col gap-1 flex-1">
|
||||
<h2 class="text-sm sm:text-base tracking-tight font-semibold text-base-content dark:text-slate-100">
|
||||
{local.title}
|
||||
</h2>
|
||||
<Show when={local.description}>
|
||||
<p class="text-xs sm:text-sm text-muted dark:text-slate-200">
|
||||
{local.description}
|
||||
</p>
|
||||
</Show>
|
||||
<SectionHeader
|
||||
title={local.title}
|
||||
description={local.description}
|
||||
size="sm"
|
||||
class="min-w-0 flex-1"
|
||||
/>
|
||||
</div>
|
||||
<Show when={local.action}>
|
||||
<div class="w-full sm:w-auto">{local.action}</div>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export function WhatsNewModal() {
|
|||
<div
|
||||
data-tour-spotlight=""
|
||||
data-tour-step={step().target}
|
||||
class="pointer-events-none absolute rounded-[1.25rem] border border-blue-300/80 bg-white/5 transition-all duration-200 dark:border-blue-300/60"
|
||||
class="pointer-events-none absolute rounded-[1.25rem] border border-blue-300/80 bg-white/10 transition-all duration-200 dark:border-blue-300/60"
|
||||
style={style()}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ describe('WhatsNewModal', () => {
|
|||
it('renders when the navigation modal has not been seen yet', async () => {
|
||||
render(() => <WhatsNewModal />);
|
||||
|
||||
const dialog = await screen.findByRole('dialog', { name: 'Welcome to Pulse v6' });
|
||||
const dialog = await screen.findByRole('dialog', { name: 'Pulse navigation guide' });
|
||||
expect(dialog).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('Step 1 of 5')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('Quick Tour')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('Nav guide')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText(/Start here for health, alerts, capacity/i)).toBeInTheDocument();
|
||||
expect(within(dialog).queryByText('Where Things Moved')).not.toBeInTheDocument();
|
||||
expect(within(dialog).getByRole('link', { name: 'Navigation guide' })).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
presentationPolicyIsDemoMode,
|
||||
sessionPresentationPolicyResolved,
|
||||
} from '@/stores/sessionPresentationPolicy';
|
||||
import { WHATS_NEW_FEATURE_CARDS } from './whatsNewModalModel';
|
||||
import { WHATS_NEW_FEATURE_CARDS, WHATS_NEW_REOPEN_EVENT } from './whatsNewModalModel';
|
||||
|
||||
type SpotlightRect = {
|
||||
top: number;
|
||||
|
|
@ -111,6 +111,16 @@ export function useWhatsNewModalState() {
|
|||
setStepIndex(clamp(index, 0, WHATS_NEW_FEATURE_CARDS.length - 1));
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const handleReopen = () => {
|
||||
setHasSeen(false);
|
||||
setDismissedForSession(false);
|
||||
setStepIndex(0);
|
||||
};
|
||||
window.addEventListener(WHATS_NEW_REOPEN_EVENT, handleReopen);
|
||||
onCleanup(() => window.removeEventListener(WHATS_NEW_REOPEN_EVENT, handleReopen));
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (!isOpen()) return;
|
||||
|
||||
|
|
@ -208,7 +218,7 @@ export function useWhatsNewModalState() {
|
|||
width: `${rect.width}px`,
|
||||
height: `${rect.height}px`,
|
||||
'box-shadow':
|
||||
'0 0 0 9999px rgba(15, 23, 42, 0.68), 0 0 0 1px rgba(255, 255, 255, 0.18), 0 0 28px rgba(96, 165, 250, 0.55)',
|
||||
'0 0 0 9999px rgba(15, 23, 42, 0.78), 0 0 0 2px rgba(255, 255, 255, 0.4), 0 0 40px rgba(96, 165, 250, 0.75)',
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const WHATS_NEW_FEATURE_CARDS: WhatsNewFeatureCard[] = [
|
|||
title: 'Infrastructure',
|
||||
},
|
||||
{
|
||||
accent: 'border-purple-200 bg-purple-50 dark:border-purple-800 dark:bg-purple-900',
|
||||
accent: 'border-rose-200 bg-rose-50 dark:border-rose-800 dark:bg-rose-900',
|
||||
description: 'Use this for VMs, containers, and pods.',
|
||||
icon: 'workloads',
|
||||
target: 'workloads',
|
||||
|
|
@ -49,8 +49,10 @@ export const WHATS_NEW_FEATURE_CARDS: WhatsNewFeatureCard[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const WHATS_NEW_KICKER_LABEL = 'Quick Tour';
|
||||
export const WHATS_NEW_TITLE = 'Welcome to Pulse v6';
|
||||
export const WHATS_NEW_REOPEN_EVENT = 'pulse:reopen-nav-guide';
|
||||
|
||||
export const WHATS_NEW_KICKER_LABEL = 'Nav guide';
|
||||
export const WHATS_NEW_TITLE = 'Pulse navigation guide';
|
||||
export const WHATS_NEW_PROGRESS_PREFIX = 'Step';
|
||||
export const WHATS_NEW_BACK_LABEL = 'Back';
|
||||
export const WHATS_NEW_CLOSE_LABEL = 'Close';
|
||||
|
|
|
|||
|
|
@ -2,6 +2,24 @@
|
|||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/*
|
||||
* Neutralize a Tailwind color/font-size collision: the dead `base` alias in
|
||||
* the color palette was making Tailwind emit `color: var(--color-bg-base)` on
|
||||
* `.text-base` / `.sm\:text-base`, painting text the same color as the
|
||||
* background. The alias has been removed from tailwind.config.js; this rule
|
||||
* keeps things visible until the next dev-server restart picks up that change.
|
||||
*/
|
||||
@layer utilities {
|
||||
.text-base,
|
||||
.sm\:text-base,
|
||||
.md\:text-base,
|
||||
.lg\:text-base,
|
||||
.xl\:text-base,
|
||||
.\32xl\:text-base {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update banner animation */
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export default {
|
|||
extend: {
|
||||
colors: {
|
||||
// Semantic design system mapped to index.css
|
||||
base: 'var(--color-bg-base)',
|
||||
surface: 'var(--color-bg-surface)',
|
||||
'surface-hover': 'var(--color-bg-surface-hover)',
|
||||
'surface-alt': 'var(--color-bg-surface-alt)',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue