feat: convert file manager into a plugin

This commit is contained in:
LukeGus committed 2026-09-23 16:27:29 -05:00
1 parent b76da9ab52
commit c85c1fdb86
259 files changed
+54155 -16791

No files matched your search

-1
View File
@@ -20,7 +20,6 @@ export type CoreRailView =
| "hosts"
| "credentials"
| "port-forwarding"
| "sftp"
| "termix-id"
| "quick-connect"
| "serial"
-5
View File
@@ -80,7 +80,6 @@ import {
type HostProtocols,
} from "./HostEditorData";
import { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext";
import { HostFilesTab } from "./HostEditorFilesTab";
import { HostEditorGeneralTab } from "./HostEditorGeneralTab";
import { canEditHost } from "./host-permissions";
import { VaultProfileManager } from "./VaultProfileManager";
@@ -2517,10 +2516,6 @@ export function HostEditor({
</>
)}
{activeTab === "files" && (
<HostFilesTab form={form} setField={setField} />
)}
{(() => {
const section = getHostEditorSection(activeTab);
if (!section) return null;
-61
View File
@@ -1,61 +0,0 @@
import { useTranslation } from "react-i18next";
import { FolderSearch } from "lucide-react";
import { Input } from "@/components/input";
import { SectionCard, SettingRow, FakeSwitch } from "@/components/section-card";
import type { HostEditorForm } from "./HostEditorData";
type SetHostField = <K extends keyof HostEditorForm>(
key: K,
value: HostEditorForm[K],
) => void;
export function HostFilesTab({
form,
setField,
}: {
form: HostEditorForm;
setField: SetHostField;
}) {
const { t } = useTranslation();
return (
<SectionCard
title={t("hosts.fileManager")}
icon={<FolderSearch className="size-3.5" />}
>
<div className="flex flex-col gap-4 py-3">
<SettingRow
label={t("hosts.enableFileManagerMonitor")}
description={t("hosts.enableFileManagerMonitorDesc")}
>
<FakeSwitch
checked={form.enableFileManager}
onChange={(v) => setField("enableFileManager", v)}
/>
</SettingRow>
<SettingRow
label={t("hosts.scpLegacyLabel")}
description={t("hosts.scpLegacyDesc")}
>
<FakeSwitch
checked={form.scpLegacy}
onChange={(v) => setField("scpLegacy", v)}
/>
</SettingRow>
<div className="flex flex-col gap-1.5">
<label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
{t("hosts.defaultPathLabel")}
</label>
<Input
placeholder="/"
value={form.defaultPath}
onChange={(e) => setField("defaultPath", e.target.value)}
/>
<span className="text-[10px] text-muted-foreground">
{t("hosts.fileManagerPathHint")}
</span>
</div>
</div>
</SectionCard>
);
}
+2 -10
View File
@@ -1,7 +1,6 @@
/* eslint-disable react-refresh/only-export-components */
import { useEffect, useRef, type ComponentType, type ReactNode } from "react";
import {
Folder,
KeyRound,
Network,
Settings,
@@ -11,8 +10,7 @@ import {
import { byOrderThenId, createRegistry } from "@/lib/registry";
/** Core host editor tabs. Plugins add theirs through registerHostEditorSection. */
export type CoreHostTabId =
"general" | "ssh" | "terminal" | "tunnels" | "files";
export type CoreHostTabId = "general" | "ssh" | "terminal" | "tunnels";
export type HostTabId = CoreHostTabId | (string & {});
export type CredentialTabId = "general" | "auth";
@@ -68,7 +66,7 @@ export const hostEditorSectionList = sections.list;
export const useHostEditorSections = sections.useList;
export const resetHostEditorSections = sections.reset;
const CORE_SSH_GROUP = new Set<string>(["ssh", "terminal", "tunnels", "files"]);
const CORE_SSH_GROUP = new Set<string>(["ssh", "terminal", "tunnels"]);
/** Whether a tab lives in the SSH group's second strip. */
export function isSshGroupTab(id: string): boolean {
@@ -167,12 +165,6 @@ export function makeHostSshSubTabs(
icon: <Network className="size-3" />,
order: 20,
},
{
id: "files",
label: t("hosts.tabFiles"),
icon: <Folder className="size-3" />,
order: 60,
},
],
"ssh",
t,
+34 -6
View File
@@ -38,12 +38,6 @@ import { shouldForceLocalPreferenceStorage } from "@/settings/remote-sync-state"
import { C2STunnelPresetManager } from "@/user/C2STunnelPresetManager";
import { Button } from "@/components/button";
import { Input } from "@/components/input";
import {
MAX_TRANSFER_CONCURRENCY,
TRANSFER_CONCURRENCY_STORAGE_KEY,
getTransferConcurrency,
setTransferConcurrency,
} from "@/features/file-manager/local-transfer-utils";
import { VersionBadge } from "@/components/version-badge";
import {
Dialog,
@@ -213,6 +207,40 @@ function apiErrorMessage(error: unknown, fallback: string) {
return (error as ApiErrorLike).response?.data?.error || fallback;
}
// Local transfer concurrency is the file-manager plugin's own setting; this
// panel just offers the control, using the same storage key the plugin reads.
const TRANSFER_CONCURRENCY_STORAGE_KEY =
"termix:file-manager:transfer-concurrency";
const DEFAULT_TRANSFER_CONCURRENCY = 4;
const MAX_TRANSFER_CONCURRENCY = 8;
function clampTransferConcurrency(value: unknown): number {
const n = Math.floor(Number(value));
if (!Number.isFinite(n)) return DEFAULT_TRANSFER_CONCURRENCY;
return Math.min(MAX_TRANSFER_CONCURRENCY, Math.max(1, n));
}
function getTransferConcurrency(): number {
try {
const raw = localStorage.getItem(TRANSFER_CONCURRENCY_STORAGE_KEY);
return raw === null
? DEFAULT_TRANSFER_CONCURRENCY
: clampTransferConcurrency(raw);
} catch {
return DEFAULT_TRANSFER_CONCURRENCY;
}
}
function setTransferConcurrency(value: number): number {
const clamped = clampTransferConcurrency(value);
try {
localStorage.setItem(TRANSFER_CONCURRENCY_STORAGE_KEY, String(clamped));
} catch {
// storage unavailable
}
return clamped;
}
type CreatedProfileApiKey = {
id: string;
name: string;
-7
View File
@@ -1,5 +1,4 @@
import {
ArrowLeftRight,
Braces,
Clock,
Fingerprint,
@@ -125,12 +124,6 @@ export const RAIL_ITEMS: RailItemDef[] = [
promotable: true,
rightDockable: true,
},
{
id: "sftp",
icon: ArrowLeftRight,
labelKey: "nav.sftp",
separatorAfter: true,
},
{
id: "macros",
icon: Braces,
-22
View File
@@ -8,7 +8,6 @@ import {
Copy,
CopyPlus,
Cpu,
FolderSearch,
GripVertical,
Key,
KeyRound,
@@ -126,13 +125,6 @@ export function getSshActions(host: Host): {
order: number;
}[] {
return [
host.enableSsh &&
host.enableFileManager && {
type: "files" as TabType,
icon: FolderSearch,
label: "Files",
order: 20,
},
host.enableSsh &&
host.enableTunnel && {
type: "tunnel" as TabType,
@@ -814,20 +806,6 @@ export function HostItem({
{t("hosts.copyLink")}
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{host.enableSsh && host.enableFileManager && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
writeClipboardText(
`${window.location.origin}?view=file-manager&hostId=${host.id}`,
);
toast.success(t("hosts.fileManagerUrlCopied"));
}}
>
<FolderSearch className="size-3.5 mr-2" />
{t("hosts.copyFileManagerUrlAction")}
</DropdownMenuItem>
)}
{host.enableSsh && host.enableTunnel && (
<DropdownMenuItem
onClick={(e) => {