diff --git a/application/i18n/locales/en/vault.ts b/application/i18n/locales/en/vault.ts index 57976778..961bb3f3 100644 --- a/application/i18n/locales/en/vault.ts +++ b/application/i18n/locales/en/vault.ts @@ -485,6 +485,7 @@ export const enVaultMessages: Messages = { 'hostDetails.distro.option.redhat': 'Red Hat / RHEL', 'hostDetails.distro.option.almalinux': 'AlmaLinux', 'hostDetails.distro.option.alinux': 'Alibaba Cloud Linux', + 'hostDetails.distro.option.openeuler': 'openEuler', 'hostDetails.distro.option.oracle': 'Oracle Linux', 'hostDetails.distro.option.kali': 'Kali Linux', 'hostDetails.distro.option.cisco': 'Cisco', diff --git a/application/i18n/locales/ru/vault.ts b/application/i18n/locales/ru/vault.ts index cb11eab3..64183591 100644 --- a/application/i18n/locales/ru/vault.ts +++ b/application/i18n/locales/ru/vault.ts @@ -520,6 +520,7 @@ export const ruVaultMessages: Messages = { 'hostDetails.distro.option.redhat': 'Red Hat / RHEL', 'hostDetails.distro.option.almalinux': 'AlmaLinux', 'hostDetails.distro.option.alinux': 'Alibaba Cloud Linux', + 'hostDetails.distro.option.openeuler': 'openEuler', 'hostDetails.distro.option.oracle': 'Oracle Linux', 'hostDetails.distro.option.kali': 'Kali Linux', 'hostDetails.distro.option.cisco': 'Cisco', diff --git a/application/i18n/locales/zh-CN/vault.ts b/application/i18n/locales/zh-CN/vault.ts index 03772573..6bc66a4d 100644 --- a/application/i18n/locales/zh-CN/vault.ts +++ b/application/i18n/locales/zh-CN/vault.ts @@ -66,6 +66,7 @@ export const zhCNVaultMessages: Messages = { 'hostDetails.distro.option.redhat': 'Red Hat / RHEL', 'hostDetails.distro.option.almalinux': 'AlmaLinux', 'hostDetails.distro.option.alinux': '阿里云 Linux', + 'hostDetails.distro.option.openeuler': 'openEuler', 'hostDetails.distro.option.oracle': 'Oracle Linux', 'hostDetails.distro.option.kali': 'Kali Linux', 'hostDetails.distro.option.cisco': '思科', diff --git a/components/DistroAvatar.tsx b/components/DistroAvatar.tsx index ff7ba59e..1e9da20a 100644 --- a/components/DistroAvatar.tsx +++ b/components/DistroAvatar.tsx @@ -19,6 +19,7 @@ export const DISTRO_LOGOS: Record = { kali: "/distro/kali.svg", almalinux: "/distro/almalinux.svg", alinux: "/distro/alinux.svg", + openeuler: "/distro/openeuler.svg", // OS-level logos (used by local terminal tab icons) macos: "/distro/macos.svg", windows: "/distro/windows.svg", @@ -50,6 +51,7 @@ export const DISTRO_COLORS: Record = { kali: "bg-[#0F6DB3]", almalinux: "bg-[#173B66]", alinux: "bg-[#FF6A00]", + openeuler: "bg-[#002FA7]", // OS-level colors macos: "bg-[#333333]", windows: "bg-[#0078D4]", diff --git a/components/systemManager/SystemManagerSidePanel.tsx b/components/systemManager/SystemManagerSidePanel.tsx index ed113b10..0c272315 100644 --- a/components/systemManager/SystemManagerSidePanel.tsx +++ b/components/systemManager/SystemManagerSidePanel.tsx @@ -5,6 +5,7 @@ import { useSystemManagerBackend } from '../../application/state/useSystemManage import type { TerminalSettings } from '../../domain/models'; import type { Host } from '../../domain/models/connection'; import type { SystemManagerSubTab } from '../../domain/systemManager/types'; +import { resolveCapabilityPanelState } from '../../domain/systemManagerPanelState'; import { buildSystemManagerTabs } from '../../domain/systemManager/systemTarget'; import type { Snippet, TerminalSession } from '../../types'; import { cn } from '../../lib/utils'; @@ -52,7 +53,7 @@ export const SystemManagerSidePanel = memo(function SystemManagerSidePanel({ const capabilitiesTtlMs = terminalSettings.systemManagerProcessRefreshInterval * 1000; - const { capabilities, probing, refreshCapabilities } = useSessionCapabilities(sessionId, isConnected, backend, isVisible, capabilitiesTtlMs); + const { capabilities, refreshCapabilities } = useSessionCapabilities(sessionId, isConnected, backend, isVisible, capabilitiesTtlMs); const availableTabs = useMemo( () => buildSystemManagerTabs(sessionHost, capabilities, session), @@ -158,10 +159,16 @@ export const SystemManagerSidePanel = memo(function SystemManagerSidePanel({ const tmuxReady = capabilities?.hasTmux === true; const dockerReady = capabilities?.hasDocker === true; - const tmuxUnavailable = !probing && capabilities !== undefined && !tmuxReady; - const dockerUnavailable = !probing && capabilities !== undefined && !dockerReady; - const tmuxChecking = resolvedTab === 'tmux' && !tmuxReady && !tmuxUnavailable; - const dockerChecking = resolvedTab === 'docker' && !dockerReady && !dockerUnavailable; + const tmuxPanelState = resolveCapabilityPanelState({ + isActive: resolvedTab === 'tmux', + ready: tmuxReady, + capabilitiesKnown: capabilities !== undefined, + }); + const dockerPanelState = resolveCapabilityPanelState({ + isActive: resolvedTab === 'docker', + ready: dockerReady, + capabilitiesKnown: capabilities !== undefined, + }); return ( @@ -194,15 +201,15 @@ export const SystemManagerSidePanel = memo(function SystemManagerSidePanel({ refreshIntervalSec={terminalSettings.systemManagerProcessRefreshInterval} /> - {tmuxUnavailable && resolvedTab === 'tmux' ? ( + {tmuxPanelState === 'unavailable' ? (
- ) : tmuxChecking ? ( + ) : tmuxPanelState === 'checking' ? (
- ) : tmuxReady ? ( + ) : tmuxPanelState === 'ready' ? (
) : null} - {dockerUnavailable && resolvedTab === 'docker' ? ( + {dockerPanelState === 'unavailable' ? (
- ) : dockerChecking ? ( + ) : dockerPanelState === 'checking' ? (
- ) : dockerReady ? ( + ) : dockerPanelState === 'ready' ? (
{ + assert.equal(normalizeDistroId("openeuler"), "openeuler"); + assert.equal(normalizeDistroId("openEuler"), "openeuler"); + assert.notEqual(normalizeDistroId("openeuler"), "linux"); +}); + test("shouldProbeSessionCwd allows the probe on a plain Linux host", () => { assert.equal( shouldProbeSessionCwd({ isNetworkDevice: false, remoteSshVersion: "OpenSSH_9.6" }), diff --git a/domain/host.ts b/domain/host.ts index d29a0b7f..b6713e68 100644 --- a/domain/host.ts +++ b/domain/host.ts @@ -47,6 +47,7 @@ export const LINUX_DISTRO_OPTIONS = [ 'oracle', 'kali', 'alinux', + 'openeuler', ] as const; /** @@ -86,6 +87,7 @@ export const normalizeDistroId = (value?: string) => { if (v.includes('almalinux')) return 'almalinux'; if (v.includes('oracle')) return 'oracle'; if (v.includes('kali')) return 'kali'; + if (v.includes('openeuler') || v.includes('open euler')) return 'openeuler'; // Alibaba Cloud Linux: os-release ID is `alinux` (older branding: Aliyun // Linux / `aliyun`). Must come before the generic `linux` fallback because // 'alinux'.includes('linux') is true and would otherwise resolve to 'linux'. diff --git a/domain/systemManagerPanelState.test.ts b/domain/systemManagerPanelState.test.ts new file mode 100644 index 00000000..b5117243 --- /dev/null +++ b/domain/systemManagerPanelState.test.ts @@ -0,0 +1,37 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { resolveCapabilityPanelState } from "./systemManagerPanelState.ts"; + +test("keeps unavailable state visible while a known-missing capability is refreshed", () => { + assert.equal( + resolveCapabilityPanelState({ + isActive: true, + ready: false, + capabilitiesKnown: true, + }), + "unavailable", + ); +}); + +test("shows checking only before capabilities are known", () => { + assert.equal( + resolveCapabilityPanelState({ + isActive: true, + ready: false, + capabilitiesKnown: false, + }), + "checking", + ); +}); + +test("hides inactive capability panels", () => { + assert.equal( + resolveCapabilityPanelState({ + isActive: false, + ready: false, + capabilitiesKnown: true, + }), + "hidden", + ); +}); diff --git a/domain/systemManagerPanelState.ts b/domain/systemManagerPanelState.ts new file mode 100644 index 00000000..48c46cb9 --- /dev/null +++ b/domain/systemManagerPanelState.ts @@ -0,0 +1,16 @@ +export type CapabilityPanelState = "hidden" | "checking" | "unavailable" | "ready"; + +export function resolveCapabilityPanelState({ + isActive, + ready, + capabilitiesKnown, +}: { + isActive: boolean; + ready: boolean; + capabilitiesKnown: boolean; +}): CapabilityPanelState { + if (!isActive) return "hidden"; + if (ready) return "ready"; + if (capabilitiesKnown) return "unavailable"; + return "checking"; +} diff --git a/public/distro/openeuler.svg b/public/distro/openeuler.svg new file mode 100644 index 00000000..b42cba6b --- /dev/null +++ b/public/distro/openeuler.svg @@ -0,0 +1 @@ +openEuler