parent
c4333fd2dc
commit
8887fcc3ee
@ -0,0 +1,57 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
import { BiError } from "react-icons/bi";
|
||||||
|
import { i18n, useTranslation } from "next-i18next";
|
||||||
|
|
||||||
|
import Node from "./node";
|
||||||
|
|
||||||
|
export default function Longhorn({ options }) {
|
||||||
|
const { expanded, total, labels, include, nodes } = options;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data, error } = useSWR(`/api/widgets/longhorn`, {
|
||||||
|
refreshInterval: 1500
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error || data?.error) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
|
||||||
|
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
|
||||||
|
<div className="flex flex-row self-center flex-wrap justify-between" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
|
||||||
|
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||||
|
{data.nodes
|
||||||
|
.filter((node) => {
|
||||||
|
if (node.id === 'total' && total) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!nodes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (include && !include.includes(node.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((node) =>
|
||||||
|
<div key={node.id}>
|
||||||
|
<Node data={{ node }} expanded={expanded} labels={labels} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
import { FiHardDrive } from "react-icons/fi";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
|
import UsageBar from "../resources/usage-bar";
|
||||||
|
|
||||||
|
export default function Node({ data, expanded, labels }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">{t("common.bytes", { value: data.node.available })}</div>
|
||||||
|
<div className="pr-1">{t("resources.free")}</div>
|
||||||
|
</span>
|
||||||
|
{expanded && (
|
||||||
|
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">{t("common.bytes", { value: data.node.maximum })}</div>
|
||||||
|
<div className="pr-1">{t("resources.total")}</div>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<UsageBar percent={Math.round((data.node.available / data.node.maximum) * 100)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{labels && (
|
||||||
|
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{data.node.id}</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
import { httpProxy } from "../../../utils/proxy/http";
|
||||||
|
import createLogger from "../../../utils/logger";
|
||||||
|
import { getSettings } from "../../../utils/config/config";
|
||||||
|
|
||||||
|
const logger = createLogger("longhorn");
|
||||||
|
|
||||||
|
function parseLonghornData(data) {
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
|
||||||
|
if (!json) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = json.data.map((node) => {
|
||||||
|
let available = 0;
|
||||||
|
let maximum = 0;
|
||||||
|
let reserved = 0;
|
||||||
|
let scheduled = 0;
|
||||||
|
Object.keys(node.disks).forEach((diskKey) => {
|
||||||
|
const disk = node.disks[diskKey];
|
||||||
|
available += disk.storageAvailable;
|
||||||
|
maximum += disk.storageMaximum;
|
||||||
|
reserved += disk.storageReserved;
|
||||||
|
scheduled += disk.storageScheduled;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
available,
|
||||||
|
maximum,
|
||||||
|
reserved,
|
||||||
|
scheduled,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const total = nodes.reduce((summary, node) => ({
|
||||||
|
available: summary.available + node.available,
|
||||||
|
maximum: summary.maximum + node.maximum,
|
||||||
|
reserved: summary.reserved + node.reserved,
|
||||||
|
scheduled: summary.scheduled + node.scheduled,
|
||||||
|
}));
|
||||||
|
total.id = "total";
|
||||||
|
nodes.push(total);
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
const settings = getSettings();
|
||||||
|
const longhornSettings = settings?.providers?.longhorn;
|
||||||
|
const {url, username, password} = longhornSettings;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
const errorMessage = "Missing Longhorn URL";
|
||||||
|
logger.error(errorMessage);
|
||||||
|
return res.status(400).json({ error: errorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = `${url}/v1/nodes`;
|
||||||
|
const headers = {
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
};
|
||||||
|
if (username && password) {
|
||||||
|
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
||||||
|
}
|
||||||
|
const params = { method: "GET", headers };
|
||||||
|
|
||||||
|
const [status, contentType, data] = await httpProxy(apiUrl, params);
|
||||||
|
|
||||||
|
if (status === 401) {
|
||||||
|
logger.error("Authorization failure getting data from Longhorn API. Data: %s", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.error("HTTP %d getting data from Longhorn API. Data: %s", status, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType) res.setHeader("Content-Type", contentType);
|
||||||
|
|
||||||
|
const nodes = parseLonghornData(data);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
nodes,
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue