mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
feat: convert tmux monitoring into a plugin (B10)
This commit is contained in:
1 parent
79d77678c9
commit
708eedb861
145 files changed
+30308
-4458
No files matched your search
@@ -20,7 +20,6 @@ import {
|
||||
Search,
|
||||
User,
|
||||
KeyRound,
|
||||
Layers, // --- tmux-monitor ---
|
||||
Clock,
|
||||
Folder,
|
||||
Pencil,
|
||||
@@ -57,26 +56,6 @@ interface CommandPaletteProps {
|
||||
onOpenPanel?: (view: string) => void;
|
||||
}
|
||||
|
||||
/** Core per-host tools. Plugins add theirs as host actions. */
|
||||
function getSshActions(host: Host): {
|
||||
type: TabType;
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
}[] {
|
||||
return [
|
||||
// --- tmux-monitor --- opt-in per host, off by default
|
||||
host.enableTmuxMonitor && {
|
||||
type: "tmux_monitor",
|
||||
icon: Layers,
|
||||
label: "Tmux Monitor",
|
||||
},
|
||||
].filter(Boolean) as {
|
||||
type: TabType;
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function CommandPalette({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
@@ -535,24 +514,6 @@ export function CommandPalette({
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
{host.enableSsh &&
|
||||
getSshActions(host).map(
|
||||
({ type, icon: Icon, label }) => (
|
||||
<button
|
||||
key={type}
|
||||
title={label}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleAction(() =>
|
||||
onOpenTab(type, host.name),
|
||||
);
|
||||
}}
|
||||
className="flex items-center justify-center size-7 rounded text-muted-foreground/50 hover:text-foreground hover:bg-muted-foreground/10 transition-colors"
|
||||
>
|
||||
<Icon className="size-3.5" />
|
||||
</button>
|
||||
),
|
||||
)}
|
||||
{hostActionsFor(hostActions, host).map((action) => {
|
||||
const Icon = action.icon;
|
||||
const label =
|
||||
|
||||
@@ -146,9 +146,12 @@ export function TabProvider({ children }: TabProviderProps) {
|
||||
|
||||
const addTab = useCallback(
|
||||
(tabData: Omit<Tab, "id">): number => {
|
||||
// --- tmux-monitor --- (tmux_monitor is a singleton tab like ssh_manager:
|
||||
// re-opening focuses the existing tab instead of adding a duplicate)
|
||||
if (tabData.type === "ssh_manager" || tabData.type === "tmux_monitor") {
|
||||
// A singleton tab: re-opening focuses the existing tab instead of
|
||||
// adding a duplicate.
|
||||
if (
|
||||
tabData.type === "ssh_manager" ||
|
||||
getTabType(tabData.type)?.singleton
|
||||
) {
|
||||
const existingTab = tabs.find((t) => t.type === tabData.type);
|
||||
if (existingTab) {
|
||||
setTabs((prev) =>
|
||||
@@ -179,11 +182,12 @@ export function TabProvider({ children }: TabProviderProps) {
|
||||
const isCommandTarget = !!getTabType(tabData.type)?.commandTarget;
|
||||
const needsUniqueTitle =
|
||||
isCommandTarget || tabData.type === "file_manager";
|
||||
const singletonTitleKey = getTabType(tabData.type)?.singleton
|
||||
? getTabType(tabData.type)?.titleKey
|
||||
: undefined;
|
||||
const effectiveTitle = needsUniqueTitle
|
||||
? computeUniqueTitle(tabData.type, tabData.title)
|
||||
: tabData.type === "tmux_monitor" // --- tmux-monitor ---
|
||||
? tabData.title || t("nav.tmuxMonitor")
|
||||
: tabData.title || "";
|
||||
: tabData.title || (singletonTitleKey ? t(singletonTitleKey) : "");
|
||||
const newTab: Tab = {
|
||||
...tabData,
|
||||
id,
|
||||
@@ -204,7 +208,7 @@ export function TabProvider({ children }: TabProviderProps) {
|
||||
setAllSplitScreenTab((prev) => prev.filter((tid) => tid !== id));
|
||||
return id;
|
||||
},
|
||||
[computeUniqueTitle, tabs, t], // --- tmux-monitor --- (added t)
|
||||
[computeUniqueTitle, tabs, t],
|
||||
);
|
||||
|
||||
const pendingCurrentTabRef = useRef<number | null>(null);
|
||||
|
||||
@@ -19,7 +19,6 @@ import { getTabType } from "./tab-registry";
|
||||
* not a meaningful part of an arrangement.
|
||||
*/
|
||||
const CORE_CAPTURABLE = new Set([
|
||||
"tmux_monitor",
|
||||
"serial",
|
||||
"homepage",
|
||||
"termix-id",
|
||||
@@ -30,7 +29,6 @@ const CORE_CAPTURABLE = new Set([
|
||||
|
||||
/** Core types reopened as singletons, with an optional preselected host. */
|
||||
const CORE_SINGLETON = new Set([
|
||||
"tmux_monitor",
|
||||
"homepage",
|
||||
"termix-id",
|
||||
"session-logs",
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Settings,
|
||||
Usb,
|
||||
User,
|
||||
Layers, // --- tmux-monitor ---
|
||||
Fingerprint,
|
||||
Hammer,
|
||||
ScrollText,
|
||||
@@ -37,11 +36,6 @@ const CollabRoomTab = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const loadTmuxMonitor = () =>
|
||||
import("@/features/tmux-monitor/TmuxMonitor").then((m) => ({
|
||||
default: m.TmuxMonitor,
|
||||
}));
|
||||
const TmuxMonitor = lazy(loadTmuxMonitor);
|
||||
const DashboardTab = lazy(() =>
|
||||
import("@/dashboard/DashboardTab").then((m) => ({
|
||||
default: m.DashboardTab,
|
||||
@@ -73,13 +67,9 @@ const MacrosPanel = lazy(() =>
|
||||
const SshToolsPanel = lazy(() =>
|
||||
import("@/sidebar/SshToolsPanel").then((m) => ({ default: m.SshToolsPanel })),
|
||||
);
|
||||
const tabSurfaceLoaders: Partial<Record<TabType, () => Promise<unknown>>> = {
|
||||
tmux_monitor: loadTmuxMonitor,
|
||||
};
|
||||
|
||||
/** Download a likely next tab without starting a connection or mounting UI. */
|
||||
export function preloadTabSurface(type: TabType): void {
|
||||
const loader = tabSurfaceLoaders[type] ?? getTabType(type)?.preload;
|
||||
const loader = getTabType(type)?.preload;
|
||||
if (loader) runAdaptiveBackgroundTask("module", `tab:${type}`, loader);
|
||||
}
|
||||
|
||||
@@ -144,9 +134,6 @@ export function tabIcon(type: TabType) {
|
||||
return <User className="size-3.5" />;
|
||||
case "admin-settings":
|
||||
return <Settings className="size-3.5" />;
|
||||
// --- tmux-monitor ---
|
||||
case "tmux_monitor":
|
||||
return <Layers className="size-3.5" />;
|
||||
case "serial":
|
||||
return <Usb className="size-3.5" />;
|
||||
case "homepage":
|
||||
@@ -239,7 +226,6 @@ export interface TabRenderContext {
|
||||
|
||||
export function renderTabContent(tab: Tab, context: TabRenderContext) {
|
||||
const { shell, isVisible = true, isFocusedPane = true, panelProps } = context;
|
||||
const { host } = tab;
|
||||
|
||||
switch (tab.type) {
|
||||
case "dashboard":
|
||||
@@ -251,15 +237,6 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) {
|
||||
/>,
|
||||
);
|
||||
|
||||
// --- tmux-monitor ---
|
||||
case "tmux_monitor":
|
||||
return withTabSuspense(
|
||||
<TmuxMonitor
|
||||
initialHostId={host ? parseInt(host.id, 10) : undefined}
|
||||
isVisible={isVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
case "serial":
|
||||
if (!tab.serialConfig)
|
||||
return <EmptyState icon={Usb} messageKey="serial.notSupportedTitle" />;
|
||||
|
||||
Reference in new issue
Block a user