Merge branch 'dev-1.10.1' into feature/add-network-graph

This commit is contained in:
Luke Gustafson
2026-01-12 03:05:50 -05:00
committed by GitHub
268 changed files with 156304 additions and 17798 deletions

View File

@@ -1,7 +1,9 @@
import React, { useEffect, useRef, useState, useMemo } from "react";
import { Terminal } from "@/ui/desktop/apps/terminal/Terminal.tsx";
import { Server as ServerView } from "@/ui/desktop/apps/server/Server.tsx";
import { FileManager } from "@/ui/desktop/apps/file-manager/FileManager.tsx";
import { Terminal } from "@/ui/desktop/apps/features/terminal/Terminal.tsx";
import { ServerStats as ServerView } from "@/ui/desktop/apps/features/server-stats/ServerStats.tsx";
import { FileManager } from "@/ui/desktop/apps/features/file-manager/FileManager.tsx";
import { TunnelManager } from "@/ui/desktop/apps/features/tunnel/TunnelManager.tsx";
import { DockerManager } from "@/ui/desktop/apps/features/docker/DockerManager.tsx";
import { NetworkGraphView } from "@/ui/desktop/dashboard/network-graph/NetworkGraphView.tsx";
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
import {
@@ -17,6 +19,7 @@ import {
TERMINAL_THEMES,
DEFAULT_TERMINAL_CONFIG,
} from "@/constants/terminal-themes";
import { useTheme } from "@/components/theme-provider";
import { SSHAuthDialog } from "@/ui/desktop/navigation/SSHAuthDialog.tsx";
interface TabData {
@@ -52,14 +55,23 @@ export function AppView({
removeTab: (id: number) => void;
};
const { state: sidebarState } = useSidebar();
const { theme: appTheme } = useTheme();
const isDarkMode = useMemo(() => {
if (appTheme === "dark") return true;
if (appTheme === "light") return false;
return window.matchMedia("(prefers-color-scheme: dark)").matches;
}, [appTheme]);
const terminalTabs = useMemo(
() =>
tabs.filter(
(tab: TabData) =>
tab.type === "terminal" ||
tab.type === "server" ||
tab.type === "server_stats" ||
tab.type === "file_manager" ||
tab.type === "tunnel" ||
tab.type === "docker" ||
tab.type === "network_graph",
),
[tabs],
@@ -90,13 +102,18 @@ export function AppView({
const splitIds = allSplitScreenTab as number[];
visibleIds.push(currentTab, ...splitIds.filter((i) => i !== currentTab));
}
terminalTabs.forEach((t: TabData) => {
if (visibleIds.includes(t.id)) {
const ref = t.terminalRef?.current;
if (ref?.fit) ref.fit();
if (ref?.notifyResize) ref.notifyResize();
if (ref?.refresh) ref.refresh();
}
const operations = terminalTabs
.filter((t: TabData) => visibleIds.includes(t.id))
.map((t: TabData) => t.terminalRef?.current)
.filter((ref) => ref?.fit);
requestAnimationFrame(() => {
operations.forEach((ref) => {
ref.fit?.();
ref.notifyResize?.();
ref.refresh?.();
});
});
}, [allSplitScreenTab, currentTab, terminalTabs]);
@@ -106,18 +123,14 @@ export function AppView({
cancelAnimationFrame(layoutScheduleRef.current);
layoutScheduleRef.current = requestAnimationFrame(() => {
updatePanelRects();
layoutScheduleRef.current = requestAnimationFrame(() => {
fitActiveAndNotify();
});
fitActiveAndNotify();
});
}, [updatePanelRects, fitActiveAndNotify]);
const hideThenFit = React.useCallback(() => {
requestAnimationFrame(() => {
updatePanelRects();
requestAnimationFrame(() => {
fitActiveAndNotify();
});
fitActiveAndNotify();
});
}, [updatePanelRects, fitActiveAndNotify]);
@@ -212,7 +225,10 @@ export function AppView({
const mainTab = terminalTabs.find((tab: TabData) => tab.id === currentTab);
if (allSplitScreenTab.length === 0 && mainTab) {
const isFileManagerTab = mainTab.type === "file_manager";
const isFileManagerTab =
mainTab.type === "file_manager" ||
mainTab.type === "tunnel" ||
mainTab.type === "docker";
const newStyle = {
position: "absolute" as const,
top: isFileManagerTab ? 0 : 4,
@@ -223,7 +239,6 @@ export function AppView({
display: "block" as const,
pointerEvents: "auto" as const,
opacity: 1,
transition: "opacity 150ms ease-in-out",
};
styles[mainTab.id] = newStyle;
previousStylesRef.current[mainTab.id] = newStyle;
@@ -242,7 +257,6 @@ export function AppView({
display: "block" as const,
pointerEvents: "auto" as const,
opacity: 1,
transition: "opacity 150ms ease-in-out",
};
styles[t.id] = newStyle;
previousStylesRef.current[t.id] = newStyle;
@@ -259,9 +273,14 @@ export function AppView({
const isVisible =
hasStyle || (allSplitScreenTab.length === 0 && t.id === currentTab);
const effectiveVisible = isVisible;
const previousStyle = previousStylesRef.current[t.id];
const isFileManagerTab = t.type === "file_manager";
const isFileManagerTab =
t.type === "file_manager" ||
t.type === "tunnel" ||
t.type === "docker";
const standardStyle = {
position: "absolute" as const,
top: isFileManagerTab ? 0 : 4,
@@ -272,25 +291,40 @@ export function AppView({
const finalStyle: React.CSSProperties = hasStyle
? { ...styles[t.id], overflow: "hidden" }
: ({
...(previousStyle || standardStyle),
opacity: 0,
pointerEvents: "none",
zIndex: 0,
transition: "opacity 150ms ease-in-out",
overflow: "hidden",
} as React.CSSProperties);
const effectiveVisible = isVisible;
: effectiveVisible
? {
...(previousStyle || standardStyle),
opacity: 1,
pointerEvents: "auto",
zIndex: 20,
display: "block",
overflow: "hidden",
}
: ({
...(previousStyle || standardStyle),
opacity: 0,
pointerEvents: "none",
zIndex: 0,
display: "none",
overflow: "hidden",
} as React.CSSProperties);
const isTerminal = t.type === "terminal";
const terminalConfig = {
...DEFAULT_TERMINAL_CONFIG,
...(t.hostConfig as any)?.terminalConfig,
};
const themeColors =
TERMINAL_THEMES[terminalConfig.theme]?.colors ||
TERMINAL_THEMES.termix.colors;
// Auto-switch between termixDark and termixLight based on app theme
let themeColors;
if (terminalConfig.theme === "termix") {
themeColors = isDarkMode
? TERMINAL_THEMES.termixDark.colors
: TERMINAL_THEMES.termixLight.colors;
} else {
themeColors =
TERMINAL_THEMES[terminalConfig.theme]?.colors ||
TERMINAL_THEMES.termixDark.colors;
}
const backgroundColor = themeColors.background;
return (
@@ -298,7 +332,9 @@ export function AppView({
<div
className="absolute inset-0 rounded-md overflow-hidden"
style={{
backgroundColor: isTerminal ? backgroundColor : "#18181b",
backgroundColor: isTerminal
? backgroundColor
: "var(--bg-base)",
}}
>
{t.type === "terminal" ? (
@@ -311,7 +347,7 @@ export function AppView({
splitScreen={allSplitScreenTab.length > 0}
onClose={() => removeTab(t.id)}
/>
) : t.type === "server" ? (
) : t.type === "server_stats" ? (
<ServerView
hostConfig={t.hostConfig}
title={t.title}
@@ -325,6 +361,22 @@ export function AppView({
rightSidebarOpen={rightSidebarOpen}
rightSidebarWidth={rightSidebarWidth}
isStandalone={true}
) : t.type === "tunnel" ? (
<TunnelManager
hostConfig={t.hostConfig}
title={t.title}
isVisible={effectiveVisible}
isTopbarOpen={isTopbarOpen}
embedded
/>
) : t.type === "docker" ? (
<DockerManager
hostConfig={t.hostConfig}
title={t.title}
isVisible={effectiveVisible}
isTopbarOpen={isTopbarOpen}
embedded
onClose={() => removeTab(t.id)}
/>
) : (
<FileManager
@@ -347,7 +399,7 @@ export function AppView({
variant="ghost"
onClick={onClick}
aria-label="Reset split sizes"
className="absolute top-0 right-0 h-[28px] w-[28px] !rounded-none border-l-1 border-b-1 border-dark-border-panel bg-dark-bg-panel hover:bg-dark-bg-panel-hover text-white flex items-center justify-center p-0"
className="absolute top-0 right-0 h-[28px] w-[28px] !rounded-none border-l-1 border-b-1 border-edge-panel bg-surface hover:bg-surface-hover text-foreground flex items-center justify-center p-0"
>
<RefreshCcw className="h-4 w-4" />
</Button>
@@ -367,7 +419,7 @@ export function AppView({
const handleStyle = {
pointerEvents: "auto",
zIndex: 12,
background: "var(--color-dark-border)",
background: "var(--border-base)",
} as React.CSSProperties;
const commonGroupProps: {
onLayout: () => void;
@@ -400,7 +452,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col bg-transparent relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{a.title}
</div>
</div>
@@ -419,7 +471,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col bg-transparent relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{b.title}
<ResetButton onClick={handleReset} />
</div>
@@ -467,7 +519,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{a.title}
</div>
</div>
@@ -486,7 +538,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{b.title}
<ResetButton onClick={handleReset} />
</div>
@@ -508,7 +560,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{c.title}
</div>
</div>
@@ -555,7 +607,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{a.title}
</div>
</div>
@@ -574,7 +626,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{b.title}
<ResetButton onClick={handleReset} />
</div>
@@ -610,7 +662,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{c.title}
</div>
</div>
@@ -629,7 +681,7 @@ export function AppView({
}}
className="h-full w-full flex flex-col relative"
>
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
<div className="bg-surface text-foreground text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-edge-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{d.title}
</div>
</div>
@@ -645,6 +697,8 @@ export function AppView({
const currentTabData = tabs.find((tab: TabData) => tab.id === currentTab);
const isFileManager = currentTabData?.type === "file_manager";
const isTunnel = currentTabData?.type === "tunnel";
const isDocker = currentTabData?.type === "docker";
const isTerminal = currentTabData?.type === "terminal";
const isSplitScreen = allSplitScreenTab.length > 0;
@@ -652,18 +706,25 @@ export function AppView({
...DEFAULT_TERMINAL_CONFIG,
...(currentTabData?.hostConfig as any)?.terminalConfig,
};
const themeColors =
TERMINAL_THEMES[terminalConfig.theme]?.colors ||
TERMINAL_THEMES.termix.colors;
const terminalBackgroundColor = themeColors.background;
let containerThemeColors;
if (terminalConfig.theme === "termix") {
containerThemeColors = isDarkMode
? TERMINAL_THEMES.termixDark.colors
: TERMINAL_THEMES.termixLight.colors;
} else {
containerThemeColors =
TERMINAL_THEMES[terminalConfig.theme]?.colors ||
TERMINAL_THEMES.termixDark.colors;
}
const terminalBackgroundColor = containerThemeColors.background;
const topMarginPx = isTopbarOpen ? 74 : 26;
const leftMarginPx = sidebarState === "collapsed" ? 26 : 8;
const bottomMarginPx = 8;
let containerBackground = "var(--color-dark-bg)";
if (isFileManager && !isSplitScreen) {
containerBackground = "var(--color-dark-bg-darkest)";
let containerBackground = "var(--color-canvas)";
if ((isFileManager || isTunnel || isDocker) && !isSplitScreen) {
containerBackground = "var(--color-deepest)";
} else if (isTerminal) {
containerBackground = terminalBackgroundColor;
}
@@ -671,7 +732,7 @@ export function AppView({
return (
<div
ref={containerRef}
className="border-2 border-dark-border rounded-lg overflow-hidden overflow-x-hidden relative"
className="border-2 border-edge rounded-lg overflow-hidden overflow-x-hidden relative"
style={{
background: containerBackground,
marginLeft: leftMarginPx,