mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
feat: convert file manager into a plugin
This commit is contained in:
1 parent
b76da9ab52
commit
c85c1fdb86
259 files changed
+54155
-16791
No files matched your search
@@ -20,7 +20,6 @@ export type CoreRailView =
|
||||
| "hosts"
|
||||
| "credentials"
|
||||
| "port-forwarding"
|
||||
| "sftp"
|
||||
| "termix-id"
|
||||
| "quick-connect"
|
||||
| "serial"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in new issue
Block a user