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

@@ -37,6 +37,9 @@ import {
Loader2,
Terminal,
FolderOpen,
Activity,
Container,
ArrowDownUp,
} from "lucide-react";
import { Status } from "@/components/ui/shadcn-io/status";
import { BsLightning } from "react-icons/bs";
@@ -303,14 +306,48 @@ export function Dashboard({
title: item.hostName,
hostConfig: host,
});
} else if (item.type === "server_stats") {
addTab({
type: "server_stats",
title: item.hostName,
hostConfig: host,
});
} else if (item.type === "tunnel") {
addTab({
type: "tunnel",
title: item.hostName,
hostConfig: host,
});
} else if (item.type === "docker") {
addTab({
type: "docker",
title: item.hostName,
hostConfig: host,
});
}
});
};
const handleServerStatClick = (serverId: number, serverName: string) => {
getSSHHosts().then((hosts) => {
const host = hosts.find((h: { id: number }) => h.id === serverId);
if (!host) return;
addTab({
type: "server_stats",
title: serverName,
hostConfig: host,
});
});
};
const handleAddHost = () => {
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
if (sshManagerTab) {
updateTab(sshManagerTab.id, { initialTab: "add_host" });
updateTab(sshManagerTab.id, {
initialTab: "add_host",
hostConfig: undefined,
});
setCurrentTab(sshManagerTab.id);
} else {
const id = addTab({
@@ -325,7 +362,10 @@ export function Dashboard({
const handleAddCredential = () => {
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
if (sshManagerTab) {
updateTab(sshManagerTab.id, { initialTab: "add_credential" });
updateTab(sshManagerTab.id, {
initialTab: "add_credential",
hostConfig: undefined,
});
setCurrentTab(sshManagerTab.id);
} else {
const id = addTab({
@@ -375,7 +415,7 @@ export function Dashboard({
</div>
) : (
<div
className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden flex min-w-0"
className="bg-canvas text-foreground rounded-lg border-2 border-edge overflow-hidden flex min-w-0"
style={{
marginLeft: leftMarginPx,
marginRight: rightSidebarOpen
@@ -390,7 +430,7 @@ export function Dashboard({
>
<div className="flex flex-col relative z-10 w-full h-full min-w-0">
<div className="flex flex-row items-center justify-between w-full px-3 mt-3 min-w-0 flex-wrap gap-2">
<div className="text-2xl text-white font-semibold shrink-0">
<div className="text-2xl text-foreground font-semibold shrink-0">
{t("dashboard.title")}
</div>
<div className="flex flex-row gap-3 flex-wrap min-w-0">
@@ -400,7 +440,7 @@ export function Dashboard({
</p>
</div>
<Button
className="font-semibold shrink-0"
className="font-semibold shrink-0 !bg-canvas"
variant="outline"
onClick={() =>
window.open(
@@ -412,7 +452,7 @@ export function Dashboard({
{t("dashboard.github")}
</Button>
<Button
className="font-semibold shrink-0"
className="font-semibold shrink-0 !bg-canvas"
variant="outline"
onClick={() =>
window.open(
@@ -424,7 +464,7 @@ export function Dashboard({
{t("dashboard.support")}
</Button>
<Button
className="font-semibold shrink-0"
className="font-semibold shrink-0 !bg-canvas"
variant="outline"
onClick={() =>
window.open(
@@ -436,7 +476,7 @@ export function Dashboard({
{t("dashboard.discord")}
</Button>
<Button
className="font-semibold shrink-0"
className="font-semibold shrink-0 !bg-canvas"
variant="outline"
onClick={() =>
window.open("https://github.com/sponsors/LukeGus", "_blank")
@@ -451,20 +491,16 @@ export function Dashboard({
<div className="flex flex-col flex-1 my-5 mx-5 gap-4 min-h-0 min-w-0">
<div className="flex flex-row flex-1 gap-4 min-h-0 min-w-0">
<div className="flex-1 min-w-0 border-2 border-dark-border rounded-md bg-dark-bg-darker flex flex-col overflow-hidden">
<div className="flex flex-col mx-3 my-2 overflow-y-auto overflow-x-hidden">
<div className="flex-1 min-w-0 border-2 border-edge rounded-md bg-elevated flex flex-col overflow-hidden transition-all duration-150 hover:border-primary/20">
<div className="flex flex-col mx-3 my-2 overflow-y-auto overflow-x-hidden thin-scrollbar">
<p className="text-xl font-semibold mb-3 mt-1 flex flex-row items-center">
<Server className="mr-3" />
{t("dashboard.serverOverview")}
</p>
<div className="bg-dark-bg w-full h-auto border-2 border-dark-border rounded-md px-3 py-3">
<div className="bg-canvas w-full h-auto border-2 border-edge rounded-md px-3 py-3">
<div className="flex flex-row items-center justify-between mb-3 min-w-0 gap-2">
<div className="flex flex-row items-center min-w-0">
<History
size={20}
color="#FFFFFF"
className="shrink-0"
/>
<History size={20} className="shrink-0" />
<p className="ml-2 leading-none truncate">
{t("dashboard.version")}
</p>
@@ -477,7 +513,7 @@ export function Dashboard({
<Button
variant="outline"
size="sm"
className={`ml-2 text-sm border-1 border-dark-border ${versionStatus === "up_to_date" ? "text-green-400" : "text-yellow-400"}`}
className={`ml-2 text-sm border-1 border-edge ${versionStatus === "up_to_date" ? "text-green-400" : "text-yellow-400"}`}
>
{versionStatus === "up_to_date"
? t("dashboard.upToDate")
@@ -489,11 +525,7 @@ export function Dashboard({
<div className="flex flex-row items-center justify-between mb-5 min-w-0 gap-2">
<div className="flex flex-row items-center min-w-0">
<Clock
size={20}
color="#FFFFFF"
className="shrink-0"
/>
<Clock size={20} className="shrink-0" />
<p className="ml-2 leading-none truncate">
{t("dashboard.uptime")}
</p>
@@ -508,11 +540,7 @@ export function Dashboard({
<div className="flex flex-row items-center justify-between min-w-0 gap-2">
<div className="flex flex-row items-center min-w-0">
<Database
size={20}
color="#FFFFFF"
className="shrink-0"
/>
<Database size={20} className="shrink-0" />
<p className="ml-2 leading-none truncate">
{t("dashboard.database")}
</p>
@@ -530,13 +558,9 @@ export function Dashboard({
</div>
</div>
<div className="flex flex-col grid grid-cols-2 gap-2 mt-2">
<div className="flex flex-row items-center justify-between bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3 min-w-0 gap-2">
<div className="flex flex-row items-center justify-between bg-canvas w-full h-auto mt-3 border-2 border-edge rounded-md px-3 py-3 min-w-0 gap-2">
<div className="flex flex-row items-center min-w-0">
<Server
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<Server size={16} className="mr-3 shrink-0" />
<p className="m-0 leading-none truncate">
{t("dashboard.totalServers")}
</p>
@@ -545,13 +569,9 @@ export function Dashboard({
{totalServers}
</p>
</div>
<div className="flex flex-row items-center justify-between bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3 min-w-0 gap-2">
<div className="flex flex-row items-center justify-between bg-canvas w-full h-auto mt-3 border-2 border-edge rounded-md px-3 py-3 min-w-0 gap-2">
<div className="flex flex-row items-center min-w-0">
<Network
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<ArrowDownUp size={16} className="mr-3 shrink-0" />
<p className="m-0 leading-none truncate">
{t("dashboard.totalTunnels")}
</p>
@@ -562,13 +582,9 @@ export function Dashboard({
</div>
</div>
<div className="flex flex-col grid grid-cols-2 gap-2 mt-2">
<div className="flex flex-row items-center justify-between bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3 min-w-0 gap-2">
<div className="flex flex-row items-center justify-between bg-canvas w-full h-auto mt-3 border-2 border-edge rounded-md px-3 py-3 min-w-0 gap-2">
<div className="flex flex-row items-center min-w-0">
<Key
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<Key size={16} className="mr-3 shrink-0" />
<p className="m-0 leading-none truncate">
{t("dashboard.totalCredentials")}
</p>
@@ -580,7 +596,7 @@ export function Dashboard({
</div>
</div>
</div>
<div className="flex-1 min-w-0 border-2 border-dark-border rounded-md bg-dark-bg-darker flex flex-col overflow-hidden transition-all duration-150 hover:border-primary/20">
<div className="flex-1 min-w-0 border-2 border-edge rounded-md bg-elevated flex flex-col overflow-hidden transition-all duration-150 hover:border-primary/20">
<div className="flex flex-col mx-3 my-2 flex-1 overflow-hidden">
<div className="flex flex-row items-center justify-between mb-3 mt-1">
<p className="text-xl font-semibold flex flex-row items-center">
@@ -614,6 +630,64 @@ export function Dashboard({
{t("dashboard.reset")}
</Button>
</div>
<Button
variant="outline"
size="sm"
className="border-2 !border-edge h-7 !bg-canvas"
onClick={handleResetActivity}
>
{t("dashboard.reset")}
</Button>
</div>
<div
className={`grid gap-4 grid-cols-3 auto-rows-min overflow-x-hidden thin-scrollbar ${recentActivityLoading ? "overflow-y-hidden" : "overflow-y-auto"}`}
>
{recentActivityLoading ? (
<div className="flex flex-row items-center text-muted-foreground text-sm animate-pulse">
<Loader2 className="animate-spin mr-2" size={16} />
<span>{t("dashboard.loadingRecentActivity")}</span>
</div>
) : recentActivity.length === 0 ? (
<p className="text-muted-foreground text-sm">
{t("dashboard.noRecentActivity")}
</p>
) : (
recentActivity
.filter((item, index, array) => {
if (index === 0) return true;
const prevItem = array[index - 1];
return !(
item.hostId === prevItem.hostId &&
item.type === prevItem.type
);
})
.map((item) => (
<Button
key={item.id}
variant="outline"
className="border-2 !border-edge !bg-canvas min-w-0"
onClick={() => handleActivityClick(item)}
>
{item.type === "terminal" ? (
<Terminal size={20} className="shrink-0" />
) : item.type === "file_manager" ? (
<FolderOpen size={20} className="shrink-0" />
) : item.type === "server_stats" ? (
<Server size={20} className="shrink-0" />
) : item.type === "tunnel" ? (
<ArrowDownUp size={20} className="shrink-0" />
) : item.type === "docker" ? (
<Container size={20} className="shrink-0" />
) : (
<Terminal size={20} className="shrink-0" />
)}
<p className="truncate ml-2 font-semibold">
{item.hostName}
</p>
</Button>
))
)}
</div>
{showNetworkGraph ? (
<NetworkGraphView />
@@ -658,78 +732,130 @@ export function Dashboard({
</div>
</div>
<div className="flex flex-row flex-1 gap-4 min-h-0 min-w-0">
<div className="flex-1 min-w-0 border-2 border-dark-border rounded-md bg-dark-bg-darker flex flex-col overflow-hidden transition-all duration-150 hover:border-primary/20">
<div className="flex flex-col mx-3 my-2 overflow-y-auto overflow-x-hidden">
<div className="flex-1 min-w-0 border-2 border-edge rounded-md bg-elevated flex flex-col overflow-hidden transition-all duration-150 hover:border-primary/20">
<div className="flex flex-col mx-3 my-2 overflow-y-auto overflow-x-hidden thin-scrollbar">
<p className="text-xl font-semibold mb-3 mt-1 flex flex-row items-center">
<FastForward className="mr-3" />
{t("dashboard.quickActions")}
</p>
<div className="grid gap-4 grid-cols-3 auto-rows-min overflow-y-auto overflow-x-hidden">
<div className="grid gap-4 grid-cols-3 auto-rows-min overflow-y-auto overflow-x-hidden thin-scrollbar">
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3 min-w-0"
className="border-2 !border-edge flex flex-col items-center justify-center h-auto p-3 !bg-canvas"
onClick={handleAddHost}
>
<Server
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span className="font-semibold text-sm mt-2">
{t("dashboard.addHost")}
</span>
<div className="flex flex-col items-center w-full max-w-full">
<Server
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span
className="font-semibold text-sm mt-2 text-center block"
style={{
wordWrap: "break-word",
overflowWrap: "break-word",
width: "100%",
maxWidth: "100%",
hyphens: "auto",
display: "block",
whiteSpace: "normal",
}}
>
{t("dashboard.addHost")}
</span>
</div>
</Button>
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3 min-w-0"
className="border-2 !border-edge flex flex-col items-center justify-center h-auto p-3 !bg-canvas"
onClick={handleAddCredential}
>
<Key
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span className="font-semibold text-sm mt-2">
{t("dashboard.addCredential")}
</span>
<div className="flex flex-col items-center w-full max-w-full">
<Key
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span
className="font-semibold text-sm mt-2 text-center block"
style={{
wordWrap: "break-word",
overflowWrap: "break-word",
width: "100%",
maxWidth: "100%",
hyphens: "auto",
display: "block",
whiteSpace: "normal",
}}
>
{t("dashboard.addCredential")}
</span>
</div>
</Button>
{isAdmin && (
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3 min-w-0"
className="border-2 !border-edge flex flex-col items-center justify-center h-auto p-3 !bg-canvas"
onClick={handleOpenAdminSettings}
>
<Settings
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span className="font-semibold text-sm mt-2">
{t("dashboard.adminSettings")}
</span>
<div className="flex flex-col items-center w-full max-w-full">
<Settings
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span
className="font-semibold text-sm mt-2 text-center block"
style={{
wordWrap: "break-word",
overflowWrap: "break-word",
width: "100%",
maxWidth: "100%",
hyphens: "auto",
display: "block",
whiteSpace: "normal",
}}
>
{t("dashboard.adminSettings")}
</span>
</div>
</Button>
)}
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3 min-w-0"
className="border-2 !border-edge flex flex-col items-center justify-center h-auto p-3 !bg-canvas"
onClick={handleOpenUserProfile}
>
<User
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span className="font-semibold text-sm mt-2">
{t("dashboard.userProfile")}
</span>
<div className="flex flex-col items-center w-full max-w-full">
<User
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span
className="font-semibold text-sm mt-2 text-center block"
style={{
wordWrap: "break-word",
overflowWrap: "break-word",
width: "100%",
maxWidth: "100%",
hyphens: "auto",
display: "block",
whiteSpace: "normal",
}}
>
{t("dashboard.userProfile")}
</span>
</div>
</Button>
</div>
</div>
</div>
<div className="flex-1 min-w-0 border-2 border-dark-border rounded-md bg-dark-bg-darker flex flex-col overflow-hidden transition-all duration-150 hover:border-primary/20">
<div className="flex-1 min-w-0 border-2 border-edge rounded-md bg-elevated flex flex-col overflow-hidden transition-all duration-150 hover:border-primary/20">
<div className="flex flex-col mx-3 my-2 flex-1 overflow-hidden">
<p className="text-xl font-semibold mb-3 mt-1 flex flex-row items-center">
<ChartLine className="mr-3" />
{t("dashboard.serverStats")}
</p>
<div
className={`grid gap-4 grid-cols-3 auto-rows-min overflow-x-hidden ${serverStatsLoading ? "overflow-y-hidden" : "overflow-y-auto"}`}
className={`grid gap-4 grid-cols-3 auto-rows-min overflow-x-hidden thin-scrollbar ${serverStatsLoading ? "overflow-y-hidden" : "overflow-y-auto"}`}
>
{serverStatsLoading ? (
<div className="flex flex-row items-center text-muted-foreground text-sm animate-pulse">
@@ -745,7 +871,10 @@ export function Dashboard({
<Button
key={server.id}
variant="outline"
className="border-2 !border-dark-border bg-dark-bg h-auto p-3 min-w-0"
className="border-2 !border-edge bg-canvas h-auto p-3 min-w-0 !bg-canvas"
onClick={() =>
handleServerStatClick(server.id, server.name)
}
>
<div className="flex flex-col w-full">
<div className="flex flex-row items-center mb-2">