Added option to clone host

This commit is contained in:
jedi04
2025-09-15 19:50:46 +05:30
parent 2323885b6c
commit abfc9ad371
5 changed files with 38 additions and 5 deletions

View File

@@ -418,6 +418,7 @@
"mustSelectValidSshConfig": "Must select a valid SSH configuration from the list", "mustSelectValidSshConfig": "Must select a valid SSH configuration from the list",
"addHost": "Add Host", "addHost": "Add Host",
"editHost": "Edit Host", "editHost": "Edit Host",
"cloneHost": "Clone Host",
"updateHost": "Update Host", "updateHost": "Update Host",
"hostUpdatedSuccessfully": "Host \"{{name}}\" updated successfully!", "hostUpdatedSuccessfully": "Host \"{{name}}\" updated successfully!",
"hostAddedSuccessfully": "Host \"{{name}}\" added successfully!", "hostAddedSuccessfully": "Host \"{{name}}\" added successfully!",

View File

@@ -404,6 +404,7 @@
"mustSelectValidSshConfig": "必须从列表中选择有效的 SSH 配置", "mustSelectValidSshConfig": "必须从列表中选择有效的 SSH 配置",
"addHost": "添加主机", "addHost": "添加主机",
"editHost": "编辑主机", "editHost": "编辑主机",
"cloneHost": "克隆主机",
"deleteHost": "删除主机", "deleteHost": "删除主机",
"authType": "认证类型", "authType": "认证类型",
"passwordAuth": "密码", "passwordAuth": "密码",

View File

@@ -82,7 +82,11 @@ export function HostManager({
{t("hosts.hostViewer")} {t("hosts.hostViewer")}
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="add_host"> <TabsTrigger value="add_host">
{editingHost ? t("hosts.editHost") : t("hosts.addHost")} {editingHost
? editingHost.id
? t("hosts.editHost")
: t("hosts.cloneHost")
: t("hosts.addHost")}
</TabsTrigger> </TabsTrigger>
<div className="h-6 w-px bg-dark-border mx-1"></div> <div className="h-6 w-px bg-dark-border mx-1"></div>
<TabsTrigger value="credentials"> <TabsTrigger value="credentials">

View File

@@ -343,7 +343,7 @@ export function HostManagerEditor({
if (defaultAuthType === "password") { if (defaultAuthType === "password") {
formData.password = cleanedHost.password || ""; formData.password = cleanedHost.password || "";
} else if (defaultAuthType === "key") { } else if (defaultAuthType === "key") {
formData.key = "existing_key"; formData.key = editingHost.id ? "existing_key" : editingHost.key;
formData.keyPassword = cleanedHost.keyPassword || ""; formData.keyPassword = cleanedHost.keyPassword || "";
formData.keyType = (cleanedHost.keyType as any) || "auto"; formData.keyType = (cleanedHost.keyType as any) || "auto";
} else if (defaultAuthType === "credential") { } else if (defaultAuthType === "credential") {
@@ -420,7 +420,7 @@ export function HostManagerEditor({
submitData.keyType = null; submitData.keyType = null;
if (data.authType === "credential") { if (data.authType === "credential") {
if (data.credentialId === "existing_credential") { if (data.credentialId === "existing_credential" && editingHost && editingHost.id) {
delete submitData.credentialId; delete submitData.credentialId;
} else { } else {
submitData.credentialId = data.credentialId; submitData.credentialId = data.credentialId;
@@ -440,7 +440,7 @@ export function HostManagerEditor({
submitData.keyType = data.keyType; submitData.keyType = data.keyType;
} }
if (editingHost) { if (editingHost && editingHost.id) {
const updatedHost = await updateSSHHost(editingHost.id, submitData); const updatedHost = await updateSSHHost(editingHost.id, submitData);
toast.success(t("hosts.hostUpdatedSuccessfully", { name: data.name })); toast.success(t("hosts.hostUpdatedSuccessfully", { name: data.name }));
@@ -1497,7 +1497,7 @@ export function HostManagerEditor({
<footer className="shrink-0 w-full pb-0"> <footer className="shrink-0 w-full pb-0">
<Separator className="p-0.25" /> <Separator className="p-0.25" />
<Button className="translate-y-2" type="submit" variant="outline"> <Button className="translate-y-2" type="submit" variant="outline">
{editingHost ? t("hosts.updateHost") : t("hosts.addHost")} {editingHost ? editingHost.id ? t("hosts.updateHost") : t("hosts.cloneHost") : t("hosts.addHost")}
</Button> </Button>
</footer> </footer>
</form> </form>

View File

@@ -41,6 +41,7 @@ import {
Check, Check,
Pencil, Pencil,
FolderMinus, FolderMinus,
Copy,
} from "lucide-react"; } from "lucide-react";
import type { import type {
SSHHost, SSHHost,
@@ -206,6 +207,14 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
} }
}; };
const handleClone = (host: SSHHost) => {
if(onEditHost) {
const clonedHost = {...host};
delete clonedHost.id;
onEditHost(clonedHost);
}
}
const handleRemoveFromFolder = async (host: SSHHost) => { const handleRemoveFromFolder = async (host: SSHHost) => {
confirmWithToast( confirmWithToast(
t("hosts.confirmRemoveFromFolder", { t("hosts.confirmRemoveFromFolder", {
@@ -1009,6 +1018,24 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
<p>Export host</p> <p>Export host</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="ghost"
onClick={(e) => {
e.stopPropagation();
handleClone(host);
}}
className="h-5 w-5 p-0 text-emerald-500 hover:text-emerald-700 hover:bg-emerald-500/10"
>
<Copy className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Clone host</p>
</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>