import React, { useMemo, useRef } from "react"; import { loadLib } from "../../../util"; import type { INetwork, INetworkDbNode, INetworkServerNode } from "../../../server/network"; import { useUserDataRetriever } from "../../components/user/UserDataRetriever"; import { useCallback, useEffect, useState } from "react"; import type { ILogsRecord, ILogsResult, IPingsResult } from "../../../server/services/base/LoggingProvider"; import type { IServerPingDataPing } from "../../../server"; import { useRootRetriever } from "../../../client/components/root/RootRetriever"; interface ID3Node { // id of the node id: string; // group it belongs to group: string; // name of the network node name: string; // icon in svg format icon: string; color: string; alive: boolean; } interface ID3Link { source: string; target: string; value: number; alive: boolean; } const ELASTIC = (c: string, alive: boolean) => ''; const REDIS = (c: string, alive: boolean) => ''; const PG = (c: string, alive: boolean) => ''; const WEB = (c: string, alive: boolean) => ''; const SERVER = (c: string, alive: boolean) => ''; const GLOBAL_COLOR = "#FF0000"; const COLORS = [ "purple", "blue", "green", "olive", "navy", "teal", "darkslateblue", "firebrick", "indigo", ]; const width = 900; const height = 600; /** * Quick and dirty network explorer * Can only be used by administrators * Generates a force directed graph of the current network * * @returns */ export function NetworkExplorer() { const userData = useUserDataRetriever(); const [network, setNetwork] = useState(null); const [err, setErr] = useState(null); const [loading, setLoading] = useState(true); const [pings, setPings] = useState>(null); const [pingsErr, setPingsErr] = useState(null); const [loadingPings, setLoadingPings] = useState(true); const [adminKey, setAdminKey] = useState(""); const [currentAdminResponse, setCurrentAdminResponse] = useState(""); const updateAdminKey = useCallback((e: React.ChangeEvent) => { setAdminKey(e.target.value); }, []); const [logs, setLogs] = useState(null); const [logsErr, setLogsErr] = useState(null); const [loadingLogs, setLoadingLogs] = useState(true); const [currentNode, setCurrentNode] = useState(null); const [useTimeOption, setUseTimeOption] = useState(-300000); const [useTimeCriteria, setUsageTimeCriteria] = useState({ from: null, to: null }); const [logWindowOption, setLogWindowOption] = useState(-300000); const [useLogWindowCriteria, setLogWindowCriteria] = useState({ from: null, to: null, type: "any" }); const [jsonUpdate, setJsonUpdate] = useState(""); const [dictionary, setDictionary] = useState("simple"); const [language, setLanguage] = useState("en"); const updateJsonUpdate = useCallback((e: React.ChangeEvent) => { setJsonUpdate(e.target.value); }, []); const updateDictionary = useCallback((e: React.ChangeEvent) => { setDictionary(e.target.value); }, []); const updateLanguage = useCallback((e: React.ChangeEvent) => { setLanguage(e.target.value); }, []); const rootInfo = useRootRetriever(); const [id, setId] = useState(""); const [version, setVersion] = useState(""); const [executingAdminOperation, setExecutingAdminOperation] = useState(null as string); const updateId = useCallback((e: React.ChangeEvent) => { setId(e.target.value); }, []); const updateVersion = useCallback((e: React.ChangeEvent) => { setVersion(e.target.value); }, []); const deleteNode = useCallback(async () => { try { const rs = await fetch("/rest/admin/nodes/" + currentNode.nodeId, { method: "delete", credentials: "omit", headers: { 'Token': userData.token, "adminkey": adminKey, }, }); if (rs.status !== 200) { try { const json = await rs.json(); if (json.status) { alert(json.status); } else { alert("UNKNOWN_ERROR"); } } catch (err) { console.warn(err); alert("UNKNOWN_ERROR"); } } else { try { alert("SUCCESS"); setCurrentNode(null); loadNetwork(); } catch (err) { console.warn(err); alert("INVALID_SERVER_RESPONSE"); } } } catch (err) { alert("NETWORK_ERROR"); } }, [currentNode, adminKey]); const [selectablesIdef, selectablesElasticIdef] = useMemo(() => { const idefs: string[] = []; const idefsElastic: string[] = []; rootInfo.root.getAllModules().forEach((m) => m.getAllChildDefinitionsRecursive().forEach((v) => { idefs.push(v.getQualifiedPathName()); if (v.isSearchEngineEnabled()) { idefsElastic.push(v.getQualifiedPathName()); } })); return [idefs, idefsElastic]; }, [rootInfo.root]); const [idef, setIdef] = useState(selectablesIdef[0] || ""); const [eidef, setEidef] = useState(selectablesElasticIdef[0] || ""); const rebuildIndex = useCallback(async (reindex: boolean) => { setExecutingAdminOperation(reindex ? "reindex" : "recheck"); try { const rs = await fetch("/rest/admin/elastic/" + (reindex ? "reindex" : "recheck") + "/" + eidef, { method: "post", credentials: "omit", headers: { 'Token': userData.token, "adminkey": adminKey, }, }); if (rs.status !== 200) { try { const json = await rs.json(); if (json.status) { alert(json.status); } else { alert("UNKNOWN_ERROR"); } } catch (err) { console.warn(err); alert("UNKNOWN_ERROR"); } } else { try { alert("SUCCESS"); } catch (err) { console.warn(err); alert("INVALID_SERVER_RESPONSE"); } } } catch (err) { alert("NETWORK_ERROR"); } setExecutingAdminOperation(null); }, [adminKey, eidef]); const updateIdef = useCallback((e: React.ChangeEvent) => { setIdef(e.target.value); }, []); const updateEidef = useCallback((e: React.ChangeEvent) => { setEidef(e.target.value); }, []); const forceDelete = useCallback(async () => { const confirm = prompt("Are you sure you want to delete this item y/n", "n"); if (confirm !== "y") { return; } try { const rs = await fetch("/rest/admin/item/" + idef + "/" + id + "/" + (version || "_"), { credentials: "omit", method: "delete", headers: { 'Token': userData.token, "adminkey": adminKey, }, }); if (rs.status !== 200) { try { const json = await rs.json(); setCurrentAdminResponse(JSON.stringify(json, null, 2)); } catch (err) { console.warn(err); setCurrentAdminResponse("INVALID_SERVER_RESPONSE"); } } else { try { const json = await rs.json(); setCurrentAdminResponse(JSON.stringify(json, null, 2)); } catch (err) { console.warn(err); setCurrentAdminResponse("INVALID_SERVER_RESPONSE"); } } } catch (err) { setCurrentAdminResponse("NETWORK_ERROR"); } }, [adminKey, id, version, idef]); const forceUpdate = useCallback(async () => { let parsed: any = null; try { parsed = JSON.parse(jsonUpdate); const rs = await fetch("/rest/admin/item/" + idef + "/" + id + "/" + (version || "_"), { credentials: "omit", method: "put", headers: { 'Token': userData.token, "adminkey": adminKey, 'Content-Type': 'application/json', }, body: JSON.stringify({update: parsed, dictionary, language}), }); if (rs.status !== 200) { try { const json = await rs.json(); setCurrentAdminResponse(JSON.stringify(json, null, 2)); } catch (err) { console.warn(err); setCurrentAdminResponse("INVALID_SERVER_RESPONSE"); } } else { try { const json = await rs.json(); setCurrentAdminResponse(JSON.stringify(json, null, 2)); } catch (err) { console.warn(err); setCurrentAdminResponse("INVALID_SERVER_RESPONSE"); } } } catch (err) { setCurrentAdminResponse(!parsed ? "INVALID_JSON" : "NETWORK_ERROR"); } }, [adminKey, id, version, idef, jsonUpdate, dictionary, language]); const retrieveItem = useCallback(async () => { try { const rs = await fetch("/rest/admin/item/" + idef + "/" + id + "/" + (version || "_"), { credentials: "omit", headers: { 'Token': userData.token, "adminkey": adminKey, }, }); if (rs.status !== 200) { try { const json = await rs.json(); setCurrentAdminResponse(JSON.stringify(json, null, 2)); } catch (err) { console.warn(err); setCurrentAdminResponse("INVALID_SERVER_RESPONSE"); } } else { try { const json = await rs.json(); setCurrentAdminResponse(JSON.stringify(json, null, 2)); } catch (err) { console.warn(err); setCurrentAdminResponse("INVALID_SERVER_RESPONSE"); } } } catch (err) { setCurrentAdminResponse("NETWORK_ERROR"); } }, [adminKey, id, version, idef]); const onChangeLogCriteriaType = useCallback((e: React.ChangeEvent) => { setLogWindowCriteria({ ...useLogWindowCriteria, type: e.target.value, }); }, [useLogWindowCriteria]); const onChangeUsageTime = useCallback((e: React.ChangeEvent) => { const value = parseInt(e.target.value); setUseTimeOption(value); const nodeIsDead = !(currentNode as INetworkServerNode).alive; if (nodeIsDead) { const lastHeard = (currentNode as INetworkServerNode).lastHeard; const lastHeardMs = new Date(lastHeard).getTime() + 1000; setUsageTimeCriteria({ from: lastHeardMs + useTimeOption, to: lastHeardMs }); } else { setUsageTimeCriteria({ from: (new Date()).getTime() + useTimeOption, to: null }); } }, [currentNode]); const moveUsageTimeLeft = useCallback(() => { setUsageTimeCriteria({ from: useTimeCriteria.from + useTimeOption, to: useTimeCriteria.from, }); }, [useTimeCriteria, useTimeOption]); const moveUsageTimeRight = useCallback(() => { let newTo = useTimeCriteria.to - useTimeOption; if (newTo >= (new Date()).getTime()) { newTo = null; } setUsageTimeCriteria({ from: useTimeCriteria.to, to: newTo, }); }, [useTimeCriteria, useTimeOption]); const onChangeLogWindowTime = useCallback((e: React.ChangeEvent) => { const value = parseInt(e.target.value); setLogWindowOption(value); const nodeIsDead = !(currentNode as INetworkServerNode).alive; if (nodeIsDead) { const lastHeard = (currentNode as INetworkServerNode).lastHeard; const lastHeardMs = new Date(lastHeard).getTime() + 1000; setLogWindowCriteria({ from: lastHeardMs + useTimeOption, to: lastHeardMs, type: useLogWindowCriteria.type }); } else { setLogWindowCriteria({ from: (new Date()).getTime() + useTimeOption, to: null, type: useLogWindowCriteria.type }); } }, [useLogWindowCriteria, currentNode]); const moveLogWindowLeft = useCallback(() => { setLogWindowCriteria({ from: useLogWindowCriteria.from + logWindowOption, to: useLogWindowCriteria.from, type: useLogWindowCriteria.type, }); }, [useLogWindowCriteria, logWindowOption]); const moveLogWindowRight = useCallback(() => { let newTo = useLogWindowCriteria.to - logWindowOption; if (newTo >= (new Date()).getTime()) { newTo = null; } setLogWindowCriteria({ from: useLogWindowCriteria.to, to: newTo, type: useLogWindowCriteria.type, }); }, [useLogWindowCriteria, logWindowOption]); const d3Ref = useRef(); const d3CpuRef = useRef(); const d3MemRef = useRef(); const d3MemRef2 = useRef(); const [d3loaded, setd3Loaded] = useState(false); const currentRenderedNetwork = useRef(null); const currentRenderedPings = useRef>(null); const [showDead, setShowDead] = useState(false); const toggleDead = useCallback(() => { setShowDead(!showDead); renderCurrentNetwork(!showDead); }, [showDead]); const loadNetwork = useCallback(async () => { setLoading(true); try { const rs = await fetch("/rest/admin/network", { credentials: "omit", headers: { 'Token': userData.token, }, }); if (rs.status !== 200) { try { const json = await rs.json(); if (json.status) { setErr(json.status); setLoading(false); } else { setErr("UNKNOWN_ERROR"); setLoading(false); } } catch (err) { console.warn(err); setErr("UNKNOWN_ERROR"); setLoading(false); } } else { try { const network: INetwork = await rs.json(); setNetwork(network); setErr(null); setLoading(false); } catch (err) { console.warn(err); setErr("INVALID_SERVER_RESPONSE"); setLoading(false); } } } catch (err) { setErr("NETWORK_ERROR"); setLoading(false); } }, []); useEffect(() => { loadNetwork(); (async () => { await loadLib("D3CDN", "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js", () => { return !!(window as any).d3; }); setd3Loaded(true); })(); }, []); const openInfoOn = useCallback((id: string) => { const nodeToOpen = currentRenderedNetwork.current.nodes.find((n) => n.nodeId === id); setUsageTimeCriteria({ from: null, to: null }); setLogWindowCriteria({ from: null, to: null, type: "any" }); setCurrentNode(nodeToOpen); if (nodeToOpen.nodeType === "server") { const nodeIsDead = !(nodeToOpen as INetworkServerNode).alive; if (nodeIsDead) { const lastHeard = nodeToOpen.lastHeard; const lastHeardMs = new Date(lastHeard).getTime() + 1000; setUsageTimeCriteria({ from: lastHeardMs + useTimeOption, to: lastHeardMs }); setLogWindowCriteria({ from: lastHeardMs + logWindowOption, to: lastHeardMs, type: "any" }); } else { setUsageTimeCriteria({ from: (new Date()).getTime() + useTimeOption, to: null }); setLogWindowCriteria({ from: (new Date()).getTime() + logWindowOption, to: null, type: "any" }); } } }, []); const renderPings = useCallback(() => { const pingsToRender = currentRenderedPings.current; const heightToUse = height / 2; const widthToUse = width - 48; const heightNoMargin = heightToUse - 80; const widthNoMargin = widthToUse - 80; const d3 = (window as any).d3; const x = d3.scaleTime() .domain(d3.extent(pingsToRender.pings, (d: ILogsRecord) => { return new Date(d.createdAt) })) .range([0, widthNoMargin]); const svgCanvasEle = d3CpuRef.current; const svgCanvasEleMem = d3MemRef.current; const svgCanvasEleMem2 = d3MemRef2.current; let svg = d3.select(svgCanvasEle); svg.selectAll('*').remove(); svg = svg.append("g").attr("transform", "translate(40,40)"); let svgMem = d3.select(svgCanvasEleMem); svgMem.selectAll('*').remove(); let svgMem2 = d3.select(svgCanvasEleMem2); svgMem2.selectAll('*').remove(); svgMem = svgMem.append("g").attr("transform", "translate(40,40)"); svgMem2 = svgMem2.append("g").attr("transform", "translate(40,40)"); [svg, svgMem, svgMem2].forEach((svgobj) => { svgobj.append("g") .attr("transform", "translate(0," + heightNoMargin + ")") .call(d3.axisBottom(x)); }); const maxMem = (Math.max.apply(null, pingsToRender.pings.map((m) => Math.max( //m.data.freeMem, m.data.memoryUsage.arrayBuffers, m.data.memoryUsage.external, m.data.memoryUsage.heapUsed, m.data.memoryUsage.rss, m.data.memoryUsage.heapTotal, ))) / 1024) / 1024; const minMem = (Math.min.apply(null, pingsToRender.pings.map((m) => Math.min( //m.data.freeMem, m.data.memoryUsage.arrayBuffers, m.data.memoryUsage.external, m.data.memoryUsage.heapUsed, m.data.memoryUsage.rss, m.data.memoryUsage.heapTotal, ))) / 1024) / 1024; const y = d3.scaleLinear() .domain([0, 100]) .range([heightNoMargin, 0]); const yMem = d3.scaleLinear() .domain([minMem, maxMem]) .range([heightNoMargin, 0]); const yMem2 = d3.scaleLinear() .domain(d3.extent(pingsToRender.pings, (d: ILogsRecord) => { return (d.data.freeMem / 1024) / 1024 })) .range([heightNoMargin, 0]); svg.append("g") .call(d3.axisLeft(y)); svgMem.append("g") .call(d3.axisLeft(yMem)); svgMem2.append("g") .call(d3.axisLeft(yMem2)); svg.append("path") .datum(pingsToRender.pings) .attr("fill", "none") .attr("stroke", "#7b1fa2") .attr("stroke-width", 1.5) .attr("opacity", 0.7) .attr("d", d3.line() .x((d: ILogsRecord) => { return x(new Date(d.createdAt)) }) .y((d: ILogsRecord) => { return y(d.data.cpuPercent) }) ); // Add the points svg .append("g") .selectAll("dot") .data(pingsToRender.pings) .enter() .append("circle") .attr("cx", (d: ILogsRecord) => { return x(new Date(d.createdAt)) }) .attr("cy", (d: ILogsRecord) => { return y(d.data.cpuPercent) }) .attr("r", 5) .attr("fill", "#7b1fa2") .attr("opacity", 0.7); svg.append("path") .datum(pingsToRender.pings) .attr("fill", "none") .attr("stroke", "#f57c00") .attr("stroke-width", 1.5) .attr("opacity", 0.7) .attr("d", d3.line() .x((d: ILogsRecord) => { return x(new Date(d.createdAt)) }) .y((d: ILogsRecord) => { return y(d.data.loadAvg60) }) ); svg .append("g") .selectAll("dot") .data(pingsToRender.pings) .enter() .append("circle") .attr("cx", (d: ILogsRecord) => { return x(new Date(d.createdAt)) }) .attr("cy", (d: ILogsRecord) => { return y(d.data.loadAvg60) }) .attr("r", 5) .attr("fill", "#f57c00") .attr("opacity", 0.7); [ ["#000", "freeMem", svgMem2, yMem2], ["#d32f2f", "arrayBuffers", svgMem, yMem], ["#00796b", "heapUsed", svgMem, yMem], ["#0097a7", "heapTotal", svgMem, yMem], ["#303f9f", "external", svgMem, yMem], ["#6d4c41", "rss", svgMem, yMem] ].forEach((v) => { v[2].append("path") .datum(pingsToRender.pings) .attr("fill", "none") .attr("stroke", v[0]) .attr("stroke-width", 1.5) .attr("opacity", 0.7) .attr("d", d3.line() .x((d: ILogsRecord) => { return x(new Date(d.createdAt)) }) .y((d: ILogsRecord) => { return v[3](((d.data[v[1]] || d.data.memoryUsage[v[1]] || 0) / 1024) / 1024) }) ); // Add the points v[2] .append("g") .selectAll("dot") .data(pingsToRender.pings) .enter() .append("circle") .attr("cx", (d: ILogsRecord) => { return x(new Date(d.createdAt)) }) .attr("cy", (d: ILogsRecord) => { return v[3](((d.data[v[1]] || d.data.memoryUsage[v[1]] || 0) / 1024) / 1024) }) .attr("r", 5) .attr("fill", v[0]) .attr("opacity", 0.7); }); }, []); const renderCurrentNetwork = useCallback((wdead: boolean) => { const networkToRender = currentRenderedNetwork.current; const allGroups: string[] = []; networkToRender.nodes.forEach((v) => { if (!allGroups.includes(v.groupId)) { allGroups.push(v.groupId); } }); allGroups.sort(); const allGroupsColor: { [groupId: string]: string } = {}; allGroups.forEach((gId, index) => { allGroupsColor[gId] = gId === "GLOBAL" ? GLOBAL_COLOR : COLORS[index % COLORS.length]; }); const nodes: ID3Node[] = []; const links: ID3Link[] = []; networkToRender.nodes.forEach((n) => { const alive = typeof (n as INetworkServerNode).alive === "boolean" ? (n as INetworkServerNode).alive : true; if (!alive && !wdead) { return; } const nodeColor = allGroupsColor[n.groupId]; nodes.push({ id: n.nodeId, group: n.groupId, name: n.name, icon: ((n as INetworkDbNode).type === "elastic" ? ELASTIC(nodeColor, alive) : ( (n as INetworkDbNode).type === "pg" ? PG(nodeColor, alive) : ( (n as INetworkDbNode).type === "redis" ? REDIS(nodeColor, alive) : ( (n as INetworkServerNode).type === "extended" ? WEB(nodeColor, alive) : SERVER(nodeColor, alive) ) ) )), color: nodeColor, alive, }); if ((n as INetworkDbNode).type !== "elastic" && (n as INetworkDbNode).type !== "pg" && (n as INetworkDbNode).type !== "redis") { n.links.forEach((l) => { links.push({ source: n.nodeId, target: l.id, // global manager and global database and elasticsearch // anything cluster tighter together value: (l.type.includes("global") || l.type.includes("search")) ? 1 : 2, alive, }); }); } }); const d3 = (window as any).d3; const svgCanvasEle = d3Ref.current; const svg = d3.select(svgCanvasEle); svg.selectAll('*').remove(); const simulation = d3.forceSimulation(nodes) .force("link", d3.forceLink(links) .id((d: any) => d.id) .distance((d: ID3Link) => d.value * 20) .strength((d: ID3Link) => d.value / 200) ) .force("charge", d3.forceManyBody()) .force("center", d3.forceCenter(width / 2, height / 2)) .on("tick", ticked); const link = svg.append("g") .attr("stroke", "#000") .selectAll() .data(links) .join("line") .attr("stroke-opacity", (l: ID3Link) => l.alive ? 0.8 : 0.4) .attr("stroke-width", (d: ID3Link) => d.value) .attr("stroke-dasharray", (d: ID3Link) => d.alive ? "1" : "10,10") const node = svg.append("g") .attr("stroke", "#999") .attr("stroke-width", 1.5) .selectAll() .data(nodes) .enter().append('g').attr("style", "cursor:pointer"); node.on("click", expandNode); node.each(function (d: any) { const icon = new DOMParser().parseFromString(d.icon, 'image/svg+xml').documentElement; d3.select(this).node().appendChild(icon); }); node.append("title") .text((d: ID3Node) => d.name); // Add a drag behavior. node.call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); simulation .nodes(nodes) .on('tick', ticked); simulation.force('link') .links(links); function ticked() { link .attr('x1', (d: any) => d.source.x) .attr('y1', (d: any) => d.source.y) .attr('x2', (d: any) => d.target.x) .attr('y2', (d: any) => d.target.y); node .attr("transform", (d: any) => "translate(" + (d.x - 15) + "," + (d.y - 15) + ")"); } function dragstarted(event: any) { if (!event.active) simulation.alphaTarget(0.3).restart(); event.subject.fx = event.subject.x; event.subject.fy = event.subject.y; } function dragged(event: any) { event.subject.fx = event.x; event.subject.fy = event.y; } function dragended(event: any, d) { if (!event.active) simulation.alphaTarget(0); event.subject.fx = null; event.subject.fy = null; } function expandNode(event: any, d) { openInfoOn(d.id); } }, []); useEffect(() => { if (d3loaded && currentRenderedNetwork.current !== network) { currentRenderedNetwork.current = network; renderCurrentNetwork(showDead); } }, [d3loaded, network]); useEffect(() => { if (d3loaded && currentRenderedPings.current !== pings) { currentRenderedPings.current = pings; if (pings) { renderPings(); } } }, [d3loaded, pings]); useEffect(() => { if (currentNode?.nodeType === "server" && useTimeCriteria.from) { (async () => { setLoadingPings(true); try { const rs = await fetch("/rest/admin/nodes/" + currentNode.nodeId + "/pings/ping_data?from=" + useTimeCriteria.from + "&to=" + (useTimeCriteria.to || ""), { credentials: "omit", headers: { 'Token': userData.token, }, }); if (rs.status !== 200) { try { const json = await rs.json(); if (json.status) { setPingsErr(json.status); setLoadingPings(false); } else { setPingsErr("UNKNOWN_ERROR"); setLoadingPings(false); } } catch (err) { console.warn(err); setPingsErr("UNKNOWN_ERROR"); setLoadingPings(false); } } else { try { const pings: { pings: IPingsResult } = await rs.json(); setPings(pings.pings); setPingsErr(null); setLoadingPings(false); } catch (err) { console.warn(err); setPingsErr("INVALID_SERVER_RESPONSE"); setLoadingPings(false); } } } catch (err) { setPingsErr("NETWORK_ERROR"); setLoadingPings(false); } })(); } }, [useTimeCriteria, currentNode]); useEffect(() => { if (currentNode?.nodeType === "server" && useLogWindowCriteria.from) { (async () => { setLoadingLogs(true); try { const rs = await fetch("/rest/admin/nodes/" + currentNode.nodeId + "/logs/" + useLogWindowCriteria.type + "?from=" + useLogWindowCriteria.from + "&to=" + (useLogWindowCriteria.to || ""), { credentials: "omit", headers: { 'Token': userData.token, }, }); if (rs.status !== 200) { try { const json = await rs.json(); if (json.status) { setLogsErr(json.status); setLoadingLogs(false); } else { setLogsErr("UNKNOWN_ERROR"); setLoadingLogs(false); } } catch (err) { console.warn(err); setLogsErr("UNKNOWN_ERROR"); setLoadingLogs(false); } } else { try { const logs: { logs: ILogsResult } = await rs.json(); setLogs(logs.logs); setLogsErr(null); setLoadingLogs(false); } catch (err) { console.warn(err); setLogsErr("INVALID_SERVER_RESPONSE"); setLoadingLogs(false); } } } catch (err) { setLogsErr("NETWORK_ERROR"); setLoadingLogs(false); } })(); } }, [useLogWindowCriteria, currentNode]); const lastHeardDate = currentNode && (new Date((currentNode as INetworkServerNode).lastHeard)); const createdAtDate = currentNode && (new Date((currentNode as INetworkServerNode).createdAt)); return (
{!d3loaded ? "Loading D3 please wait..." : (loading ? "Loading network please wait..." : (err ? "ERR: " + err : "Admin Node Explorer"))}
{currentNode ?
{currentNode.name} - {currentNode.nodeId} [{currentNode.groupId}]
{ (currentNode as INetworkDbNode).host ?
HOST: {(currentNode as INetworkDbNode).host}
: null } { (currentNode as INetworkServerNode).type ?
TYPE: {(currentNode as INetworkServerNode).type}
: null } { typeof (currentNode as INetworkServerNode).alive === "boolean" ?
STATUS: {(currentNode as INetworkServerNode).alive ? " RUNNING" : " DEAD"}
: null } { (currentNode as INetworkServerNode).lastHeard ?
LAST HEARD: {lastHeardDate.toLocaleDateString()} {lastHeardDate.toLocaleTimeString()}
: null } { (currentNode as INetworkServerNode).createdAt ?
SPAWNED AT: {createdAtDate.toLocaleDateString()} {createdAtDate.toLocaleTimeString()}
: null } { (currentNode as INetworkServerNode).envData ?
ENV
{Object.keys((currentNode as INetworkServerNode).envData).map((k) => { return ( ) })}
{k} {JSON.stringify((currentNode as INetworkServerNode).envData[k])}
: null } { currentNode.links.length ?
LINKS
{currentNode.links.map((k, i) => { const nodeInQuestion = currentRenderedNetwork.current.nodes.find((n) => n.nodeId === k.id); const isDead = nodeInQuestion.nodeType === "server" && !(nodeInQuestion as INetworkServerNode).alive; if (isDead && !showDead) { return null; } return ( ); })}
{k.type} {k.id} {isDead ? "DEAD" : "RUNNING"}
: null }
: null} {currentNode ?
Administrative actions on the node
{currentNode.nodeType === "server" ?
{currentNode.alive ? null : ( <> )}
: ( currentNode.nodeType === "database" ? ( (currentNode as INetworkDbNode).type === "elastic" && (currentNode as INetworkDbNode).links.some((l) => l.type === "uses-as-search-database") ? (
) : ((currentNode as INetworkDbNode).type === "pg" ? ( <>



{currentAdminResponse} ) : null) ) : null )}
: null} {currentNode && currentNode.nodeType === "server" ?
MEMORY AND CPU USAGE {loadingPings ? "(Loading please wait...)" : (pingsErr ? "(" + pingsErr + ")" : "")}
{useTimeCriteria.from && useTimeCriteria.from > (new Date(currentNode.createdAt)).getTime() ? : null} {useTimeCriteria.to === null || (!currentNode.alive && (new Date(currentNode.lastHeard)).getTime() < useTimeCriteria.to) ? null : ( )}
{useTimeCriteria.from ?
viewing logs from {(new Date(useTimeCriteria.from)).toLocaleDateString()} {(new Date(useTimeCriteria.from)).toLocaleTimeString()}-{useTimeCriteria.to ? ((new Date(useTimeCriteria.to)).toLocaleDateString() + " " + (new Date(useTimeCriteria.to)).toLocaleTimeString()) : "now"}
: null}
CPU usage
CPU process load
60 second entire CPU average load
Memory usage
Process array buffers memory (MB)
Process heap used (MB)
Process heap total (MB)
Process external usage (MB)
Process RSS (MB)
Free memory
Total free memory (MB)
: null} {currentNode && currentNode.nodeType === "server" ?
LOGS {loadingLogs ? "(Loading please wait...)" : (logsErr ? "(" + logsErr + ")" : "")}
{useLogWindowCriteria.from && useLogWindowCriteria.from > (new Date(currentNode.createdAt)).getTime() ? : null} {useLogWindowCriteria.to === null || (!currentNode.alive && (new Date(currentNode.lastHeard)).getTime() < useLogWindowCriteria.to) ? null : ( )}
{logs ?
{logs.level.toUpperCase() + " logs (oldest first)"}
: null} {useLogWindowCriteria.from ?
viewing logs from {(new Date(useLogWindowCriteria.from)).toLocaleDateString()} {(new Date(useLogWindowCriteria.from)).toLocaleTimeString()}-{useLogWindowCriteria.to ? ((new Date(useLogWindowCriteria.to)).toLocaleDateString() + " " + (new Date(useLogWindowCriteria.to)).toLocaleTimeString()) : "now"}
: null}
{logs && logs.records.map((r, i) => { const timeDate = new Date(r.createdAt); return (
{r.data.orphan ? (
ORPHAN
) : null} {r.data.className || r.data.methodName || r.data.endpoint || r.data.functionName ?
{r.data.className ?
class: {r.data.className}
: null} {r.data.methodName ?
method: .{r.data.methodName}(...)
: null} {r.data.functionName ?
function: {r.data.functionName}(...)
: null} {r.data.endpoint ?
endpoint: {r.data.endpoint}
: null}
: null} {r.data.message ?
{r.data.message}
: null} {r.data.errMessage ?
{r.data.errMessage}
: null} {r.data.errStack ?
{r.data.errStack}
: null}
{timeDate.toLocaleDateString()} {timeDate.toLocaleTimeString()}
) })}
: null}
); }