import "./styles.sass"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { faArrowDown, faArrowUp, faCircleNotch, faClock, faExclamationTriangle, faKey, faPen, faServer, faTableTennisPaddleBall, faTrash } from "@fortawesome/free-solid-svg-icons"; import React, {useContext, useEffect, useState} from "react"; import {NodeContext} from "@/common/contexts/Node"; import {useAlert} from "@/common/contexts/Alert"; import {ToastNotificationContext} from "@/common/contexts/ToastNotification"; import {baseRequest, patchRequest} from "@/common/utils/RequestUtil"; import {t} from "i18next"; import {Trans} from "react-i18next"; import {getIconBySpeed} from "@/common/utils/TestUtil"; import {ConfigContext} from "@/common/contexts/Config"; import {PreferencesContext} from "@/common/contexts/Preferences"; import {convertSpeed, getSpeedUnit} from "@/common/utils/FormatUtil"; import {useNavigate} from "react-router-dom"; import ContextMenu from "@/common/components/ContextMenu"; export const NodeContainer = (node) => { const updateNodes = useContext(NodeContext)[1]; const updateCurrentNode = useContext(NodeContext)[3]; const reloadConfig = useContext(ConfigContext)[1]; const [preferences] = useContext(PreferencesContext); const speedUnit = getSpeedUnit(preferences); const updateToast = useContext(ToastNotificationContext); const alert = useAlert(); const [nodeData, setNodeData] = useState(null); const [nodeError, setNodeError] = useState(undefined); const [contextMenu, setContextMenu] = useState(null); const navigate = useNavigate(); const prefix = node.currentNode ? "" : "/nodes/" + node.id; const updateData = async () => { const testRequest = await baseRequest(prefix + "/speedtests?limit=1"); if (testRequest.status === 401) return setNodeError("PASSWORD_CHANGED"); if (!testRequest.ok) return setNodeError("SERVER_NOT_REACHABLE"); const tests = await testRequest.json(); if (tests.length < 0) return setNodeError("SERVER_NOT_REACHABLE"); const configRequest = await baseRequest(prefix + "/config"); if (configRequest.status === 401) return setNodeError("PASSWORD_CHANGED"); if (!configRequest.ok) return setNodeError("SERVER_NOT_REACHABLE"); const config = await configRequest.json(); if (config.viewMode) return setNodeError("PASSWORD_CHANGED"); if (tests[0] === undefined) return setNodeData({pending: true}); setNodeError(undefined); setNodeData({ ping: tests[0]?.ping, download: Math.round(tests[0]?.download), upload: Math.round(tests[0]?.upload), pingIcon: getIconBySpeed(tests[0]?.ping, config.ping, false), downloadIcon: getIconBySpeed(tests[0]?.download, config.download, true), uploadIcon: getIconBySpeed(tests[0]?.upload, config.upload, true) }); } const updatePassword = async (wrong = false) => { const result = await alert.openInput(t("nodes.password_outdated"), { inputType: "password", description: wrong ? {t("dialog.password.wrong")} : t("nodes.update_password"), placeholder: t("dialog.password.placeholder"), buttonText: t("dialog.update") }); if (result) { const res = await (await baseRequest(`/nodes/${node.id}/password`, "PATCH", {password: result})).json(); if (res.type === "PASSWORD_UPDATED") { updateData().catch(() => setNodeError("SERVER_NOT_REACHABLE")); updateToast(t("nodes.password_updated"), "green", faKey); } else { updatePassword(true); } } }; useEffect(() => { updateData().catch(() => setNodeError("SERVER_NOT_REACHABLE")); const interval = setInterval(() => updateData().catch(() => setNodeError("SERVER_NOT_REACHABLE")), 10000); return () => clearInterval(interval); }, []); const switchNode = () => { if (nodeError || !nodeData) { if (nodeError === "PASSWORD_CHANGED") updatePassword(); return; } navigate("/"); updateCurrentNode(node.id); reloadConfig(); } const onContext = async (event) => { event.preventDefault(); event.stopPropagation(); if (node.currentNode) return; setContextMenu({x: event.clientX, y: event.clientY}); }; const closeContextMenu = () => setContextMenu(null); const handleRename = async () => { const newName = await alert.openInput(t("nodes.rename.title"), { placeholder: t("nodes.rename.placeholder"), buttonText: t("dialog.save"), value: node.name }); if (newName && newName !== node.name) { await patchRequest(`/nodes/${node.id}/name`, {name: newName}); updateToast(t("nodes.rename.success"), "green", faPen); updateNodes(); } }; const handleDelete = async () => { const confirmed = await alert.openConfirm( t("nodes.delete.title"), }} values={node}>nodes.delete.description, { buttonText: t("nodes.delete.yes"), danger: true } ); if (confirmed) { await baseRequest("/nodes/" + node.id, "DELETE"); updateToast(t("nodes.delete.success"), "green", faServer); updateNodes(); } }; const contextMenuItems = [ { icon: faPen, label: t("nodes.rename.title"), onClick: handleRename }, {divider: true}, { icon: faTrash, label: t("nodes.delete.title"), onClick: handleDelete, danger: true } ]; return ( <> {contextMenu && ( )}

{node.name}

{node.url.replace(/(^\w+:|^)\/\//, '')}

{nodeError === "SERVER_NOT_REACHABLE" && (

{t("nodes.messages.not_reachable")}

)} {nodeError === "PASSWORD_CHANGED" && (

{t("nodes.messages.password_changed")}

)} {!nodeError && !nodeData && ( )} {nodeData && nodeData.pending && !nodeError && (

{t("nodes.messages.tests_pending")}

)} {nodeData && !nodeData.pending && !nodeError && ( <>

{nodeData.ping} {t("latest.ping_unit")}

{convertSpeed(nodeData.download, preferences)} {speedUnit}

{convertSpeed(nodeData.upload, preferences)} {speedUnit}

)}
); }