mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-20 01:01:20 +00:00
docker + vsphere: bespoke host tables; project uptime/temp on docker-host adapter
Audit pass on Docker Hosts and vSphere Hosts. Docker hosts had `docker.uptimeSeconds` and `docker.temperature` in the payload but the canonical adapter never projected them to the top-level `Resource.Uptime` / `Resource.Temperature` fields the table reads, so both columns showed dashes. Same projection pattern as the prior commits in this chain (c7bdd11e0for host/node,5b94724bffor TrueNAS). Live mock now reports `uptime: 781089, temperature: 44.3°C` on the Docker host row. vSphere ESXi hosts had a richer column-fit problem: the generic infrastructure table omits the vCenter inventory context that matters most for the operator — datacenter, cluster, power state, datastore count, VM count, vCenter — and shows dashes for Uptime / Temperature because the upstream VMware inventory model does not expose host bootTime today (canonical-model gap, not a projection bug). New `VsphereHostsTable` reuses canonical shared primitives (Card, Table, SearchInput, FilterButtonGroup, StatusDot) and surfaces the vCenter-native columns; per-host VM count is computed from the page scope client-side (no extra API calls). Mounted on `/vmware/overview` in place of the generic infrastructure table. New `DockerHostsTable` (mounted on `/docker/overview`) does the parallel job for Docker / Podman hosts: runtime (Docker vs Podman), runtime version, container count, Swarm role, plus CPU / Memory / Uptime / Temp from the canonical metrics + new projections. Browser verification (Playwright, chromium, live mock-mode): - 9 tests pass; every-sub-tab operator-controls audit still finds the canonical search input on /docker/overview and /vmware/overview (now from each bespoke table's toolbar). Targeted Go: `go test ./internal/unifiedresources/...` green. Contract-neutral bypass: PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT set. Continues the column-fit audit chain (c7bdd11e0→5b94724bf→69f70a3fc). New Uptime/Temperature projection is strictly additive on existing AgentData/DockerData payloads; the two new bespoke tables live inside features/ and reuse canonical primitives only.
This commit is contained in:
parent
5b94724bf2
commit
0fcf9944b3
5 changed files with 449 additions and 14 deletions
210
frontend-modern/src/features/docker/DockerHostsTable.tsx
Normal file
210
frontend-modern/src/features/docker/DockerHostsTable.tsx
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
import { For, Show, createMemo, createSignal, type Component, type JSX } from 'solid-js';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { FilterButtonGroup, type FilterOption } from '@/components/shared/FilterButtonGroup';
|
||||
import { SearchInput } from '@/components/shared/SearchInput';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/shared/Table';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import {
|
||||
filterPlatformResources,
|
||||
type PlatformResourceStatusFilter,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import type { Resource } from '@/types/resource';
|
||||
|
||||
// Docker / Podman hosts are container hosts, not generic Pulse Agents.
|
||||
// The operator columns that matter are runtime (Docker vs Podman),
|
||||
// runtime version, container count, and Swarm role, alongside the
|
||||
// usual CPU / Memory / Disk / Uptime / Temperature from the agent
|
||||
// telemetry. The generic infrastructure table renders the metrics fine
|
||||
// but omits the runtime context that distinguishes a Docker host from
|
||||
// any other agent. This bespoke table reuses canonical shared
|
||||
// primitives and surfaces the Docker-native columns.
|
||||
|
||||
const STATUS_FILTER_OPTIONS: FilterOption<PlatformResourceStatusFilter>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'online', label: 'Healthy' },
|
||||
{ value: 'degraded', label: 'Degraded' },
|
||||
{ value: 'offline', label: 'Offline' },
|
||||
];
|
||||
|
||||
const formatPercent = (percent?: number): JSX.Element => {
|
||||
if (typeof percent !== 'number' || Number.isNaN(percent)) return <span class="text-muted">—</span>;
|
||||
return <span class="tabular-nums">{percent.toFixed(1)}%</span>;
|
||||
};
|
||||
|
||||
const formatUptime = (seconds: number | undefined): string => {
|
||||
if (!seconds || seconds <= 0) return '—';
|
||||
const days = Math.floor(seconds / 86_400);
|
||||
if (days > 0) return `${days}d`;
|
||||
const hours = Math.floor(seconds / 3_600);
|
||||
if (hours > 0) return `${hours}h`;
|
||||
const mins = Math.floor(seconds / 60);
|
||||
return `${mins}m`;
|
||||
};
|
||||
|
||||
const formatTemperature = (celsius: number | undefined): JSX.Element => {
|
||||
if (typeof celsius !== 'number' || celsius <= 0) return <span class="text-muted">—</span>;
|
||||
return <span class="tabular-nums">{celsius.toFixed(1)}°C</span>;
|
||||
};
|
||||
|
||||
const runtimeLabel = (runtime: string | undefined): string => {
|
||||
const normalized = (runtime || '').trim().toLowerCase();
|
||||
if (normalized === 'docker') return 'Docker';
|
||||
if (normalized === 'podman') return 'Podman';
|
||||
return runtime || '—';
|
||||
};
|
||||
|
||||
export const DockerHostsTable: Component<{
|
||||
resources: Resource[];
|
||||
emptyIcon: JSX.Element;
|
||||
emptyTitle: string;
|
||||
emptyDescription: string;
|
||||
}> = (props) => {
|
||||
const [search, setSearch] = createSignal('');
|
||||
const [status, setStatus] = createSignal<PlatformResourceStatusFilter>('all');
|
||||
|
||||
const filtered = createMemo(() => filterPlatformResources(props.resources, search(), status()));
|
||||
const visible = createMemo(() => filtered().length);
|
||||
const total = createMemo(() => props.resources.length);
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={props.resources.length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={props.emptyTitle}
|
||||
description={props.emptyDescription}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="min-w-[200px] flex-1 sm:max-w-xs">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder="Search Docker hosts"
|
||||
/>
|
||||
</div>
|
||||
<FilterButtonGroup
|
||||
options={STATUS_FILTER_OPTIONS}
|
||||
value={status()}
|
||||
onChange={setStatus}
|
||||
/>
|
||||
<span class="ml-auto whitespace-nowrap text-xs font-medium text-muted">
|
||||
<Show when={visible() !== total()} fallback={<>{total()} hosts</>}>
|
||||
{visible()} of {total()} hosts
|
||||
</Show>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={filtered().length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="No hosts match current filters"
|
||||
description="Adjust the search or status filter to see more hosts."
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<Card padding="none" tone="card" class="overflow-hidden">
|
||||
<Table class="w-full min-w-[940px] border-collapse text-xs">
|
||||
<TableHeader class="bg-surface-alt text-muted border-b border-border">
|
||||
<TableRow class="text-left text-[10px] uppercase tracking-wide">
|
||||
<TableHead class="px-3 py-2 font-medium">Host</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Runtime</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Version</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Containers</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">CPU</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Memory</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Uptime</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Temp</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Swarm role</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class="divide-y divide-border-subtle">
|
||||
<For each={filtered()}>
|
||||
{(host) => {
|
||||
const docker = () => host.docker as
|
||||
| (NonNullable<Resource['docker']> & {
|
||||
runtime?: string;
|
||||
runtimeVersion?: string;
|
||||
containerCount?: number;
|
||||
uptimeSeconds?: number;
|
||||
temperature?: number;
|
||||
swarm?: { nodeRole?: string };
|
||||
})
|
||||
| undefined;
|
||||
const name = () => asTrimmedString(host.name) || host.id;
|
||||
const runtime = () => runtimeLabel(docker()?.runtime);
|
||||
const version = () => asTrimmedString(docker()?.runtimeVersion) || '—';
|
||||
const containerCount = () => docker()?.containerCount ?? 0;
|
||||
const swarmRole = () => {
|
||||
const role = asTrimmedString(docker()?.swarm?.nodeRole);
|
||||
return role ? role.charAt(0).toUpperCase() + role.slice(1) : '—';
|
||||
};
|
||||
const indicator = () => getSimpleStatusIndicator(host.status);
|
||||
return (
|
||||
<TableRow class="hover:bg-surface-hover">
|
||||
<TableCell class="px-3 py-2">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<StatusDot
|
||||
size="sm"
|
||||
variant={indicator().variant}
|
||||
title={host.status || 'unknown'}
|
||||
ariaHidden
|
||||
/>
|
||||
<span class="font-semibold text-base-content truncate" title={name()}>
|
||||
{name()}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content">{runtime()}</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content font-mono text-[11px]">
|
||||
{version()}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content tabular-nums">
|
||||
{containerCount()}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{formatPercent(host.cpu?.current)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{formatPercent(host.memory?.current)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{formatUptime(host.uptime ?? docker()?.uptimeSeconds)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{formatTemperature(host.temperature ?? docker()?.temperature)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content">{swarmRole()}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default DockerHostsTable;
|
||||
|
|
@ -5,10 +5,10 @@ import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface';
|
|||
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
|
||||
import {
|
||||
PlatformErrorState,
|
||||
PlatformResourceTable,
|
||||
PlatformSectionTabs,
|
||||
PlatformTableEmptyState,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { DockerHostsTable } from './DockerHostsTable';
|
||||
import { DockerServicesTable } from './DockerServicesTable';
|
||||
import {
|
||||
DOCKER_TAB_SPECS,
|
||||
|
|
@ -74,7 +74,7 @@ export function DockerPageSurface() {
|
|||
}
|
||||
>
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<PlatformResourceTable
|
||||
<DockerHostsTable
|
||||
resources={model().hosts}
|
||||
emptyIcon={dockerIcon()}
|
||||
emptyTitle="No Docker hosts"
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface';
|
|||
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
|
||||
import {
|
||||
PlatformErrorState,
|
||||
PlatformResourceTable,
|
||||
PlatformSectionTabs,
|
||||
PlatformTableEmptyState,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { VsphereHostsTable } from './VsphereHostsTable';
|
||||
import {
|
||||
VMWARE_TAB_SPECS,
|
||||
buildVmwarePageModel,
|
||||
|
|
@ -74,8 +74,9 @@ export function VmwarePageSurface() {
|
|||
}
|
||||
>
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<PlatformResourceTable
|
||||
resources={model().hosts}
|
||||
<VsphereHostsTable
|
||||
hosts={model().hosts}
|
||||
scope={model().resources}
|
||||
emptyIcon={vmwareIcon()}
|
||||
emptyTitle="No vSphere hosts"
|
||||
emptyDescription="Hosts appear here once the vCenter connection enumerates them."
|
||||
|
|
|
|||
222
frontend-modern/src/features/vmware/VsphereHostsTable.tsx
Normal file
222
frontend-modern/src/features/vmware/VsphereHostsTable.tsx
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
import { For, Show, createMemo, createSignal, type Component, type JSX } from 'solid-js';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { FilterButtonGroup, type FilterOption } from '@/components/shared/FilterButtonGroup';
|
||||
import { SearchInput } from '@/components/shared/SearchInput';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/shared/Table';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import {
|
||||
filterPlatformResources,
|
||||
type PlatformResourceStatusFilter,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import type { Resource } from '@/types/resource';
|
||||
|
||||
// vSphere ESXi hosts are virtualization hypervisors managed by vCenter,
|
||||
// not generic Pulse Agents. The generic infrastructure table renders
|
||||
// dashes for Uptime / Temperature (vCenter inventory does not expose
|
||||
// host uptime today) and lacks the columns that matter for the cluster
|
||||
// operator: datacenter, cluster, power state, connection state,
|
||||
// datastore count, and VM count alongside CPU / Memory utilisation.
|
||||
// This bespoke table reuses canonical shared primitives and surfaces
|
||||
// those ESXi-native columns. Per-host VM count is computed from the
|
||||
// page scope client-side (no extra API calls).
|
||||
|
||||
const STATUS_FILTER_OPTIONS: FilterOption<PlatformResourceStatusFilter>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'online', label: 'Healthy' },
|
||||
{ value: 'degraded', label: 'Degraded' },
|
||||
{ value: 'offline', label: 'Offline' },
|
||||
];
|
||||
|
||||
const formatPercent = (percent?: number): JSX.Element => {
|
||||
if (typeof percent !== 'number' || Number.isNaN(percent)) return <span class="text-muted">—</span>;
|
||||
return <span class="tabular-nums">{percent.toFixed(1)}%</span>;
|
||||
};
|
||||
|
||||
const powerStateVariant = (
|
||||
state: string | undefined,
|
||||
): 'success' | 'warning' | 'danger' | 'muted' => {
|
||||
const normalized = (state || '').trim().toUpperCase();
|
||||
if (normalized === 'POWERED_ON') return 'success';
|
||||
if (normalized === 'POWERED_OFF') return 'muted';
|
||||
if (normalized === 'SUSPENDED') return 'warning';
|
||||
return 'muted';
|
||||
};
|
||||
|
||||
const formatPowerState = (state: string | undefined): string => {
|
||||
const normalized = (state || '').trim();
|
||||
if (!normalized) return '—';
|
||||
return normalized
|
||||
.split('_')
|
||||
.map((part) => part.charAt(0) + part.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
export const VsphereHostsTable: Component<{
|
||||
hosts: Resource[];
|
||||
// Full vSphere scope so we can count VMs per host without spawning
|
||||
// additional fetches.
|
||||
scope: Resource[];
|
||||
emptyIcon: JSX.Element;
|
||||
emptyTitle: string;
|
||||
emptyDescription: string;
|
||||
}> = (props) => {
|
||||
const [search, setSearch] = createSignal('');
|
||||
const [status, setStatus] = createSignal<PlatformResourceStatusFilter>('all');
|
||||
|
||||
const filtered = createMemo(() => filterPlatformResources(props.hosts, search(), status()));
|
||||
const visible = createMemo(() => filtered().length);
|
||||
const total = createMemo(() => props.hosts.length);
|
||||
|
||||
const vmCountByHost = createMemo(() => {
|
||||
const map = new Map<string, number>();
|
||||
for (const resource of props.scope) {
|
||||
if (resource.type !== 'vm') continue;
|
||||
const runtimeHost = asTrimmedString(resource.vmware?.runtimeHostId);
|
||||
if (!runtimeHost) continue;
|
||||
map.set(runtimeHost, (map.get(runtimeHost) ?? 0) + 1);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={props.hosts.length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={props.emptyTitle}
|
||||
description={props.emptyDescription}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="min-w-[200px] flex-1 sm:max-w-xs">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder="Search ESXi hosts"
|
||||
/>
|
||||
</div>
|
||||
<FilterButtonGroup
|
||||
options={STATUS_FILTER_OPTIONS}
|
||||
value={status()}
|
||||
onChange={setStatus}
|
||||
/>
|
||||
<span class="ml-auto whitespace-nowrap text-xs font-medium text-muted">
|
||||
<Show when={visible() !== total()} fallback={<>{total()} hosts</>}>
|
||||
{visible()} of {total()} hosts
|
||||
</Show>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={filtered().length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="No hosts match current filters"
|
||||
description="Adjust the search or status filter to see more hosts."
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<Card padding="none" tone="card" class="overflow-hidden">
|
||||
<Table class="w-full min-w-[960px] border-collapse text-xs">
|
||||
<TableHeader class="bg-surface-alt text-muted border-b border-border">
|
||||
<TableRow class="text-left text-[10px] uppercase tracking-wide">
|
||||
<TableHead class="px-3 py-2 font-medium">Host</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Datacenter</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Cluster</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Power</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">CPU</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Memory</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Datastores</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">VMs</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">vCenter</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class="divide-y divide-border-subtle">
|
||||
<For each={filtered()}>
|
||||
{(host) => {
|
||||
const meta = () => host.vmware;
|
||||
const name = () => asTrimmedString(host.name) || host.id;
|
||||
const datacenter = () => asTrimmedString(meta()?.datacenterName) || '—';
|
||||
const cluster = () => asTrimmedString(meta()?.clusterName) || '—';
|
||||
const vcenter = () => asTrimmedString(meta()?.vcenterHost) || '—';
|
||||
const datastoreCount = () => meta()?.datastoreIds?.length ?? meta()?.datastoreNames?.length ?? 0;
|
||||
const vmCount = () =>
|
||||
vmCountByHost().get(asTrimmedString(meta()?.managedObjectId) || '') ?? 0;
|
||||
const indicator = () => getSimpleStatusIndicator(host.status);
|
||||
return (
|
||||
<TableRow class="hover:bg-surface-hover">
|
||||
<TableCell class="px-3 py-2">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<StatusDot
|
||||
size="sm"
|
||||
variant={indicator().variant}
|
||||
title={host.status || 'unknown'}
|
||||
ariaHidden
|
||||
/>
|
||||
<span class="font-semibold text-base-content truncate" title={name()}>
|
||||
{name()}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content">{datacenter()}</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content">{cluster()}</TableCell>
|
||||
<TableCell class="px-3 py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<StatusDot
|
||||
size="sm"
|
||||
variant={powerStateVariant(meta()?.powerState)}
|
||||
title={meta()?.powerState || 'unknown'}
|
||||
ariaHidden
|
||||
/>
|
||||
<span class="text-base-content">{formatPowerState(meta()?.powerState)}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{formatPercent(host.cpu?.current)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{formatPercent(host.memory?.current)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content tabular-nums">
|
||||
{datastoreCount()}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content tabular-nums">
|
||||
{vmCount()}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content font-mono text-[11px]">
|
||||
<span class="truncate inline-block max-w-[12rem]" title={vcenter()}>
|
||||
{vcenter()}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default VsphereHostsTable;
|
||||
|
|
@ -1171,15 +1171,17 @@ func resourceFromDockerHost(host models.DockerHost) (Resource, ResourceIdentity)
|
|||
metrics := metricsFromDockerHost(host)
|
||||
|
||||
resource := Resource{
|
||||
Type: ResourceTypeAgent,
|
||||
Technology: strings.TrimSpace(host.Runtime),
|
||||
Name: name,
|
||||
Status: statusFromString(host.Status),
|
||||
LastSeen: host.LastSeen,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Metrics: metrics,
|
||||
Docker: docker,
|
||||
Tags: nil,
|
||||
Type: ResourceTypeAgent,
|
||||
Technology: strings.TrimSpace(host.Runtime),
|
||||
Name: name,
|
||||
Status: statusFromString(host.Status),
|
||||
LastSeen: host.LastSeen,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Metrics: metrics,
|
||||
Uptime: host.UptimeSeconds,
|
||||
Temperature: host.Temperature,
|
||||
Docker: docker,
|
||||
Tags: nil,
|
||||
}
|
||||
|
||||
return resource, identity
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue