mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-20 18:55:40 +00:00
Hoist workload filter to top on platform overview pages
Move the WorkloadsFilter to the top of the Proxmox, Kubernetes, TrueNAS, and vSphere overview surfaces so every workload page renders the filter bar above its tables, matching the Docker pattern. The mid-page filter placement was inconsistent across pages and pushed the search input below the fold on busy hosts/nodes/clusters tables. Each page lifts useWorkloadsState to its overview component, renders WorkloadsFilter directly, and passes the state into WorkloadsSurface (removing showFilterToolbar and the filter* configuration props from the surface). Filter scope is unchanged — it still governs the embedded workloads table only.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import ShipWheelIcon from 'lucide-solid/icons/ship-wheel';
|
||||
import { Show, createMemo } from 'solid-js';
|
||||
import { Show, createMemo, type Accessor } from 'solid-js';
|
||||
import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter';
|
||||
import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface';
|
||||
import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState';
|
||||
import type { WorkloadsStatusOption } from '@/components/Workloads/workloadsFilterModel';
|
||||
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
|
||||
import {
|
||||
@@ -11,7 +13,11 @@ import {
|
||||
import { KubernetesClustersTable } from './KubernetesClustersTable';
|
||||
import { KubernetesDeploymentsTable } from './KubernetesDeploymentsTable';
|
||||
import { KubernetesNodesTable } from './KubernetesNodesTable';
|
||||
import { KUBERNETES_TAB_SPECS, buildKubernetesPageModel } from './kubernetesPageModel';
|
||||
import {
|
||||
KUBERNETES_TAB_SPECS,
|
||||
buildKubernetesPageModel,
|
||||
type KubernetesPageModel,
|
||||
} from './kubernetesPageModel';
|
||||
|
||||
// Include `agent` rows so K8s nodes that the backend registry merged onto
|
||||
// the linked agent host (sources=['agent','kubernetes']) still appear in the
|
||||
@@ -76,51 +82,7 @@ export function KubernetesPageSurface() {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<KubernetesClustersTable
|
||||
clusters={model().clusters}
|
||||
scope={model().resources}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No clusters reported"
|
||||
emptyDescription="Kubernetes clusters appear here once at least one agent reports cluster context."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<KubernetesNodesTable
|
||||
resources={model().nodes}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No nodes reported"
|
||||
emptyDescription="Kubernetes nodes appear here as soon as the agent enumerates them."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<WorkloadsSurface
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
showFilterToolbar
|
||||
suppressPlatformFilter
|
||||
allowEmbeddedScopeFilters
|
||||
forcedPlatform={KUBERNETES_PLATFORM_FILTER}
|
||||
forcedViewMode={KUBERNETES_WORKLOAD_FORCED_VIEW_MODE}
|
||||
filterAriaLabel="Kubernetes pod filters"
|
||||
filterSearchPlaceholder="Search pods by name, namespace, image, cluster, or node"
|
||||
filterSearchEmptyMessage="Recent Kubernetes pod searches appear here."
|
||||
filterStatusOptions={KUBERNETES_POD_STATUS_OPTIONS}
|
||||
columnVisibilityStorageScope={KUBERNETES_WORKLOAD_COLUMN_SCOPE}
|
||||
compactGroupHeaders
|
||||
/>
|
||||
<Show when={model().deployments.length > 0}>
|
||||
<KubernetesDeploymentsTable
|
||||
resources={model().deployments}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No deployments reported"
|
||||
emptyDescription="Deployments appear here once the cluster reports them."
|
||||
showToolbar={false}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<KubernetesOverview model={model} />
|
||||
</Show>
|
||||
</Show>
|
||||
</Show>
|
||||
@@ -128,4 +90,112 @@ export function KubernetesPageSurface() {
|
||||
);
|
||||
}
|
||||
|
||||
interface KubernetesOverviewProps {
|
||||
model: Accessor<KubernetesPageModel>;
|
||||
}
|
||||
|
||||
function KubernetesOverview(props: KubernetesOverviewProps) {
|
||||
const workloadsState = useWorkloadsState({
|
||||
vms: [],
|
||||
containers: [],
|
||||
nodes: [],
|
||||
useWorkloads: true,
|
||||
embedded: true,
|
||||
tableOnly: true,
|
||||
forcedPlatform: KUBERNETES_PLATFORM_FILTER,
|
||||
forcedViewMode: KUBERNETES_WORKLOAD_FORCED_VIEW_MODE,
|
||||
showFilterToolbar: true,
|
||||
suppressPlatformFilter: true,
|
||||
allowEmbeddedScopeFilters: true,
|
||||
columnVisibilityStorageScope: KUBERNETES_WORKLOAD_COLUMN_SCOPE,
|
||||
compactGroupHeaders: true,
|
||||
});
|
||||
const showSharedFilterToolbar = createMemo(
|
||||
() =>
|
||||
workloadsState.surfaceConnected() &&
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 0,
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
<Show when={showSharedFilterToolbar()}>
|
||||
<div data-summary-clear-ignore>
|
||||
<WorkloadsFilter
|
||||
search={workloadsState.search}
|
||||
setSearch={workloadsState.setSearch}
|
||||
viewMode={workloadsState.viewMode}
|
||||
setViewMode={workloadsState.setViewMode}
|
||||
statusMode={workloadsState.statusMode}
|
||||
setStatusMode={workloadsState.setStatusMode}
|
||||
groupingMode={workloadsState.groupingMode}
|
||||
setGroupingMode={workloadsState.setGroupingMode}
|
||||
setSortKey={workloadsState.setSortKey}
|
||||
setSortDirection={workloadsState.setSortDirection}
|
||||
onBeforeAutoFocus={workloadsState.handleBeforeAutoFocus}
|
||||
ariaLabel="Kubernetes pod filters"
|
||||
searchPlaceholder="Search pods by name, namespace, image, cluster, or node"
|
||||
searchEmptyMessage="Recent Kubernetes pod searches appear here."
|
||||
statusOptions={KUBERNETES_POD_STATUS_OPTIONS}
|
||||
columnVisibility={workloadsState.workloadsFilterColumnVisibility()}
|
||||
containerRuntimeFilter={workloadsState.containerRuntimeFilterConfig()}
|
||||
hostFilter={workloadsState.hostFilterConfig()}
|
||||
namespaceFilter={workloadsState.namespaceFilterConfig()}
|
||||
platformFilter={undefined}
|
||||
suppressTypeFilter
|
||||
metricDisplayMode={workloadsState.workloadMetricDisplayMode}
|
||||
setMetricDisplayMode={workloadsState.setWorkloadMetricDisplayMode}
|
||||
metricHistoryRange={workloadsState.workloadMetricHistoryRange}
|
||||
setMetricHistoryRange={workloadsState.setWorkloadMetricHistoryRange}
|
||||
forcedPlatform={KUBERNETES_PLATFORM_FILTER}
|
||||
pinnedSelectionActive={() =>
|
||||
Boolean(
|
||||
workloadsState.selectedGuestId() ||
|
||||
workloadsState.focusedSummaryWorkloadGroupId(),
|
||||
)
|
||||
}
|
||||
onClearPinnedSelection={workloadsState.clearPinnedSummaryScope}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<KubernetesClustersTable
|
||||
clusters={props.model().clusters}
|
||||
scope={props.model().resources}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No clusters reported"
|
||||
emptyDescription="Kubernetes clusters appear here once at least one agent reports cluster context."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<KubernetesNodesTable
|
||||
resources={props.model().nodes}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No nodes reported"
|
||||
emptyDescription="Kubernetes nodes appear here as soon as the agent enumerates them."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<WorkloadsSurface
|
||||
state={workloadsState}
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
forcedPlatform={KUBERNETES_PLATFORM_FILTER}
|
||||
forcedViewMode={KUBERNETES_WORKLOAD_FORCED_VIEW_MODE}
|
||||
compactGroupHeaders
|
||||
/>
|
||||
<Show when={props.model().deployments.length > 0}>
|
||||
<KubernetesDeploymentsTable
|
||||
resources={props.model().deployments}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No deployments reported"
|
||||
emptyDescription="Deployments appear here once the cluster reports them."
|
||||
showToolbar={false}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default KubernetesPageSurface;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useLocation } from '@solidjs/router';
|
||||
import { Show, createMemo } from 'solid-js';
|
||||
import { Show, createMemo, type Accessor } from 'solid-js';
|
||||
import StorageSurface from '@/components/Storage/Storage';
|
||||
import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter';
|
||||
import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface';
|
||||
import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState';
|
||||
import {
|
||||
DEFAULT_WORKLOADS_METRIC_DISPLAY_MODE,
|
||||
type WorkloadsStatusOption,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
PROXMOX_TAB_SPECS,
|
||||
buildProxmoxPageModel,
|
||||
buildVisibleProxmoxTabSpecs,
|
||||
type ProxmoxPageModel,
|
||||
type ProxmoxPageTabId,
|
||||
} from './proxmoxPageModel';
|
||||
|
||||
@@ -132,42 +135,13 @@ export function ProxmoxPageSurface() {
|
||||
}
|
||||
>
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<div class="space-y-4">
|
||||
<ProxmoxNodesTable
|
||||
nodes={model().pveNodes}
|
||||
guests={model().guests}
|
||||
metricDisplayMode={metricDisplayMode}
|
||||
metricHistoryRange={metricHistoryRange}
|
||||
emptyIcon={<ProxmoxIcon class="h-6 w-6 text-slate-400" />}
|
||||
emptyTitle="No Proxmox VE nodes"
|
||||
emptyDescription="Proxmox VE nodes appear here once a PVE host reports inventory."
|
||||
/>
|
||||
<WorkloadsSurface
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
showFilterToolbar
|
||||
suppressPlatformFilter
|
||||
allowEmbeddedScopeFilters
|
||||
forcedPlatform={PROXMOX_PLATFORM_FILTER}
|
||||
statusModeStorageScope={PROXMOX_WORKLOAD_STATUS_STORAGE_SCOPE}
|
||||
filterAriaLabel="Proxmox workload filters"
|
||||
filterSearchPlaceholder="Search VMs and LXCs by name, VMID, node, or status"
|
||||
filterSearchEmptyMessage="Recent Proxmox workload searches appear here."
|
||||
filterStatusOptions={PROXMOX_WORKLOAD_STATUS_OPTIONS}
|
||||
compactGroupHeaders
|
||||
groupNodeDrawerMode="disabled"
|
||||
metricDisplayMode={metricDisplayMode}
|
||||
onMetricDisplayModeChange={setMetricDisplayMode}
|
||||
metricHistoryRange={metricHistoryRange}
|
||||
onMetricHistoryRangeChange={setMetricHistoryRange}
|
||||
emptyStateTitle="No Proxmox workloads"
|
||||
emptyStateDescription="Proxmox VMs and LXCs appear here when inventory is available."
|
||||
/>
|
||||
</div>
|
||||
<ProxmoxOverview
|
||||
model={model}
|
||||
metricDisplayMode={metricDisplayMode}
|
||||
setMetricDisplayMode={setMetricDisplayMode}
|
||||
metricHistoryRange={metricHistoryRange}
|
||||
setMetricHistoryRange={setMetricHistoryRange}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={activeTab() === 'storage'}>
|
||||
<StorageSurface
|
||||
@@ -212,4 +186,106 @@ export function ProxmoxPageSurface() {
|
||||
);
|
||||
}
|
||||
|
||||
interface ProxmoxOverviewProps {
|
||||
model: Accessor<ProxmoxPageModel>;
|
||||
metricDisplayMode: Accessor<WorkloadsMetricDisplayMode>;
|
||||
setMetricDisplayMode: (value: WorkloadsMetricDisplayMode) => void;
|
||||
metricHistoryRange: Accessor<WorkloadTableMetricHistoryRange>;
|
||||
setMetricHistoryRange: (value: WorkloadTableMetricHistoryRange) => void;
|
||||
}
|
||||
|
||||
function ProxmoxOverview(props: ProxmoxOverviewProps) {
|
||||
const workloadsState = useWorkloadsState({
|
||||
vms: [],
|
||||
containers: [],
|
||||
nodes: [],
|
||||
useWorkloads: true,
|
||||
embedded: true,
|
||||
tableOnly: true,
|
||||
forcedPlatform: PROXMOX_PLATFORM_FILTER,
|
||||
showFilterToolbar: true,
|
||||
suppressPlatformFilter: true,
|
||||
allowEmbeddedScopeFilters: true,
|
||||
statusModeStorageScope: PROXMOX_WORKLOAD_STATUS_STORAGE_SCOPE,
|
||||
compactGroupHeaders: true,
|
||||
groupNodeDrawerMode: 'disabled',
|
||||
metricDisplayMode: props.metricDisplayMode,
|
||||
onMetricDisplayModeChange: props.setMetricDisplayMode,
|
||||
metricHistoryRange: props.metricHistoryRange,
|
||||
onMetricHistoryRangeChange: props.setMetricHistoryRange,
|
||||
});
|
||||
const showSharedFilterToolbar = createMemo(
|
||||
() =>
|
||||
workloadsState.surfaceConnected() &&
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 0,
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
<Show when={showSharedFilterToolbar()}>
|
||||
<div data-summary-clear-ignore>
|
||||
<WorkloadsFilter
|
||||
search={workloadsState.search}
|
||||
setSearch={workloadsState.setSearch}
|
||||
viewMode={workloadsState.viewMode}
|
||||
setViewMode={workloadsState.setViewMode}
|
||||
statusMode={workloadsState.statusMode}
|
||||
setStatusMode={workloadsState.setStatusMode}
|
||||
groupingMode={workloadsState.groupingMode}
|
||||
setGroupingMode={workloadsState.setGroupingMode}
|
||||
setSortKey={workloadsState.setSortKey}
|
||||
setSortDirection={workloadsState.setSortDirection}
|
||||
onBeforeAutoFocus={workloadsState.handleBeforeAutoFocus}
|
||||
ariaLabel="Proxmox workload filters"
|
||||
searchPlaceholder="Search VMs and LXCs by name, VMID, node, or status"
|
||||
searchEmptyMessage="Recent Proxmox workload searches appear here."
|
||||
statusOptions={PROXMOX_WORKLOAD_STATUS_OPTIONS}
|
||||
columnVisibility={workloadsState.workloadsFilterColumnVisibility()}
|
||||
containerRuntimeFilter={workloadsState.containerRuntimeFilterConfig()}
|
||||
hostFilter={workloadsState.hostFilterConfig()}
|
||||
namespaceFilter={workloadsState.namespaceFilterConfig()}
|
||||
platformFilter={undefined}
|
||||
metricDisplayMode={workloadsState.workloadMetricDisplayMode}
|
||||
setMetricDisplayMode={workloadsState.setWorkloadMetricDisplayMode}
|
||||
metricHistoryRange={workloadsState.workloadMetricHistoryRange}
|
||||
setMetricHistoryRange={workloadsState.setWorkloadMetricHistoryRange}
|
||||
forcedPlatform={PROXMOX_PLATFORM_FILTER}
|
||||
pinnedSelectionActive={() =>
|
||||
Boolean(
|
||||
workloadsState.selectedGuestId() ||
|
||||
workloadsState.focusedSummaryWorkloadGroupId(),
|
||||
)
|
||||
}
|
||||
onClearPinnedSelection={workloadsState.clearPinnedSummaryScope}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<ProxmoxNodesTable
|
||||
nodes={props.model().pveNodes}
|
||||
guests={props.model().guests}
|
||||
metricDisplayMode={props.metricDisplayMode}
|
||||
metricHistoryRange={props.metricHistoryRange}
|
||||
emptyIcon={<ProxmoxIcon class="h-6 w-6 text-slate-400" />}
|
||||
emptyTitle="No Proxmox VE nodes"
|
||||
emptyDescription="Proxmox VE nodes appear here once a PVE host reports inventory."
|
||||
/>
|
||||
<WorkloadsSurface
|
||||
state={workloadsState}
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
forcedPlatform={PROXMOX_PLATFORM_FILTER}
|
||||
compactGroupHeaders
|
||||
groupNodeDrawerMode="disabled"
|
||||
emptyStateTitle="No Proxmox workloads"
|
||||
emptyStateDescription="Proxmox VMs and LXCs appear here when inventory is available."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProxmoxPageSurface;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useLocation } from '@solidjs/router';
|
||||
import DatabaseIcon from 'lucide-solid/icons/database';
|
||||
import { Show, createMemo } from 'solid-js';
|
||||
import { Show, createMemo, type Accessor } from 'solid-js';
|
||||
import StorageSurface from '@/components/Storage/Storage';
|
||||
import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter';
|
||||
import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface';
|
||||
import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState';
|
||||
import type { WorkloadsStatusOption } from '@/components/Workloads/workloadsFilterModel';
|
||||
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
|
||||
import {
|
||||
@@ -14,6 +16,7 @@ import { TrueNASSystemsTable } from './TrueNASSystemsTable';
|
||||
import {
|
||||
TRUENAS_TAB_SPECS,
|
||||
buildTrueNASPageModel,
|
||||
type TrueNASPageModel,
|
||||
type TrueNASPageTabId,
|
||||
} from './truenasPageModel';
|
||||
|
||||
@@ -86,36 +89,7 @@ export function TrueNASPageSurface() {
|
||||
}
|
||||
>
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<div class="space-y-4">
|
||||
<TrueNASSystemsTable
|
||||
systems={model().systems}
|
||||
scope={model().resources}
|
||||
emptyIcon={truenasIcon()}
|
||||
emptyTitle="No TrueNAS systems"
|
||||
emptyDescription="TrueNAS systems appear here once a TrueNAS connection reports its top-level appliance."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<Show when={model().apps.length > 0}>
|
||||
<WorkloadsSurface
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
showFilterToolbar
|
||||
suppressPlatformFilter
|
||||
allowEmbeddedScopeFilters
|
||||
forcedPlatform={TRUENAS_PLATFORM_FILTER}
|
||||
forcedViewMode="app-container"
|
||||
filterAriaLabel="TrueNAS app filters"
|
||||
filterSearchPlaceholder="Search TrueNAS apps by name, image, namespace, or system"
|
||||
filterSearchEmptyMessage="Recent TrueNAS app searches appear here."
|
||||
filterStatusOptions={TRUENAS_APP_STATUS_OPTIONS}
|
||||
compactGroupHeaders
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<TrueNASOverview model={model} />
|
||||
</Show>
|
||||
<Show when={activeTab() === 'storage'}>
|
||||
<StorageSurface
|
||||
@@ -135,4 +109,98 @@ export function TrueNASPageSurface() {
|
||||
);
|
||||
}
|
||||
|
||||
interface TrueNASOverviewProps {
|
||||
model: Accessor<TrueNASPageModel>;
|
||||
}
|
||||
|
||||
function TrueNASOverview(props: TrueNASOverviewProps) {
|
||||
const workloadsState = useWorkloadsState({
|
||||
vms: [],
|
||||
containers: [],
|
||||
nodes: [],
|
||||
useWorkloads: true,
|
||||
embedded: true,
|
||||
tableOnly: true,
|
||||
forcedPlatform: TRUENAS_PLATFORM_FILTER,
|
||||
forcedViewMode: 'app-container',
|
||||
showFilterToolbar: true,
|
||||
suppressPlatformFilter: true,
|
||||
allowEmbeddedScopeFilters: true,
|
||||
compactGroupHeaders: true,
|
||||
});
|
||||
const showSharedFilterToolbar = createMemo(
|
||||
() =>
|
||||
props.model().apps.length > 0 &&
|
||||
workloadsState.surfaceConnected() &&
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 0,
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
<Show when={showSharedFilterToolbar()}>
|
||||
<div data-summary-clear-ignore>
|
||||
<WorkloadsFilter
|
||||
search={workloadsState.search}
|
||||
setSearch={workloadsState.setSearch}
|
||||
viewMode={workloadsState.viewMode}
|
||||
setViewMode={workloadsState.setViewMode}
|
||||
statusMode={workloadsState.statusMode}
|
||||
setStatusMode={workloadsState.setStatusMode}
|
||||
groupingMode={workloadsState.groupingMode}
|
||||
setGroupingMode={workloadsState.setGroupingMode}
|
||||
setSortKey={workloadsState.setSortKey}
|
||||
setSortDirection={workloadsState.setSortDirection}
|
||||
onBeforeAutoFocus={workloadsState.handleBeforeAutoFocus}
|
||||
ariaLabel="TrueNAS app filters"
|
||||
searchPlaceholder="Search TrueNAS apps by name, image, namespace, or system"
|
||||
searchEmptyMessage="Recent TrueNAS app searches appear here."
|
||||
statusOptions={TRUENAS_APP_STATUS_OPTIONS}
|
||||
columnVisibility={workloadsState.workloadsFilterColumnVisibility()}
|
||||
containerRuntimeFilter={workloadsState.containerRuntimeFilterConfig()}
|
||||
hostFilter={workloadsState.hostFilterConfig()}
|
||||
namespaceFilter={workloadsState.namespaceFilterConfig()}
|
||||
platformFilter={undefined}
|
||||
suppressTypeFilter
|
||||
metricDisplayMode={workloadsState.workloadMetricDisplayMode}
|
||||
setMetricDisplayMode={workloadsState.setWorkloadMetricDisplayMode}
|
||||
metricHistoryRange={workloadsState.workloadMetricHistoryRange}
|
||||
setMetricHistoryRange={workloadsState.setWorkloadMetricHistoryRange}
|
||||
forcedPlatform={TRUENAS_PLATFORM_FILTER}
|
||||
pinnedSelectionActive={() =>
|
||||
Boolean(
|
||||
workloadsState.selectedGuestId() ||
|
||||
workloadsState.focusedSummaryWorkloadGroupId(),
|
||||
)
|
||||
}
|
||||
onClearPinnedSelection={workloadsState.clearPinnedSummaryScope}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<TrueNASSystemsTable
|
||||
systems={props.model().systems}
|
||||
scope={props.model().resources}
|
||||
emptyIcon={truenasIcon()}
|
||||
emptyTitle="No TrueNAS systems"
|
||||
emptyDescription="TrueNAS systems appear here once a TrueNAS connection reports its top-level appliance."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<Show when={props.model().apps.length > 0}>
|
||||
<WorkloadsSurface
|
||||
state={workloadsState}
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
forcedPlatform={TRUENAS_PLATFORM_FILTER}
|
||||
forcedViewMode="app-container"
|
||||
compactGroupHeaders
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TrueNASPageSurface;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useLocation } from '@solidjs/router';
|
||||
import CpuIcon from 'lucide-solid/icons/cpu';
|
||||
import { Show, createMemo } from 'solid-js';
|
||||
import { Show, createMemo, type Accessor } from 'solid-js';
|
||||
import StorageSurface from '@/components/Storage/Storage';
|
||||
import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter';
|
||||
import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface';
|
||||
import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState';
|
||||
import type { WorkloadsStatusOption } from '@/components/Workloads/workloadsFilterModel';
|
||||
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
|
||||
import {
|
||||
@@ -11,7 +13,12 @@ import {
|
||||
PlatformTableEmptyState,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { VsphereHostsTable } from './VsphereHostsTable';
|
||||
import { VMWARE_TAB_SPECS, buildVmwarePageModel, type VmwarePageTabId } from './vmwarePageModel';
|
||||
import {
|
||||
VMWARE_TAB_SPECS,
|
||||
buildVmwarePageModel,
|
||||
type VmwarePageModel,
|
||||
type VmwarePageTabId,
|
||||
} from './vmwarePageModel';
|
||||
|
||||
// `datastore` is not a first-class type token at the API boundary; vSphere
|
||||
// datastores are emitted as canonical `storage` rows. Including it
|
||||
@@ -80,34 +87,7 @@ export function VmwarePageSurface() {
|
||||
}
|
||||
>
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<div class="space-y-4">
|
||||
<VsphereHostsTable
|
||||
hosts={model().hosts}
|
||||
scope={model().resources}
|
||||
emptyIcon={vmwareIcon()}
|
||||
emptyTitle="No vSphere hosts"
|
||||
emptyDescription="Hosts appear here once the vCenter connection enumerates them."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<WorkloadsSurface
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
showFilterToolbar
|
||||
suppressPlatformFilter
|
||||
allowEmbeddedScopeFilters
|
||||
forcedPlatform={VMWARE_PLATFORM_FILTER}
|
||||
forcedViewMode="vm"
|
||||
filterAriaLabel="vSphere VM filters"
|
||||
filterSearchPlaceholder="Search vSphere VMs by name, VM ID, host, or status"
|
||||
filterSearchEmptyMessage="Recent vSphere VM searches appear here."
|
||||
filterStatusOptions={VMWARE_VM_STATUS_OPTIONS}
|
||||
compactGroupHeaders
|
||||
/>
|
||||
</div>
|
||||
<VmwareOverview model={model} />
|
||||
</Show>
|
||||
<Show when={activeTab() === 'storage'}>
|
||||
<StorageSurface
|
||||
@@ -128,4 +108,95 @@ export function VmwarePageSurface() {
|
||||
);
|
||||
}
|
||||
|
||||
interface VmwareOverviewProps {
|
||||
model: Accessor<VmwarePageModel>;
|
||||
}
|
||||
|
||||
function VmwareOverview(props: VmwareOverviewProps) {
|
||||
const workloadsState = useWorkloadsState({
|
||||
vms: [],
|
||||
containers: [],
|
||||
nodes: [],
|
||||
useWorkloads: true,
|
||||
embedded: true,
|
||||
tableOnly: true,
|
||||
forcedPlatform: VMWARE_PLATFORM_FILTER,
|
||||
forcedViewMode: 'vm',
|
||||
showFilterToolbar: true,
|
||||
suppressPlatformFilter: true,
|
||||
allowEmbeddedScopeFilters: true,
|
||||
compactGroupHeaders: true,
|
||||
});
|
||||
const showSharedFilterToolbar = createMemo(
|
||||
() =>
|
||||
workloadsState.surfaceConnected() &&
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 0,
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
<Show when={showSharedFilterToolbar()}>
|
||||
<div data-summary-clear-ignore>
|
||||
<WorkloadsFilter
|
||||
search={workloadsState.search}
|
||||
setSearch={workloadsState.setSearch}
|
||||
viewMode={workloadsState.viewMode}
|
||||
setViewMode={workloadsState.setViewMode}
|
||||
statusMode={workloadsState.statusMode}
|
||||
setStatusMode={workloadsState.setStatusMode}
|
||||
groupingMode={workloadsState.groupingMode}
|
||||
setGroupingMode={workloadsState.setGroupingMode}
|
||||
setSortKey={workloadsState.setSortKey}
|
||||
setSortDirection={workloadsState.setSortDirection}
|
||||
onBeforeAutoFocus={workloadsState.handleBeforeAutoFocus}
|
||||
ariaLabel="vSphere VM filters"
|
||||
searchPlaceholder="Search vSphere VMs by name, VM ID, host, or status"
|
||||
searchEmptyMessage="Recent vSphere VM searches appear here."
|
||||
statusOptions={VMWARE_VM_STATUS_OPTIONS}
|
||||
columnVisibility={workloadsState.workloadsFilterColumnVisibility()}
|
||||
containerRuntimeFilter={workloadsState.containerRuntimeFilterConfig()}
|
||||
hostFilter={workloadsState.hostFilterConfig()}
|
||||
namespaceFilter={workloadsState.namespaceFilterConfig()}
|
||||
platformFilter={undefined}
|
||||
suppressTypeFilter
|
||||
metricDisplayMode={workloadsState.workloadMetricDisplayMode}
|
||||
setMetricDisplayMode={workloadsState.setWorkloadMetricDisplayMode}
|
||||
metricHistoryRange={workloadsState.workloadMetricHistoryRange}
|
||||
setMetricHistoryRange={workloadsState.setWorkloadMetricHistoryRange}
|
||||
forcedPlatform={VMWARE_PLATFORM_FILTER}
|
||||
pinnedSelectionActive={() =>
|
||||
Boolean(
|
||||
workloadsState.selectedGuestId() ||
|
||||
workloadsState.focusedSummaryWorkloadGroupId(),
|
||||
)
|
||||
}
|
||||
onClearPinnedSelection={workloadsState.clearPinnedSummaryScope}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<VsphereHostsTable
|
||||
hosts={props.model().hosts}
|
||||
scope={props.model().resources}
|
||||
emptyIcon={vmwareIcon()}
|
||||
emptyTitle="No vSphere hosts"
|
||||
emptyDescription="Hosts appear here once the vCenter connection enumerates them."
|
||||
showToolbar={false}
|
||||
/>
|
||||
<WorkloadsSurface
|
||||
state={workloadsState}
|
||||
vms={[]}
|
||||
containers={[]}
|
||||
nodes={[]}
|
||||
useWorkloads
|
||||
embedded
|
||||
tableOnly
|
||||
forcedPlatform={VMWARE_PLATFORM_FILTER}
|
||||
forcedViewMode="vm"
|
||||
compactGroupHeaders
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VmwarePageSurface;
|
||||
|
||||
Reference in New Issue
Block a user