197 lines
4.2 KiB
TypeScript
197 lines
4.2 KiB
TypeScript
import React, { useState } from "react";
|
|
import { Button, ListGroup, Collapse } from "react-bootstrap";
|
|
import {
|
|
Dash,
|
|
ChevronDown,
|
|
ChevronRight,
|
|
CircleFill,
|
|
} from "react-bootstrap-icons";
|
|
import { Container, Row, Col, Table, Card } from "react-bootstrap";
|
|
import { atom, useAtomValue } from "jotai";
|
|
import { atomWithQuery } from "jotai-tanstack-query";
|
|
|
|
const diagQueryFn = async () => {
|
|
const resp = await fetch("api/diagnostics");
|
|
if (!resp.ok) {
|
|
throw new Error("Network response was not ok");
|
|
}
|
|
return resp.json();
|
|
};
|
|
|
|
const diagQueryAtom = atomWithQuery(() => ({
|
|
queryKey: ["diagnostics"],
|
|
queryFn: diagQueryFn,
|
|
}));
|
|
|
|
const diagAtom = atom((get) => {
|
|
const diag = get(diagQueryAtom);
|
|
if (diag.isPending) {
|
|
return "Loading...";
|
|
}
|
|
if (diag.isError) {
|
|
return "Error loading!";
|
|
}
|
|
if (!diag.data.status) {
|
|
return "Failed to get diagnostic information.";
|
|
}
|
|
return diag.data.message;
|
|
});
|
|
|
|
export function DiagnosticsCard() {
|
|
const diagnostics = useAtomValue(diagAtom);
|
|
|
|
const diagContent = () => {
|
|
if (typeof diagnostics === "string" || diagnostics instanceof String) {
|
|
return <pre> {diagnostics} </pre>;
|
|
}
|
|
return <TreeView data={diagnostics} />;
|
|
};
|
|
|
|
return (
|
|
<Card className="padded">
|
|
<Card.Title>Diagnostics</Card.Title>
|
|
<Card.Body>{diagContent()}</Card.Body>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
function TreeView(props: Any) {
|
|
const data = props.data;
|
|
return (
|
|
<ListGroup>
|
|
{data.children.map((node) => (
|
|
<SingleNode key={node.id} node={node} />
|
|
))}
|
|
</ListGroup>
|
|
);
|
|
}
|
|
|
|
function LevelIndicator(props: Any) {
|
|
const level = props.level;
|
|
if (level == "ERROR") {
|
|
return <CircleFill color="red" />;
|
|
}
|
|
if (level == "WARN") {
|
|
return <CircleFill color="orange" />;
|
|
}
|
|
if (level == "OK") {
|
|
return <CircleFill color="green" />;
|
|
}
|
|
return <CircleFill color="grey" />;
|
|
}
|
|
|
|
function MessageText(props: Any) {
|
|
if (props.message === "") {
|
|
return <div></div>;
|
|
}
|
|
return <div>Message: {props.message}</div>;
|
|
}
|
|
|
|
function ValuesTable(props: Any) {
|
|
const values = props.values;
|
|
if (Object.keys(values).length === 0) {
|
|
return <div></div>;
|
|
}
|
|
return (
|
|
<Table>
|
|
<thead>
|
|
<th>Key</th>
|
|
<th>Value</th>
|
|
</thead>
|
|
<tbody>
|
|
{Object.entries(values).map(([key, value]) => (
|
|
<tr key={key}>
|
|
<td>{key}</td>
|
|
<td>{value}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</Table>
|
|
);
|
|
}
|
|
|
|
function StatusContent(props: Any) {
|
|
const status = props.status;
|
|
console.log(status);
|
|
return (
|
|
<div>
|
|
<p>{status.name}</p>
|
|
<p>
|
|
<MessageText message={status.message} />
|
|
</p>
|
|
<p>
|
|
<ValuesTable values={status.values} />
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function DirectionIcon(props: Any) {
|
|
let icon = <ChevronRight />;
|
|
if (props.isOpen) {
|
|
icon = <ChevronDown />;
|
|
}
|
|
if (!props.hasChildren) {
|
|
icon = <Dash />;
|
|
}
|
|
|
|
return (
|
|
<Button variant="link" onClick={props.onClick}>
|
|
{icon}
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
function ChildStatuses(props: Any) {
|
|
const isOpen = props.isOpen;
|
|
const node = props.node;
|
|
return (
|
|
<Collapse in={isOpen}>
|
|
<div>
|
|
<ListGroup>
|
|
{node.children.map((child) => (
|
|
<SingleNode key={child.id} node={child} />
|
|
))}
|
|
</ListGroup>
|
|
</div>
|
|
</Collapse>
|
|
);
|
|
}
|
|
|
|
function SingleNode(props: Any) {
|
|
const node = props.node;
|
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const hasChildren = node.children && node.children.length > 0;
|
|
const openFunction = () => {
|
|
if (hasChildren) {
|
|
setIsOpen(!isOpen);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ListGroup.Item>
|
|
<Container>
|
|
<Row>
|
|
<Col xs={1}>
|
|
<DirectionIcon
|
|
isOpen={isOpen}
|
|
hasChildren={hasChildren}
|
|
onClick={openFunction}
|
|
/>
|
|
</Col>
|
|
<Col xs={1}>
|
|
<LevelIndicator level={node.status.level} />
|
|
</Col>
|
|
<Col xs={"auto"}>
|
|
<StatusContent status={node.status} />
|
|
</Col>
|
|
</Row>
|
|
<Row>
|
|
<ChildStatuses isOpen={isOpen} node={node} />
|
|
</Row>
|
|
</Container>
|
|
</ListGroup.Item>
|
|
);
|
|
}
|