Compare commits

..

4 Commits

Author SHA1 Message Date
858f42c39b Merge branch 'service-card-restructure' 2024-01-27 22:45:26 +01:00
fadeead858 Finish service card 2024-01-27 22:41:15 +01:00
58b67da193 Better layout 2024-01-26 23:39:26 +01:00
94ee8da3bd Service card 2024-01-26 23:09:05 +01:00
3 changed files with 136 additions and 72 deletions

View File

@ -1,15 +1,14 @@
import React from "react"; import React, { ReactElement } from "react";
import { Service, Node, StatusPerTenant } from "../../types"; import { Service, Node, StatusPerTenant } from "../../types";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent"; import CardContent from "@mui/material/CardContent";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import IconButton from "@mui/material/IconButton";
import LaunchIcon from "@mui/icons-material/Launch";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip";
import Grid from "@mui/material/Unstable_Grid2";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Link from "@mui/material/Link";
import Avatar from "@mui/material/Avatar";
interface ServiceCardProps { interface ServiceCardProps {
service: Service; service: Service;
@ -23,71 +22,88 @@ function normalizeServiceName(name: string): string {
.replace(/\b\w/g, (s) => s.toUpperCase()); .replace(/\b\w/g, (s) => s.toUpperCase());
} }
function TenantsStatusTooltip(tenantsStatus: StatusPerTenant) {} interface TenantsStatusTooltipProps {
tenantsStatus: StatusPerTenant;
}
function TenantsStatusTooltip({ tenantsStatus }: TenantsStatusTooltipProps) {
const statusItems = Object.entries(tenantsStatus).map(([key, value]) => {
console.log(`key: ${key}, value: ${value}`);
return null;
});
return (
<Box className="tenants-status-tooltip">
<Typography color="inherit">Tooltip with HTML</Typography>
<em>{"And here's"}</em> <b>{"some"}</b> <u>{"amazing content"}</u>. {"It's very engaging. Right?"}
</Box>
);
}
function getNodeNameElement(node: Node): React.ReactElement {
let statusMessage;
const statusOk = node.health_check_status?.status_ok;
if (statusOk) {
statusMessage = `Node ${node.name} is healthy`;
} else {
statusMessage = node.health_check_status?.message;
}
return (
<Tooltip title={statusMessage}>
<Avatar variant="square" className="node-name">
{node.name}
</Avatar>
</Tooltip>
);
}
interface TenantsStatusProps {
statusPerTenant: StatusPerTenant | null;
}
function TenantsStatus({ statusPerTenant }: TenantsStatusProps) {
if (!statusPerTenant) return <></>;
let statuses: ReactElement[] = [];
for (const [tenantId, statusOk] of Object.entries(statusPerTenant)) {
statuses.push(
<Typography component="div" className={`tenant-status ${statusOk ? "status-ok" : "status-error"}`}>
{tenantId}
</Typography>
);
}
return (
<Stack direction="row" spacing={1} className="tenants-status-container">
<Typography component="div" className="tenants-status-title">
Tenants
</Typography>
{statuses}
</Stack>
);
}
interface ServiceNodePropps { interface ServiceNodePropps {
node: Node; node: Node;
} }
function ServiceNode({ node }: ServiceNodePropps) { function ServiceNode({ node }: ServiceNodePropps) {
const nodeName = node.health_check_status?.status_ok ? ( const nodeUrl = node.url.replace("http://", "").replace("https://", "");
<Tooltip title={`Node ${node.name} is healthy`}> const statusOk = node.health_check_status?.status_ok;
<Chip label={node.name} color="success" className="node-name" />
</Tooltip>
) : (
<Tooltip title={node.health_check_status?.message}>
<Chip label={node.name} color="error" className="node-name" />
</Tooltip>
);
let tenantsHealth: boolean[] = [];
if (node.health_check_status?.status_per_tenant) {
tenantsHealth = Object.values(node.health_check_status?.status_per_tenant);
}
let tenantsStatus = null;
if (tenantsHealth.length > 0) {
const okValues = tenantsHealth.filter((t) => t === true);
switch (okValues.length) {
case 0:
tenantsStatus = (
<Tooltip
title={
<Box className="tenants-status-tooltip">
<Typography color="inherit">Tooltip with HTML</Typography>
<em>{"And here's"}</em> <b>{"some"}</b> <u>{"amazing content"}</u>.{" "}
{"It's very engaging. Right?"}
</Box>
}
>
<Chip label="tenants" color="error" className="node-name" />
</Tooltip>
);
break;
case tenantsHealth.length:
tenantsStatus = <Chip label="tenants" color="success" className="node-name" />;
break;
default:
tenantsStatus = <Chip label="tenants" color="warning" className="node-name" />;
}
}
return ( return (
<Grid container spacing={2} className="service-node" sx={{ alignItems: "center" }}> <Stack direction="row" className={`service-node ${statusOk ? "status-ok" : "status-error"}`}>
<Grid xs={2}>{nodeName}</Grid> <Box>{getNodeNameElement(node)}</Box>
<Grid xs={6}> <Box className="node-details">
<Typography className="version">{node.app_details?.app_version || "no version"}</Typography> <Typography className="version">{node.app_details?.app_version || "no version"}</Typography>
</Grid> <Link href={node.url} target="_blank">
<Grid xs={4} sx={{ textAlign: "right" }} className="status"> {nodeUrl}
<Tooltip title={node.url}> </Link>
<IconButton target="_blank" href={node.url} className="docs-url"> <TenantsStatus statusPerTenant={node.health_check_status?.status_per_tenant} />
<LaunchIcon /> </Box>
</IconButton> </Stack>
</Tooltip>
{tenantsStatus}
</Grid>
</Grid>
); );
} }

View File

@ -1,9 +1,11 @@
$color-ok: #27cb30; @use "sass:color";
$color-ok: #1dad24;
$color-warning: #ed6c02; $color-warning: #ed6c02;
$color-danger: #d32f2f; $color-danger: #d32f2f;
.service-card { .service-card {
width: 400px; width: 450px;
.service-title-container { .service-title-container {
color: white; color: white;
background-color: #15232d; background-color: #15232d;
@ -29,21 +31,67 @@ $color-danger: #d32f2f;
} }
.service-node { .service-node {
align-items: center;
gap: 0.5rem;
.node-name { .node-name {
font-weight: bold; font-weight: bold;
border-radius: 10px;
} }
.status {
display: flex; .node-details {
border-left: 4px solid #c1c1c1;
padding-left: 0.5rem;
.version {
font-weight: bold;
}
.tenants-status-container {
align-items: center; align-items: center;
.docs-url { margin-top: 0.5rem;
margin-left: auto; flex-flow: wrap;
.tenants-status-title {
font-weight: bold;
} }
.status-icon {
margin-left: 0.5rem; .tenant-status {
color: white;
padding: 0.2rem 0.5rem;
border-radius: 10px;
&.status-ok {
background-color: color.change($color-ok, $alpha: 0.6);
}
&.status-error {
background-color: color.change($color-danger, $alpha: 0.6);
} }
} }
} }
} }
.MuiTooltip-tooltip .tenants-status-tooltip { &.status-ok {
.node-name {
background-color: $color-ok;
}
.node-details {
border-left-color: color.change($color-ok, $alpha: 0.6);
}
}
&.status-error {
.node-name {
background-color: $color-danger;
}
.node-details {
border-left-color: color.change($color-danger, $alpha: 0.6);
}
}
a {
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
}
.MuiCardContent-root {
padding-bottom: 16px !important;
}
} }

View File

@ -6,7 +6,7 @@ export type EnvTab = {
export type StatusPerTenant = { [tenantId: string]: boolean }; export type StatusPerTenant = { [tenantId: string]: boolean };
type HealthCheckStatus = { export type HealthCheckStatus = {
status_ok: boolean; status_ok: boolean;
message: string; message: string;
status_per_tenant: StatusPerTenant | null; status_per_tenant: StatusPerTenant | null;