2025-11-05 10:36:16 -06:00
|
|
|
import React, { useState, useEffect, useRef } from "react";
|
2025-12-31 22:20:12 -06:00
|
|
|
import { HostManagerViewer } from "@/ui/desktop/apps/host-manager/hosts/HostManagerViewer.tsx";
|
2025-09-12 14:42:00 -05:00
|
|
|
import {
|
|
|
|
|
Tabs,
|
|
|
|
|
TabsContent,
|
|
|
|
|
TabsList,
|
|
|
|
|
TabsTrigger,
|
|
|
|
|
} from "@/components/ui/tabs.tsx";
|
|
|
|
|
import { Separator } from "@/components/ui/separator.tsx";
|
2025-12-31 22:20:12 -06:00
|
|
|
import { HostManagerEditor } from "@/ui/desktop/apps/host-manager/hosts/HostManagerEditor.tsx";
|
|
|
|
|
import { CredentialsManager } from "@/ui/desktop/apps/host-manager/credentials/CredentialsManager.tsx";
|
|
|
|
|
import { CredentialEditor } from "@/ui/desktop/apps/host-manager/credentials/CredentialEditor.tsx";
|
2025-09-12 14:42:00 -05:00
|
|
|
import { useSidebar } from "@/components/ui/sidebar.tsx";
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
import type { SSHHost, HostManagerProps } from "../../../types/index";
|
|
|
|
|
|
|
|
|
|
export function HostManager({
|
|
|
|
|
isTopbarOpen,
|
2025-11-05 10:36:16 -06:00
|
|
|
initialTab = "host_viewer",
|
|
|
|
|
hostConfig,
|
2025-12-31 22:20:12 -06:00
|
|
|
_updateTimestamp,
|
2025-11-17 09:46:05 -06:00
|
|
|
rightSidebarOpen = false,
|
|
|
|
|
rightSidebarWidth = 400,
|
2025-12-31 22:20:12 -06:00
|
|
|
currentTabId,
|
|
|
|
|
updateTab,
|
2025-09-12 14:42:00 -05:00
|
|
|
}: HostManagerProps): React.ReactElement {
|
|
|
|
|
const { t } = useTranslation();
|
2025-11-05 10:36:16 -06:00
|
|
|
const [activeTab, setActiveTab] = useState(initialTab);
|
|
|
|
|
const [editingHost, setEditingHost] = useState<SSHHost | null>(
|
|
|
|
|
hostConfig || null,
|
|
|
|
|
);
|
2025-09-12 14:42:00 -05:00
|
|
|
|
2025-12-31 22:20:12 -06:00
|
|
|
useEffect(() => {}, [editingHost]);
|
|
|
|
|
|
2025-11-05 10:36:16 -06:00
|
|
|
const [editingCredential, setEditingCredential] = useState<{
|
|
|
|
|
id: number;
|
|
|
|
|
name?: string;
|
|
|
|
|
username: string;
|
|
|
|
|
} | null>(null);
|
2025-09-12 14:42:00 -05:00
|
|
|
const { state: sidebarState } = useSidebar();
|
2025-11-05 10:36:16 -06:00
|
|
|
const ignoreNextHostConfigChangeRef = useRef<boolean>(false);
|
|
|
|
|
const lastProcessedHostIdRef = useRef<number | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-12-31 22:20:12 -06:00
|
|
|
if (_updateTimestamp !== undefined) {
|
|
|
|
|
if (initialTab && initialTab !== activeTab) {
|
|
|
|
|
setActiveTab(initialTab);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hostConfig && hostConfig.id !== editingHost?.id) {
|
|
|
|
|
setEditingHost(hostConfig);
|
|
|
|
|
lastProcessedHostIdRef.current = hostConfig.id;
|
|
|
|
|
} else if (
|
|
|
|
|
!hostConfig &&
|
|
|
|
|
editingHost &&
|
|
|
|
|
editingHost.id !== lastProcessedHostIdRef.current
|
|
|
|
|
) {
|
|
|
|
|
setEditingHost(null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (initialTab !== "add_credential" && editingCredential) {
|
|
|
|
|
setEditingCredential(null);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (initialTab) {
|
|
|
|
|
setActiveTab(initialTab);
|
|
|
|
|
}
|
|
|
|
|
if (hostConfig) {
|
|
|
|
|
setEditingHost(hostConfig);
|
|
|
|
|
lastProcessedHostIdRef.current = hostConfig.id;
|
|
|
|
|
}
|
2025-11-05 10:36:16 -06:00
|
|
|
}
|
2025-12-31 22:20:12 -06:00
|
|
|
}, [_updateTimestamp, initialTab, hostConfig?.id]);
|
2025-09-12 14:42:00 -05:00
|
|
|
|
|
|
|
|
const handleEditHost = (host: SSHHost) => {
|
|
|
|
|
setEditingHost(host);
|
|
|
|
|
setActiveTab("add_host");
|
2025-11-05 10:36:16 -06:00
|
|
|
lastProcessedHostIdRef.current = host.id;
|
2025-12-31 22:20:12 -06:00
|
|
|
|
|
|
|
|
if (updateTab && currentTabId !== undefined) {
|
|
|
|
|
updateTab(currentTabId, { initialTab: "add_host" });
|
|
|
|
|
}
|
2025-09-12 14:42:00 -05:00
|
|
|
};
|
|
|
|
|
|
2025-11-05 10:36:16 -06:00
|
|
|
const handleFormSubmit = () => {
|
|
|
|
|
ignoreNextHostConfigChangeRef.current = true;
|
2025-09-12 14:42:00 -05:00
|
|
|
setEditingHost(null);
|
|
|
|
|
setActiveTab("host_viewer");
|
2025-11-05 10:36:16 -06:00
|
|
|
setTimeout(() => {
|
|
|
|
|
lastProcessedHostIdRef.current = undefined;
|
|
|
|
|
}, 500);
|
2025-09-12 14:42:00 -05:00
|
|
|
};
|
|
|
|
|
|
2025-11-05 10:36:16 -06:00
|
|
|
const handleEditCredential = (credential: {
|
|
|
|
|
id: number;
|
|
|
|
|
name?: string;
|
|
|
|
|
username: string;
|
|
|
|
|
}) => {
|
2025-09-12 14:42:00 -05:00
|
|
|
setEditingCredential(credential);
|
|
|
|
|
setActiveTab("add_credential");
|
2025-12-31 22:20:12 -06:00
|
|
|
|
|
|
|
|
if (updateTab && currentTabId !== undefined) {
|
|
|
|
|
updateTab(currentTabId, { initialTab: "add_credential" });
|
|
|
|
|
}
|
2025-09-12 14:42:00 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCredentialFormSubmit = () => {
|
|
|
|
|
setEditingCredential(null);
|
|
|
|
|
setActiveTab("credentials");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleTabChange = (value: string) => {
|
2025-11-17 09:46:05 -06:00
|
|
|
if (activeTab === "add_host" && value !== "add_host") {
|
2025-09-12 14:42:00 -05:00
|
|
|
setEditingHost(null);
|
|
|
|
|
}
|
2025-11-17 09:46:05 -06:00
|
|
|
if (activeTab === "add_credential" && value !== "add_credential") {
|
2025-09-12 14:42:00 -05:00
|
|
|
setEditingCredential(null);
|
|
|
|
|
}
|
2025-11-17 09:46:05 -06:00
|
|
|
setActiveTab(value);
|
2025-12-31 22:20:12 -06:00
|
|
|
|
|
|
|
|
if (updateTab && currentTabId !== undefined) {
|
|
|
|
|
updateTab(currentTabId, { initialTab: value });
|
|
|
|
|
}
|
2025-09-12 14:42:00 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const topMarginPx = isTopbarOpen ? 74 : 26;
|
|
|
|
|
const leftMarginPx = sidebarState === "collapsed" ? 26 : 8;
|
|
|
|
|
const bottomMarginPx = 8;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<div className="w-full">
|
|
|
|
|
<div
|
2025-12-31 22:20:12 -06:00
|
|
|
className="bg-canvas text-foreground p-4 pt-0 rounded-lg border-2 border-edge flex flex-col min-h-0 overflow-hidden"
|
2025-09-12 14:42:00 -05:00
|
|
|
style={{
|
|
|
|
|
marginLeft: leftMarginPx,
|
2025-11-17 09:46:05 -06:00
|
|
|
marginRight: rightSidebarOpen
|
|
|
|
|
? `calc(var(--right-sidebar-width, ${rightSidebarWidth}px) + 8px)`
|
|
|
|
|
: 17,
|
2025-09-12 14:42:00 -05:00
|
|
|
marginTop: topMarginPx,
|
|
|
|
|
marginBottom: bottomMarginPx,
|
|
|
|
|
height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`,
|
2025-11-17 09:46:05 -06:00
|
|
|
transition:
|
|
|
|
|
"margin-left 200ms linear, margin-right 200ms linear, margin-top 200ms linear",
|
2025-09-12 14:42:00 -05:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Tabs
|
|
|
|
|
value={activeTab}
|
|
|
|
|
onValueChange={handleTabChange}
|
|
|
|
|
className="flex-1 flex flex-col h-full min-h-0"
|
|
|
|
|
>
|
2025-12-31 22:20:12 -06:00
|
|
|
<TabsList className="bg-elevated border-2 border-edge mt-1.5">
|
|
|
|
|
<TabsTrigger
|
|
|
|
|
value="host_viewer"
|
|
|
|
|
className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge"
|
|
|
|
|
>
|
2025-09-12 14:42:00 -05:00
|
|
|
{t("hosts.hostViewer")}
|
|
|
|
|
</TabsTrigger>
|
2025-12-31 22:20:12 -06:00
|
|
|
<TabsTrigger
|
|
|
|
|
value="add_host"
|
|
|
|
|
className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge"
|
|
|
|
|
>
|
2025-10-01 15:40:10 -05:00
|
|
|
{editingHost
|
|
|
|
|
? editingHost.id
|
|
|
|
|
? t("hosts.editHost")
|
|
|
|
|
: t("hosts.cloneHost")
|
|
|
|
|
: t("hosts.addHost")}
|
2025-09-12 14:42:00 -05:00
|
|
|
</TabsTrigger>
|
2025-12-31 22:20:12 -06:00
|
|
|
<div className="h-6 w-px bg-border-base mx-1"></div>
|
|
|
|
|
<TabsTrigger
|
|
|
|
|
value="credentials"
|
|
|
|
|
className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge"
|
|
|
|
|
>
|
2025-09-12 14:42:00 -05:00
|
|
|
{t("credentials.credentialsViewer")}
|
|
|
|
|
</TabsTrigger>
|
2025-12-31 22:20:12 -06:00
|
|
|
<TabsTrigger
|
|
|
|
|
value="add_credential"
|
|
|
|
|
className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge"
|
|
|
|
|
>
|
2025-09-12 14:42:00 -05:00
|
|
|
{editingCredential
|
|
|
|
|
? t("credentials.editCredential")
|
|
|
|
|
: t("credentials.addCredential")}
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
</TabsList>
|
|
|
|
|
<TabsContent
|
|
|
|
|
value="host_viewer"
|
|
|
|
|
className="flex-1 flex flex-col h-full min-h-0"
|
|
|
|
|
>
|
|
|
|
|
<Separator className="p-0.25 -mt-0.5 mb-1" />
|
|
|
|
|
<HostManagerViewer onEditHost={handleEditHost} />
|
|
|
|
|
</TabsContent>
|
|
|
|
|
<TabsContent
|
|
|
|
|
value="add_host"
|
|
|
|
|
className="flex-1 flex flex-col h-full min-h-0"
|
|
|
|
|
>
|
|
|
|
|
<Separator className="p-0.25 -mt-0.5 mb-1" />
|
|
|
|
|
<div className="flex flex-col h-full min-h-0">
|
|
|
|
|
<HostManagerEditor
|
|
|
|
|
editingHost={editingHost}
|
|
|
|
|
onFormSubmit={handleFormSubmit}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
<TabsContent
|
|
|
|
|
value="credentials"
|
|
|
|
|
className="flex-1 flex flex-col h-full min-h-0"
|
|
|
|
|
>
|
|
|
|
|
<Separator className="p-0.25 -mt-0.5 mb-1" />
|
2025-12-31 22:20:12 -06:00
|
|
|
<div className="flex flex-col h-full min-h-0 overflow-auto thin-scrollbar">
|
2025-09-12 14:42:00 -05:00
|
|
|
<CredentialsManager onEditCredential={handleEditCredential} />
|
|
|
|
|
</div>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
<TabsContent
|
|
|
|
|
value="add_credential"
|
|
|
|
|
className="flex-1 flex flex-col h-full min-h-0"
|
|
|
|
|
>
|
|
|
|
|
<Separator className="p-0.25 -mt-0.5 mb-1" />
|
|
|
|
|
<div className="flex flex-col h-full min-h-0">
|
|
|
|
|
<CredentialEditor
|
|
|
|
|
editingCredential={editingCredential}
|
|
|
|
|
onFormSubmit={handleCredentialFormSubmit}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
</Tabs>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|