mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
fix: simplify host status to online/offline and keep it live without a refresh
This commit is contained in:
1 parent
2646b649d7
commit
0a436ab3d8
48 files changed
+549
-789
No files matched your search
@@ -117,7 +117,7 @@ export function AdminSettingsPanel({
|
||||
const [passwordLoginForced, setPasswordLoginForced] = useState(false);
|
||||
const [allowPasswordReset, setAllowPasswordReset] = useState(true);
|
||||
const [sessionTimeout, setSessionTimeout] = useState("24");
|
||||
const [statusInterval, setStatusInterval] = useState("60");
|
||||
const [statusInterval, setStatusInterval] = useState("30");
|
||||
const [logLevel, setLogLevel] = useState("info");
|
||||
const [notificationPrivateEndpoints, setNotificationPrivateEndpoints] =
|
||||
useState<string[]>([]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getErrorMessage } from "../lib/error-message.js";
|
||||
import { enabledHostProtocols, useHostProtocols } from "./host-protocols";
|
||||
import { useSshAuthProviders } from "@/hooks/useSshAuthProviders";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { HostData } from "@/types/index";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
@@ -53,6 +53,8 @@ import {
|
||||
import type { SSHHostWithStatus } from "@/main-axios";
|
||||
import type { Host, HostFolder, TabType } from "@/types/ui-types";
|
||||
import { sortHostTree, type SortKey } from "@/sidebar/host-sort";
|
||||
import { withLiveStatus } from "@/sidebar/live-host-status";
|
||||
import { useServerStatus } from "@/lib/ServerStatusContext";
|
||||
import { useHostSidebarPreferences } from "@/sidebar/tree/hooks/useHostSidebarPreferences";
|
||||
import { useArrangeLock } from "@/sidebar/use-arrange-lock";
|
||||
import type {
|
||||
@@ -137,9 +139,7 @@ function hostPassesFilters(
|
||||
if (filters.status.length > 0) {
|
||||
const ok =
|
||||
(filters.status.includes("online") && host.online) ||
|
||||
(filters.status.includes("offline") &&
|
||||
host.status !== "reachable" &&
|
||||
!host.online) ||
|
||||
(filters.status.includes("offline") && host.status === "offline") ||
|
||||
(filters.status.includes("pinned") && !!host.pin);
|
||||
if (!ok) return false;
|
||||
}
|
||||
@@ -219,6 +219,13 @@ export function HostsPanel({
|
||||
active?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { statuses, getStatus } = useServerStatus();
|
||||
const liveHostTree = useMemo(
|
||||
() => (hostTree ? withLiveStatus(hostTree, getStatus) : undefined),
|
||||
// statuses changes identity whenever a host's status changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[hostTree, statuses, getStatus],
|
||||
);
|
||||
const hostSwitchPlugins = usePluginHostSections().filter(
|
||||
(plugin) => !!plugin.contributes?.settings?.host?.enableKey,
|
||||
);
|
||||
@@ -1028,10 +1035,10 @@ export function HostsPanel({
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<SidebarTree
|
||||
children={
|
||||
hostTree
|
||||
liveHostTree
|
||||
? groupHosts(
|
||||
applyFilters(
|
||||
sortHostTree(hostTree, sortKey, pinnedFirst),
|
||||
sortHostTree(liveHostTree, sortKey, pinnedFirst),
|
||||
filterState,
|
||||
hostSwitches,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { Host, HostFolder } from "@/types/ui-types";
|
||||
import type { StatusValue } from "@/lib/server-status-store";
|
||||
import { isFolder } from "./tree/visible-rows";
|
||||
|
||||
/**
|
||||
* Copies each host's live status onto the tree so sorting, filtering and
|
||||
* grouping by status read the same value as the row's dot. A host with
|
||||
* status checks off gets no status.
|
||||
*/
|
||||
export function withLiveStatus(
|
||||
folder: HostFolder,
|
||||
getStatus: (hostId: number) => StatusValue,
|
||||
): HostFolder {
|
||||
return {
|
||||
...folder,
|
||||
children: folder.children.map((child) => {
|
||||
if (isFolder(child)) return withLiveStatus(child, getStatus);
|
||||
const host = child as Host;
|
||||
const status =
|
||||
host.statusCheckEnabled === false
|
||||
? undefined
|
||||
: getStatus(Number(host.id));
|
||||
if (host.status === status) return host;
|
||||
return { ...host, status, online: status === "online" };
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/** The flat-list version of withLiveStatus. */
|
||||
export function withLiveHostStatus(
|
||||
hosts: Host[],
|
||||
getStatus: (hostId: number) => StatusValue,
|
||||
): Host[] {
|
||||
return withLiveStatus({ name: "", children: hosts }, getStatus)
|
||||
.children as Host[];
|
||||
}
|
||||
@@ -15,23 +15,12 @@ import { HostItem, statusCheckEnabled } from "../HostItem/HostItem";
|
||||
import { isFolder, folderHasMatch, collectAllHosts } from "../visible-rows";
|
||||
import { FolderActions } from "./FolderActions";
|
||||
|
||||
export function folderHostCount(folder: HostFolder): {
|
||||
total: number;
|
||||
online: number;
|
||||
} {
|
||||
let total = 0,
|
||||
online = 0;
|
||||
export function folderHostCount(folder: HostFolder): number {
|
||||
let total = 0;
|
||||
for (const child of folder.children) {
|
||||
if (isFolder(child)) {
|
||||
const c = folderHostCount(child);
|
||||
total += c.total;
|
||||
online += c.online;
|
||||
} else {
|
||||
total++;
|
||||
if (child.online) online++;
|
||||
}
|
||||
total += isFolder(child) ? folderHostCount(child) : 1;
|
||||
}
|
||||
return { total, online };
|
||||
return total;
|
||||
}
|
||||
|
||||
export function FolderItem({
|
||||
@@ -140,13 +129,11 @@ export function FolderItem({
|
||||
onReorderHoverChange?: (edge: "before" | "after" | null) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { getStatus, initialLoadComplete } = useServerStatus();
|
||||
const { total } = folderHostCount(folder);
|
||||
const online = initialLoadComplete
|
||||
? collectAllHosts(folder.children).filter(
|
||||
(h) => statusCheckEnabled(h) && getStatus(Number(h.id)) === "online",
|
||||
).length
|
||||
: folderHostCount(folder).online;
|
||||
const { getStatus } = useServerStatus();
|
||||
const total = folderHostCount(folder);
|
||||
const online = collectAllHosts(folder.children).filter(
|
||||
(h) => statusCheckEnabled(h) && getStatus(Number(h.id)) === "online",
|
||||
).length;
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
const reorderEdge = isReorderHovered ? reorderHoverEdge : null;
|
||||
|
||||
|
||||
@@ -59,11 +59,7 @@ import {
|
||||
useStatusColorScheme,
|
||||
getStatusClasses,
|
||||
} from "@/hooks/use-status-color-scheme";
|
||||
import {
|
||||
useHostStatus,
|
||||
useHostStatusReason,
|
||||
useServerStatusMeta,
|
||||
} from "@/lib/ServerStatusContext";
|
||||
import { useHostStatus } from "@/lib/ServerStatusContext";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -101,16 +97,16 @@ export function statusCheckEnabled(host: Host): boolean {
|
||||
|
||||
export function buildStatusTooltip(
|
||||
host: Host,
|
||||
status: "online" | "reachable" | "offline",
|
||||
status: "online" | "offline" | "unknown",
|
||||
t: (key: string) => string = (k) => k,
|
||||
): string {
|
||||
if (!statusCheckEnabled(host)) return t("hosts.status.monitoringDisabled");
|
||||
const statusLabel =
|
||||
status === "online"
|
||||
? t("hosts.status.available")
|
||||
: status === "reachable"
|
||||
? t("hosts.status.reachable")
|
||||
: t("hosts.status.offline");
|
||||
if (!statusCheckEnabled(host)) return t("hosts.status.monitoringDisabled");
|
||||
? t("hosts.status.online")
|
||||
: status === "offline"
|
||||
? t("hosts.status.offline")
|
||||
: t("hosts.status.checking");
|
||||
const protocols: string[] = [];
|
||||
if (host.enableSsh) protocols.push("SSH");
|
||||
for (const protocol of enabledHostProtocols(host)) {
|
||||
@@ -283,22 +279,11 @@ export function HostItem({
|
||||
const pluginMenuItems = hostMenuItemsFor(useHostContextMenuItems(), host);
|
||||
const splitTargets = useSplitTargets();
|
||||
const statusScheme = useStatusColorScheme();
|
||||
const { initialLoadComplete } = useServerStatusMeta();
|
||||
const statusCheckOn = statusCheckEnabled(host);
|
||||
const statusLoading = !initialLoadComplete && statusCheckOn;
|
||||
// Per-host subscription — status polls only re-render rows that flipped.
|
||||
const liveStatus = useHostStatus(Number(host.id), statusCheckOn);
|
||||
const statusReason = useHostStatusReason(Number(host.id), statusCheckOn);
|
||||
// Per-host subscription, so a poll only re-renders rows that flipped.
|
||||
const availability =
|
||||
liveStatus === "online" ||
|
||||
liveStatus === "reachable" ||
|
||||
liveStatus === "offline"
|
||||
? liveStatus
|
||||
: host.status === "reachable"
|
||||
? "reachable"
|
||||
: host.online
|
||||
? "online"
|
||||
: "offline";
|
||||
useHostStatus(Number(host.id), statusCheckOn) ?? "offline";
|
||||
const statusLoading = availability === "unknown";
|
||||
const isOnline = availability === "online";
|
||||
const previousAvailability = useRef(availability);
|
||||
const [statusLocking, setStatusLocking] = useState(false);
|
||||
@@ -1136,9 +1121,7 @@ export function HostItem({
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
{statusReason === "host_key_changed"
|
||||
? `${t("hostKey.keyChangedWarning")}: ${t("hostKey.keyChangedDescription")}`
|
||||
: buildStatusTooltip(host, availability, t)}
|
||||
{buildStatusTooltip(host, availability, t)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -502,7 +502,7 @@ export function SidebarTree({
|
||||
|
||||
function handleDeleteFolder(folder: HostFolder) {
|
||||
const folderPath = folder.path ?? folder.name;
|
||||
const { total } = folderHostCount(folder);
|
||||
const total = folderHostCount(folder);
|
||||
setConfirmDialog({
|
||||
message: t("hosts.deleteFolderConfirm", {
|
||||
name: folder.name,
|
||||
|
||||
Reference in new issue
Block a user