feat: Complete light mode implementation with semantic theme system (#450)

- Add comprehensive light/dark mode CSS variables with semantic naming
- Implement theme-aware scrollbars using CSS variables
- Add light mode backgrounds: --bg-base, --bg-elevated, --bg-surface, etc.
- Add theme-aware borders: --border-base, --border-panel, --border-subtle
- Add semantic text colors: --foreground-secondary, --foreground-subtle
- Convert oklch colors to hex for better compatibility
- Add theme awareness to CodeMirror editors
- Update dark mode colors for consistency (background, sidebar, card, muted, input)
- Add Tailwind color mappings for semantic classes

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
This commit was merged in pull request #450.
This commit is contained in:
Peet McKinney
2025-12-23 15:35:49 -07:00
committed by GitHub
parent 186ba34c66
commit e6a70e3a02
84 changed files with 1084 additions and 664 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-dark-border 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",
"transition-all duration-200 ease-out",
!isOpen && "scale-95 opacity-0",
)}
@@ -251,7 +251,7 @@ export function CommandPalette({
/>
<CommandList
key={recentActivity.length}
className="w-full h-auto flex-grow overflow-y-auto"
className="w-full h-auto flex-grow overflow-y-auto thin-scrollbar"
style={{ maxHeight: "inherit" }}
>
{recentActivity.length > 0 && (
@@ -324,7 +324,7 @@ export function CommandPalette({
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="!px-2 h-7 border-1 border-dark-border"
className="!px-2 h-7 border-1 border-edge"
onClick={(e) => e.stopPropagation()}
>
<EllipsisVertical className="h-3 w-3" />
@@ -333,14 +333,14 @@ export function CommandPalette({
<DropdownMenuContent
align="end"
side="right"
className="w-56 bg-dark-bg border-dark-border text-white"
className="w-56 bg-canvas border-edge text-foreground"
>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handleHostServerDetailsClick(host);
}}
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300"
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
>
<Server className="h-4 w-4" />
<span className="flex-1">
@@ -352,7 +352,7 @@ export function CommandPalette({
e.stopPropagation();
handleHostFileManagerClick(host);
}}
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300"
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
>
<FolderOpen className="h-4 w-4" />
<span className="flex-1">
@@ -364,7 +364,7 @@ export function CommandPalette({
e.stopPropagation();
handleHostEditClick(host);
}}
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300"
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
>
<Pencil className="h-4 w-4" />
<span className="flex-1">
@@ -400,7 +400,7 @@ export function CommandPalette({
</CommandItem>
</CommandGroup>
</CommandList>
<div className="border-t border-dark-border px-4 py-2 bg-dark-hover/50 flex items-center justify-between text-xs text-muted-foreground">
<div className="border-t border-edge px-4 py-2 bg-hover/50 flex items-center justify-between text-xs text-muted-foreground">
<div className="flex items-center gap-2">
<span>{t("commandPalette.press")}</span>
<KbdGroup>

View File

@@ -32,7 +32,9 @@ import {
import { useTranslation } from "react-i18next";
import CodeMirror from "@uiw/react-codemirror";
import { oneDark } from "@codemirror/theme-one-dark";
import { githubLight } from "@uiw/codemirror-theme-github";
import { EditorView } from "@codemirror/view";
import { useTheme } from "@/components/theme-provider";
import type {
Credential,
CredentialEditorProps,
@@ -44,6 +46,14 @@ export function CredentialEditor({
onFormSubmit,
}: CredentialEditorProps) {
const { t } = useTranslation();
const { theme: appTheme } = useTheme();
// Determine CodeMirror theme based on app theme
const isDarkMode =
appTheme === "dark" ||
(appTheme === "system" &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
const editorTheme = isDarkMode ? oneDark : githubLight;
const [, setCredentials] = useState<Credential[]>([]);
const [folders, setFolders] = useState<string[]>([]);
const [, setLoading] = useState(true);
@@ -473,11 +483,11 @@ export function CredentialEditor({
onValueChange={setActiveTab}
className="w-full"
>
<TabsList>
<TabsTrigger value="general">
<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">
{t("credentials.general")}
</TabsTrigger>
<TabsTrigger value="authentication">
<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>
@@ -561,7 +571,7 @@ export function CredentialEditor({
{folderDropdownOpen && filteredFolders.length > 0 && (
<div
ref={folderDropdownRef}
className="absolute top-full left-0 z-50 mt-1 w-full bg-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
className="absolute top-full left-0 z-50 mt-1 w-full bg-canvas border border-input rounded-md shadow-lg max-h-40 overflow-y-auto thin-scrollbar p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{filteredFolders.map((folder) => (
@@ -590,7 +600,7 @@ export function CredentialEditor({
<FormItem className="col-span-10 overflow-visible">
<FormLabel>{t("credentials.tags")}</FormLabel>
<FormControl>
<div className="flex flex-wrap items-center gap-1 border border-input rounded-md px-3 py-2 bg-dark-bg-input focus-within:ring-2 ring-ring min-h-[40px]">
<div className="flex flex-wrap items-center gap-1 border border-input rounded-md px-3 py-2 bg-field focus-within:ring-2 ring-ring min-h-[40px]">
{(field.value || []).map(
(tag: string, idx: number) => (
<span
@@ -682,11 +692,11 @@ export function CredentialEditor({
}}
className="flex-1 flex flex-col h-full min-h-0"
>
<TabsList>
<TabsTrigger value="password">
<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">
{t("credentials.password")}
</TabsTrigger>
<TabsTrigger value="key">
<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>
@@ -927,7 +937,7 @@ export function CredentialEditor({
placeholder={t(
"placeholders.pastePrivateKey",
)}
theme={oneDark}
theme={editorTheme}
className="border border-input rounded-md"
minHeight="120px"
basicSetup={{
@@ -943,6 +953,8 @@ export function CredentialEditor({
EditorView.theme({
".cm-scroller": {
overflow: "auto",
scrollbarWidth: "thin",
scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)",
},
}),
]}
@@ -1087,7 +1099,7 @@ export function CredentialEditor({
debouncedPublicKeyDetection(value);
}}
placeholder={t("placeholders.pastePublicKey")}
theme={oneDark}
theme={editorTheme}
className="border border-input rounded-md"
minHeight="120px"
basicSetup={{
@@ -1103,6 +1115,8 @@ export function CredentialEditor({
EditorView.theme({
".cm-scroller": {
overflow: "auto",
scrollbarWidth: "thin",
scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)",
},
}),
]}

View File

@@ -165,7 +165,7 @@ export function CredentialSelector({
/>
</div>
<div className="max-h-60 overflow-y-auto p-2">
<div className="max-h-60 overflow-y-auto thin-scrollbar p-2">
{loading ? (
<div className="p-3 text-center text-sm text-muted-foreground">
{t("common.loading")}

View File

@@ -184,7 +184,7 @@ const CredentialViewer: React.FC<CredentialViewerProps> = ({
return (
<Sheet open={true} onOpenChange={onClose}>
<SheetContent className="w-[600px] max-w-[50vw] overflow-y-auto">
<SheetContent className="w-[600px] max-w-[50vw] overflow-y-auto thin-scrollbar">
<SheetHeader className="space-y-6 pb-8">
<SheetTitle className="flex items-center space-x-4">
<div className="p-2 rounded-lg bg-zinc-100 dark:bg-zinc-800">

View File

@@ -603,7 +603,7 @@ export function CredentialsManager({
handleDragStart(e, credential)
}
onDragEnd={handleDragEnd}
className={`bg-dark-bg-input border border-input rounded-lg cursor-pointer hover:shadow-lg hover:border-blue-400/50 hover:bg-dark-hover-alt transition-all duration-200 p-3 group relative ${
className={`bg-field border border-input rounded-lg cursor-pointer hover:shadow-lg hover:border-blue-400/50 hover:bg-hover-alt transition-all duration-200 p-3 group relative ${
draggedCredential?.id === credential.id
? "opacity-50 scale-95"
: ""
@@ -808,7 +808,7 @@ export function CredentialsManager({
)}
<Sheet open={showDeployDialog} onOpenChange={setShowDeployDialog}>
<SheetContent className="w-[500px] max-w-[50vw] overflow-y-auto bg-dark-bg">
<SheetContent className="w-[500px] max-w-[50vw] overflow-y-auto thin-scrollbar bg-canvas">
<div className="px-4 py-4">
<div className="space-y-3 pb-4">
<div className="flex items-center space-x-3">
@@ -917,7 +917,7 @@ export function CredentialsManager({
? t("credentials.noHostsAvailable")
: t("credentials.noHostsMatchSearch")}
</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
{availableHosts.map((host) => (
<CommandItem
key={host.id}

View File

@@ -382,7 +382,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
@@ -397,7 +397,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">
@@ -458,18 +458,17 @@ 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"
/>
<p className="ml-2 leading-none truncate">
@@ -484,7 +483,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")
@@ -498,7 +497,6 @@ export function Dashboard({
<div className="flex flex-row items-center min-w-0">
<Clock
size={20}
color="#FFFFFF"
className="shrink-0"
/>
<p className="ml-2 leading-none truncate">
@@ -517,7 +515,6 @@ export function Dashboard({
<div className="flex flex-row items-center min-w-0">
<Database
size={20}
color="#FFFFFF"
className="shrink-0"
/>
<p className="ml-2 leading-none truncate">
@@ -537,11 +534,10 @@ 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"
/>
<p className="m-0 leading-none truncate">
@@ -552,11 +548,10 @@ 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"
/>
<p className="m-0 leading-none truncate">
@@ -569,11 +564,10 @@ 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"
/>
<p className="m-0 leading-none truncate">
@@ -587,7 +581,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">
@@ -597,14 +591,14 @@ export function Dashboard({
<Button
variant="outline"
size="sm"
className="border-2 !border-dark-border h-7"
className="border-2 !border-edge h-7"
onClick={handleResetActivity}
>
{t("dashboard.reset")}
</Button>
</div>
<div
className={`grid gap-4 grid-cols-3 auto-rows-min overflow-x-hidden ${recentActivityLoading ? "overflow-y-hidden" : "overflow-y-auto"}`}
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">
@@ -632,7 +626,7 @@ export function Dashboard({
<Button
key={item.id}
variant="outline"
className="border-2 !border-dark-border bg-dark-bg min-w-0"
className="border-2 !border-edge bg-canvas min-w-0"
onClick={() => handleActivityClick(item)}
>
{item.type === "terminal" ? (
@@ -651,16 +645,16 @@ 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 min-w-0"
onClick={handleAddHost}
>
<Server
@@ -673,7 +667,7 @@ export function Dashboard({
</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 min-w-0"
onClick={handleAddCredential}
>
<Key
@@ -687,7 +681,7 @@ export function Dashboard({
{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 min-w-0"
onClick={handleOpenAdminSettings}
>
<Settings
@@ -701,7 +695,7 @@ export function Dashboard({
)}
<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 min-w-0"
onClick={handleOpenUserProfile}
>
<User
@@ -715,14 +709,14 @@ 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">
<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">
@@ -738,7 +732,7 @@ 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"
onClick={() =>
handleServerStatClick(server.id, server.name)
}

View File

@@ -97,7 +97,7 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) {
<Button
variant="outline"
size="sm"
className="ml-2 text-sm border-1 border-dark-border text-muted-foreground"
className="ml-2 text-sm border-1 border-edge text-muted-foreground"
onClick={() => setIsOpen(true)}
>
{t("common.updatesAndReleases")}
@@ -106,10 +106,10 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) {
<Sheet open={isOpen} onOpenChange={setIsOpen}>
<SheetContent
side="right"
className="w-[500px] bg-dark-bg border-l-2 border-dark-border text-white sm:max-w-[500px] p-0 flex flex-col [&>button]:hidden"
className="w-[500px] bg-canvas border-l-2 border-edge text-foreground sm:max-w-[500px] p-0 flex flex-col [&>button]:hidden"
>
<div className="flex items-center justify-between p-4 border-b border-dark-border">
<h2 className="text-lg font-semibold text-white">
<div className="flex items-center justify-between p-4 border-b border-edge">
<h2 className="text-lg font-semibold text-foreground">
{t("common.updatesAndReleases")}
</h2>
<Button
@@ -123,13 +123,13 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) {
</Button>
</div>
<div className="flex-1 overflow-y-auto p-4">
<div className="flex-1 overflow-y-auto p-4 thin-scrollbar">
{versionInfo && versionInfo.status === "requires_update" && (
<Alert className="bg-dark-bg-darker border-dark-border text-white mb-3">
<AlertTitle className="text-white">
<Alert className="bg-elevated border-edge text-foreground mb-3">
<AlertTitle className="text-foreground">
{t("common.updateAvailable")}
</AlertTitle>
<AlertDescription className="text-gray-300">
<AlertDescription className="text-foreground-secondary">
{t("common.newVersionAvailable", {
version: versionInfo.version,
})}
@@ -161,11 +161,11 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) {
{releases?.items.map((release) => (
<div
key={release.id}
className="border border-dark-border rounded-lg p-3 hover:bg-dark-bg-darker transition-colors cursor-pointer bg-dark-bg-darker/50"
className="border border-edge rounded-lg p-3 hover:bg-elevated transition-colors cursor-pointer bg-elevated/50"
onClick={() => window.open(release.link, "_blank")}
>
<div className="flex items-start justify-between mb-2">
<h4 className="font-semibold text-sm leading-tight flex-1 text-white">
<h4 className="font-semibold text-sm leading-tight flex-1 text-foreground">
{release.title}
</h4>
{release.isPrerelease && (
@@ -175,11 +175,11 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) {
)}
</div>
<p className="text-xs text-gray-300 mb-2 leading-relaxed">
<p className="text-xs text-foreground-secondary mb-2 leading-relaxed">
{formatDescription(release.description)}
</p>
<div className="flex items-center text-xs text-gray-400">
<div className="flex items-center text-xs text-muted-foreground">
<span>
{new Date(release.pubDate).toLocaleDateString()}
</span>
@@ -198,11 +198,11 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) {
</div>
{releases && releases.items.length === 0 && !loading && (
<Alert className="bg-dark-bg-darker border-dark-border text-gray-300">
<AlertTitle className="text-gray-300">
<Alert className="bg-elevated border-edge text-foreground-secondary">
<AlertTitle className="text-foreground-secondary">
{t("common.noReleases")}
</AlertTitle>
<AlertDescription className="text-gray-400">
<AlertDescription className="text-muted-foreground">
{t("common.noReleasesFound")}
</AlertDescription>
</Alert>

View File

@@ -78,7 +78,7 @@ export function ContainerDetail({
<TabsContent
value="logs"
className="flex-1 overflow-auto px-3 pb-3 mt-3"
className="flex-1 overflow-auto thin-scrollbar px-3 pb-3 mt-3"
>
<LogViewer
sessionId={sessionId}
@@ -89,7 +89,7 @@ export function ContainerDetail({
<TabsContent
value="stats"
className="flex-1 overflow-auto px-3 pb-3 mt-3"
className="flex-1 overflow-auto thin-scrollbar px-3 pb-3 mt-3"
>
<ContainerStats
sessionId={sessionId}

View File

@@ -102,7 +102,7 @@ export function ContainerList({
</div>
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-3 overflow-auto pb-2">
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-3 overflow-auto thin-scrollbar pb-2">
{filteredContainers.map((container) => (
<ContainerCard
key={container.id}

View File

@@ -109,7 +109,7 @@ export function ContainerStats({
const memPercent = parseFloat(stats.memoryPercent) || 0;
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 h-full overflow-auto">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 h-full overflow-auto thin-scrollbar">
{/* CPU Usage */}
<Card className="py-3">
<CardHeader className="pb-2 px-4">

View File

@@ -230,7 +230,7 @@ export function LogViewer({
<SimpleLoader size="lg" />
</div>
) : (
<div className="h-full overflow-auto">
<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">
{filteredLogs || (
<span className="text-gray-500">No logs available</span>

View File

@@ -79,7 +79,7 @@ export function DragIndicator({
<div
className={cn(
"fixed top-4 right-4 z-50 min-w-[300px] max-w-[400px]",
"bg-dark-bg border border-dark-border rounded-lg shadow-lg",
"bg-canvas border border-edge rounded-lg shadow-lg",
"p-4 transition-all duration-300 ease-in-out",
isVisible ? "opacity-100 translate-x-0" : "opacity-0 translate-x-full",
className,
@@ -109,7 +109,7 @@ export function DragIndicator({
</div>
{(isDownloading || isDragging) && !error && (
<div className="w-full bg-dark-border rounded-full h-2 mb-2">
<div className="w-full bg-border-base rounded-full h-2 mb-2">
<div
className={cn(
"h-2 rounded-full transition-all duration-300",

View File

@@ -1939,11 +1939,11 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
}
return (
<div className="h-full flex flex-col bg-dark-bg">
<div className="flex-shrink-0 border-b border-dark-border">
<div className="h-full flex flex-col bg-canvas">
<div className="flex-shrink-0 border-b border-edge">
<div className="flex items-center justify-between p-3">
<div className="flex items-center gap-2">
<h2 className="font-semibold text-white">{currentHost.name}</h2>
<h2 className="font-semibold text-foreground">{currentHost.name}</h2>
<span className="text-sm text-muted-foreground">
{currentHost.ip}:{currentHost.port}
</span>
@@ -1956,11 +1956,11 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
placeholder={t("fileManager.searchFiles")}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-8 w-48 h-9 bg-dark-bg-button border-dark-border"
className="pl-8 w-48 h-9 bg-button border-edge"
/>
</div>
<div className="flex border border-dark-border rounded-md">
<div className="flex border border-edge rounded-md">
<Button
variant={viewMode === "grid" ? "default" : "ghost"}
size="sm"

View File

@@ -513,7 +513,7 @@ export function FileManagerContextMenu({
<div
data-context-menu
className={cn(
"fixed bg-dark-bg border border-dark-border rounded-lg shadow-xl min-w-[180px] max-w-[250px] z-[99995] overflow-hidden",
"fixed bg-canvas border border-edge rounded-lg shadow-xl min-w-[180px] max-w-[250px] z-[99995] overflow-hidden",
)}
style={{
left: menuPosition.x,
@@ -525,7 +525,7 @@ export function FileManagerContextMenu({
return (
<div
key={`separator-${index}`}
className="border-t border-dark-border"
className="border-t border-edge"
/>
);
}
@@ -535,7 +535,7 @@ export function FileManagerContextMenu({
key={index}
className={cn(
"w-full px-3 py-2 text-left text-sm flex items-center justify-between",
"hover:bg-dark-hover transition-colors",
"hover:bg-hover transition-colors",
"first:rounded-t-lg last:rounded-b-lg",
item.disabled && "opacity-50 cursor-not-allowed",
item.danger && "text-red-400 hover:bg-red-500/10",

View File

@@ -873,14 +873,14 @@ export function FileManagerGrid({
]);
return (
<div className="h-full flex flex-col bg-dark-bg overflow-hidden relative">
<div className="flex-shrink-0 border-b border-dark-border">
<div className="flex items-center gap-1 p-2 border-b border-dark-border">
<div className="h-full flex flex-col bg-canvas overflow-hidden relative">
<div className="flex-shrink-0 border-b border-edge">
<div className="flex items-center gap-1 p-2 border-b border-edge">
<button
onClick={goBack}
disabled={historyIndex <= 0}
className={cn(
"p-1 rounded hover:bg-dark-hover",
"p-1 rounded hover:bg-hover",
historyIndex <= 0 && "opacity-50 cursor-not-allowed",
)}
title={t("common.back")}
@@ -891,7 +891,7 @@ export function FileManagerGrid({
onClick={goForward}
disabled={historyIndex >= navigationHistory.length - 1}
className={cn(
"p-1 rounded hover:bg-dark-hover",
"p-1 rounded hover:bg-hover",
historyIndex >= navigationHistory.length - 1 &&
"opacity-50 cursor-not-allowed",
)}
@@ -903,7 +903,7 @@ export function FileManagerGrid({
onClick={goUp}
disabled={currentPath === "/"}
className={cn(
"p-1 rounded hover:bg-dark-hover",
"p-1 rounded hover:bg-hover",
currentPath === "/" && "opacity-50 cursor-not-allowed",
)}
title={t("fileManager.parentDirectory")}
@@ -912,7 +912,7 @@ export function FileManagerGrid({
</button>
<button
onClick={onRefresh}
className="p-1 rounded hover:bg-dark-hover"
className="p-1 rounded hover:bg-hover"
title={t("common.refresh")}
>
<RefreshCw className="w-4 h-4" />
@@ -933,7 +933,7 @@ export function FileManagerGrid({
cancelEditingPath();
}
}}
className="flex-1 px-2 py-1 bg-dark-hover border border-dark-border rounded text-sm focus:outline-none focus:ring-1 focus:ring-primary"
className="flex-1 px-2 py-1 bg-hover border border-edge rounded text-sm focus:outline-none focus:ring-1 focus:ring-primary"
placeholder={t("fileManager.enterPath")}
autoFocus
/>
@@ -973,7 +973,7 @@ export function FileManagerGrid({
))}
<button
onClick={startEditingPath}
className="ml-2 p-1 rounded hover:bg-dark-hover opacity-60 hover:opacity-100 flex items-center justify-center"
className="ml-2 p-1 rounded hover:bg-hover opacity-60 hover:opacity-100 flex items-center justify-center"
title={t("fileManager.editPath")}
>
<Edit className="w-3 h-3" />
@@ -1246,7 +1246,7 @@ export function FileManagerGrid({
</div>
</div>
<div className="flex-shrink-0 border-t border-dark-border px-4 py-2 text-xs text-muted-foreground">
<div className="flex-shrink-0 border-t border-edge px-4 py-2 text-xs text-muted-foreground">
<div className="flex justify-between items-center">
<span>{t("fileManager.itemCount", { count: files.length })}</span>
{selectedFiles.length > 0 && (

View File

@@ -375,9 +375,9 @@ export function FileManagerSidebar({
<div key={item.id}>
<div
className={cn(
"flex items-center gap-2 py-1.5 text-sm cursor-pointer hover:bg-dark-hover rounded",
"flex items-center gap-2 py-1.5 text-sm cursor-pointer hover:bg-hover rounded",
isActive && "bg-primary/20 text-primary",
"text-white",
"text-foreground",
)}
style={{ paddingLeft: `${12 + level * 16}px`, paddingRight: "12px" }}
onClick={() => handleItemClick(item)}
@@ -397,7 +397,7 @@ export function FileManagerSidebar({
e.stopPropagation();
toggleFolder(item.id, item.path);
}}
className="p-0.5 hover:bg-dark-hover rounded"
className="p-0.5 hover:bg-hover rounded"
>
{isExpanded ? (
<ChevronDown className="w-3 h-3" />
@@ -454,7 +454,7 @@ export function FileManagerSidebar({
return (
<>
<div className="h-full flex flex-col bg-dark-bg border-r border-dark-border">
<div className="h-full flex flex-col bg-canvas border-r border-edge">
<div className="flex-1 relative overflow-hidden">
<div className="absolute inset-1.5 overflow-y-auto thin-scrollbar space-y-4">
{renderSection(
@@ -475,7 +475,7 @@ export function FileManagerSidebar({
<div
className={cn(
hasQuickAccessItems && "pt-4 border-t border-dark-border",
hasQuickAccessItems && "pt-4 border-t border-edge",
)}
>
<div className="flex items-center gap-2 px-3 py-2 text-xs font-medium text-muted-foreground uppercase tracking-wider">
@@ -495,7 +495,7 @@ export function FileManagerSidebar({
<div className="fixed inset-0 z-40" />
<div
data-sidebar-context-menu
className="fixed bg-dark-bg border border-dark-border rounded-lg shadow-xl min-w-[160px] z-50 overflow-hidden"
className="fixed bg-canvas border border-edge rounded-lg shadow-xl min-w-[160px] z-50 overflow-hidden"
style={{
left: contextMenu.x,
top: contextMenu.y,
@@ -504,7 +504,7 @@ export function FileManagerSidebar({
{contextMenu.item.type === "recent" && (
<>
<button
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-dark-hover text-white first:rounded-t-lg last:rounded-b-lg"
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-hover text-foreground first:rounded-t-lg last:rounded-b-lg"
onClick={() => {
handleRemoveRecentFile(contextMenu.item!);
closeContextMenu();
@@ -519,9 +519,9 @@ export function FileManagerSidebar({
</button>
{recentItems.length > 1 && (
<>
<div className="border-t border-dark-border" />
<div className="border-t border-edge" />
<button
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-dark-hover text-red-400 hover:bg-red-500/10 first:rounded-t-lg last:rounded-b-lg"
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-hover text-red-400 hover:bg-red-500/10 first:rounded-t-lg last:rounded-b-lg"
onClick={() => {
handleClearAllRecent();
closeContextMenu();
@@ -541,7 +541,7 @@ export function FileManagerSidebar({
{contextMenu.item.type === "pinned" && (
<button
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-dark-hover text-white first:rounded-t-lg last:rounded-b-lg"
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-hover text-foreground first:rounded-t-lg last:rounded-b-lg"
onClick={() => {
handleUnpinFile(contextMenu.item!);
closeContextMenu();
@@ -556,7 +556,7 @@ export function FileManagerSidebar({
{contextMenu.item.type === "shortcut" && (
<button
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-dark-hover text-white first:rounded-t-lg last:rounded-b-lg"
className="w-full px-3 py-2 text-left text-sm flex items-center gap-3 hover:bg-hover text-foreground first:rounded-t-lg last:rounded-b-lg"
onClick={() => {
handleRemoveShortcut(contextMenu.item!);
closeContextMenu();

View File

@@ -71,7 +71,7 @@ export function CompressDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px] bg-dark-bg border-2 border-dark-border">
<DialogContent className="sm:max-w-[500px] bg-canvas border-2 border-edge">
<DialogHeader>
<DialogTitle>{t("fileManager.compressFiles")}</DialogTitle>
<DialogDescription className="text-muted-foreground">
@@ -123,8 +123,8 @@ export function CompressDialog({
</Select>
</div>
<div className="rounded-md bg-dark-hover/50 border border-dark-border p-3">
<p className="text-sm text-gray-400 mb-2">
<div className="rounded-md bg-hover/50 border border-edge p-3">
<p className="text-sm text-muted-foreground mb-2">
{t("fileManager.selectedFiles")}:
</p>
<ul className="text-sm space-y-1">
@@ -134,7 +134,7 @@ export function CompressDialog({
</li>
))}
{fileNames.length > 5 && (
<li className="text-gray-400 italic">
<li className="text-muted-foreground italic">
{t("fileManager.andMoreFiles", {
count: fileNames.length - 5,
})}

View File

@@ -210,7 +210,7 @@ export function DiffViewer({
if (isLoading) {
return (
<div className="h-full flex items-center justify-center bg-dark-bg">
<div className="h-full flex items-center justify-center bg-canvas">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">
@@ -223,7 +223,7 @@ export function DiffViewer({
if (error) {
return (
<div className="h-full flex items-center justify-center bg-dark-bg">
<div className="h-full flex items-center justify-center bg-canvas">
<div className="text-center max-w-md">
<FileText className="w-16 h-16 mx-auto mb-4 text-red-500 opacity-50" />
<p className="text-red-500 mb-4">{error}</p>
@@ -237,8 +237,8 @@ export function DiffViewer({
}
return (
<div className="h-full flex flex-col bg-dark-bg">
<div className="flex-shrink-0 border-b border-dark-border p-3">
<div className="h-full flex flex-col bg-canvas">
<div className="flex-shrink-0 border-b border-edge p-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="text-sm">

View File

@@ -133,14 +133,14 @@ function getLanguageIcon(filename: string): React.ReactNode {
yml: <SiYaml className="w-6 h-6 text-red-400" />,
toml: <SiToml className="w-6 h-6 text-orange-400" />,
sql: <SiMysql className="w-6 h-6 text-blue-500" />,
sh: <SiGnubash className="w-6 h-6 text-gray-700" />,
bash: <SiGnubash className="w-6 h-6 text-gray-700" />,
zsh: <SiShell className="w-6 h-6 text-gray-700" />,
sh: <SiGnubash className="w-6 h-6 text-foreground" />,
bash: <SiGnubash className="w-6 h-6 text-foreground" />,
zsh: <SiShell className="w-6 h-6 text-foreground" />,
vue: <SiVuedotjs className="w-6 h-6 text-green-500" />,
svelte: <SiSvelte className="w-6 h-6 text-orange-500" />,
md: <SiMarkdown className="w-6 h-6 text-gray-600" />,
conf: <SiShell className="w-6 h-6 text-gray-600" />,
ini: <Code className="w-6 h-6 text-gray-600" />,
md: <SiMarkdown className="w-6 h-6 text-muted-foreground" />,
conf: <SiShell className="w-6 h-6 text-muted-foreground" />,
ini: <Code className="w-6 h-6 text-muted-foreground" />,
};
return iconMap[ext] || <Code className="w-6 h-6 text-yellow-500" />;
@@ -238,7 +238,7 @@ function getFileType(filename: string): {
return {
type: "unknown",
icon: <FileIcon className="w-6 h-6" />,
color: "text-gray-500",
color: "text-foreground-subtle",
};
}
}
@@ -423,7 +423,7 @@ export function FileViewer({
<div className="h-full flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-sm text-gray-600">Loading file...</p>
<p className="text-sm text-muted-foreground">Loading file...</p>
</div>
</div>
);
@@ -812,6 +812,8 @@ export function FileViewer({
},
".cm-scroller": {
overflow: "auto",
scrollbarWidth: "thin",
scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)",
},
".cm-editor": {
height: "100%",
@@ -835,7 +837,7 @@ export function FileViewer({
}}
/>
) : (
<div className="h-full p-4 font-mono text-sm whitespace-pre-wrap overflow-auto bg-background text-foreground">
<div className="h-full p-4 font-mono text-sm whitespace-pre-wrap overflow-auto thin-scrollbar bg-background text-foreground">
{editedContent || content || t("fileManager.fileIsEmpty")}
</div>
)}
@@ -969,7 +971,7 @@ export function FileViewer({
</div>
</div>
<div className="flex-1 overflow-auto bg-muted/10">
<div className="flex-1 overflow-auto thin-scrollbar bg-muted/10">
<div className="p-4">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
@@ -1041,7 +1043,7 @@ export function FileViewer({
</blockquote>
),
table: ({ children }) => (
<div className="mb-3 overflow-x-auto">
<div className="mb-3 overflow-x-auto thin-scrollbar">
<table className="min-w-full border border-border rounded-lg text-sm">
{children}
</table>
@@ -1084,7 +1086,7 @@ export function FileViewer({
</div>
</>
) : (
<div className="flex-1 overflow-auto p-6">
<div className="flex-1 overflow-auto thin-scrollbar p-6">
<div className="max-w-4xl mx-auto">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
@@ -1154,7 +1156,7 @@ export function FileViewer({
</blockquote>
),
table: ({ children }) => (
<div className="mb-4 overflow-x-auto">
<div className="mb-4 overflow-x-auto thin-scrollbar">
<table className="min-w-full border border-border rounded-lg">
{children}
</table>
@@ -1252,7 +1254,7 @@ export function FileViewer({
</div>
</div>
<div className="flex-1 overflow-auto p-6 bg-gray-100 dark:bg-gray-900">
<div className="flex-1 overflow-auto thin-scrollbar p-6 bg-gray-100 dark:bg-gray-900">
<div className="flex justify-center">
{pdfError ? (
<div className="text-center text-muted-foreground p-8">

View File

@@ -140,7 +140,7 @@ export function PermissionsDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px] bg-dark-bg border-2 border-dark-border">
<DialogContent className="sm:max-w-[500px] bg-canvas border-2 border-edge">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Shield className="w-5 h-5" />
@@ -155,7 +155,7 @@ export function PermissionsDialog({
<div className="space-y-6 py-4">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<Label className="text-gray-400">
<Label className="text-muted-foreground">
{t("fileManager.currentPermissions")}
</Label>
<p className="font-mono text-lg mt-1">
@@ -163,7 +163,7 @@ export function PermissionsDialog({
</p>
</div>
<div>
<Label className="text-gray-400">
<Label className="text-muted-foreground">
{t("fileManager.newPermissions")}
</Label>
<p className="font-mono text-lg mt-1">{octal}</p>

View File

@@ -98,7 +98,7 @@ export function HostManager({
<div>
<div className="w-full">
<div
className="bg-dark-bg text-white p-4 pt-0 rounded-lg border-2 border-dark-border flex flex-col min-h-0 overflow-hidden"
className="bg-canvas text-foreground p-4 pt-0 rounded-lg border-2 border-edge flex flex-col min-h-0 overflow-hidden"
style={{
marginLeft: leftMarginPx,
marginRight: rightSidebarOpen
@@ -116,22 +116,22 @@ export function HostManager({
onValueChange={handleTabChange}
className="flex-1 flex flex-col h-full min-h-0"
>
<TabsList className="bg-dark-bg border-2 border-dark-border mt-1.5">
<TabsTrigger value="host_viewer">
<TabsList className="bg-elevated border-2 border-edge mt-1.5">
<TabsTrigger value="host_viewer" className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge">
{t("hosts.hostViewer")}
</TabsTrigger>
<TabsTrigger value="add_host">
<TabsTrigger value="add_host" className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge">
{editingHost
? editingHost.id
? t("hosts.editHost")
: t("hosts.cloneHost")
: t("hosts.addHost")}
</TabsTrigger>
<div className="h-6 w-px bg-dark-border mx-1"></div>
<TabsTrigger value="credentials">
<div className="h-6 w-px bg-border-base mx-1"></div>
<TabsTrigger value="credentials" className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge">
{t("credentials.credentialsViewer")}
</TabsTrigger>
<TabsTrigger value="add_credential">
<TabsTrigger value="add_credential" className="bg-elevated data-[state=active]:bg-button data-[state=active]:border data-[state=active]:border-edge">
{editingCredential
? t("credentials.editCredential")
: t("credentials.addCredential")}
@@ -161,7 +161,7 @@ export function HostManager({
className="flex-1 flex flex-col h-full min-h-0"
>
<Separator className="p-0.25 -mt-0.5 mb-1" />
<div className="flex flex-col h-full min-h-0 overflow-auto">
<div className="flex flex-col h-full min-h-0 overflow-auto thin-scrollbar">
<CredentialsManager onEditCredential={handleEditCredential} />
</div>
</TabsContent>

View File

@@ -140,7 +140,7 @@ function JumpHostItem({
<Command>
<CommandInput placeholder={t("hosts.searchServers")} />
<CommandEmpty>{t("hosts.noServerFound")}</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
{hosts
.filter((h) => !editingHost || h.id !== editingHost.id)
.map((host) => (
@@ -246,7 +246,7 @@ function QuickActionItem({
<Command>
<CommandInput placeholder={t("hosts.searchSnippets")} />
<CommandEmpty>{t("hosts.noSnippetFound")}</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
{snippets.map((snippet) => (
<CommandItem
key={snippet.id}
@@ -1159,19 +1159,19 @@ export function HostManagerEditor({
onValueChange={setActiveTab}
className="w-full"
>
<TabsList>
<TabsTrigger value="general">
<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">
{t("hosts.general")}
</TabsTrigger>
<TabsTrigger value="terminal">
<TabsTrigger value="terminal" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.terminal")}
</TabsTrigger>
<TabsTrigger value="docker">{t("hosts.docker")}</TabsTrigger>
<TabsTrigger value="tunnel">{t("hosts.tunnel")}</TabsTrigger>
<TabsTrigger value="file_manager">
<TabsTrigger value="docker" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">Docker</TabsTrigger>
<TabsTrigger value="tunnel" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">{t("hosts.tunnel")}</TabsTrigger>
<TabsTrigger value="file_manager" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.fileManager")}
</TabsTrigger>
<TabsTrigger value="statistics">
<TabsTrigger value="statistics" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.statistics")}
</TabsTrigger>
{!editingHost?.isShared && (
@@ -1307,7 +1307,7 @@ export function HostManagerEditor({
{folderDropdownOpen && filteredFolders.length > 0 && (
<div
ref={folderDropdownRef}
className="absolute top-full left-0 z-50 mt-1 w-full bg-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
className="absolute top-full left-0 z-50 mt-1 w-full bg-canvas border border-input rounded-md shadow-lg max-h-40 overflow-y-auto thin-scrollbar p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{filteredFolders.map((folder) => (
@@ -1336,7 +1336,7 @@ export function HostManagerEditor({
<FormItem className="col-span-10 overflow-visible">
<FormLabel>{t("hosts.tags")}</FormLabel>
<FormControl>
<div className="flex flex-wrap items-center gap-1 border border-input rounded-md px-3 py-2 bg-dark-bg-input focus-within:ring-2 ring-ring min-h-[40px]">
<div className="flex flex-wrap items-center gap-1 border border-input rounded-md px-3 py-2 bg-field focus-within:ring-2 ring-ring min-h-[40px]">
{field.value.map((tag: string, idx: number) => (
<span
key={tag + idx}
@@ -1443,15 +1443,15 @@ export function HostManagerEditor({
}}
className="flex-1 flex flex-col h-full min-h-0"
>
<TabsList>
<TabsTrigger value="password">
<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">
{t("hosts.password")}
</TabsTrigger>
<TabsTrigger value="key">{t("hosts.key")}</TabsTrigger>
<TabsTrigger value="credential">
<TabsTrigger value="key" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">{t("hosts.key")}</TabsTrigger>
<TabsTrigger value="credential" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.credential")}
</TabsTrigger>
<TabsTrigger value="none">{t("hosts.none")}</TabsTrigger>
<TabsTrigger value="none" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">{t("hosts.none")}</TabsTrigger>
</TabsList>
<TabsContent value="password">
<FormField
@@ -1573,6 +1573,8 @@ export function HostManagerEditor({
EditorView.theme({
".cm-scroller": {
overflow: "auto",
scrollbarWidth: "thin",
scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)",
},
}),
]}
@@ -1611,7 +1613,7 @@ export function HostManagerEditor({
ref={keyTypeButtonRef}
type="button"
variant="outline"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-dark-bg border border-input text-foreground"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-canvas border border-input text-foreground"
onClick={() =>
setKeyTypeDropdownOpen((open) => !open)
}
@@ -1623,7 +1625,7 @@ export function HostManagerEditor({
{keyTypeDropdownOpen && (
<div
ref={keyTypeDropdownRef}
className="absolute bottom-full left-0 z-50 mb-1 w-full bg-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
className="absolute bottom-full left-0 z-50 mb-1 w-full bg-canvas border border-input rounded-md shadow-lg max-h-40 overflow-y-auto thin-scrollbar p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{keyTypeOptions.map((opt) => (
@@ -1632,7 +1634,7 @@ export function HostManagerEditor({
type="button"
variant="ghost"
size="sm"
className="w-full justify-start text-left rounded-md px-2 py-1.5 bg-dark-bg text-foreground hover:bg-white/15 focus:bg-white/20 focus:outline-none"
className="w-full justify-start text-left rounded-md px-2 py-1.5 bg-canvas text-foreground hover:bg-white/15 focus:bg-white/20 focus:outline-none"
onClick={() => {
field.onChange(opt.value);
setKeyTypeDropdownOpen(false);
@@ -1686,7 +1688,7 @@ export function HostManagerEditor({
control={form.control}
name="overrideCredentialUsername"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.overrideCredentialUsername")}
@@ -1732,7 +1734,7 @@ export function HostManagerEditor({
control={form.control}
name="forceKeyboardInteractive"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.forceKeyboardInteractive")}
@@ -2353,7 +2355,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.cursorBlink"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>{t("hosts.cursorBlink")}</FormLabel>
<FormDescription>
@@ -2446,7 +2448,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.rightClickSelectsWord"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.rightClickSelectsWord")}
@@ -2568,7 +2570,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.agentForwarding"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.sshAgentForwarding")}
@@ -2666,7 +2668,7 @@ export function HostManagerEditor({
<CommandEmpty>
{t("hosts.noSnippetFound")}
</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
<CommandItem
value="none"
onSelect={() => {
@@ -2727,7 +2729,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.autoMosh"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>{t("hosts.autoMosh")}</FormLabel>
<FormDescription>
@@ -3152,7 +3154,7 @@ export function HostManagerEditor({
index
] = el;
}}
className="absolute top-full left-0 z-50 mt-1 w-full bg-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
className="absolute top-full left-0 z-50 mt-1 w-full bg-canvas border border-input rounded-md shadow-lg max-h-40 overflow-y-auto thin-scrollbar p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{getFilteredSshConfigs(
@@ -3364,7 +3366,7 @@ export function HostManagerEditor({
control={form.control}
name="statsConfig.statusCheckEnabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.statusCheckEnabled")}
@@ -3461,7 +3463,7 @@ export function HostManagerEditor({
control={form.control}
name="statsConfig.metricsEnabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>{t("hosts.metricsEnabled")}</FormLabel>
<FormDescription>

View File

@@ -1155,7 +1155,7 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
draggable
onDragStart={(e) => handleDragStart(e, host)}
onDragEnd={handleDragEnd}
className={`bg-dark-bg-input border border-input rounded-lg cursor-pointer hover:shadow-lg hover:border-blue-400/50 hover:bg-dark-hover-alt transition-all duration-200 p-3 group relative ${
className={`bg-field border border-input rounded-lg cursor-pointer hover:shadow-lg hover:border-blue-400/50 hover:bg-hover-alt transition-all duration-200 p-3 group relative ${
draggedHost?.id === host.id
? "opacity-50 scale-95"
: ""

View File

@@ -343,7 +343,7 @@ export function HostSharingTab({
<Command>
<CommandInput placeholder={t("rbac.searchUsers")} />
<CommandEmpty>{t("rbac.noUserFound")}</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
{availableUsers.map((user) => (
<CommandItem
key={user.id}
@@ -396,7 +396,7 @@ export function HostSharingTab({
<Command>
<CommandInput placeholder={t("rbac.searchRoles")} />
<CommandEmpty>{t("rbac.noRoleFound")}</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
{roles.map((role) => (
<CommandItem
key={role.id}

View File

@@ -94,7 +94,7 @@ export function FolderEditDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px] bg-dark-bg border-2 border-dark-border">
<DialogContent className="sm:max-w-[500px] bg-canvas border-2 border-edge">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Folder className="w-5 h-5" />
@@ -119,7 +119,7 @@ export function FolderEditDialog({
className={`h-12 rounded-md border-2 transition-all hover:scale-105 ${
selectedColor === color.value
? "border-white shadow-lg scale-105"
: "border-dark-border"
: "border-edge"
}`}
style={{ backgroundColor: color.value }}
onClick={() => setSelectedColor(color.value)}
@@ -141,7 +141,7 @@ export function FolderEditDialog({
className={`h-14 rounded-md border-2 transition-all hover:scale-105 flex items-center justify-center ${
selectedIcon === value
? "border-primary bg-primary/10"
: "border-dark-border bg-dark-bg-darker"
: "border-edge bg-elevated"
}`}
onClick={() => setSelectedIcon(value)}
title={label}
@@ -156,7 +156,7 @@ export function FolderEditDialog({
<Label className="text-base font-semibold text-foreground">
{t("hosts.preview")}
</Label>
<div className="flex items-center gap-3 p-4 rounded-md bg-dark-bg-darker border border-dark-border">
<div className="flex items-center gap-3 p-4 rounded-md bg-elevated border border-edge">
{(() => {
const IconComponent =
AVAILABLE_ICONS.find((i) => i.value === selectedIcon)?.Icon ||

View File

@@ -346,8 +346,8 @@ export function ServerStats({
};
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}>
@@ -435,7 +435,7 @@ export function ServerStats({
>
{isRefreshing ? (
<div className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"></div>
<div className="w-4 h-4 border-2 border-foreground-secondary border-t-transparent rounded-full animate-spin"></div>
{t("serverStats.refreshing")}
</div>
) : (
@@ -473,15 +473,15 @@ export function ServerStats({
</div>
<Separator className="p-0.25 w-full" />
<div className="flex-1 overflow-y-auto min-h-0">
<div className="flex-1 overflow-y-auto min-h-0 thin-scrollbar">
{(metricsEnabled && showStatsUI) ||
(currentHostConfig?.quickActions &&
currentHostConfig.quickActions.length > 0) ? (
<div className="rounded-lg border-dark-border m-3 p-1 overflow-y-auto relative flex-1 flex flex-col">
<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">
{currentHostConfig?.quickActions &&
currentHostConfig.quickActions.length > 0 && (
<div className={metricsEnabled && showStatsUI ? "mb-4" : ""}>
<h3 className="text-sm font-semibold text-gray-400 mb-2">
<h3 className="text-sm font-semibold text-muted-foreground mb-2">
{t("serverStats.quickActions")}
</h3>
<div className="flex flex-wrap gap-2">
@@ -569,7 +569,7 @@ export function ServerStats({
>
{isExecuting ? (
<div className="flex items-center gap-2">
<div className="w-3 h-3 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"></div>
<div className="w-3 h-3 border-2 border-foreground-secondary border-t-transparent rounded-full animate-spin"></div>
{action.name}
</div>
) : (
@@ -589,10 +589,10 @@ export function ServerStats({
<div className="w-12 h-12 mx-auto mb-3 rounded-full bg-red-500/20 flex items-center justify-center">
<div className="w-6 h-6 border-2 border-red-400 rounded-full"></div>
</div>
<p className="text-gray-300 mb-1">
<p className="text-foreground-secondary mb-1">
{t("serverStats.serverOffline")}
</p>
<p className="text-sm text-gray-500">
<p className="text-sm text-foreground-subtle">
{t("serverStats.cannotFetchMetrics")}
</p>
</div>

View File

@@ -30,10 +30,11 @@ export function CpuWidget({ metrics, metricsHistory }: CpuWidgetProps) {
}, [metricsHistory]);
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.cpuUsage")}
</h3>
</div>
@@ -45,13 +46,13 @@ export function CpuWidget({ metrics, metricsHistory }: CpuWidgetProps) {
? `${metrics.cpu.percent}%`
: "N/A"}
</div>
<div className="text-xs text-gray-400">
<div className="text-xs text-muted-foreground">
{typeof metrics?.cpu?.cores === "number"
? t("serverStats.cpuCores", { count: metrics.cpu.cores })
: t("serverStats.naCpus")}
</div>
</div>
<div className="text-xs text-gray-500 flex-shrink-0">
<div className="text-xs text-foreground-subtle flex-shrink-0">
{metrics?.cpu?.load
? t("serverStats.loadAverage", {
avg1: metrics.cpu.load[0].toFixed(2),

View File

@@ -27,10 +27,11 @@ export function DiskWidget({ metrics }: DiskWidgetProps) {
}, [metrics]);
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.diskUsage")}
</h3>
</div>
@@ -74,7 +75,7 @@ export function DiskWidget({ metrics }: DiskWidgetProps) {
</ResponsiveContainer>
</div>
<div className="flex-shrink-0 space-y-1 text-center pb-2">
<div className="text-xs text-gray-400">
<div className="text-xs text-muted-foreground">
{(() => {
const used = metrics?.disk?.usedHuman;
const total = metrics?.disk?.totalHuman;
@@ -84,7 +85,7 @@ export function DiskWidget({ metrics }: DiskWidgetProps) {
return "N/A";
})()}
</div>
<div className="text-xs text-gray-500">
<div className="text-xs text-foreground-subtle">
{(() => {
const available = metrics?.disk?.availableHuman;
return available

View File

@@ -35,18 +35,19 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
const uniqueIPs = loginStats?.uniqueIPs || 0;
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.loginStats")}
</h3>
</div>
<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-dark-bg-darker p-2 rounded border border-dark-border/30">
<div className="flex items-center gap-1 text-xs text-gray-400 mb-1">
<div className="bg-elevated p-2 rounded border border-edge/30">
<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>
</div>
@@ -54,8 +55,8 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
{totalLogins}
</div>
</div>
<div className="bg-dark-bg-darker p-2 rounded border border-dark-border/30">
<div className="flex items-center gap-1 text-xs text-gray-400 mb-1">
<div className="bg-elevated p-2 rounded border border-edge/30">
<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>
</div>
@@ -63,37 +64,42 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
</div>
</div>
<div className="flex-1 min-h-0 overflow-y-auto space-y-2">
<div className="flex-1 min-h-0 overflow-y-auto thin-scrollbar space-y-2">
<div className="flex-shrink-0">
<div className="flex items-center gap-2 mb-1">
<UserCheck className="h-4 w-4 text-green-400" />
<span className="text-sm font-semibold text-gray-300">
<span className="text-sm font-semibold text-foreground-secondary">
{t("serverStats.recentSuccessfulLogins")}
</span>
</div>
{recentLogins.length === 0 ? (
<div className="text-xs text-gray-500 italic p-2">
<div className="text-xs text-foreground-subtle italic p-2">
{t("serverStats.noRecentLoginData")}
</div>
) : (
<div className="space-y-1">
{recentLogins.slice(0, 5).map((login) => (
<div
key={`${login.user}-${login.time}-${login.ip}`}
className="text-xs bg-dark-bg-darker p-2 rounded border border-dark-border/30 flex justify-between items-center"
key={idx}
className="text-xs bg-elevated p-2 rounded border border-edge/30 flex justify-between items-center"
>
<div className="flex items-center gap-2 min-w-0">
<span className="text-green-400 font-mono truncate">
{login.user}
</span>
<span className="text-gray-500">
<span className="text-foreground-subtle">
{t("serverStats.from")}
</span>
<span className="text-blue-400 font-mono truncate">
{login.ip}
</span>
</div>
<span className="text-gray-500 text-[10px] flex-shrink-0 ml-2">
<span className="text-foreground-subtle text-[10px] flex-shrink-0 ml-2">
{new Date(login.time).toLocaleString()}
</span>
</div>
@@ -106,7 +112,7 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
<div className="flex-shrink-0">
<div className="flex items-center gap-2 mb-1">
<UserX className="h-4 w-4 text-red-400" />
<span className="text-sm font-semibold text-gray-300">
<span className="text-sm font-semibold text-foreground-secondary">
{t("serverStats.recentFailedAttempts")}
</span>
</div>
@@ -120,14 +126,14 @@ export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
<span className="text-red-400 font-mono truncate">
{login.user}
</span>
<span className="text-gray-500">
<span className="text-foreground-subtle">
{t("serverStats.from")}
</span>
<span className="text-blue-400 font-mono truncate">
{login.ip}
</span>
</div>
<span className="text-gray-500 text-[10px] flex-shrink-0 ml-2">
<span className="text-foreground-subtle text-[10px] flex-shrink-0 ml-2">
{new Date(login.time).toLocaleString()}
</span>
</div>

View File

@@ -30,10 +30,11 @@ export function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {
}, [metricsHistory]);
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.memoryUsage")}
</h3>
</div>
@@ -45,7 +46,7 @@ export function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {
? `${metrics.memory.percent}%`
: "N/A"}
</div>
<div className="text-xs text-gray-400">
<div className="text-xs text-muted-foreground">
{(() => {
const used = metrics?.memory?.usedGiB;
const total = metrics?.memory?.totalGiB;
@@ -56,7 +57,7 @@ export function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {
})()}
</div>
</div>
<div className="text-xs text-gray-500 flex-shrink-0">
<div className="text-xs text-foreground-subtle flex-shrink-0">
{(() => {
const used = metrics?.memory?.usedGiB;
const total = metrics?.memory?.totalGiB;

View File

@@ -24,17 +24,18 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
const interfaces = network?.interfaces || [];
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.networkInterfaces")}
</h3>
</div>
<div className="space-y-2.5 overflow-auto flex-1">
<div className="space-y-2.5 overflow-auto thin-scrollbar flex-1">
{interfaces.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
<WifiOff className="h-10 w-10 mb-3 opacity-50" />
<p className="text-sm">{t("serverStats.noInterfacesFound")}</p>
</div>
@@ -42,14 +43,14 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
interfaces.map((iface, index: number) => (
<div
key={index}
className="p-3 rounded-lg bg-dark-bg/50 border border-dark-border/30 hover:bg-dark-bg/60 transition-colors"
className="p-3 rounded-lg bg-canvas/50 border border-edge/30 hover:bg-canvas/60 transition-colors"
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Wifi
className={`h-4 w-4 ${iface.state === "UP" ? "text-green-400" : "text-gray-500"}`}
className={`h-4 w-4 ${iface.state === "UP" ? "text-green-400" : "text-foreground-subtle"}`}
/>
<span className="text-sm font-semibold text-white font-mono">
<span className="text-sm font-semibold text-foreground font-mono">
{iface.name}
</span>
</div>
@@ -57,13 +58,13 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
className={`text-xs px-2.5 py-0.5 rounded-full font-medium ${
iface.state === "UP"
? "bg-green-500/20 text-green-400"
: "bg-gray-500/20 text-gray-500"
: "bg-gray-500/20 text-foreground-subtle"
}`}
>
{iface.state}
</span>
</div>
<div className="text-xs text-gray-400 font-mono font-medium">
<div className="text-xs text-muted-foreground font-mono font-medium">
{iface.ip}
</div>
</div>

View File

@@ -28,22 +28,23 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
const topProcesses = processes?.top || [];
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.processes")}
</h3>
</div>
<div className="flex items-center justify-between mb-3 pb-2 border-b border-dark-border/30">
<div className="text-sm text-gray-400">
<div className="flex items-center justify-between mb-3 pb-2 border-b border-edge/30">
<div className="text-sm text-muted-foreground">
{t("serverStats.totalProcesses")}:{" "}
<span className="text-white font-semibold">
<span className="text-foreground font-semibold">
{processes?.total ?? "N/A"}
</span>
</div>
<div className="text-sm text-gray-400">
<div className="text-sm text-muted-foreground">
{t("serverStats.running")}:{" "}
<span className="text-green-400 font-semibold">
{processes?.running ?? "N/A"}
@@ -51,9 +52,9 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
</div>
</div>
<div className="overflow-auto flex-1">
<div className="overflow-auto thin-scrollbar flex-1">
{topProcesses.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
<Activity className="h-10 w-10 mb-3 opacity-50" />
<p className="text-sm">{t("serverStats.noProcessesFound")}</p>
</div>
@@ -61,11 +62,16 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
<div className="space-y-2">
{topProcesses.map((proc) => (
<div
key={proc.pid}
className="p-2.5 rounded-lg bg-dark-bg/30 hover:bg-dark-bg/50 transition-colors border border-dark-border/20"
key={index}
className="p-2.5 rounded-lg bg-canvas/30 hover:bg-canvas/50 transition-colors border border-edge/20"
>
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-mono text-gray-400 font-medium">
<span className="text-xs font-mono text-muted-foreground font-medium">
PID: {proc.pid}
</span>
<div className="flex gap-3 text-xs font-medium">
@@ -73,10 +79,10 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
<span className="text-green-400">MEM: {proc.mem}%</span>
</div>
</div>
<div className="text-xs text-white font-mono truncate mb-1">
<div className="text-xs text-foreground font-mono truncate mb-1">
{proc.command}
</div>
<div className="text-xs text-gray-500">User: {proc.user}</div>
<div className="text-xs text-foreground-subtle">User: {proc.user}</div>
</div>
))}
</div>

View File

@@ -21,10 +21,11 @@ export function SystemWidget({ metrics }: SystemWidgetProps) {
const system = metricsWithSystem?.system;
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.systemInfo")}
</h3>
</div>
@@ -33,10 +34,10 @@ export function SystemWidget({ metrics }: SystemWidgetProps) {
<div className="flex items-start gap-3">
<Info className="h-4 w-4 text-purple-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 mb-1.5">
<p className="text-xs text-muted-foreground mb-1.5">
{t("serverStats.hostname")}
</p>
<p className="text-sm text-white font-mono truncate font-medium">
<p className="text-sm text-foreground font-mono truncate font-medium">
{system?.hostname || "N/A"}
</p>
</div>
@@ -45,10 +46,10 @@ export function SystemWidget({ metrics }: SystemWidgetProps) {
<div className="flex items-start gap-3">
<Info className="h-4 w-4 text-purple-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 mb-1.5">
<p className="text-xs text-muted-foreground mb-1.5">
{t("serverStats.operatingSystem")}
</p>
<p className="text-sm text-white font-mono truncate font-medium">
<p className="text-sm text-foreground font-mono truncate font-medium">
{system?.os || "N/A"}
</p>
</div>
@@ -57,10 +58,10 @@ export function SystemWidget({ metrics }: SystemWidgetProps) {
<div className="flex items-start gap-3">
<Info className="h-4 w-4 text-purple-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 mb-1.5">
<p className="text-xs text-muted-foreground mb-1.5">
{t("serverStats.kernel")}
</p>
<p className="text-sm text-white font-mono truncate font-medium">
<p className="text-sm text-foreground font-mono truncate font-medium">
{system?.kernel || "N/A"}
</p>
</div>

View File

@@ -20,10 +20,11 @@ export function UptimeWidget({ metrics }: UptimeWidgetProps) {
const uptime = metricsWithUptime?.uptime;
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg-darker border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<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="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-white">
<h3 className="font-semibold text-lg text-foreground">
{t("serverStats.uptime")}
</h3>
</div>
@@ -39,11 +40,11 @@ export function UptimeWidget({ metrics }: UptimeWidgetProps) {
<div className="text-3xl font-bold text-cyan-400 mb-2">
{uptime?.formatted || "N/A"}
</div>
<div className="text-sm text-gray-400">
<div className="text-sm text-muted-foreground">
{t("serverStats.totalUptime")}
</div>
{uptime?.seconds && (
<div className="text-xs text-gray-500 mt-2">
<div className="text-xs text-foreground-subtle mt-2">
{Math.floor(uptime.seconds).toLocaleString()}{" "}
{t("serverStats.seconds")}
</div>

View File

@@ -27,6 +27,7 @@ import {
TERMINAL_FONTS,
} from "@/constants/terminal-themes";
import type { TerminalConfig } from "@/types";
import { useTheme } from "@/components/theme-provider";
import { useCommandTracker } from "@/ui/hooks/useCommandTracker";
import { highlightTerminalOutput } from "@/lib/terminal-syntax-highlighter.ts";
import { useCommandHistory as useCommandHistoryHook } from "@/ui/hooks/useCommandHistory";
@@ -95,10 +96,23 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
const { instance: terminal, ref: xtermRef } = useXTerm();
const commandHistoryContext = useCommandHistory();
const { confirmWithToast } = useConfirmation();
const { theme: appTheme } = useTheme();
const config = { ...DEFAULT_TERMINAL_CONFIG, ...hostConfig.terminalConfig };
const themeColors =
TERMINAL_THEMES[config.theme]?.colors || TERMINAL_THEMES.termix.colors;
// Auto-switch terminal theme based on app theme when using "termix" (default)
const isDarkMode = appTheme === "dark" ||
(appTheme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
let themeColors;
if (config.theme === "termix") {
// Auto-switch between termixDark and termixLight based on app theme
themeColors = isDarkMode
? TERMINAL_THEMES.termixDark.colors
: TERMINAL_THEMES.termixLight.colors;
} else {
themeColors = TERMINAL_THEMES[config.theme]?.colors || TERMINAL_THEMES.termixDark.colors;
}
const backgroundColor = themeColors.background;
const fitAddonRef = useRef<FitAddon | null>(null);
const webSocketRef = useRef<WebSocket | null>(null);
@@ -1046,8 +1060,15 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
...hostConfig.terminalConfig,
};
const themeColors =
TERMINAL_THEMES[config.theme]?.colors || TERMINAL_THEMES.termix.colors;
// Auto-switch terminal theme based on app theme when using "termix" (default)
let themeColors;
if (config.theme === "termix") {
themeColors = isDarkMode
? TERMINAL_THEMES.termixDark.colors
: TERMINAL_THEMES.termixLight.colors;
} else {
themeColors = TERMINAL_THEMES[config.theme]?.colors || TERMINAL_THEMES.termixDark.colors;
}
const fontConfig = TERMINAL_FONTS.find(
(f) => f.value === config.fontFamily,
@@ -1228,7 +1249,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
}
webSocketRef.current?.close();
};
}, [xtermRef, terminal, hostConfig]);
}, [xtermRef, terminal, hostConfig, isDarkMode]);
useEffect(() => {
if (!terminal) return;
@@ -1579,20 +1600,32 @@ style.innerHTML = `
font-display: swap;
}
/* Light theme scrollbars */
.xterm .xterm-viewport::-webkit-scrollbar {
width: 8px;
background: transparent;
}
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
background: rgba(180,180,180,0.7);
background: rgba(0,0,0,0.3);
border-radius: 4px;
}
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
background: rgba(120,120,120,0.9);
background: rgba(0,0,0,0.5);
}
.xterm .xterm-viewport {
scrollbar-width: thin;
scrollbar-color: rgba(180,180,180,0.7) transparent;
scrollbar-color: rgba(0,0,0,0.3) transparent;
}
/* Dark theme scrollbars */
.dark .xterm .xterm-viewport::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.3);
}
.dark .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.5);
}
.dark .xterm .xterm-viewport {
scrollbar-color: rgba(255,255,255,0.3) transparent;
}
.xterm {

View File

@@ -1,5 +1,6 @@
import type { TerminalTheme } from "@/constants/terminal-themes";
import { TERMINAL_THEMES, TERMINAL_FONTS } from "@/constants/terminal-themes";
import { useTheme } from "@/components/theme-provider";
interface TerminalPreviewProps {
theme: string;
@@ -20,6 +21,15 @@ export function TerminalPreview({
letterSpacing = 0,
lineHeight = 1.2,
}: TerminalPreviewProps) {
const { theme: appTheme } = useTheme();
// Resolve "termix" to termixDark or termixLight based on app theme
const resolvedTheme = theme === "termix"
? (appTheme === "dark" || (appTheme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches)
? "termixDark"
: "termixLight")
: theme;
return (
<div className="border border-input rounded-md overflow-hidden">
<div
@@ -31,33 +41,33 @@ export function TerminalPreview({
TERMINAL_FONTS[0].fallback,
letterSpacing: `${letterSpacing}px`,
lineHeight,
background: TERMINAL_THEMES[theme]?.colors.background || "#18181b",
color: TERMINAL_THEMES[theme]?.colors.foreground || "#f7f7f7",
background: TERMINAL_THEMES[resolvedTheme]?.colors.background || "var(--bg-base)",
color: TERMINAL_THEMES[resolvedTheme]?.colors.foreground || "var(--foreground)",
}}
>
<div>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.green }}>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.green }}>
user@termix
</span>
<span>:</span>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.blue }}>~</span>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.blue }}>~</span>
<span>$ ls -la</span>
</div>
<div>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.blue }}>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.blue }}>
drwxr-xr-x
</span>
<span> 5 user </span>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.cyan }}>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.cyan }}>
docs
</span>
</div>
<div>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.green }}>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.green }}>
-rwxr-xr-x
</span>
<span> 1 user </span>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.green }}>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.green }}>
script.sh
</span>
</div>
@@ -67,11 +77,11 @@ export function TerminalPreview({
<span>README.md</span>
</div>
<div>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.green }}>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.green }}>
user@termix
</span>
<span>:</span>
<span style={{ color: TERMINAL_THEMES[theme]?.colors.blue }}>~</span>
<span style={{ color: TERMINAL_THEMES[resolvedTheme]?.colors.blue }}>~</span>
<span>$ </span>
<span
className="inline-block"
@@ -83,7 +93,7 @@ export function TerminalPreview({
: cursorStyle === "bar"
? `${fontSize}px`
: `${fontSize}px`,
background: TERMINAL_THEMES[theme]?.colors.cursor || "#f7f7f7",
background: TERMINAL_THEMES[resolvedTheme]?.colors.cursor || "#f7f7f7",
animation: cursorBlink ? "blink 1s step-end infinite" : "none",
verticalAlign:
cursorStyle === "underline" ? "bottom" : "text-bottom",

View File

@@ -38,7 +38,7 @@ export function CommandAutocomplete({
return (
<div
ref={containerRef}
className="fixed z-[9999] bg-dark-bg border border-dark-border rounded-md shadow-lg min-w-[200px] max-w-[600px] flex flex-col"
className="fixed z-[9999] bg-canvas border border-edge rounded-md shadow-lg min-w-[200px] max-w-[600px] flex flex-col"
style={{
top: `${position.top}px`,
left: `${position.left}px`,
@@ -46,7 +46,7 @@ export function CommandAutocomplete({
}}
>
<div
className="overflow-y-auto"
className="overflow-y-auto thin-scrollbar"
style={{ maxHeight: `${maxSuggestionsHeight}px` }}
>
{suggestions.map((suggestion, index) => (
@@ -55,8 +55,8 @@ export function CommandAutocomplete({
ref={index === selectedIndex ? selectedRef : null}
className={cn(
"px-3 py-1.5 text-sm font-mono cursor-pointer transition-colors",
"hover:bg-dark-hover",
index === selectedIndex && "bg-gray-500/20 text-gray-400",
"hover:bg-hover",
index === selectedIndex && "bg-gray-500/20 text-muted-foreground",
)}
onClick={() => onSelect(suggestion)}
onMouseEnter={() => {}}
@@ -65,7 +65,7 @@ export function CommandAutocomplete({
</div>
))}
</div>
<div className="px-3 py-1 text-xs text-muted-foreground border-t border-dark-border bg-dark-bg/50 shrink-0">
<div className="px-3 py-1 text-xs text-muted-foreground border-t border-edge bg-canvas/50 shrink-0">
Tab/Enter to complete to navigate Esc to close
</div>
</div>

View File

@@ -1113,7 +1113,7 @@ export function SSHToolsSidebar({
className="pointer-events-auto"
>
<SidebarHeader>
<SidebarGroupLabel className="text-lg font-bold text-white">
<SidebarGroupLabel className="text-lg font-bold text-foreground">
{t("nav.tools")}
<div className="absolute right-5 flex gap-1">
<Button
@@ -1158,7 +1158,7 @@ export function SSHToolsSidebar({
</TabsList>
<TabsContent value="ssh-tools" className="space-y-4">
<h3 className="font-semibold text-white">
<h3 className="font-semibold text-foreground">
{t("sshTools.keyRecording")}
</h3>
@@ -1186,10 +1186,10 @@ export function SSHToolsSidebar({
{isRecording && (
<>
<div className="space-y-2">
<label className="text-sm font-medium text-white">
<label className="text-sm font-medium text-foreground">
{t("sshTools.selectTerminals")}
</label>
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto">
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto thin-scrollbar">
{terminalTabs.map((tab) => (
<Button
key={tab.id}
@@ -1198,8 +1198,8 @@ export function SSHToolsSidebar({
size="sm"
className={`rounded-full px-3 py-1 text-xs flex items-center gap-1 ${
selectedTabIds.includes(tab.id)
? "text-white bg-gray-700"
: "text-gray-500"
? "text-foreground bg-gray-700"
: "text-foreground-subtle"
}`}
onClick={() => handleTabToggle(tab.id)}
>
@@ -1210,7 +1210,7 @@ export function SSHToolsSidebar({
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">
<label className="text-sm font-medium text-foreground">
{t("sshTools.typeCommands")}
</label>
<Input
@@ -1234,7 +1234,7 @@ export function SSHToolsSidebar({
<Separator />
<h3 className="font-semibold text-white">
<h3 className="font-semibold text-foreground">
{t("sshTools.settings")}
</h3>
@@ -1246,7 +1246,7 @@ export function SSHToolsSidebar({
/>
<label
htmlFor="enable-copy-paste"
className="text-sm font-medium leading-none text-white cursor-pointer"
className="text-sm font-medium leading-none text-foreground cursor-pointer"
>
{t("sshTools.enableRightClickCopyPaste")}
</label>
@@ -1271,7 +1271,7 @@ export function SSHToolsSidebar({
})
: t("snippets.executeOnCurrent")}
</p>
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto">
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto thin-scrollbar">
{terminalTabs.map((tab) => (
<Button
key={tab.id}
@@ -1280,8 +1280,8 @@ export function SSHToolsSidebar({
size="sm"
className={`rounded-full px-3 py-1 text-xs flex items-center gap-1 ${
selectedSnippetTabIds.includes(tab.id)
? "text-white bg-gray-700"
: "text-gray-500"
? "text-foreground bg-gray-700"
: "text-foreground-subtle"
}`}
onClick={() => handleSnippetTabToggle(tab.id)}
>
@@ -1327,7 +1327,7 @@ export function SSHToolsSidebar({
</div>
) : (
<TooltipProvider>
<div className="space-y-3 overflow-y-auto flex-1 min-h-0">
<div className="space-y-3 overflow-y-auto flex-1 min-h-0 thin-scrollbar">
{Array.from(groupSnippetsByFolder()).map(
([folderName, folderSnippets]) => {
const folderMetadata = snippetFolders.find(
@@ -1338,7 +1338,7 @@ export function SSHToolsSidebar({
return (
<div key={folderName || "uncategorized"}>
<div className="flex items-center gap-2 mb-2 hover:bg-dark-hover-alt p-2 rounded-lg transition-colors group/folder">
<div className="flex items-center gap-2 mb-2 hover:bg-hover-alt p-2 rounded-lg transition-colors group/folder">
<div
className="flex items-center gap-2 flex-1 cursor-pointer"
onClick={() => toggleFolder(folderName)}
@@ -1429,7 +1429,7 @@ export function SSHToolsSidebar({
}
onDrop={(e) => handleDrop(e, snippet)}
onDragEnd={handleDragEnd}
className={`bg-dark-bg-input border border-input rounded-lg cursor-move hover:shadow-lg hover:border-gray-400/50 hover:bg-dark-hover-alt transition-all duration-200 p-3 group ${
className={`bg-field border border-input rounded-lg cursor-move hover:shadow-lg hover:border-gray-400/50 hover:bg-hover-alt transition-all duration-200 p-3 group ${
draggedSnippet?.id === snippet.id
? "opacity-50"
: ""
@@ -1438,7 +1438,7 @@ export function SSHToolsSidebar({
<div className="mb-2 flex items-center gap-2">
<GripVertical className="h-4 w-4 text-muted-foreground flex-shrink-0 opacity-50 group-hover:opacity-100 transition-opacity" />
<div className="flex-1 min-w-0">
<h3 className="text-sm font-medium text-white mb-1">
<h3 className="text-sm font-medium text-foreground mb-1">
{snippet.name}
</h3>
{snippet.description && (
@@ -1645,16 +1645,16 @@ export function SSHToolsSidebar({
) : (
<div
ref={commandHistoryScrollRef}
className="space-y-2 overflow-y-auto h-full"
className="space-y-2 overflow-y-auto h-full thin-scrollbar"
>
{filteredCommands.map((command, index) => (
<div
key={index}
className="bg-dark-bg border-2 border-dark-border rounded-md px-3 py-2.5 hover:bg-dark-hover-alt hover:border-gray-600 transition-all duration-200 group h-12 flex items-center"
className="bg-canvas border-2 border-edge rounded-md px-3 py-2.5 hover:bg-hover-alt hover:border-gray-600 transition-all duration-200 group h-12 flex items-center"
>
<div className="flex items-center justify-between gap-2 w-full min-w-0">
<span
className="flex-1 font-mono text-sm cursor-pointer text-white truncate"
className="flex-1 font-mono text-sm cursor-pointer text-foreground truncate"
onClick={() => handleCommandSelect(command)}
title={command}
>
@@ -1684,7 +1684,7 @@ export function SSHToolsSidebar({
value="split-screen"
className="flex flex-col flex-1 overflow-hidden"
>
<div className="space-y-4 flex-1 overflow-y-auto overflow-x-hidden pb-4">
<div className="space-y-4 flex-1 overflow-y-auto overflow-x-hidden pb-4 thin-scrollbar">
<Tabs
value={splitMode}
onValueChange={(value) =>
@@ -1718,7 +1718,7 @@ export function SSHToolsSidebar({
<p className="text-xs text-muted-foreground mb-2">
{t("splitScreen.dragTabsHint")}
</p>
<div className="space-y-1 max-h-[200px] overflow-y-auto">
<div className="space-y-1 max-h-[200px] overflow-y-auto thin-scrollbar">
{splittableTabs.map((tab) => {
const isAssigned = Array.from(
splitAssignments.values(),
@@ -1737,8 +1737,8 @@ export function SSHToolsSidebar({
px-3 py-2 rounded-md text-sm cursor-move transition-all
${
isAssigned
? "bg-dark-bg/50 text-muted-foreground cursor-not-allowed opacity-50"
: "bg-dark-bg border border-dark-border hover:border-gray-400 hover:bg-dark-bg-input"
? "bg-canvas/50 text-muted-foreground cursor-not-allowed opacity-50"
: "bg-canvas border border-edge hover:border-gray-400 hover:bg-field"
}
${isDragging ? "opacity-50" : ""}
`}
@@ -1787,12 +1787,12 @@ export function SSHToolsSidebar({
onDragLeave={handleTabDragLeave}
onDrop={() => handleTabDrop(idx)}
className={`
relative bg-dark-bg border-2 rounded-md p-3 min-h-[100px]
relative bg-canvas border-2 rounded-md p-3 min-h-[100px]
flex flex-col items-center justify-center transition-all
${splitMode === "3" && idx === 2 ? "col-span-2" : ""}
${
isEmpty
? "border-dashed border-dark-border"
? "border-dashed border-edge"
: "border-solid border-gray-400 bg-gray-500/10"
}
${
@@ -1804,7 +1804,7 @@ export function SSHToolsSidebar({
>
{assignedTab ? (
<>
<span className="text-sm text-white truncate w-full text-center mb-2">
<span className="text-sm text-foreground truncate w-full text-center mb-2">
{assignedTab.title}
</span>
<Button
@@ -1872,13 +1872,13 @@ export function SSHToolsSidebar({
left: "-8px",
width: "18px",
backgroundColor: isResizing
? "var(--dark-active)"
? "var(--bg-active)"
: "transparent",
}}
onMouseEnter={(e) => {
if (!isResizing) {
e.currentTarget.style.backgroundColor =
"var(--dark-border-hover)";
"var(--border-hover)";
}
}}
onMouseLeave={(e) => {
@@ -1900,11 +1900,11 @@ export function SSHToolsSidebar({
onClick={() => setShowDialog(false)}
>
<div
className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"
className="bg-canvas border-2 border-edge rounded-lg p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto thin-scrollbar"
onClick={(e) => e.stopPropagation()}
>
<div className="mb-6">
<h2 className="text-xl font-semibold text-white">
<h2 className="text-xl font-semibold text-foreground">
{editingSnippet ? t("snippets.edit") : t("snippets.create")}
</h2>
<p className="text-sm text-muted-foreground mt-1">
@@ -1916,7 +1916,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.name")}
<span className="text-destructive">*</span>
</label>
@@ -1937,7 +1937,7 @@ export function SSHToolsSidebar({
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">
<label className="text-sm font-medium text-foreground">
{t("snippets.description")}
<span className="text-muted-foreground ml-1">
({t("common.optional")})
@@ -1953,7 +1953,7 @@ export function SSHToolsSidebar({
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white flex items-center gap-2">
<label className="text-sm font-medium text-foreground flex items-center gap-2">
<Folder className="h-4 w-4" />
{t("snippets.folder")}
<span className="text-muted-foreground">
@@ -1999,7 +1999,7 @@ export function SSHToolsSidebar({
</div>
<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.content")}
<span className="text-destructive">*</span>
</label>
@@ -2044,11 +2044,11 @@ export function SSHToolsSidebar({
onClick={() => setShowFolderDialog(false)}
>
<div
className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-lg w-full mx-4"
className="bg-canvas border-2 border-edge rounded-lg p-6 max-w-lg w-full mx-4"
onClick={(e) => e.stopPropagation()}
>
<div className="mb-6">
<h2 className="text-xl font-semibold text-white">
<h2 className="text-xl font-semibold text-foreground">
{editingFolder
? t("snippets.editFolder")
: t("snippets.createFolder")
@@ -2097,7 +2097,7 @@ export function SSHToolsSidebar({
className={`h-12 rounded-md border-2 transition-all hover:scale-105 ${
folderFormData.color === color.value
? "border-white shadow-lg scale-105"
: "border-dark-border"
: "border-edge"
}`}
style={{ backgroundColor: color.value }}
onClick={() =>
@@ -2124,7 +2124,7 @@ export function SSHToolsSidebar({
className={`h-14 rounded-md border-2 transition-all hover:scale-105 flex items-center justify-center ${
folderFormData.icon === value
? "border-primary bg-primary/10"
: "border-dark-border bg-dark-bg-darker"
: "border-edge bg-elevated"
}`}
onClick={() =>
setFolderFormData({ ...folderFormData, icon: value })
@@ -2141,7 +2141,7 @@ export function SSHToolsSidebar({
<Label className="text-base font-semibold text-white">
{t("snippets.preview")}
</Label>
<div className="flex items-center gap-3 p-4 rounded-md bg-dark-bg-darker border border-dark-border">
<div className="flex items-center gap-3 p-4 rounded-md bg-elevated border border-edge">
{(() => {
const IconComponent =
AVAILABLE_ICONS.find(

View File

@@ -43,7 +43,7 @@ export function TunnelViewer({
return (
<div className="w-full h-full flex flex-col overflow-hidden p-3 min-h-0">
<div className="min-h-0 flex-1 overflow-auto pr-1">
<div className="min-h-0 flex-1 overflow-auto thin-scrollbar pr-1">
<div className="grid grid-cols-[repeat(auto-fit,minmax(320px,1fr))] gap-3 auto-rows-min content-start w-full">
{activeHost.tunnelConnections.map((t, idx) => (
<TunnelObject