chore: updating/match themes and split admin settings

This commit is contained in:
LukeGus
2025-12-24 02:40:39 -06:00
parent 07895257fd
commit 053017a502
36 changed files with 1780 additions and 1386 deletions

View File

@@ -239,7 +239,7 @@ export function CommandPalette({
>
<Command
className={cn(
"w-3/4 max-w-2xl max-h-[60vh] rounded-lg border-2 border-edge shadow-md flex flex-col",
"w-3/4 max-w-2xl max-h-[60vh] rounded-lg border-2 border-edge shadow-md flex flex-col bg-elevated",
"transition-all duration-200 ease-out",
!isOpen && "scale-95 opacity-0",
)}

View File

@@ -484,10 +484,16 @@ export function CredentialEditor({
className="w-full"
>
<TabsList className="bg-button border border-edge-medium">
<TabsTrigger value="general" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
<TabsTrigger
value="general"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
>
{t("credentials.general")}
</TabsTrigger>
<TabsTrigger value="authentication" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
<TabsTrigger
value="authentication"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
>
{t("credentials.authentication")}
</TabsTrigger>
</TabsList>
@@ -693,10 +699,16 @@ export function CredentialEditor({
className="flex-1 flex flex-col h-full min-h-0"
>
<TabsList className="bg-button border border-edge-medium">
<TabsTrigger value="password" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
<TabsTrigger
value="password"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
>
{t("credentials.password")}
</TabsTrigger>
<TabsTrigger value="key" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
<TabsTrigger
value="key"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
>
{t("credentials.key")}
</TabsTrigger>
</TabsList>
@@ -719,7 +731,7 @@ export function CredentialEditor({
</TabsContent>
<TabsContent value="key">
<div className="mt-2">
<div className="mb-3 p-3 bg-muted/20 border border-muted rounded-md">
<div className="mb-3 p-3 border border-muted rounded-md">
<FormLabel className="mb-2 font-bold block">
{t("credentials.generateKeyPair")}
</FormLabel>
@@ -954,7 +966,8 @@ export function CredentialEditor({
".cm-scroller": {
overflow: "auto",
scrollbarWidth: "thin",
scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)",
scrollbarColor:
"var(--scrollbar-thumb) var(--scrollbar-track)",
},
}),
]}
@@ -1116,7 +1129,8 @@ export function CredentialEditor({
".cm-scroller": {
overflow: "auto",
scrollbarWidth: "thin",
scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)",
scrollbarColor:
"var(--scrollbar-thumb) var(--scrollbar-track)",
},
}),
]}

View File

@@ -407,7 +407,7 @@ export function Dashboard({
</p>
</div>
<Button
className="font-semibold shrink-0"
className="font-semibold shrink-0 !bg-canvas"
variant="outline"
onClick={() =>
window.open(
@@ -419,7 +419,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(
@@ -431,7 +431,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(
@@ -443,7 +443,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")
@@ -467,10 +467,7 @@ export function Dashboard({
<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}
className="shrink-0"
/>
<History size={20} className="shrink-0" />
<p className="ml-2 leading-none truncate">
{t("dashboard.version")}
</p>
@@ -495,10 +492,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}
className="shrink-0"
/>
<Clock size={20} className="shrink-0" />
<p className="ml-2 leading-none truncate">
{t("dashboard.uptime")}
</p>
@@ -513,10 +507,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}
className="shrink-0"
/>
<Database size={20} className="shrink-0" />
<p className="ml-2 leading-none truncate">
{t("dashboard.database")}
</p>
@@ -536,10 +527,7 @@ export function Dashboard({
<div className="flex flex-col grid grid-cols-2 gap-2 mt-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}
className="mr-3 shrink-0"
/>
<Server size={16} className="mr-3 shrink-0" />
<p className="m-0 leading-none truncate">
{t("dashboard.totalServers")}
</p>
@@ -550,10 +538,7 @@ export function Dashboard({
</div>
<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}
className="mr-3 shrink-0"
/>
<Network size={16} className="mr-3 shrink-0" />
<p className="m-0 leading-none truncate">
{t("dashboard.totalTunnels")}
</p>
@@ -566,10 +551,7 @@ export function Dashboard({
<div className="flex flex-col grid grid-cols-2 gap-2 mt-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}
className="mr-3 shrink-0"
/>
<Key size={16} className="mr-3 shrink-0" />
<p className="m-0 leading-none truncate">
{t("dashboard.totalCredentials")}
</p>
@@ -591,7 +573,7 @@ export function Dashboard({
<Button
variant="outline"
size="sm"
className="border-2 !border-edge h-7"
className="border-2 !border-edge h-7 !bg-canvas"
onClick={handleResetActivity}
>
{t("dashboard.reset")}
@@ -626,7 +608,7 @@ export function Dashboard({
<Button
key={item.id}
variant="outline"
className="border-2 !border-edge bg-canvas min-w-0"
className="border-2 !border-edge !bg-canvas min-w-0"
onClick={() => handleActivityClick(item)}
>
{item.type === "terminal" ? (
@@ -654,7 +636,7 @@ export function Dashboard({
<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-edge 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 min-w-0 !bg-canvas"
onClick={handleAddHost}
>
<Server
@@ -667,7 +649,7 @@ export function Dashboard({
</Button>
<Button
variant="outline"
className="border-2 !border-edge 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 min-w-0 !bg-canvas"
onClick={handleAddCredential}
>
<Key
@@ -681,7 +663,7 @@ export function Dashboard({
{isAdmin && (
<Button
variant="outline"
className="border-2 !border-edge 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 min-w-0 !bg-canvas"
onClick={handleOpenAdminSettings}
>
<Settings
@@ -695,7 +677,7 @@ export function Dashboard({
)}
<Button
variant="outline"
className="border-2 !border-edge 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 min-w-0 !bg-canvas"
onClick={handleOpenUserProfile}
>
<User
@@ -732,7 +714,7 @@ export function Dashboard({
<Button
key={server.id}
variant="outline"
className="border-2 !border-edge bg-canvas 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)
}

View File

@@ -230,8 +230,8 @@ export function DockerManager({
};
const containerClass = embedded
? "h-full w-full text-white overflow-hidden bg-transparent"
: "bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden";
? "h-full w-full text-foreground overflow-hidden bg-transparent"
: "bg-canvas text-foreground rounded-lg border-2 border-edge overflow-hidden";
// Check if Docker is enabled
if (!currentHostConfig?.enableDocker) {
@@ -252,9 +252,7 @@ export function DockerManager({
<div className="flex-1 overflow-hidden min-h-0 p-4">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
{t("docker.notEnabled")}
</AlertDescription>
<AlertDescription>{t("docker.notEnabled")}</AlertDescription>
</Alert>
</div>
</div>
@@ -281,7 +279,7 @@ export function DockerManager({
<div className="flex-1 overflow-hidden min-h-0 p-4 flex items-center justify-center">
<div className="text-center">
<SimpleLoader size="lg" />
<p className="text-gray-400 mt-4">
<p className="text-muted-foreground mt-4">
{isValidating
? t("docker.validating")
: t("docker.connectingToHost")}
@@ -338,7 +336,7 @@ export function DockerManager({
{currentHostConfig?.folder} / {title}
</h1>
{dockerValidation?.version && (
<p className="text-xs text-gray-400">
<p className="text-xs text-muted-foreground">
{t("docker.version", { version: dockerValidation.version })}
</p>
)}
@@ -363,7 +361,7 @@ export function DockerManager({
/>
) : (
<div className="text-center py-8">
<p className="text-gray-400">No session available</p>
<p className="text-muted-foreground">No session available</p>
</div>
)}
</div>
@@ -377,7 +375,7 @@ export function DockerManager({
/>
) : (
<div className="text-center py-8">
<p className="text-gray-400">
<p className="text-muted-foreground">
Select a container to view details
</p>
</div>

View File

@@ -93,9 +93,18 @@ export function ConsoleTerminal({
terminal.options.cursorBlink = true;
terminal.options.fontSize = 14;
terminal.options.fontFamily = "monospace";
// Get theme colors from CSS variables
const backgroundColor = getComputedStyle(document.documentElement)
.getPropertyValue("--bg-elevated")
.trim();
const foregroundColor = getComputedStyle(document.documentElement)
.getPropertyValue("--foreground")
.trim();
terminal.options.theme = {
background: "#18181b",
foreground: "#c9d1d9",
background: backgroundColor || "#ffffff",
foreground: foregroundColor || "#09090b",
};
setTimeout(() => {
@@ -152,12 +161,11 @@ export function ConsoleTerminal({
if (terminal) {
try {
terminal.clear();
terminal.write(`${t("docker.disconnectedFromContainer")}\r\n`);
} catch (error) {
// Terminal might be disposed
}
}
}, [terminal]);
}, [terminal, t]);
const connect = React.useCallback(() => {
if (!terminal || containerState !== "running") {
@@ -216,7 +224,15 @@ export function ConsoleTerminal({
case "connected":
setIsConnected(true);
setIsConnecting(false);
toast.success(t("docker.connectedTo", { containerName }));
// Check if shell was changed due to unavailability
if (msg.data?.shellChanged) {
toast.warning(
`Shell "${msg.data.requestedShell}" not available. Using "${msg.data.shell}" instead.`,
);
} else {
toast.success(t("docker.connectedTo", { containerName }));
}
// Fit terminal and send resize to ensure correct dimensions
setTimeout(() => {
@@ -251,7 +267,9 @@ export function ConsoleTerminal({
case "error":
setIsConnecting(false);
toast.error(msg.message || t("docker.consoleError"));
terminal.write(`\r\n\x1b[1;31m${t("docker.errorMessage", { message: msg.message })}\x1b[0m\r\n`);
terminal.write(
`\r\n\x1b[1;31m${t("docker.errorMessage", { message: msg.message })}\x1b[0m\r\n`,
);
break;
}
} catch (error) {
@@ -341,9 +359,11 @@ export function ConsoleTerminal({
return (
<div className="flex items-center justify-center h-full">
<div className="text-center space-y-2">
<TerminalIcon className="h-12 w-12 text-gray-600 mx-auto" />
<p className="text-gray-400 text-lg">{t("docker.containerNotRunning")}</p>
<p className="text-gray-500 text-sm">
<TerminalIcon className="h-12 w-12 text-muted-foreground/50 mx-auto" />
<p className="text-muted-foreground text-lg">
{t("docker.containerNotRunning")}
</p>
<p className="text-muted-foreground text-sm">
{t("docker.startContainerToAccess")}
</p>
</div>
@@ -359,7 +379,9 @@ export function ConsoleTerminal({
<div className="flex flex-col sm:flex-row gap-2 items-center sm:items-center">
<div className="flex items-center gap-2 flex-1">
<TerminalIcon className="h-5 w-5" />
<span className="text-base font-medium">{t("docker.console")}</span>
<span className="text-base font-medium">
{t("docker.console")}
</span>
</div>
<Select
value={selectedShell}
@@ -423,9 +445,11 @@ export function ConsoleTerminal({
{!isConnected && !isConnecting && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center space-y-2">
<TerminalIcon className="h-12 w-12 text-gray-600 mx-auto" />
<p className="text-gray-400">{t("docker.notConnected")}</p>
<p className="text-gray-500 text-sm">
<TerminalIcon className="h-12 w-12 text-muted-foreground/50 mx-auto" />
<p className="text-muted-foreground">
{t("docker.notConnected")}
</p>
<p className="text-muted-foreground text-sm">
{t("docker.clickToConnect")}
</p>
</div>
@@ -437,7 +461,7 @@ export function ConsoleTerminal({
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center">
<SimpleLoader size="lg" />
<p className="text-gray-400 mt-4">
<p className="text-muted-foreground mt-4">
{t("docker.connectingTo", { containerName })}
</p>
</div>

View File

@@ -16,6 +16,7 @@ import {
PlayCircle,
} from "lucide-react";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import type { DockerContainer } from "@/types/index.js";
import {
startDockerContainer,
@@ -57,6 +58,7 @@ export function ContainerCard({
isSelected = false,
onRefresh,
}: ContainerCardProps): React.ReactElement {
const { t } = useTranslation();
const [isStarting, setIsStarting] = React.useState(false);
const [isStopping, setIsStopping] = React.useState(false);
const [isRestarting, setIsRestarting] = React.useState(false);
@@ -102,10 +104,10 @@ export function ContainerCard({
badge: "bg-purple-500/20 text-purple-300 border-purple-500/30",
},
dead: {
bg: "bg-gray-500/10",
border: "border-gray-500/20",
text: "text-gray-400",
badge: "bg-gray-500/20 text-gray-300 border-gray-500/30",
bg: "bg-muted/10",
border: "border-muted/20",
text: "text-muted-foreground",
badge: "bg-muted/20 text-muted-foreground border-muted/30",
},
};
@@ -260,26 +262,32 @@ export function ContainerCard({
<CardContent className="space-y-3 px-4 pb-3">
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<span className="text-gray-400 min-w-[50px] text-xs">{t("docker.image")}</span>
<span className="truncate text-gray-200 text-xs">
<span className="text-muted-foreground min-w-[50px] text-xs">
{t("docker.image")}
</span>
<span className="truncate text-foreground text-xs">
{container.image}
</span>
</div>
<div className="flex items-center gap-2">
<span className="text-gray-400 min-w-[50px] text-xs">{t("docker.idLabel")}</span>
<span className="font-mono text-xs text-gray-200">
<span className="text-muted-foreground min-w-[50px] text-xs">
{t("docker.idLabel")}
</span>
<span className="font-mono text-xs text-foreground">
{container.id.substring(0, 12)}
</span>
</div>
<div className="flex items-start gap-2">
<span className="text-gray-400 min-w-[50px] text-xs shrink-0">{t("docker.ports")}</span>
<span className="text-muted-foreground min-w-[50px] text-xs shrink-0">
{t("docker.ports")}
</span>
<div className="flex flex-wrap gap-1">
{portsList.length > 0 ? (
portsList.map((port, idx) => (
<Badge
key={idx}
variant="outline"
className="text-xs font-mono bg-gray-500/10 text-gray-400 border-gray-500/30"
className="text-xs font-mono bg-muted/10 text-muted-foreground border-muted/30"
>
{port}
</Badge>
@@ -287,7 +295,7 @@ export function ContainerCard({
) : (
<Badge
variant="outline"
className="text-xs bg-gray-500/10 text-gray-400 border-gray-500/30"
className="text-xs bg-muted/10 text-muted-foreground border-muted/30"
>
{t("docker.noPorts")}
</Badge>
@@ -295,8 +303,10 @@ export function ContainerCard({
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-gray-400 min-w-[50px] text-xs">{t("docker.created")}</span>
<span className="text-gray-200 text-xs">
<span className="text-muted-foreground min-w-[50px] text-xs">
{t("docker.created")}
</span>
<span className="text-foreground text-xs">
{formatCreatedDate(container.created)}
</span>
</div>
@@ -390,7 +400,8 @@ export function ContainerCard({
)}
</Button>
</TooltipTrigger>
<TooltipContent>{t("docker.restart")}</TooltipContent> </Tooltip>
<TooltipContent>{t("docker.restart")}</TooltipContent>{" "}
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
@@ -407,7 +418,8 @@ export function ContainerCard({
<Trash2 className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{t("docker.remove")}</TooltipContent> </Tooltip>
<TooltipContent>{t("docker.remove")}</TooltipContent>{" "}
</Tooltip>
</TooltipProvider>
</div>
</CardContent>
@@ -431,7 +443,9 @@ export function ContainerCard({
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isRemoving}>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogCancel disabled={isRemoving}>
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction
onClick={(e) => {
e.preventDefault();

View File

@@ -8,6 +8,7 @@ import {
TabsTrigger,
} from "@/components/ui/tabs.tsx";
import { ArrowLeft } from "lucide-react";
import { useTranslation } from "react-i18next";
import type { DockerContainer, SSHHost } from "@/types/index.js";
import { LogViewer } from "./LogViewer.tsx";
import { ContainerStats } from "./ContainerStats.tsx";
@@ -28,6 +29,7 @@ export function ContainerDetail({
hostConfig,
onBack,
}: ContainerDetailProps): React.ReactElement {
const { t } = useTranslation();
const [activeTab, setActiveTab] = React.useState("logs");
const container = containers.find((c) => c.id === containerId);
@@ -36,7 +38,9 @@ export function ContainerDetail({
return (
<div className="flex items-center justify-center h-full">
<div className="text-center space-y-2">
<p className="text-gray-400 text-lg">{t("docker.containerNotFound")}</p>
<p className="text-muted-foreground text-lg">
{t("docker.containerNotFound")}
</p>
<Button onClick={onBack} variant="outline">
<ArrowLeft className="h-4 w-4 mr-2" />
{t("docker.backToList")}
@@ -56,7 +60,9 @@ export function ContainerDetail({
</Button>
<div className="min-w-0 flex-1">
<h2 className="font-bold text-lg truncate">{container.name}</h2>
<p className="text-sm text-gray-400 truncate">{container.image}</p>
<p className="text-sm text-muted-foreground truncate">
{container.image}
</p>
</div>
</div>
<Separator className="p-0.25 w-full" />
@@ -72,7 +78,9 @@ export function ContainerDetail({
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="logs">{t("docker.logs")}</TabsTrigger>
<TabsTrigger value="stats">{t("docker.stats")}</TabsTrigger>
<TabsTrigger value="console">{t("docker.consoleTab")}</TabsTrigger>
<TabsTrigger value="console">
{t("docker.consoleTab")}
</TabsTrigger>
</TabsList>
</div>

View File

@@ -8,6 +8,7 @@ import {
SelectValue,
} from "@/components/ui/select.tsx";
import { Search, Filter } from "lucide-react";
import { useTranslation } from "react-i18next";
import type { DockerContainer } from "@/types/index.js";
import { ContainerCard } from "./ContainerCard.tsx";
@@ -26,6 +27,7 @@ export function ContainerList({
selectedContainerId = null,
onRefresh,
}: ContainerListProps): React.ReactElement {
const { t } = useTranslation();
const [searchQuery, setSearchQuery] = React.useState("");
const [statusFilter, setStatusFilter] = React.useState<string>("all");
@@ -55,8 +57,12 @@ export function ContainerList({
return (
<div className="flex items-center justify-center h-full">
<div className="text-center space-y-2">
<p className="text-gray-400 text-lg">{t("docker.noContainersFound")}</p>
<p className="text-gray-500 text-sm">{t("docker.noContainersFoundHint")}</p>
<p className="text-muted-foreground text-lg">
{t("docker.noContainersFound")}
</p>
<p className="text-muted-foreground text-sm">
{t("docker.noContainersFoundHint")}
</p>
</div>
</div>
);
@@ -67,7 +73,7 @@ export function ContainerList({
{/* Search and Filter Bar */}
<div className="flex flex-col sm:flex-row gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t("docker.searchPlaceholder")}
value={searchQuery}
@@ -76,16 +82,23 @@ export function ContainerList({
/>
</div>
<div className="flex items-center gap-2 sm:min-w-[200px]">
<Filter className="h-4 w-4 text-gray-400" />
<Filter className="h-4 w-4 text-muted-foreground" />
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full">
<SelectValue placeholder={t("docker.filterByStatusPlaceholder")} />
<SelectValue
placeholder={t("docker.filterByStatusPlaceholder")}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="all">{t("docker.allContainersCount", { count: containers.length })}</SelectItem>
<SelectItem value="all">
{t("docker.allContainersCount", { count: containers.length })}
</SelectItem>
{Object.entries(statusCounts).map(([status, count]) => (
<SelectItem key={status} value={status}>
{t("docker.statusCount", { status: status.charAt(0).toUpperCase() + status.slice(1), count })}
{t("docker.statusCount", {
status: status.charAt(0).toUpperCase() + status.slice(1),
count,
})}
</SelectItem>
))}
</SelectContent>
@@ -97,8 +110,12 @@ export function ContainerList({
{filteredContainers.length === 0 ? (
<div className="flex items-center justify-center flex-1">
<div className="text-center space-y-2">
<p className="text-gray-400">{t("docker.noContainersMatchFilters")}</p>
<p className="text-gray-500 text-sm">{t("docker.noContainersMatchFiltersHint")}</p>
<p className="text-muted-foreground">
{t("docker.noContainersMatchFilters")}
</p>
<p className="text-muted-foreground text-sm">
{t("docker.noContainersMatchFiltersHint")}
</p>
</div>
</div>
) : (

View File

@@ -42,7 +42,9 @@ export function ContainerStats({
const data = await getContainerStats(sessionId, containerId);
setStats(data);
} catch (err) {
setError(err instanceof Error ? err.message : t("docker.failedToFetchStats"));
setError(
err instanceof Error ? err.message : t("docker.failedToFetchStats"),
);
} finally {
setIsLoading(false);
}
@@ -61,11 +63,11 @@ export function ContainerStats({
return (
<div className="flex items-center justify-center h-full">
<div className="text-center space-y-2">
<Activity className="h-12 w-12 text-gray-600 mx-auto" />
<p className="text-gray-400 text-lg">
<Activity className="h-12 w-12 text-muted-foreground/50 mx-auto" />
<p className="text-muted-foreground text-lg">
{t("docker.containerNotRunning")}
</p>
<p className="text-gray-500 text-sm">
<p className="text-muted-foreground text-sm">
{t("docker.startContainerToViewStats")}
</p>
</div>
@@ -78,7 +80,9 @@ export function ContainerStats({
<div className="flex items-center justify-center h-full">
<div className="text-center">
<SimpleLoader size="lg" />
<p className="text-gray-400 mt-4">{t("docker.loadingStats")}</p>
<p className="text-muted-foreground mt-4">
{t("docker.loadingStats")}
</p>
</div>
</div>
);
@@ -91,7 +95,7 @@ export function ContainerStats({
<p className="text-red-400 text-lg">
{t("docker.errorLoadingStats")}
</p>
<p className="text-gray-500 text-sm">{error}</p>
<p className="text-muted-foreground text-sm">{error}</p>
</div>
</div>
);
@@ -100,7 +104,7 @@ export function ContainerStats({
if (!stats) {
return (
<div className="flex items-center justify-center h-full">
<p className="text-gray-400">{t("docker.noStatsAvailable")}</p>
<p className="text-muted-foreground">{t("docker.noStatsAvailable")}</p>
</div>
);
}
@@ -121,7 +125,10 @@ export function ContainerStats({
<CardContent className="px-4 pb-3">
<div className="space-y-2">
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.current")}</span> <span className="font-mono font-semibold text-blue-300">
<span className="text-muted-foreground">
{t("docker.current")}
</span>{" "}
<span className="font-mono font-semibold text-blue-400">
{stats.cpu}
</span>
</div>
@@ -141,14 +148,18 @@ export function ContainerStats({
<CardContent className="px-4 pb-3">
<div className="space-y-2">
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.usedLimit")}</span>
<span className="font-mono font-semibold text-purple-300">
<span className="text-muted-foreground">
{t("docker.usedLimit")}
</span>
<span className="font-mono font-semibold text-purple-400">
{stats.memoryUsed} / {stats.memoryLimit}
</span>
</div>
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.percentage")}</span>
<span className="font-mono text-purple-300">
<span className="text-muted-foreground">
{t("docker.percentage")}
</span>
<span className="font-mono text-purple-400">
{stats.memoryPercent}
</span>
</div>
@@ -168,12 +179,14 @@ export function ContainerStats({
<CardContent className="px-4 pb-3">
<div className="space-y-2">
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.input")}</span>
<span className="font-mono text-green-300">{stats.netInput}</span>
<span className="text-muted-foreground">{t("docker.input")}</span>
<span className="font-mono text-green-400">{stats.netInput}</span>
</div>
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.output")}</span>
<span className="font-mono text-green-300">
<span className="text-muted-foreground">
{t("docker.output")}
</span>
<span className="font-mono text-green-400">
{stats.netOutput}
</span>
</div>
@@ -192,21 +205,23 @@ export function ContainerStats({
<CardContent className="px-4 pb-3">
<div className="space-y-2">
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.read")}</span>
<span className="font-mono text-orange-300">
<span className="text-muted-foreground">{t("docker.read")}</span>
<span className="font-mono text-orange-400">
{stats.blockRead}
</span>
</div>
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.write")}</span>
<span className="font-mono text-orange-300">
<span className="text-muted-foreground">{t("docker.write")}</span>
<span className="font-mono text-orange-400">
{stats.blockWrite}
</span>
</div>
{stats.pids && (
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">{t("docker.pids")}</span>
<span className="font-mono text-orange-300">{stats.pids}</span>
<span className="text-muted-foreground">
{t("docker.pids")}
</span>
<span className="font-mono text-orange-400">{stats.pids}</span>
</div>
)}
</div>
@@ -224,17 +239,17 @@ export function ContainerStats({
<CardContent className="px-4 pb-3">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 text-sm">
<div className="flex justify-between items-center">
<span className="text-gray-400">{t("docker.name")}</span>
<span className="font-mono text-gray-200">{containerName}</span>
<span className="text-muted-foreground">{t("docker.name")}</span>
<span className="font-mono text-foreground">{containerName}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-400">{t("docker.id")}</span>
<span className="font-mono text-sm text-gray-300">
<span className="text-muted-foreground">{t("docker.id")}</span>
<span className="font-mono text-sm text-foreground">
{containerId.substring(0, 12)}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-400">{t("docker.state")}</span>
<span className="text-muted-foreground">{t("docker.state")}</span>
<span className="font-semibold text-green-400 capitalize">
{containerState}
</span>

View File

@@ -209,13 +209,13 @@ export function LogViewer({
{/* Search Filter */}
<div className="mt-2">
<div className="relative">
<Filter className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Filter className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<input
type="text"
placeholder="Filter logs..."
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-dark-bg border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary"
className="w-full pl-10 pr-4 py-2 border border-input rounded-md text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
</div>
@@ -231,9 +231,11 @@ export function LogViewer({
</div>
) : (
<div className="h-full overflow-auto thin-scrollbar">
<pre className="p-4 text-xs font-mono whitespace-pre-wrap break-words text-gray-200 leading-relaxed">
<pre className="p-4 text-xs font-mono whitespace-pre-wrap break-words text-foreground leading-relaxed">
{filteredLogs || (
<span className="text-gray-500">No logs available</span>
<span className="text-muted-foreground">
No logs available
</span>
)}
<div ref={logsEndRef} />
</pre>

View File

@@ -477,7 +477,7 @@ export function ServerStats({
{(metricsEnabled && showStatsUI) ||
(currentHostConfig?.quickActions &&
currentHostConfig.quickActions.length > 0) ? (
<div className="rounded-lg border-2 border-edge m-3 bg-elevated p-4 overflow-y-auto thin-scrollbar relative flex-1 flex flex-col">
<div className="border-edge m-1 p-2 overflow-y-auto thin-scrollbar relative flex-1 flex flex-col">
{currentHostConfig?.quickActions &&
currentHostConfig.quickActions.length > 0 && (
<div className={metricsEnabled && showStatsUI ? "mb-4" : ""}>

View File

@@ -30,8 +30,7 @@ export function CpuWidget({ metrics, metricsHistory }: CpuWidgetProps) {
}, [metricsHistory]);
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<Cpu className="h-5 w-5 text-blue-400" />
<h3 className="font-semibold text-lg text-foreground">

View File

@@ -27,8 +27,7 @@ export function DiskWidget({ metrics }: DiskWidgetProps) {
}, [metrics]);
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<HardDrive className="h-5 w-5 text-orange-400" />
<h3 className="font-semibold text-lg text-foreground">

View File

@@ -35,7 +35,7 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
const uniqueIPs = loginStats?.uniqueIPs || 0;
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<UserCheck className="h-5 w-5 text-green-400" />
<h3 className="font-semibold text-lg text-foreground">
@@ -45,7 +45,7 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
<div className="flex flex-col flex-1 min-h-0 gap-3">
<div className="grid grid-cols-2 gap-2 flex-shrink-0">
<div className="bg-elevated p-2 rounded border border-edge/30">
<div className="bg-canvas/40 p-2 rounded border border-edge/30 hover:bg-canvas/50">
<div className="flex items-center gap-1 text-xs text-muted-foreground mb-1">
<Activity className="h-3 w-3" />
<span>{t("serverStats.totalLogins")}</span>
@@ -54,7 +54,7 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
{totalLogins}
</div>
</div>
<div className="bg-elevated p-2 rounded border border-edge/30">
<div className="bg-canvas/40 p-2 rounded border border-edge/30 hover:bg-canvas/50">
<div className="flex items-center gap-1 text-xs text-muted-foreground mb-1">
<MapPin className="h-3 w-3" />
<span>{t("serverStats.uniqueIPs")}</span>
@@ -80,7 +80,7 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
{recentLogins.slice(0, 5).map((login, idx) => (
<div
key={idx}
className="text-xs bg-elevated p-2 rounded border border-edge/30 flex justify-between items-center"
className="text-xs bg-canvas/40 p-2 rounded border border-edge/30 hover:bg-canvas/50 flex justify-between items-center"
>
<div className="flex items-center gap-2 min-w-0">
<span className="text-green-400 font-mono truncate">

View File

@@ -30,8 +30,7 @@ export function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {
}, [metricsHistory]);
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<MemoryStick className="h-5 w-5 text-green-400" />
<h3 className="font-semibold text-lg text-foreground">

View File

@@ -24,8 +24,7 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
const interfaces = network?.interfaces || [];
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<Network className="h-5 w-5 text-indigo-400" />
<h3 className="font-semibold text-lg text-foreground">
@@ -43,7 +42,7 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
interfaces.map((iface, index: number) => (
<div
key={index}
className="p-3 rounded-lg bg-canvas/50 border border-edge/30 hover:bg-canvas/60 transition-colors"
className="p-3 rounded-lg bg-canvas/40 border border-edge/30 hover:bg-canvas/50"
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">

View File

@@ -28,7 +28,7 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
const topProcesses = processes?.top || [];
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<List className="h-5 w-5 text-yellow-400" />
<h3 className="font-semibold text-lg text-foreground">
@@ -62,7 +62,7 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
{topProcesses.map((proc, index) => (
<div
key={index}
className="p-2.5 rounded-lg bg-canvas/30 hover:bg-canvas/50 transition-colors border border-edge/20"
className="p-2.5 rounded-lg bg-canvas/40 hover:bg-canvas/50 border border-edge/30"
>
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-mono text-muted-foreground font-medium">

View File

@@ -21,8 +21,7 @@ export function SystemWidget({ metrics }: SystemWidgetProps) {
const system = metricsWithSystem?.system;
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<Server className="h-5 w-5 text-purple-400" />
<h3 className="font-semibold text-lg text-foreground">

View File

@@ -20,8 +20,7 @@ export function UptimeWidget({ metrics }: UptimeWidgetProps) {
const uptime = metricsWithUptime?.uptime;
return (
<div className="h-full w-full p-4 rounded-lg bg-canvas/50 border border-edge/50 hover:bg-canvas/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="h-full w-full p-4 rounded-lg bg-elevated border border-edge/50 hover:bg-elevated/70 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<Clock className="h-5 w-5 text-cyan-400" />
<h3 className="font-semibold text-lg text-foreground">

View File

@@ -247,7 +247,8 @@ export function SSHToolsSidebar({
tab.type === "terminal" ||
tab.type === "server" ||
tab.type === "file_manager" ||
tab.type === "user_profile",
tab.type === "tunnel" ||
tab.type === "docker",
);
useEffect(() => {
@@ -1246,7 +1247,7 @@ export function SSHToolsSidebar({
{terminalTabs.length > 0 && (
<>
<div className="space-y-2">
<label className="text-sm font-medium text-white">
<label className="text-sm font-medium text-foreground">
{t("snippets.selectTerminals")}
</label>
<p className="text-xs text-muted-foreground">
@@ -1702,7 +1703,7 @@ export function SSHToolsSidebar({
<Separator />
<div className="space-y-2">
<label className="text-sm font-medium text-white">
<label className="text-sm font-medium text-foreground">
{t("splitScreen.availableTabs")}
</label>
<p className="text-xs text-muted-foreground mb-2">
@@ -1745,7 +1746,7 @@ export function SSHToolsSidebar({
<Separator />
<div className="space-y-2">
<label className="text-sm font-medium text-white">
<label className="text-sm font-medium text-foreground">
{t("splitScreen.layout")}
</label>
<div
@@ -1763,7 +1764,9 @@ export function SSHToolsSidebar({
const assignedTabId =
splitAssignments.get(idx);
const assignedTab = assignedTabId
? tabs.find((t) => t.id === assignedTabId)
? splittableTabs.find(
(t) => t.id === assignedTabId,
)
: null;
const isHovered = dragOverCellIndex === idx;
const isEmpty = !assignedTabId;
@@ -2050,7 +2053,7 @@ export function SSHToolsSidebar({
<div className="space-y-5">
<div className="space-y-2">
<label className="text-sm font-medium text-white flex items-center gap-1">
<label className="text-sm font-medium text-foreground flex items-center gap-1">
{t("snippets.folderName")}
<span className="text-destructive">*</span>
</label>
@@ -2074,7 +2077,7 @@ export function SSHToolsSidebar({
</div>
<div className="space-y-3">
<Label className="text-base font-semibold text-white">
<Label className="text-base font-semibold text-foreground">
{t("snippets.folderColor")}
</Label>
<div className="grid grid-cols-4 gap-3">
@@ -2101,7 +2104,7 @@ export function SSHToolsSidebar({
</div>
<div className="space-y-3">
<Label className="text-base font-semibold text-white">
<Label className="text-base font-semibold text-foreground">
{t("snippets.folderIcon")}
</Label>
<div className="grid grid-cols-5 gap-3">
@@ -2126,7 +2129,7 @@ export function SSHToolsSidebar({
</div>
<div className="space-y-3">
<Label className="text-base font-semibold text-white">
<Label className="text-base font-semibold text-foreground">
{t("snippets.preview")}
</Label>
<div className="flex items-center gap-3 p-4 rounded-md bg-elevated border border-edge">

View File

@@ -94,8 +94,8 @@ export function TunnelManager({
};
const containerClass = embedded
? "h-full w-full text-white overflow-hidden bg-transparent"
: "bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden";
? "h-full w-full text-foreground overflow-hidden bg-transparent"
: "bg-canvas text-foreground rounded-lg border-2 border-edge overflow-hidden";
return (
<div style={wrapperStyle} className={containerClass}>