Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Information Shown on Web UI #18

Merged
merged 2 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 125 additions & 74 deletions website/src/components/app/-app.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Card, CardBody, CardHeader } from '@nextui-org/card';
import { Chip } from '@nextui-org/chip';
import { Listbox, ListboxItem, ListboxSection } from '@nextui-org/listbox';
import { Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/table';
import Image from 'next/image';
import Link from 'next/link';
import { Just, Maybe, Nothing } from 'purify-ts/Maybe';
import { useState } from 'react';
import { LhpData } from './-data';
import { KVBox } from './-ui';

export function App({ data, onFlushRequest }: { data: LhpData[]; onFlushRequest: () => void }) {
const [host, setHost] = useState<Maybe<LhpData>>(Nothing);
Expand Down Expand Up @@ -54,17 +57,17 @@ export function Sidebar({ data, onHostSelect, onTabulateRequest, onFlushRequest
<ListboxItem key={host.host.name} onPress={() => onHostSelect(host)}>
<div className="flex items-center space-x-2">
<Image
src={`https://cdn.simpleicons.org/${host.distribution.id}`}
src={`https://cdn.simpleicons.org/${cloudIcon(host.cloud.name)}`}
width="16"
height="16"
alt={`logo ${host.distribution.id}`}
alt={`logo ${host.cloud.name}`}
unoptimized
/>
<Image
src={`https://cdn.simpleicons.org/${cloudIcon(host.cloud.name)}`}
src={`https://cdn.simpleicons.org/${host.distribution.id}`}
width="16"
height="16"
alt={`logo ${host.cloud.name}`}
alt={`logo ${host.distribution.id}`}
unoptimized
/>
<span>{host.host.name}</span>
Expand Down Expand Up @@ -107,9 +110,9 @@ export function TabulateHosts({ hosts, onHostSelect }: { hosts: LhpData[]; onHos
>
<TableHeader>
<TableColumn key="hostname">Hostname</TableColumn>
<TableColumn key="arch">Architecture</TableColumn>
<TableColumn key="distro">Distribution</TableColumn>
<TableColumn key="distro">Cloud</TableColumn>
<TableColumn key="distro">Distribution</TableColumn>
<TableColumn key="arch">Arch</TableColumn>
<TableColumn key="cpu" align="end">
CPU
</TableColumn>
Expand All @@ -122,34 +125,43 @@ export function TabulateHosts({ hosts, onHostSelect }: { hosts: LhpData[]; onHos
<TableColumn key="disk" align="center">
Docker
</TableColumn>
<TableColumn key="tags">Tags</TableColumn>
</TableHeader>
<TableBody items={hosts}>
{(host) => (
<TableRow key={host.host.name}>
<TableCell>
<div className="flex items-center space-x-2">
<Image
src={`https://cdn.simpleicons.org/${host.distribution.id}`}
src={`https://cdn.simpleicons.org/${cloudIcon(host.cloud.name)}`}
width="24"
height="24"
alt={`logo ${host.distribution.id}`}
alt={`logo ${host.cloud.name}`}
unoptimized
/>
<Image
src={`https://cdn.simpleicons.org/${cloudIcon(host.cloud.name)}`}
src={`https://cdn.simpleicons.org/${host.distribution.id}`}
width="24"
height="24"
alt={`logo ${host.cloud.name}`}
alt={`logo ${host.distribution.id}`}
unoptimized
/>
<span className="cursor-pointer" onClick={() => onHostSelect(host)}>
{host.host.name}
</span>
{host.host.url && (
<Link href={host.host.url} target="_blank">
🔗
</Link>
)}
</div>
</TableCell>
<TableCell>{host.kernel.machine}</TableCell>
<TableCell>
{host.cloud.name}
{host.cloud.hostRegion && <span className="text-xs text-gray-400"> {host.cloud.hostRegion}</span>}
</TableCell>
<TableCell>{host.distribution.description}</TableCell>
<TableCell>{host.cloud.name}</TableCell>
<TableCell>{host.kernel.machine}</TableCell>
<TableCell>{host.hardware.cpuCount}</TableCell>
<TableCell>{host.hardware.ramTotal}</TableCell>
<TableCell>{host.hardware.diskRoot}</TableCell>
Expand All @@ -158,6 +170,13 @@ export function TabulateHosts({ hosts, onHostSelect }: { hosts: LhpData[]; onHos
? '❌'
: `${host.dockerContainers.filter((x) => x.running).length} / ${host.dockerContainers.length}`}
</TableCell>
<TableCell className="space-x-1">
{(host.host.tags || []).map((x) => (
<Chip size="sm" color="primary" variant="flat" radius="sm">
{x}
</Chip>
))}
</TableCell>
</TableRow>
)}
</TableBody>
Expand All @@ -167,73 +186,105 @@ export function TabulateHosts({ hosts, onHostSelect }: { hosts: LhpData[]; onHos
}

export function HostDetails({ host }: { host: LhpData }) {
const kvs = [
{ key: 'Hostname', value: host.host.name },
{ key: 'Kernel Name', value: host.kernel.name },
{ key: 'Kernel Architecture', value: host.kernel.machine },
{ key: 'Kernel Release', value: host.kernel.release },
{ key: 'Operating System', value: host.kernel.os },
{ key: 'Distribution ID', value: host.distribution.id },
{ key: 'Distribution Name', value: host.distribution.name },
{ key: 'Distribution Version ID', value: host.distribution.id },
{ key: 'Distribution Version', value: host.distribution.version },
{ key: 'Distribution Version Code', value: host.distribution.codename },
{ key: 'Distribution Full Name', value: host.distribution.description },
{ key: 'Cloud', value: host.cloud.name },
{ key: 'Type', value: host.cloud.hostType },
{ key: 'Region', value: host.cloud.hostRegion },
{ key: 'CPU', value: `${host.hardware.cpuCount} cores` },
{ key: 'RAM', value: `${host.hardware.ramTotal} GB` },
{ key: 'DISK', value: `${host.hardware.diskRoot} GB` },
];

return (
<div>
<h1 className="border-b border-gray-200 bg-white p-4 text-xl font-bold">{host.host.name}</h1>

<div className="grid grid-cols-2 gap-4 p-4">
<div>
<Card radius="sm" shadow="sm">
<CardHeader className="text-lg font-bold">Host Information</CardHeader>

<CardBody>
<Listbox items={kvs}>
{({ key, value }) => (
<ListboxItem key={key} endContent={value}>
{key}
</ListboxItem>
)}
</Listbox>
</CardBody>
</Card>
<h1 className="flex flex-row justify-between border-b border-gray-200 bg-white p-4 text-xl font-bold">
<div className="space-x-2">
<span>{host.host.name}</span>
{host.host.url && (
<Link href={host.host.url} target="_blank">
🔗
</Link>
)}
</div>

<div>
<Card radius="sm" shadow="sm">
<CardHeader className="text-lg font-bold">Docker Containers</CardHeader>

<CardBody>
{host.dockerContainers ? (
<Listbox
items={[
...host.dockerContainers.sort(
(a, b) => (a.running ? 0 : 1) - (b.running ? 0 : 1) || a.name.localeCompare(b.name)
),
]}
emptyContent={<span className="text-orange-400">Docker service has no containers.</span>}
>
{({ id, image, name, running }) => (
<ListboxItem key={id} description={image} startContent={running ? <>🟢</> : <>🔴</>}>
{name}
</ListboxItem>
)}
</Listbox>
) : (
<span className="text-red-400">Docker service is not found.</span>
)}
</CardBody>
</Card>
<div className="space-x-1">
{(host.host.tags || []).map((x) => (
<Chip size="sm" color="primary" variant="flat" radius="sm">
{x}
</Chip>
))}
</div>
</h1>

<div className="p-4">
<KVBox
title="Cloud"
kvs={[
{ key: 'Name', value: host.cloud.name },
{ key: 'ID', value: host.cloud.id },
{ key: 'Type', value: host.cloud.hostType },
{ key: 'Region', value: host.cloud.hostRegion },
{ key: 'Availability Zone', value: host.cloud.hostAvailabilityZone },
{ key: 'Local Hostname', value: host.cloud.hostLocalHostname },
{ key: 'Local Address', value: host.cloud.hostLocalAddress },
{ key: 'Remote Hostname', value: host.cloud.hostRemoteHostname },
{ key: 'Remote Address', value: host.cloud.hostRemoteAddress },
{ key: 'Reserved Address', value: host.cloud.hostReservedAddress },
]}
/>
</div>

<div className="grid grid-cols-1 gap-4 p-4 lg:grid-cols-2">
<KVBox
title="Distribution"
kvs={[
{ key: 'ID', value: host.distribution.id },
{ key: 'Name', value: host.distribution.name },
{ key: 'Description', value: host.distribution.description },
{ key: 'Release', value: host.distribution.release },
{ key: 'Version', value: host.distribution.version },
{ key: 'Codename', value: host.distribution.codename },
]}
/>

<KVBox
title="Kernel"
kvs={[
{ key: 'Node', value: host.kernel.node },
{ key: 'Name', value: host.kernel.name },
{ key: 'Machine', value: host.kernel.machine },
{ key: 'Release', value: host.kernel.release },
{ key: 'Version', value: host.kernel.version },
{ key: 'Operating System', value: host.kernel.os },
]}
/>
</div>

<div className="p-4">
<Card radius="sm" shadow="sm">
<CardHeader className="text-lg font-bold">Docker Containers</CardHeader>

<CardBody>
{host.dockerContainers ? (
<Listbox
items={[
...host.dockerContainers.sort(
(a, b) => (a.running ? 0 : 1) - (b.running ? 0 : 1) || a.name.localeCompare(b.name)
),
]}
emptyContent={<span className="text-orange-400">Docker service has no containers.</span>}
>
{({ id, image, name, running, created }) => (
<ListboxItem
key={id}
description={image}
startContent={running ? <>🟢</> : <>🔴</>}
endContent={
<span className="text-xs" title="Created">
{created}
</span>
}
>
{name}
</ListboxItem>
)}
</Listbox>
) : (
<span className="text-red-400">Docker service is not found.</span>
)}
</CardBody>
</Card>
</div>
</div>
);
Expand Down
4 changes: 1 addition & 3 deletions website/src/components/app/-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ export interface LhpData {
dockerContainers:
| null
| {
created: {
[k: string]: unknown;
};
created: string;
id: string;
image: string;
name: string;
Expand Down
26 changes: 26 additions & 0 deletions website/src/components/app/-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Card, CardBody, CardHeader } from '@nextui-org/card';
import { Listbox, ListboxItem } from '@nextui-org/listbox';
import { Spinner } from '@nextui-org/react';

export function Centered({ children }: { children: React.ReactNode }) {
Expand All @@ -11,3 +13,27 @@ export function BigSpinner({ label }: { label?: string }) {
</Centered>
);
}

export function KVBox({
title,
kvs,
}: {
title: string;
kvs: { key: string; value: React.ReactNode | string | number | null | undefined }[];
}) {
return (
<Card radius="sm" shadow="sm">
<CardHeader className="text-lg font-bold">{title}</CardHeader>

<CardBody>
<Listbox items={kvs}>
{({ key, value }) => (
<ListboxItem key={key} endContent={value}>
{key}
</ListboxItem>
)}
</Listbox>
</CardBody>
</Card>
);
}