mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-04 21:26:22 +00:00
fix: Allow clearing AI findings when AI is disabled
Users who accumulated AI findings before the patrol-without-AI bug was fixed (24c4bb0b) could not dismiss them because the AI Insights tab and Clear All button were only visible when patrol was enabled. Now the AI Insights tab and Clear All button are visible whenever there are findings to clear, even if AI/patrol is not enabled. Related to #885
This commit is contained in:
@@ -18,6 +18,8 @@ import { getNodeDisplayName } from '@/utils/nodes';
|
||||
import { usePersistentSignal } from '@/hooks/usePersistentSignal';
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { useAlertsActivation } from '@/stores/alertsActivation';
|
||||
import { useColumnVisibility, type ColumnDef } from '@/hooks/useColumnVisibility';
|
||||
import { STORAGE_KEYS } from '@/utils/localStorage';
|
||||
|
||||
type StorageSortKey = 'name' | 'node' | 'type' | 'status' | 'usage' | 'free' | 'total';
|
||||
|
||||
@@ -61,6 +63,24 @@ const Storage: Component = () => {
|
||||
},
|
||||
);
|
||||
|
||||
// Column definitions for storage table
|
||||
const STORAGE_COLUMNS: ColumnDef[] = [
|
||||
{ id: 'type', label: 'Type', priority: 3 },
|
||||
{ id: 'content', label: 'Content', priority: 4 },
|
||||
{ id: 'status', label: 'Status', priority: 2 },
|
||||
{ id: 'shared', label: 'Shared', priority: 5, hiddenByDefault: true },
|
||||
{ id: 'free', label: 'Free', priority: 4 },
|
||||
{ id: 'total', label: 'Total', priority: 3 },
|
||||
];
|
||||
|
||||
// Column visibility management
|
||||
const columnVisibility = useColumnVisibility(
|
||||
STORAGE_COLUMNS,
|
||||
STORAGE_KEYS.STORAGE_HIDDEN_COLUMNS
|
||||
);
|
||||
|
||||
const isColumnVisible = (id: string) => !columnVisibility.isHiddenByUser(id);
|
||||
|
||||
// PERFORMANCE: Debounce search term to prevent jank during rapid typing
|
||||
const debouncedSearchTerm = useDebouncedValue(() => searchTerm(), 200);
|
||||
|
||||
@@ -601,6 +621,7 @@ const Storage: Component = () => {
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
searchInputRef={(el) => (searchInputRef = el)}
|
||||
columnVisibility={columnVisibility}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
@@ -794,16 +815,22 @@ const Storage: Component = () => {
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-auto">
|
||||
Storage
|
||||
</th>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Type
|
||||
</th>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[15%]">
|
||||
Content
|
||||
</th>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Status
|
||||
</th>
|
||||
<Show when={viewMode() === 'node'}>
|
||||
<Show when={isColumnVisible('type')}>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Type
|
||||
</th>
|
||||
</Show>
|
||||
<Show when={isColumnVisible('content')}>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[15%]">
|
||||
Content
|
||||
</th>
|
||||
</Show>
|
||||
<Show when={isColumnVisible('status')}>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Status
|
||||
</th>
|
||||
</Show>
|
||||
<Show when={viewMode() === 'node' && isColumnVisible('shared')}>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[6%]">
|
||||
Shared
|
||||
</th>
|
||||
@@ -811,12 +838,16 @@ const Storage: Component = () => {
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[25%] min-w-[120px]">
|
||||
Usage
|
||||
</th>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Free
|
||||
</th>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Total
|
||||
</th>
|
||||
<Show when={isColumnVisible('free')}>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Free
|
||||
</th>
|
||||
</Show>
|
||||
<Show when={isColumnVisible('total')}>
|
||||
<th class="px-1.5 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[10%]">
|
||||
Total
|
||||
</th>
|
||||
</Show>
|
||||
<th class="px-1.5 py-1.5 w-8"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1154,30 +1185,36 @@ const Storage: Component = () => {
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-0.5 px-1.5">
|
||||
<span class="inline-block px-1.5 py-0.5 text-[10px] font-medium rounded bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300">
|
||||
{storage.type}
|
||||
</span>
|
||||
</td>
|
||||
<td class="p-0.5 px-1.5">
|
||||
<span
|
||||
class="text-xs text-gray-600 dark:text-gray-400 whitespace-nowrap truncate max-w-[220px]"
|
||||
title={storage.content || '-'}
|
||||
>
|
||||
{storage.content || '-'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="p-0.5 px-1.5 text-xs whitespace-nowrap">
|
||||
<span
|
||||
class={`${storage.status === 'available'
|
||||
? 'text-green-600 dark:text-green-400'
|
||||
: 'text-red-600 dark:text-red-400'
|
||||
}`}
|
||||
>
|
||||
{storage.status || 'unknown'}
|
||||
</span>
|
||||
</td>
|
||||
<Show when={viewMode() === 'node'}>
|
||||
<Show when={isColumnVisible('type')}>
|
||||
<td class="p-0.5 px-1.5">
|
||||
<span class="inline-block px-1.5 py-0.5 text-[10px] font-medium rounded bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300">
|
||||
{storage.type}
|
||||
</span>
|
||||
</td>
|
||||
</Show>
|
||||
<Show when={isColumnVisible('content')}>
|
||||
<td class="p-0.5 px-1.5">
|
||||
<span
|
||||
class="text-xs text-gray-600 dark:text-gray-400 whitespace-nowrap truncate max-w-[220px]"
|
||||
title={storage.content || '-'}
|
||||
>
|
||||
{storage.content || '-'}
|
||||
</span>
|
||||
</td>
|
||||
</Show>
|
||||
<Show when={isColumnVisible('status')}>
|
||||
<td class="p-0.5 px-1.5 text-xs whitespace-nowrap">
|
||||
<span
|
||||
class={`${storage.status === 'available'
|
||||
? 'text-green-600 dark:text-green-400'
|
||||
: 'text-red-600 dark:text-red-400'
|
||||
}`}
|
||||
>
|
||||
{storage.status || 'unknown'}
|
||||
</span>
|
||||
</td>
|
||||
</Show>
|
||||
<Show when={viewMode() === 'node' && isColumnVisible('shared')}>
|
||||
<td class="p-0.5 px-1.5">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{storage.shared ? '✓' : '-'}
|
||||
@@ -1193,12 +1230,16 @@ const Storage: Component = () => {
|
||||
zfsPool={storage.zfsPool}
|
||||
/>
|
||||
</td>
|
||||
<td class="p-0.5 px-1.5 text-xs whitespace-nowrap">
|
||||
{formatBytes(storage.free || 0, 0)}
|
||||
</td>
|
||||
<td class="p-0.5 px-1.5 text-xs whitespace-nowrap">
|
||||
{formatBytes(storage.total || 0, 0)}
|
||||
</td>
|
||||
<Show when={isColumnVisible('free')}>
|
||||
<td class="p-0.5 px-1.5 text-xs whitespace-nowrap">
|
||||
{formatBytes(storage.free || 0, 0)}
|
||||
</td>
|
||||
</Show>
|
||||
<Show when={isColumnVisible('total')}>
|
||||
<td class="p-0.5 px-1.5 text-xs whitespace-nowrap">
|
||||
{formatBytes(storage.total || 0, 0)}
|
||||
</td>
|
||||
</Show>
|
||||
<td class="p-0.5 px-1.5"></td>
|
||||
</tr>
|
||||
<Show when={isCephStorage() && isExpanded()}>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component, Show, For, createSignal, onMount, createEffect, onCleanup } from 'solid-js';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { SearchTipsPopover } from '@/components/shared/SearchTipsPopover';
|
||||
import { ColumnPicker } from '@/components/shared/ColumnPicker';
|
||||
import type { ColumnDef } from '@/hooks/useColumnVisibility';
|
||||
import { STORAGE_KEYS } from '@/utils/localStorage';
|
||||
import { createSearchHistoryManager } from '@/utils/searchHistory';
|
||||
|
||||
@@ -17,6 +19,13 @@ interface StorageFilterProps {
|
||||
searchInputRef?: (el: HTMLInputElement) => void;
|
||||
statusFilter?: () => 'all' | 'available' | 'offline';
|
||||
setStatusFilter?: (value: 'all' | 'available' | 'offline') => void;
|
||||
// Column visibility (optional)
|
||||
columnVisibility?: {
|
||||
availableToggles: () => ColumnDef[];
|
||||
isHiddenByUser: (id: string) => boolean;
|
||||
toggle: (id: string) => void;
|
||||
resetToDefaults: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const StorageFilter: Component<StorageFilterProps> = (props) => {
|
||||
@@ -387,6 +396,17 @@ export const StorageFilter: Component<StorageFilterProps> = (props) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Column Picker */}
|
||||
<Show when={props.columnVisibility}>
|
||||
<div class="h-5 w-px bg-gray-200 dark:bg-gray-600 hidden sm:block"></div>
|
||||
<ColumnPicker
|
||||
columns={props.columnVisibility!.availableToggles()}
|
||||
isHidden={props.columnVisibility!.isHiddenByUser}
|
||||
onToggle={props.columnVisibility!.toggle}
|
||||
onReset={props.columnVisibility!.resetToDefaults}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
{/* Reset Button - Only show when filters are active */}
|
||||
<Show when={hasActiveFilters()}>
|
||||
<div class="h-5 w-px bg-gray-200 dark:bg-gray-600 hidden sm:block"></div>
|
||||
|
||||
@@ -2661,7 +2661,8 @@ function OverviewTab(props: {
|
||||
</div>
|
||||
|
||||
{/* Sub-tabs for AI Insights vs Active Alerts */}
|
||||
<Show when={patrolStatus()?.enabled}>
|
||||
{/* Show tabs when patrol is enabled OR when there are legacy findings to clear */}
|
||||
<Show when={patrolStatus()?.enabled || aiFindings().length > 0}>
|
||||
<div class="flex items-center gap-1 border-b border-gray-200 dark:border-gray-700/50 pb-1">
|
||||
<button
|
||||
class={`px-4 py-2 text-sm font-medium rounded-t-lg transition-colors ${overviewSubTab() === 'active-alerts'
|
||||
@@ -2694,8 +2695,8 @@ function OverviewTab(props: {
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* AI Insights Section - show when AI tab selected and there are findings */}
|
||||
<Show when={overviewSubTab() === 'ai-insights' && patrolStatus()?.enabled}>
|
||||
{/* AI Insights Section - show when AI tab selected and patrol enabled OR has findings to clear */}
|
||||
<Show when={overviewSubTab() === 'ai-insights' && (patrolStatus()?.enabled || aiFindings().length > 0)}>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<SectionHeader
|
||||
@@ -3778,8 +3779,8 @@ function OverviewTab(props: {
|
||||
</div>
|
||||
</Show >
|
||||
|
||||
{/* Active Alerts - show when alerts tab selected OR when patrol is disabled (no sub-tabs) */}
|
||||
< Show when={overviewSubTab() === 'active-alerts' || !patrolStatus()?.enabled
|
||||
{/* Active Alerts - show when alerts tab selected OR when patrol is disabled AND no legacy findings (no sub-tabs) */}
|
||||
< Show when={overviewSubTab() === 'active-alerts' || (!patrolStatus()?.enabled && aiFindings().length === 0)
|
||||
}>
|
||||
<div>
|
||||
<SectionHeader title="Active Alerts" size="md" class="mb-3" />
|
||||
|
||||
@@ -115,6 +115,7 @@ export const STORAGE_KEYS = {
|
||||
HOSTS_HIDDEN_COLUMNS: 'hostsHiddenColumns',
|
||||
DOCKER_HIDDEN_COLUMNS: 'dockerHiddenColumns',
|
||||
BACKUPS_HIDDEN_COLUMNS: 'backupsHiddenColumns',
|
||||
STORAGE_HIDDEN_COLUMNS: 'storageHiddenColumns',
|
||||
|
||||
// Resources search
|
||||
RESOURCES_SEARCH_HISTORY: 'resourcesSearchHistory',
|
||||
|
||||
Reference in New Issue
Block a user