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:
@@ -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>
|
||||
|
||||
@@ -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)",
|
||||
},
|
||||
}),
|
||||
]}
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
})}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
: ""
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user