Remove more inline styles and run npm updates

This commit is contained in:
LukeGus
2025-09-09 21:41:42 -05:00
parent 56a1dd0b79
commit d622c1fa03
46 changed files with 1161 additions and 1835 deletions

1873
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -488,6 +488,7 @@
"messageParseError": "Failed to parse server message",
"websocketError": "WebSocket connection error",
"reconnecting": "Reconnecting... ({{attempt}}/{{max}})",
"reconnected": "Reconnected successfully",
"maxReconnectAttemptsReached": "Maximum reconnection attempts reached"
},
"fileManager": {

View File

@@ -535,20 +535,24 @@ router.post('/file_manager/recent', authenticateJWT, async (req: Request, res: R
});
// Route: Remove recent file (requires JWT)
// DELETE /ssh/file_manager/recent/:id
router.delete('/file_manager/recent/:id', authenticateJWT, async (req: Request, res: Response) => {
// DELETE /ssh/file_manager/recent
router.delete('/file_manager/recent', authenticateJWT, async (req: Request, res: Response) => {
const userId = (req as any).userId;
const id = req.params.id;
const { hostId, path, name } = req.body;
if (!isNonEmptyString(userId) || !id) {
sshLogger.warn('Invalid userId or id for recent file deletion');
return res.status(400).json({error: 'Invalid userId or id'});
if (!isNonEmptyString(userId) || !hostId || !path) {
sshLogger.warn('Invalid data for recent file deletion');
return res.status(400).json({error: 'Invalid data'});
}
try {
await db
.delete(fileManagerRecent)
.where(and(eq(fileManagerRecent.id, Number(id)), eq(fileManagerRecent.userId, userId)));
.where(and(
eq(fileManagerRecent.userId, userId),
eq(fileManagerRecent.hostId, hostId),
eq(fileManagerRecent.path, path)
));
res.json({message: 'Recent file removed'});
} catch (err) {
@@ -629,20 +633,24 @@ router.post('/file_manager/pinned', authenticateJWT, async (req: Request, res: R
});
// Route: Remove pinned file (requires JWT)
// DELETE /ssh/file_manager/pinned/:id
router.delete('/file_manager/pinned/:id', authenticateJWT, async (req: Request, res: Response) => {
// DELETE /ssh/file_manager/pinned
router.delete('/file_manager/pinned', authenticateJWT, async (req: Request, res: Response) => {
const userId = (req as any).userId;
const id = req.params.id;
const { hostId, path, name } = req.body;
if (!isNonEmptyString(userId) || !id) {
sshLogger.warn('Invalid userId or id for pinned file deletion');
return res.status(400).json({error: 'Invalid userId or id'});
if (!isNonEmptyString(userId) || !hostId || !path) {
sshLogger.warn('Invalid data for pinned file deletion');
return res.status(400).json({error: 'Invalid data'});
}
try {
await db
.delete(fileManagerPinned)
.where(and(eq(fileManagerPinned.id, Number(id)), eq(fileManagerPinned.userId, userId)));
.where(and(
eq(fileManagerPinned.userId, userId),
eq(fileManagerPinned.hostId, hostId),
eq(fileManagerPinned.path, path)
));
res.json({message: 'Pinned file removed'});
} catch (err) {
@@ -723,20 +731,24 @@ router.post('/file_manager/shortcuts', authenticateJWT, async (req: Request, res
});
// Route: Remove shortcut (requires JWT)
// DELETE /ssh/file_manager/shortcuts/:id
router.delete('/file_manager/shortcuts/:id', authenticateJWT, async (req: Request, res: Response) => {
// DELETE /ssh/file_manager/shortcuts
router.delete('/file_manager/shortcuts', authenticateJWT, async (req: Request, res: Response) => {
const userId = (req as any).userId;
const id = req.params.id;
const { hostId, path, name } = req.body;
if (!isNonEmptyString(userId) || !id) {
sshLogger.warn('Invalid userId or id for shortcut deletion');
return res.status(400).json({error: 'Invalid userId or id'});
if (!isNonEmptyString(userId) || !hostId || !path) {
sshLogger.warn('Invalid data for shortcut deletion');
return res.status(400).json({error: 'Invalid data'});
}
try {
await db
.delete(fileManagerShortcuts)
.where(and(eq(fileManagerShortcuts.id, Number(id)), eq(fileManagerShortcuts.userId, userId)));
.where(and(
eq(fileManagerShortcuts.userId, userId),
eq(fileManagerShortcuts.hostId, hostId),
eq(fileManagerShortcuts.path, path)
));
res.json({message: 'Shortcut removed'});
} catch (err) {

View File

@@ -25,13 +25,13 @@ export function LanguageSwitcher() {
};
return (
<div className="flex items-center gap-2 relative" style={{ zIndex: 99999 }}>
<div className="flex items-center gap-2 relative z-[99999]">
<Globe className="h-4 w-4 text-muted-foreground" />
<Select value={i18n.language} onValueChange={handleLanguageChange}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder={t('placeholders.language')} />
</SelectTrigger>
<SelectContent style={{ zIndex: 99999 }}>
<SelectContent className="z-[99999]">
{languages.map((lang) => (
<SelectItem key={lang.code} value={lang.code}>
{lang.nativeName}

View File

@@ -37,13 +37,13 @@ function ResizableHandle({
<ResizablePrimitive.PanelResizeHandle
data-slot="resizable-handle"
className={cn(
"relative flex w-1 items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-1 data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90 bg-[#434345] hover:bg-[#2a2a2c] active:bg-[#1a1a1c] transition-colors duration-150",
"relative flex w-1 items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-1 data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90 bg-dark-border-hover hover:bg-dark-active active:bg-dark-pressed transition-colors duration-150",
className
)}
{...props}
>
{withHandle && (
<div className="bg-[#434345] hover:bg-[#2a2a2c] active:bg-[#1a1a1c] z-10 flex h-4 w-3 items-center justify-center rounded-xs border transition-colors duration-150">
<div className="bg-dark-border-hover hover:bg-dark-active active:bg-dark-pressed z-10 flex h-4 w-3 items-center justify-center rounded-xs border transition-colors duration-150">
<GripVerticalIcon className="size-2.5" />
</div>
)}

View File

@@ -86,6 +86,29 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
/* Custom dark theme colors - exact hex matches */
--color-dark-bg: #18181b;
--color-dark-bg-darker: #0e0e10;
--color-dark-bg-darkest: #09090b;
--color-dark-bg-input: #222225;
--color-dark-bg-button: #23232a;
--color-dark-bg-active: #1d1d1f;
--color-dark-bg-header: #131316;
--color-dark-border: #303032;
--color-dark-border-active: #2d2d30;
--color-dark-border-hover: #434345;
--color-dark-hover: #2d2d30;
--color-dark-active: #2a2a2c;
--color-dark-pressed: #1a1a1c;
--color-dark-hover-alt: #2a2a2d;
--color-dark-border-light: #5a5a5d;
--color-dark-bg-light: #141416;
--color-dark-border-medium: #373739;
--color-dark-bg-very-light: #101014;
--color-dark-bg-panel: #1b1b1e;
--color-dark-border-panel: #222224;
--color-dark-bg-panel-hover: #232327;
}
.dark {

View File

@@ -5,6 +5,7 @@ import {Button} from "@/components/ui/button.tsx";
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert.tsx";
import {Checkbox} from "@/components/ui/checkbox.tsx";
import {Input} from "@/components/ui/input.tsx";
import {PasswordInput} from "@/components/ui/password-input.tsx";
import {Label} from "@/components/ui/label.tsx";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx";
import {
@@ -219,7 +220,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
return (
<div style={wrapperStyle}
className="bg-[#18181b] text-white rounded-lg border-2 border-[#303032] overflow-hidden">
className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden">
<div className="h-full w-full flex flex-col">
<div className="flex items-center justify-between px-3 pt-2 pb-2">
<h1 className="font-bold text-lg">{t('admin.title')}</h1>
@@ -228,7 +229,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
<div className="px-6 py-4 overflow-auto">
<Tabs defaultValue="registration" className="w-full">
<TabsList className="mb-4 bg-[#18181b] border-2 border-[#303032]">
<TabsList className="mb-4 bg-dark-bg border-2 border-dark-border">
<TabsTrigger value="registration" className="flex items-center gap-2">
<Users className="h-4 w-4"/>
{t('admin.general')}
@@ -279,7 +280,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
</div>
<div className="space-y-2">
<Label htmlFor="client_secret">{t('admin.clientSecret')}</Label>
<Input id="client_secret" type="password" value={oidcConfig.client_secret}
<PasswordInput id="client_secret" value={oidcConfig.client_secret}
onChange={(e) => handleOIDCConfigChange('client_secret', e.target.value)}
placeholder={t('placeholders.clientSecret')} required/>
</div>

View File

@@ -13,6 +13,7 @@ import {
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { PasswordInput } from "@/components/ui/password-input"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
@@ -390,7 +391,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
{folderDropdownOpen && filteredFolders.length > 0 && (
<div
ref={folderDropdownRef}
className="absolute top-full left-0 z-50 mt-1 w-full bg-[#18181b] 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-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{filteredFolders.map((folder) => (
@@ -420,7 +421,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
<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-[#222225] focus-within:ring-2 ring-ring min-h-[40px]">
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]">
{(field.value || []).map((tag: string, idx: number) => (
<span key={`${tag}-${idx}`}
className="flex items-center bg-gray-200 text-gray-800 rounded-full px-2 py-0.5 text-xs">
@@ -509,7 +510,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
<FormItem>
<FormLabel>{t('credentials.password')}</FormLabel>
<FormControl>
<Input type="password" placeholder={t('placeholders.password')} {...field} />
<PasswordInput placeholder={t('placeholders.password')} {...field} />
</FormControl>
</FormItem>
)}
@@ -576,9 +577,8 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
<FormItem className="col-span-8">
<FormLabel>{t('credentials.keyPassword')}</FormLabel>
<FormControl>
<Input
<PasswordInput
placeholder={t('placeholders.keyPassword')}
type="password"
{...field}
/>
</FormControl>
@@ -597,7 +597,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
ref={keyTypeButtonRef}
type="button"
variant="outline"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-[#18181b] border border-input text-foreground"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-dark-bg border border-input text-foreground"
onClick={() => setKeyTypeDropdownOpen((open) => !open)}
>
{keyTypeOptions.find((opt) => opt.value === field.value)?.label || t('credentials.keyTypeRSA')}
@@ -605,7 +605,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
{keyTypeDropdownOpen && (
<div
ref={keyTypeDropdownRef}
className="absolute bottom-full left-0 z-50 mb-1 w-full bg-[#18181b] 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-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{keyTypeOptions.map((opt) => (
@@ -614,7 +614,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
type="button"
variant="ghost"
size="sm"
className="w-full justify-start text-left rounded-md px-2 py-1.5 bg-[#18181b] 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-dark-bg text-foreground hover:bg-white/15 focus:bg-white/20 focus:outline-none"
onClick={() => {
field.onChange(opt.value);
setKeyTypeDropdownOpen(false);
@@ -659,9 +659,8 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
<FormItem className="col-span-8">
<FormLabel>{t('credentials.keyPassword')}</FormLabel>
<FormControl>
<Input
<PasswordInput
placeholder={t('placeholders.keyPassword')}
type="password"
{...field}
/>
</FormControl>
@@ -680,7 +679,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
ref={keyTypeButtonRef}
type="button"
variant="outline"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-[#18181b] border border-input text-foreground"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-dark-bg border border-input text-foreground"
onClick={() => setKeyTypeDropdownOpen((open) => !open)}
>
{keyTypeOptions.find((opt) => opt.value === field.value)?.label || t('credentials.keyTypeRSA')}
@@ -688,7 +687,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
{keyTypeDropdownOpen && (
<div
ref={keyTypeDropdownRef}
className="absolute bottom-full left-0 z-50 mb-1 w-full bg-[#18181b] 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-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{keyTypeOptions.map((opt) => (
@@ -697,7 +696,7 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
type="button"
variant="ghost"
size="sm"
className="w-full justify-start text-left rounded-md px-2 py-1.5 bg-[#18181b] 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-dark-bg text-foreground hover:bg-white/15 focus:bg-white/20 focus:outline-none"
onClick={() => {
field.onChange(opt.value);
setKeyTypeDropdownOpen(false);
@@ -725,12 +724,9 @@ export function CredentialEditor({ editingCredential, onFormSubmit }: Credential
<footer className="shrink-0 w-full pb-0">
<Separator className="p-0.25"/>
<Button
className=""
className="translate-y-2"
type="submit"
variant="outline"
style={{
transform: 'translateY(8px)'
}}
>
{editingCredential ? t('credentials.updateCredential') : t('credentials.addCredential')}
</Button>

View File

@@ -438,7 +438,7 @@ export function CredentialsManager({ onEditCredential }: CredentialsManagerProps
draggable
onDragStart={(e) => handleDragStart(e, credential)}
onDragEnd={handleDragEnd}
className={`bg-[#222225] border border-input rounded-lg cursor-pointer hover:shadow-lg hover:border-blue-400/50 hover:bg-[#2a2a2d] transition-all duration-200 p-3 group relative ${
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 ${
draggedCredential?.id === credential.id ? 'opacity-50 scale-95' : ''
}`}
onClick={() => handleEdit(credential)}

View File

@@ -28,10 +28,11 @@ import {
} from '@/ui/main-axios.ts';
import type { SSHHost, Tab, FileManagerProps } from '../../../types/index.js';
export function FileManager({onSelectView, embedded = false, initialHost = null}: {
export function FileManager({onSelectView, embedded = false, initialHost = null, onClose}: {
onSelectView?: (view: string) => void,
embedded?: boolean,
initialHost?: SSHHost | null
initialHost?: SSHHost | null,
onClose?: () => void
}): React.ReactElement {
const {t} = useTranslation();
const [tabs, setTabs] = useState<Tab[]>([]);
@@ -75,12 +76,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
}
}, [currentHost]);
useEffect(() => {
if (activeTab === 'home' && currentHost) {
fetchHomeData();
}
}, [activeTab, currentHost]);
useEffect(() => {
if (activeTab === 'home' && currentHost) {
const interval = setInterval(() => {
@@ -130,6 +125,10 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
console.error('Failed to fetch home data:', err);
const {toast} = await import('sonner');
toast.error(t('fileManager.failedToFetchHomeData'));
// Close the file manager tab on connection failure
if (onClose) {
onClose();
}
}
}
@@ -185,7 +184,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
sshSessionId: currentSshSessionId,
hostId: currentHost?.id
});
fetchHomeData();
} catch (err: any) {
const errorMessage = formatErrorMessage(err, t('fileManager.cannotReadFile'));
toast.error(errorMessage);
@@ -218,7 +216,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
sshSessionId: file.sshSessionId,
hostId: currentHost?.id
});
fetchHomeData();
if (sidebarRef.current && sidebarRef.current.fetchFiles) {
sidebarRef.current.fetchFiles();
}
@@ -235,7 +232,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
sshSessionId: file.sshSessionId,
hostId: currentHost?.id
});
fetchHomeData();
if (sidebarRef.current && sidebarRef.current.fetchFiles) {
sidebarRef.current.fetchFiles();
}
@@ -275,7 +271,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
sshSessionId: currentHost?.id.toString(),
hostId: currentHost?.id
});
fetchHomeData();
} catch (err) {
}
};
@@ -289,7 +284,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
sshSessionId: currentHost?.id.toString(),
hostId: currentHost?.id
});
fetchHomeData();
} catch (err) {
}
};
@@ -302,9 +296,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
if (newTabs.length > 0) setActiveTab(newTabs[Math.max(0, idx - 1)].id);
else setActiveTab('home');
}
if (currentHost) {
fetchHomeData();
}
};
const setTabContent = (tabId: string | number, content: string) => {
@@ -401,13 +392,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
console.error('Failed to add recent file:', recentErr);
}
})(),
(async () => {
try {
await fetchHomeData();
} catch (refreshErr) {
console.error('Failed to refresh home data:', refreshErr);
}
})()
]).then(() => {
});
@@ -435,9 +419,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
if (sidebarRef.current && sidebarRef.current.fetchFiles) {
sidebarRef.current.fetchFiles();
}
if (currentHost) {
fetchHomeData();
}
};
const handleSuccess = (message: string) => {
@@ -479,8 +460,8 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
if (!currentHost) {
return (
<div style={{position: 'absolute', inset: 0, overflow: 'hidden'}} className="rounded-md">
<div style={{position: 'absolute', top: 0, left: 0, width: 256, height: '100%', zIndex: 20}}>
<div className="absolute inset-0 overflow-hidden rounded-md">
<div className="absolute top-0 left-0 w-64 h-full z-[20]">
<FileManagerLeftSidebar
onSelectView={onSelectView || (() => {
})}
@@ -494,17 +475,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
onPathChange={updateCurrentPath}
/>
</div>
<div style={{
position: 'absolute',
top: 0,
left: 256,
right: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#09090b'
}}>
<div className="absolute top-0 left-64 right-0 bottom-0 flex items-center justify-center bg-dark-bg-darkest">
<div className="text-center">
<h2 className="text-xl font-semibold text-white mb-2">{t('fileManager.connectToServer')}</h2>
<p className="text-muted-foreground">{t('fileManager.selectServerToEdit')}</p>
@@ -515,8 +486,8 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
}
return (
<div style={{position: 'absolute', inset: 0, overflow: 'hidden'}} className="rounded-md">
<div style={{position: 'absolute', top: 0, left: 0, width: 256, height: '100%', zIndex: 20}}>
<div className="absolute inset-0 overflow-hidden rounded-md">
<div className="absolute top-0 left-0 w-64 h-full z-[20]">
<FileManagerLeftSidebar
onSelectView={onSelectView || (() => {
})}
@@ -531,10 +502,10 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
onDeleteItem={handleDeleteFromSidebar}
/>
</div>
<div style={{position: 'absolute', top: 0, left: 256, right: 0, height: 50, zIndex: 30}}>
<div className="flex items-center w-full bg-[#18181b] border-b-2 border-[#303032] h-[50px] relative">
<div className="absolute top-0 left-64 right-0 h-[50px] z-[30]">
<div className="flex items-center w-full bg-dark-bg border-b-2 border-dark-border h-[50px] relative">
<div
className="h-full p-1 pr-2 border-r-2 border-[#303032] w-[calc(100%-6rem)] flex items-center overflow-x-auto overflow-y-hidden gap-2 thin-scrollbar">
className="h-full p-1 pr-2 border-r-2 border-dark-border w-[calc(100%-6rem)] flex items-center overflow-x-auto overflow-y-hidden gap-2 thin-scrollbar">
<FIleManagerTopNavbar
tabs={tabs.map(t => ({id: t.id, title: t.title}))}
activeTab={activeTab}
@@ -542,9 +513,6 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
closeTab={closeTab}
onHomeClick={() => {
setActiveTab('home');
if (currentHost) {
fetchHomeData();
}
}}
/>
</div>
@@ -554,13 +522,13 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
onClick={() => setShowOperations(!showOperations)}
className={cn(
'w-[30px] h-[30px]',
showOperations ? 'bg-[#2d2d30] border-[#434345]' : ''
showOperations ? 'bg-dark-hover border-dark-border-hover' : ''
)}
title={t('fileManager.fileOperations')}
>
<Settings className="h-4 w-4"/>
</Button>
<div className="p-0.25 w-px h-[30px] bg-[#303032]"></div>
<div className="p-0.25 w-px h-[30px] bg-dark-border"></div>
<Button
variant="outline"
onClick={() => {
@@ -578,18 +546,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
</div>
</div>
</div>
<div style={{
position: 'absolute',
top: 44,
left: 256,
right: 0,
bottom: 0,
overflow: 'hidden',
zIndex: 10,
background: '#101014',
display: 'flex',
flexDirection: 'column'
}}>
<div className="absolute top-[44px] left-64 right-0 bottom-0 overflow-hidden z-[10] bg-dark-bg-very-light flex flex-col">
<div className="flex h-full">
<div className="flex-1">
{activeTab === 'home' ? (
@@ -610,7 +567,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
const tab = tabs.find(t => t.id === activeTab);
if (!tab) return null;
return (
<div className="flex flex-col h-full" style={{flex: 1, minHeight: 0}}>
<div className="flex flex-col h-full flex-1 min-h-0">
<div className="flex-1 min-h-0">
<FileManagerFileEditor
content={tab.content}
@@ -624,7 +581,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
)}
</div>
{showOperations && (
<div className="w-80 border-l-2 border-[#303032] bg-[#09090b] overflow-y-auto">
<div className="w-80 border-l-2 border-dark-border bg-dark-bg-darkest overflow-y-auto">
<FileManagerOperations
currentPath={currentPath}
sshSessionId={currentHost?.id.toString() || null}
@@ -642,7 +599,7 @@ export function FileManager({onSelectView, embedded = false, initialHost = null}
<div className="absolute inset-0 bg-black/60"></div>
<div className="relative h-full flex items-center justify-center">
<div className="bg-[#18181b] border-2 border-[#303032] rounded-lg p-6 max-w-md mx-4 shadow-2xl">
<div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md mx-4 shadow-2xl">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Trash2 className="w-5 h-5 text-red-400"/>
{t('fileManager.confirmDelete')}

View File

@@ -304,24 +304,9 @@ export function FileManagerFileEditor({content, fileName, onContentChange}: File
}, []);
return (
<div style={{
width: '100%',
height: '100%',
position: 'relative',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}}>
<div className="w-full h-full relative overflow-hidden flex flex-col">
<div
style={{
width: '100%',
height: '100%',
overflow: 'auto',
flex: 1,
display: 'flex',
flexDirection: 'column',
}}
className="config-codemirror-scroll-wrapper"
className="w-full h-full overflow-auto flex-1 flex flex-col config-codemirror-scroll-wrapper"
>
<CodeMirror
value={content}
@@ -331,10 +316,10 @@ export function FileManagerFileEditor({content, fileName, onContentChange}: File
oneDark,
EditorView.theme({
'&': {
backgroundColor: '#09090b !important',
backgroundColor: 'var(--color-dark-bg-darkest) !important',
},
'.cm-gutters': {
backgroundColor: '#18181b !important',
backgroundColor: 'var(--color-dark-bg) !important',
},
})
]}
@@ -342,7 +327,7 @@ export function FileManagerFileEditor({content, fileName, onContentChange}: File
theme={undefined}
height="100%"
basicSetup={{lineNumbers: true}}
style={{minHeight: '100%', minWidth: '100%', flex: 1}}
className="min-h-full min-w-full flex-1"
/>
</div>
</div>

View File

@@ -5,7 +5,7 @@ import {Tabs, TabsList, TabsTrigger, TabsContent} from '@/components/ui/tabs.tsx
import {Input} from '@/components/ui/input.tsx';
import {useState} from 'react';
import {useTranslation} from 'react-i18next';
import type { FileItem, ShortcutItem } from '../../../types/index.js';
import type { FileItem, ShortcutItem } from '../../../types/index';
interface FileManagerHomeViewProps {
recent: FileItem[];
@@ -39,7 +39,7 @@ export function FileManagerHomeView({
const renderFileCard = (file: FileItem, onRemove: () => void, onPin?: () => void, isPinned = false) => (
<div key={file.path}
className="flex items-center gap-2 px-3 py-2 bg-[#18181b] border-2 border-[#303032] rounded hover:border-[#434345] transition-colors">
className="flex items-center gap-2 px-3 py-2 bg-dark-bg border-2 border-dark-border rounded hover:border-dark-border-hover transition-colors">
<div
className="flex items-center gap-2 flex-1 cursor-pointer min-w-0"
onClick={() => onOpenFile(file)}
@@ -59,7 +59,7 @@ export function FileManagerHomeView({
<Button
size="sm"
variant="ghost"
className="h-6 px-1.5 bg-[#23232a] hover:bg-[#2d2d30] rounded-md"
className="h-6 px-1.5 bg-dark-bg-button hover:bg-dark-hover rounded-md"
onClick={onPin}
>
<Pin
@@ -70,7 +70,7 @@ export function FileManagerHomeView({
<Button
size="sm"
variant="ghost"
className="h-6 px-1.5 bg-[#23232a] hover:bg-[#2d2d30] rounded-md"
className="h-6 px-1.5 bg-dark-bg-button hover:bg-dark-hover rounded-md"
onClick={onRemove}
>
<Trash2 className="w-3 h-3 text-red-500"/>
@@ -82,7 +82,7 @@ export function FileManagerHomeView({
const renderShortcutCard = (shortcut: ShortcutItem) => (
<div key={shortcut.path}
className="flex items-center gap-2 px-3 py-2 bg-[#18181b] border-2 border-[#303032] rounded hover:border-[#434345] transition-colors">
className="flex items-center gap-2 px-3 py-2 bg-dark-bg border-2 border-dark-border rounded hover:border-dark-border-hover transition-colors">
<div
className="flex items-center gap-2 flex-1 cursor-pointer min-w-0"
onClick={() => onOpenShortcut(shortcut)}
@@ -98,7 +98,7 @@ export function FileManagerHomeView({
<Button
size="sm"
variant="ghost"
className="h-6 px-1.5 bg-[#23232a] hover:bg-[#2d2d30] rounded-md"
className="h-6 px-1.5 bg-dark-bg-button hover:bg-dark-hover rounded-md"
onClick={() => onRemoveShortcut(shortcut)}
>
<Trash2 className="w-3 h-3 text-red-500"/>
@@ -108,12 +108,12 @@ export function FileManagerHomeView({
);
return (
<div className="p-4 flex flex-col gap-4 h-full bg-[#09090b]">
<div className="p-4 flex flex-col gap-4 h-full bg-dark-bg-darkest">
<Tabs value={tab} onValueChange={v => setTab(v as 'recent' | 'pinned' | 'shortcuts')} className="w-full">
<TabsList className="mb-4 bg-[#18181b] border-2 border-[#303032]">
<TabsTrigger value="recent" className="data-[state=active]:bg-[#23232a]">{t('fileManager.recent')}</TabsTrigger>
<TabsTrigger value="pinned" className="data-[state=active]:bg-[#23232a]">{t('fileManager.pinned')}</TabsTrigger>
<TabsTrigger value="shortcuts" className="data-[state=active]:bg-[#23232a]">{t('fileManager.folderShortcuts')}</TabsTrigger>
<TabsList className="mb-4 bg-dark-bg border-2 border-dark-border">
<TabsTrigger value="recent" className="data-[state=active]:bg-dark-bg-button">{t('fileManager.recent')}</TabsTrigger>
<TabsTrigger value="pinned" className="data-[state=active]:bg-dark-bg-button">{t('fileManager.pinned')}</TabsTrigger>
<TabsTrigger value="shortcuts" className="data-[state=active]:bg-dark-bg-button">{t('fileManager.folderShortcuts')}</TabsTrigger>
</TabsList>
<TabsContent value="recent" className="mt-0">
@@ -153,12 +153,12 @@ export function FileManagerHomeView({
</TabsContent>
<TabsContent value="shortcuts" className="mt-0">
<div className="flex items-center gap-3 mb-4 p-3 bg-[#18181b] border-2 border-[#303032] rounded-lg">
<div className="flex items-center gap-3 mb-4 p-3 bg-dark-bg border-2 border-dark-border rounded-lg">
<Input
placeholder={t('fileManager.enterFolderPath')}
value={newShortcut}
onChange={e => setNewShortcut(e.target.value)}
className="flex-1 bg-[#23232a] border-2 border-[#303032] text-white placeholder:text-muted-foreground"
className="flex-1 bg-dark-bg-button border-2 border-dark-border text-white placeholder:text-muted-foreground"
onKeyDown={(e) => {
if (e.key === 'Enter' && newShortcut.trim()) {
onAddShortcut(newShortcut.trim());
@@ -169,7 +169,7 @@ export function FileManagerHomeView({
<Button
size="sm"
variant="ghost"
className="h-8 px-2 bg-[#23232a] border-2 !border-[#303032] hover:bg-[#2d2d30] rounded-md"
className="h-8 px-2 bg-dark-bg-button border-2 !border-dark-border hover:bg-dark-hover rounded-md"
onClick={() => {
if (newShortcut.trim()) {
onAddShortcut(newShortcut.trim());

View File

@@ -358,16 +358,16 @@ const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
};
return (
<div className="flex flex-col h-full w-[256px]" style={{maxWidth: 256}}>
<div className="flex flex-col h-full w-[256px] max-w-[256px]">
<div className="flex flex-col flex-grow min-h-0">
<div className="flex-1 w-full h-full flex flex-col bg-[#09090b] border-r-2 border-[#303032] overflow-hidden p-0 relative min-h-0">
<div className="flex-1 w-full h-full flex flex-col bg-dark-bg-darkest border-r-2 border-dark-border overflow-hidden p-0 relative min-h-0">
{host && (
<div className="flex flex-col h-full w-full" style={{maxWidth: 260}}>
<div className="flex items-center gap-2 px-2 py-1.5 border-b-2 border-[#303032] bg-[#18181b] z-20" style={{maxWidth: 260}}>
<div className="flex flex-col h-full w-full max-w-[260px]">
<div className="flex items-center gap-2 px-2 py-1.5 border-b-2 border-dark-border bg-dark-bg z-20 max-w-[260px]">
<Button
size="icon"
variant="outline"
className="h-9 w-9 bg-[#18181b] border-2 border-[#303032] rounded-md hover:bg-[#2d2d30] focus:outline-none focus:ring-2 focus:ring-ring"
className="h-9 w-9 bg-dark-bg border-2 border-dark-border rounded-md hover:bg-dark-hover focus:outline-none focus:ring-2 focus:ring-ring"
onClick={() => {
let path = currentPath;
if (path && path !== '/' && path !== '') {
@@ -387,20 +387,20 @@ const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
</Button>
<Input ref={pathInputRef} value={currentPath}
onChange={e => handlePathChange(e.target.value)}
className="flex-1 bg-[#18181b] border-2 border-[#434345] text-white truncate rounded-md px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring hover:border-[#5a5a5d]"
className="flex-1 bg-dark-bg border-2 border-dark-border-hover text-white truncate rounded-md px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring hover:border-dark-border-light"
/>
</div>
<div className="px-2 py-2 border-b-1 border-[#303032] bg-[#18181b]">
<div className="px-2 py-2 border-b-1 border-dark-border bg-dark-bg">
<Input
placeholder={t('fileManager.searchFilesAndFolders')}
className="w-full h-7 text-sm bg-[#23232a] border-2 border-[#434345] text-white placeholder:text-muted-foreground rounded-md"
className="w-full h-7 text-sm bg-dark-bg-button border-2 border-dark-border-hover text-white placeholder:text-muted-foreground rounded-md"
autoComplete="off"
value={fileSearch}
onChange={e => setFileSearch(e.target.value)}
/>
</div>
<div className="flex-1 min-h-0 w-full bg-[#09090b] border-t-1 border-[#303032]">
<ScrollArea className="h-full w-full bg-[#09090b]">
<div className="flex-1 min-h-0 w-full bg-dark-bg-darkest border-t-1 border-dark-border">
<ScrollArea className="h-full w-full bg-dark-bg-darkest">
<div className="p-2 pb-0">
{connectingSSH || filesLoading ? (
<div className="text-xs text-muted-foreground">{t('common.loading')}</div>
@@ -417,10 +417,9 @@ const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
<div
key={item.path}
className={cn(
"flex items-center gap-2 px-3 py-2 bg-[#18181b] border-2 border-[#303032] rounded group max-w-full relative",
"flex items-center gap-2 px-3 py-2 bg-dark-bg border-2 border-dark-border rounded group max-w-[220px] mb-2 relative",
isOpen && "opacity-60 cursor-not-allowed pointer-events-none"
)}
style={{maxWidth: 220, marginBottom: 8}}
onContextMenu={(e) => !isOpen && handleContextMenu(e, item)}
>
{isRenaming ? (
@@ -431,7 +430,7 @@ const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
<Input
value={renamingItem.newName}
onChange={(e) => setRenamingItem(prev => prev ? {...prev, newName: e.target.value} : null)}
className="flex-1 h-6 text-sm bg-[#23232a] border border-[#434345] text-white"
className="flex-1 h-6 text-sm bg-dark-bg-button border border-dark-border-hover text-white"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') {
@@ -527,21 +526,21 @@ const FileManagerLeftSidebar = forwardRef(function FileManagerSidebar(
{contextMenu.visible && contextMenu.item && (
<div
className="fixed z-[99998] bg-[#18181b] border-2 border-[#303032] rounded-lg shadow-xl py-1 min-w-[160px]"
className="fixed z-[99998] bg-dark-bg border-2 border-dark-border rounded-lg shadow-xl py-1 min-w-[160px]"
style={{
left: contextMenu.x,
top: contextMenu.y,
}}
>
<button
className="w-full px-3 py-2 text-left text-sm text-white hover:bg-[#2d2d30] flex items-center gap-2"
className="w-full px-3 py-2 text-left text-sm text-white hover:bg-dark-hover flex items-center gap-2"
onClick={() => startRename(contextMenu.item)}
>
<Edit3 className="w-4 h-4" />
Rename
</button>
<button
className="w-full px-3 py-2 text-left text-sm text-red-400 hover:bg-[#2d2d30] flex items-center gap-2"
className="w-full px-3 py-2 text-left text-sm text-red-400 hover:bg-dark-hover flex items-center gap-2"
onClick={() => startDelete(contextMenu.item)}
>
<Trash2 className="w-4 h-4" />

View File

@@ -66,7 +66,7 @@ export function FileManagerLeftSidebarFileViewer({
return (
<div className="flex flex-col h-full">
<div className="flex-1 bg-[#09090b] p-2 overflow-y-auto">
<div className="flex-1 bg-dark-bg-darkest p-2 overflow-y-auto">
<div className="mb-2 flex items-center gap-2">
<span
className="text-xs text-muted-foreground font-semibold">{isSSHMode ? t('common.sshPath') : t('common.localPath')}</span>
@@ -80,7 +80,7 @@ export function FileManagerLeftSidebarFileViewer({
<div className="flex flex-col gap-1">
{files.map((item) => (
<Card key={item.path}
className="flex items-center gap-2 px-2 py-1 bg-[#18181b] border-2 border-[#303032] rounded">
className="flex items-center gap-2 px-2 py-1 bg-dark-bg border-2 border-dark-border rounded">
<div className="flex items-center gap-2 flex-1 cursor-pointer"
onClick={() => item.type === 'directory' ? onOpenFolder(item) : onOpenFile(item)}>
{item.type === 'directory' ? <Folder className="w-4 h-4 text-blue-400"/> :

View File

@@ -301,7 +301,7 @@ export function FileManagerOperations({
variant="outline"
size="sm"
onClick={() => setShowUpload(true)}
className="h-10 bg-[#18181b] border-2 border-[#303032] hover:border-[#434345] hover:bg-[#2d2d30]"
className="h-10 bg-dark-bg border-2 border-dark-border hover:border-dark-border-hover hover:bg-dark-hover"
title={t('fileManager.uploadFile')}
>
<Upload className={cn("w-4 h-4", showTextLabels ? "mr-2" : "")}/>
@@ -311,7 +311,7 @@ export function FileManagerOperations({
variant="outline"
size="sm"
onClick={() => setShowCreateFile(true)}
className="h-10 bg-[#18181b] border-2 border-[#303032] hover:border-[#434345] hover:bg-[#2d2d30]"
className="h-10 bg-dark-bg border-2 border-dark-border hover:border-dark-border-hover hover:bg-dark-hover"
title={t('fileManager.newFile')}
>
<FilePlus className={cn("w-4 h-4", showTextLabels ? "mr-2" : "")}/>
@@ -321,7 +321,7 @@ export function FileManagerOperations({
variant="outline"
size="sm"
onClick={() => setShowCreateFolder(true)}
className="h-10 bg-[#18181b] border-2 border-[#303032] hover:border-[#434345] hover:bg-[#2d2d30]"
className="h-10 bg-dark-bg border-2 border-dark-border hover:border-dark-border-hover hover:bg-dark-hover"
title={t('fileManager.newFolder')}
>
<FolderPlus className={cn("w-4 h-4", showTextLabels ? "mr-2" : "")}/>
@@ -331,7 +331,7 @@ export function FileManagerOperations({
variant="outline"
size="sm"
onClick={() => setShowRename(true)}
className="h-10 bg-[#18181b] border-2 border-[#303032] hover:border-[#434345] hover:bg-[#2d2d30]"
className="h-10 bg-dark-bg border-2 border-dark-border hover:border-dark-border-hover hover:bg-dark-hover"
title={t('fileManager.rename')}
>
<Edit3 className={cn("w-4 h-4", showTextLabels ? "mr-2" : "")}/>
@@ -341,7 +341,7 @@ export function FileManagerOperations({
variant="outline"
size="sm"
onClick={() => setShowDelete(true)}
className="h-10 bg-[#18181b] border-2 border-[#303032] hover:border-[#434345] hover:bg-[#2d2d30] col-span-2"
className="h-10 bg-dark-bg border-2 border-dark-border hover:border-dark-border-hover hover:bg-dark-hover col-span-2"
title={t('fileManager.deleteItem')}
>
<Trash2 className={cn("w-4 h-4", showTextLabels ? "mr-2" : "")}/>
@@ -349,7 +349,7 @@ export function FileManagerOperations({
</Button>
</div>
<div className="bg-[#141416] border-2 border-[#373739] rounded-md p-3">
<div className="bg-dark-bg-light border-2 border-dark-border-medium rounded-md p-3">
<div className="flex items-start gap-2 text-sm">
<Folder className="w-4 h-4 text-blue-400 flex-shrink-0 mt-0.5"/>
<div className="flex-1 min-w-0">
@@ -359,10 +359,10 @@ export function FileManagerOperations({
</div>
</div>
<Separator className="p-0.25 bg-[#303032]"/>
<Separator className="p-0.25 bg-dark-border"/>
{showUpload && (
<Card className="bg-[#18181b] border-2 border-[#303032] p-3 sm:p-4">
<Card className="bg-dark-bg border-2 border-dark-border p-3 sm:p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex-1 min-w-0">
<h3 className="text-base sm:text-lg font-semibold text-white flex items-center gap-2 mb-1">
@@ -384,7 +384,7 @@ export function FileManagerOperations({
</div>
<div className="space-y-3">
<div className="border-2 border-dashed border-[#434345] rounded-lg p-4 text-center">
<div className="border-2 border-dashed border-dark-border-hover rounded-lg p-4 text-center">
{uploadFile ? (
<div className="space-y-3">
<FileText className="w-12 h-12 text-blue-400 mx-auto"/>
@@ -447,7 +447,7 @@ export function FileManagerOperations({
)}
{showCreateFile && (
<Card className="bg-[#18181b] border-2 border-[#303032] p-3 sm:p-4">
<Card className="bg-dark-bg border-2 border-dark-border p-3 sm:p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex-1 min-w-0">
<h3 className="text-base sm:text-lg font-semibold text-white flex items-center gap-2">
@@ -474,7 +474,7 @@ export function FileManagerOperations({
value={newFileName}
onChange={(e) => setNewFileName(e.target.value)}
placeholder={t('placeholders.fileName')}
className="bg-[#23232a] border-2 border-[#434345] text-white text-sm"
className="bg-dark-bg-button border-2 border-dark-border-hover text-white text-sm"
onKeyDown={(e) => e.key === 'Enter' && handleCreateFile()}
/>
</div>
@@ -501,7 +501,7 @@ export function FileManagerOperations({
)}
{showCreateFolder && (
<Card className="bg-[#18181b] border-2 border-[#303032] p-3">
<Card className="bg-dark-bg border-2 border-dark-border p-3">
<div className="flex items-start justify-between mb-3">
<div className="flex-1 min-w-0">
<h3 className="text-base font-semibold text-white flex items-center gap-2">
@@ -528,7 +528,7 @@ export function FileManagerOperations({
value={newFolderName}
onChange={(e) => setNewFolderName(e.target.value)}
placeholder={t('placeholders.folderName')}
className="bg-[#23232a] border-2 border-[#434345] text-white text-sm"
className="bg-dark-bg-button border-2 border-dark-border-hover text-white text-sm"
onKeyDown={(e) => e.key === 'Enter' && handleCreateFolder()}
/>
</div>
@@ -555,7 +555,7 @@ export function FileManagerOperations({
)}
{showDelete && (
<Card className="bg-[#18181b] border-2 border-[#303032] p-3">
<Card className="bg-dark-bg border-2 border-dark-border p-3">
<div className="flex items-start justify-between mb-3">
<div className="flex-1 min-w-0">
<h3 className="text-base font-semibold text-white flex items-center gap-2">
@@ -589,7 +589,7 @@ export function FileManagerOperations({
value={deletePath}
onChange={(e) => setDeletePath(e.target.value)}
placeholder={t('placeholders.fullPath')}
className="bg-[#23232a] border-2 border-[#434345] text-white text-sm"
className="bg-dark-bg-button border-2 border-dark-border-hover text-white text-sm"
/>
</div>
@@ -599,7 +599,7 @@ export function FileManagerOperations({
id="deleteIsDirectory"
checked={deleteIsDirectory}
onChange={(e) => setDeleteIsDirectory(e.target.checked)}
className="rounded border-[#434345] bg-[#23232a] mt-0.5 flex-shrink-0"
className="rounded border-dark-border-hover bg-dark-bg-button mt-0.5 flex-shrink-0"
/>
<label htmlFor="deleteIsDirectory" className="text-sm text-white break-words">
{t('fileManager.thisIsDirectory')}
@@ -629,7 +629,7 @@ export function FileManagerOperations({
)}
{showRename && (
<Card className="bg-[#18181b] border-2 border-[#303032] p-3">
<Card className="bg-dark-bg border-2 border-dark-border p-3">
<div className="flex items-start justify-between mb-3">
<div className="flex-1 min-w-0">
<h3 className="text-base font-semibold text-white flex items-center gap-2">
@@ -656,7 +656,7 @@ export function FileManagerOperations({
value={renamePath}
onChange={(e) => setRenamePath(e.target.value)}
placeholder={t('placeholders.currentPath')}
className="bg-[#23232a] border-2 border-[#434345] text-white text-sm"
className="bg-dark-bg-button border-2 border-dark-border-hover text-white text-sm"
/>
</div>
@@ -668,7 +668,7 @@ export function FileManagerOperations({
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder={t('placeholders.newName')}
className="bg-[#23232a] border-2 border-[#434345] text-white text-sm"
className="bg-dark-bg-button border-2 border-dark-border-hover text-white text-sm"
onKeyDown={(e) => e.key === 'Enter' && handleRename()}
/>
</div>
@@ -679,7 +679,7 @@ export function FileManagerOperations({
id="renameIsDirectory"
checked={renameIsDirectory}
onChange={(e) => setRenameIsDirectory(e.target.checked)}
className="rounded border-[#434345] bg-[#23232a] mt-0.5 flex-shrink-0"
className="rounded border-dark-border-hover bg-dark-bg-button mt-0.5 flex-shrink-0"
/>
<label htmlFor="renameIsDirectory" className="text-sm text-white break-words">
{t('fileManager.thisIsDirectoryRename')}

View File

@@ -21,7 +21,7 @@ export function FileManagerTabList({tabs, activeTab, setActiveTab, closeTab, onH
<Button
onClick={onHomeClick}
variant="outline"
className={`ml-1 h-8 rounded-md flex items-center !px-2 border-1 border-[#303032] ${activeTab === 'home' ? '!bg-[#1d1d1f] !text-white !border-[#2d2d30] !hover:bg-[#1d1d1f] !active:bg-[#1d1d1f] !focus:bg-[#1d1d1f] !hover:text-white !active:text-white !focus:text-white' : ''}`}
className={`ml-1 h-8 rounded-md flex items-center !px-2 border-1 border-dark-border ${activeTab === 'home' ? '!bg-dark-bg-active !text-white !border-dark-border-active !hover:bg-dark-bg-active !active:bg-dark-bg-active !focus:bg-dark-bg-active !hover:text-white !active:text-white !focus:text-white' : ''}`}
>
<Home className="w-4 h-4"/>
</Button>
@@ -32,7 +32,7 @@ export function FileManagerTabList({tabs, activeTab, setActiveTab, closeTab, onH
<Button
onClick={() => setActiveTab(tab.id)}
variant="outline"
className={`h-8 rounded-r-none !px-2 border-1 border-[#303032] ${isActive ? '!bg-[#1d1d1f] !text-white !border-[#2d2d30] !hover:bg-[#1d1d1f] !active:bg-[#1d1d1f] !focus:bg-[#1d1d1f] !hover:text-white !active:text-white !focus:text-white' : ''}`}
className={`h-8 rounded-r-none !px-2 border-1 border-dark-border ${isActive ? '!bg-dark-bg-active !text-white !border-dark-border-active !hover:bg-dark-bg-active !active:bg-dark-bg-active !focus:bg-dark-bg-active !hover:text-white !active:text-white !focus:text-white' : ''}`}
>
{tab.title}
</Button>
@@ -40,7 +40,7 @@ export function FileManagerTabList({tabs, activeTab, setActiveTab, closeTab, onH
<Button
onClick={() => closeTab(tab.id)}
variant="outline"
className="h-8 rounded-l-none p-0 !w-9 border-1 border-[#303032]"
className="h-8 rounded-l-none p-0 !w-9 border-1 border-dark-border"
>
<X className="!w-4 !h-4" strokeWidth={2}/>
</Button>

View File

@@ -57,7 +57,7 @@ export function HostManager({onSelectView, isTopbarOpen}: HostManagerProps): Rea
<div>
<div className="w-full">
<div
className="bg-[#18181b] text-white p-4 pt-0 rounded-lg border-2 border-[#303032] flex flex-col min-h-0 overflow-hidden"
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"
style={{
marginLeft: leftMarginPx,
marginRight: 17,
@@ -68,12 +68,12 @@ export function HostManager({onSelectView, isTopbarOpen}: HostManagerProps): Rea
>
<Tabs value={activeTab} onValueChange={handleTabChange}
className="flex-1 flex flex-col h-full min-h-0">
<TabsList className="bg-[#18181b] border-2 border-[#303032] mt-1.5">
<TabsList className="bg-dark-bg border-2 border-dark-border mt-1.5">
<TabsTrigger value="host_viewer">{t('hosts.hostViewer')}</TabsTrigger>
<TabsTrigger value="add_host">
{editingHost ? t('hosts.editHost') : t('hosts.addHost')}
</TabsTrigger>
<div className="h-6 w-px bg-[#303032] mx-1"></div>
<div className="h-6 w-px bg-dark-border mx-1"></div>
<TabsTrigger value="credentials">{t('credentials.credentialsViewer')}</TabsTrigger>
<TabsTrigger value="add_credential">
{editingCredential ? t('credentials.editCredential') : t('credentials.addCredential')}

View File

@@ -13,6 +13,7 @@ import {
FormMessage,
} from "@/components/ui/form.tsx";
import {Input} from "@/components/ui/input.tsx";
import {PasswordInput} from "@/components/ui/password-input.tsx";
import {ScrollArea} from "@/components/ui/scroll-area.tsx"
import {Separator} from "@/components/ui/separator.tsx";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx";
@@ -650,7 +651,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
{folderDropdownOpen && filteredFolders.length > 0 && (
<div
ref={folderDropdownRef}
className="absolute top-full left-0 z-50 mt-1 w-full bg-[#18181b] 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-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{filteredFolders.map((folder) => (
@@ -680,7 +681,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
<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-[#222225] focus-within:ring-2 ring-ring min-h-[40px]">
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]">
{field.value.map((tag: string, idx: number) => (
<span key={tag + idx}
className="flex items-center bg-gray-200 text-gray-800 rounded-full px-2 py-0.5 text-xs">
@@ -776,7 +777,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
<FormItem>
<FormLabel>{t('hosts.password')}</FormLabel>
<FormControl>
<Input type="password" placeholder={t('placeholders.password')} {...field} />
<PasswordInput placeholder={t('placeholders.password')} {...field} />
</FormControl>
</FormItem>
)}
@@ -864,9 +865,8 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
<FormItem className="col-span-8">
<FormLabel>{t('hosts.keyPassword')}</FormLabel>
<FormControl>
<Input
<PasswordInput
placeholder={t('placeholders.keyPassword')}
type="password"
{...field}
/>
</FormControl>
@@ -885,7 +885,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
ref={keyTypeButtonRef}
type="button"
variant="outline"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-[#18181b] border border-input text-foreground"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-dark-bg border border-input text-foreground"
onClick={() => setKeyTypeDropdownOpen((open) => !open)}
>
{keyTypeOptions.find((opt) => opt.value === field.value)?.label || t('hosts.autoDetect')}
@@ -893,7 +893,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
{keyTypeDropdownOpen && (
<div
ref={keyTypeDropdownRef}
className="absolute bottom-full left-0 z-50 mb-1 w-full bg-[#18181b] 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-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{keyTypeOptions.map((opt) => (
@@ -902,7 +902,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
type="button"
variant="ghost"
size="sm"
className="w-full justify-start text-left rounded-md px-2 py-1.5 bg-[#18181b] 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-dark-bg text-foreground hover:bg-white/15 focus:bg-white/20 focus:outline-none"
onClick={() => {
field.onChange(opt.value);
setKeyTypeDropdownOpen(false);
@@ -1115,7 +1115,7 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
ref={(el) => {
sshConfigDropdownRefs.current[index] = el;
}}
className="absolute top-full left-0 z-50 mt-1 w-full bg-[#18181b] 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-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
>
<div
className="grid grid-cols-1 gap-1 p-0">
@@ -1269,12 +1269,9 @@ export function HostManagerEditor({editingHost, onFormSubmit}: SSHManagerHostEdi
<footer className="shrink-0 w-full pb-0">
<Separator className="p-0.25"/>
<Button
className=""
className="translate-y-2"
type="submit"
variant="outline"
style={{
transform: 'translateY(8px)'
}}
>
{editingHost ? t('hosts.updateHost') : t('hosts.addHost')}
</Button>

View File

@@ -568,7 +568,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
type="file"
accept=".json"
onChange={handleJsonImport}
style={{display: 'none'}}
className="hidden"
/>
<div className="flex items-center justify-center flex-1">
@@ -722,7 +722,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
type="file"
accept=".json"
onChange={handleJsonImport}
style={{display: 'none'}}
className="hidden"
/>
<div className="relative mb-3">
@@ -837,7 +837,7 @@ export function HostManagerViewer({onEditHost}: SSHManagerHostViewerProps) {
draggable
onDragStart={(e) => handleDragStart(e, host)}
onDragEnd={handleDragEnd}
className={`bg-[#222225] border border-input rounded-lg cursor-pointer hover:shadow-lg hover:border-blue-400/50 hover:bg-[#2a2a2d] transition-all duration-200 p-3 group relative ${
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 ${
draggedHost?.id === host.id ? 'opacity-50 scale-95' : ''
}`}
onClick={() => handleEdit(host)}

View File

@@ -99,7 +99,9 @@ export function Server({
if (!currentHostConfig?.id) return;
try {
const data = await getServerMetricsById(currentHostConfig.id);
if (!cancelled) setMetrics(data);
if (!cancelled) {
setMetrics(data);
}
} catch (error) {
console.error('Failed to fetch server metrics:', error);
if (!cancelled) {
@@ -151,7 +153,7 @@ export function Server({
const containerClass = embedded
? "h-full w-full text-white overflow-hidden bg-transparent"
: "bg-[#18181b] text-white rounded-lg border-2 border-[#303032] overflow-hidden";
: "bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden";
return (
<div style={wrapperStyle} className={containerClass}>
@@ -213,7 +215,7 @@ export function Server({
<Separator className="p-0.25 w-full"/>
{/* Stats */}
<div className="rounded-lg border-2 border-[#303032] m-3 bg-[#0e0e10] flex flex-row items-stretch">
<div className="rounded-lg border-2 border-dark-border m-3 bg-dark-bg-darker flex flex-row items-stretch">
{/* CPU */}
<div className="flex-1 min-w-0 px-2 py-2">
<h1 className="font-bold xt-lg flex flex-row gap-2 mb-2">
@@ -278,7 +280,7 @@ export function Server({
{/* SSH Tunnels */}
{(currentHostConfig?.tunnelConnections && currentHostConfig.tunnelConnections.length > 0) && (
<div
className="rounded-lg border-2 border-[#303032] m-3 bg-[#0e0e10] h-[360px] overflow-hidden flex flex-col min-h-0">
className="rounded-lg border-2 border-dark-border m-3 bg-dark-bg-darker h-[360px] overflow-hidden flex flex-col min-h-0">
<Tunnel
filterHostKey={(currentHostConfig?.name && currentHostConfig.name.trim() !== '') ? currentHostConfig.name : `${currentHostConfig?.username}@${currentHostConfig?.ip}`}/>
</div>

View File

@@ -13,10 +13,11 @@ interface SSHTerminalProps {
title?: string;
showTitle?: boolean;
splitScreen?: boolean;
onClose?: () => void;
}
export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
{hostConfig, isVisible, splitScreen = false},
{hostConfig, isVisible, splitScreen = false, onClose},
ref
) {
const {t} = useTranslation();
@@ -33,6 +34,8 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const reconnectAttempts = useRef(0);
const maxReconnectAttempts = 3;
const isUnmountingRef = useRef(false);
const shouldNotReconnectRef = useRef(false);
const lastSentSizeRef = useRef<{ cols: number; rows: number } | null>(null);
@@ -71,6 +74,8 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
useImperativeHandle(ref, () => ({
disconnect: () => {
isUnmountingRef.current = true;
shouldNotReconnectRef.current = true;
if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current);
pingIntervalRef.current = null;
@@ -130,6 +135,11 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
}
function attemptReconnection() {
// Don't attempt reconnection if component is unmounting or if we shouldn't reconnect
if (isUnmountingRef.current || shouldNotReconnectRef.current) {
return;
}
if (reconnectAttempts.current >= maxReconnectAttempts) {
toast.error(t('terminal.maxReconnectAttemptsReached'));
return;
@@ -139,7 +149,13 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
toast.info(t('terminal.reconnecting', { attempt: reconnectAttempts.current, max: maxReconnectAttempts }));
reconnectTimeoutRef.current = setTimeout(() => {
// Check again if component is still mounted and should reconnect
if (isUnmountingRef.current || shouldNotReconnectRef.current) {
return;
}
if (terminal && hostConfig) {
// Clear terminal before reconnecting
terminal.clear();
const cols = terminal.cols;
const rows = terminal.rows;
connectToHost(cols, rows);
@@ -163,6 +179,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
webSocketRef.current = ws;
wasDisconnectedBySSH.current = false;
setConnectionError(null);
shouldNotReconnectRef.current = false; // Reset reconnection flag
setupWebSocketListeners(ws, cols, rows);
}
@@ -172,8 +189,11 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
function setupWebSocketListeners(ws: WebSocket, cols: number, rows: number) {
ws.addEventListener('open', () => {
setIsConnected(true);
// Show reconnected toast if this was a reconnection attempt
if (reconnectAttempts.current > 0) {
toast.success(t('terminal.reconnected'));
}
reconnectAttempts.current = 0; // Reset on successful connection
toast.success(t('terminal.connected'));
ws.send(JSON.stringify({type: 'connectToHost', data: {cols, rows, hostConfig}}));
terminal.onData((data) => {
@@ -200,12 +220,20 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
if (errorMessage.toLowerCase().includes('auth') ||
errorMessage.toLowerCase().includes('password') ||
errorMessage.toLowerCase().includes('permission') ||
errorMessage.toLowerCase().includes('denied')) {
errorMessage.toLowerCase().includes('denied') ||
errorMessage.toLowerCase().includes('invalid') ||
errorMessage.toLowerCase().includes('failed') ||
errorMessage.toLowerCase().includes('incorrect')) {
toast.error(t('terminal.authError', { message: errorMessage }));
shouldNotReconnectRef.current = true; // Don't reconnect on auth errors
// Close terminal on auth errors
if (webSocketRef.current) {
webSocketRef.current.close();
}
// Close the terminal tab immediately
if (onClose) {
onClose();
}
return;
}
@@ -223,13 +251,13 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
toast.error(t('terminal.error', { message: errorMessage }));
} else if (msg.type === 'connected') {
setIsConnected(true);
toast.success(t('terminal.sshConnected'));
} else if (msg.type === 'disconnected') {
wasDisconnectedBySSH.current = true;
setIsConnected(false);
toast.info(t('terminal.disconnected', { message: msg.message }));
// Attempt reconnection for disconnections
attemptReconnection();
if (!isUnmountingRef.current && !shouldNotReconnectRef.current) {
attemptReconnection();
}
}
} catch (error) {
toast.error(t('terminal.messageParseError'));
@@ -238,8 +266,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
ws.addEventListener('close', (event) => {
setIsConnected(false);
if (!wasDisconnectedBySSH.current) {
toast.warning(t('terminal.connectionClosed'));
if (!wasDisconnectedBySSH.current && !isUnmountingRef.current && !shouldNotReconnectRef.current) {
// Attempt reconnection for unexpected disconnections
attemptReconnection();
}
@@ -248,9 +275,10 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
ws.addEventListener('error', (event) => {
setIsConnected(false);
setConnectionError(t('terminal.websocketError'));
toast.error(t('terminal.websocketError'));
// Attempt reconnection for WebSocket errors
attemptReconnection();
if (!isUnmountingRef.current && !shouldNotReconnectRef.current) {
attemptReconnection();
}
});
}
@@ -295,7 +323,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
scrollback: 10000,
fontSize: 14,
fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace',
theme: {background: '#18181b', foreground: '#f7f7f7'},
theme: {background: 'var(--color-dark-bg)', foreground: '#f7f7f7'},
allowTransparency: true,
convertEol: true,
windowsMode: false,
@@ -385,6 +413,8 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
});
return () => {
isUnmountingRef.current = true;
shouldNotReconnectRef.current = true;
resizeObserver.disconnect();
element?.removeEventListener('contextmenu', handleContextMenu);
if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current);
@@ -430,29 +460,15 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
}, [splitScreen, isVisible, terminal]);
return (
<div className="h-full w-full relative">
{/* Connection Status Indicator */}
{!isConnected && (
<div className="absolute top-2 right-2 z-10 bg-red-500 text-white px-2 py-1 rounded text-xs">
{t('terminal.disconnected')}
</div>
)}
{isConnected && (
<div className="absolute top-2 right-2 z-10 bg-green-500 text-white px-2 py-1 rounded text-xs">
{t('terminal.connected')}
</div>
)}
<div
ref={xtermRef}
className="h-full w-full m-1"
style={{opacity: visible && isVisible ? 1 : 0, overflow: 'hidden'}}
onClick={() => {
if (terminal && !splitScreen) {
terminal.focus();
}
}}
/>
</div>
<div
ref={xtermRef}
className={`h-full w-full m-1 transition-opacity duration-200 ${visible && isVisible ? 'opacity-100' : 'opacity-0'} overflow-hidden`}
onClick={() => {
if (terminal && !splitScreen) {
terminal.focus();
}
}}
/>
);
});

View File

@@ -128,29 +128,13 @@ function AppContent() {
username={username}
>
<div
className="h-screen w-full"
style={{
visibility: showTerminalView ? "visible" : "hidden",
pointerEvents: showTerminalView ? "auto" : "none",
height: showTerminalView ? "100vh" : 0,
width: showTerminalView ? "100%" : 0,
position: showTerminalView ? "static" : "absolute",
overflow: "hidden",
}}
className={`h-screen w-full ${showTerminalView ? "visible pointer-events-auto static overflow-hidden" : "invisible pointer-events-none absolute h-0 w-0"}`}
>
<AppView isTopbarOpen={isTopbarOpen} />
</div>
<div
className="h-screen w-full"
style={{
visibility: showHome ? "visible" : "hidden",
pointerEvents: showHome ? "auto" : "none",
height: showHome ? "100vh" : 0,
width: showHome ? "100%" : 0,
position: showHome ? "static" : "absolute",
overflow: "hidden",
}}
className={`h-screen w-full ${showHome ? "visible pointer-events-auto static overflow-hidden" : "invisible pointer-events-none absolute h-0 w-0"}`}
>
<Homepage
onSelectView={handleSelectView}
@@ -162,43 +146,19 @@ function AppContent() {
</div>
<div
className="h-screen w-full"
style={{
visibility: showSshManager ? "visible" : "hidden",
pointerEvents: showSshManager ? "auto" : "none",
height: showSshManager ? "100vh" : 0,
width: showSshManager ? "100%" : 0,
position: showSshManager ? "static" : "absolute",
overflow: "hidden",
}}
className={`h-screen w-full ${showSshManager ? "visible pointer-events-auto static overflow-hidden" : "invisible pointer-events-none absolute h-0 w-0"}`}
>
<HostManager onSelectView={handleSelectView} isTopbarOpen={isTopbarOpen} />
</div>
<div
className="h-screen w-full"
style={{
visibility: showAdmin ? "visible" : "hidden",
pointerEvents: showAdmin ? "auto" : "none",
height: showAdmin ? "100vh" : 0,
width: showAdmin ? "100%" : 0,
position: showAdmin ? "static" : "absolute",
overflow: "hidden",
}}
className={`h-screen w-full ${showAdmin ? "visible pointer-events-auto static overflow-hidden" : "invisible pointer-events-none absolute h-0 w-0"}`}
>
<AdminSettings isTopbarOpen={isTopbarOpen} />
</div>
<div
className="h-screen w-full"
style={{
visibility: showProfile ? "visible" : "hidden",
pointerEvents: showProfile ? "auto" : "none",
height: showProfile ? "100vh" : 0,
width: showProfile ? "100%" : 0,
position: showProfile ? "static" : "absolute",
overflow: "auto",
}}
className={`h-screen w-full ${showProfile ? "visible pointer-events-auto static overflow-auto" : "invisible pointer-events-none absolute h-0 w-0"}`}
>
<UserProfile isTopbarOpen={isTopbarOpen} />
</div>

View File

@@ -107,64 +107,51 @@ export function Homepage({
}}>
<div className="flex flex-row items-center justify-center gap-8 relative z-10">
<div className="flex flex-col items-center gap-6 w-[400px]">
<div
className="text-center bg-[#18181b] border-2 border-[#303032] rounded-lg p-6 w-full shadow-lg">
<h3 className="text-xl font-bold mb-3 text-white">{t('homepage.loggedInTitle')}</h3>
<p className="text-gray-300 leading-relaxed">
{t('homepage.loggedInMessage')}
</p>
</div>
<HomepageUpdateLog
loggedIn={loggedIn}
/>
<div className="flex flex-row items-center gap-3">
<Button
variant="outline"
size="sm"
className="text-sm border-[#303032] text-gray-300 hover:text-white hover:bg-[#18181b] transition-colors"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://github.com/LukeGus/Termix', '_blank')}
>
GitHub
</Button>
<div className="w-px h-4 bg-[#303032]"></div>
<div className="w-px h-4 bg-dark-border"></div>
<Button
variant="outline"
size="sm"
className="text-sm border-[#303032] text-gray-300 hover:text-white hover:bg-[#18181b] transition-colors"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://github.com/LukeGus/Termix/issues/new', '_blank')}
>
Feedback
</Button>
<div className="w-px h-4 bg-[#303032]"></div>
<div className="w-px h-4 bg-dark-border"></div>
<Button
variant="outline"
size="sm"
className="text-sm border-[#303032] text-gray-300 hover:text-white hover:bg-[#18181b] transition-colors"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://discord.com/invite/jVQGdvHDrf', '_blank')}
>
Discord
</Button>
<div className="w-px h-4 bg-[#303032]"></div>
<div className="w-px h-4 bg-dark-border"></div>
<Button
variant="outline"
size="sm"
className="text-sm border-[#303032] text-gray-300 hover:text-white hover:bg-[#18181b] transition-colors"
className="text-sm border-dark-border text-gray-300 hover:text-white hover:bg-dark-bg transition-colors"
onClick={() => window.open('https://github.com/sponsors/LukeGus', '_blank')}
>
Donate
</Button>
</div>
</div>
<HomepageUpdateLog
loggedIn={loggedIn}
/>
</div>
</div>
)}
<HomepageAlertManager
userId={userId}
loggedIn={loggedIn}
/>
</div>
);
}

View File

@@ -3,6 +3,7 @@ import {Eye, EyeOff} from "lucide-react";
import {cn} from "@/lib/utils.ts";
import {Button} from "@/components/ui/button.tsx";
import {Input} from "@/components/ui/input.tsx";
import {PasswordInput} from "@/components/ui/password-input.tsx";
import {Label} from "@/components/ui/label.tsx";
import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert.tsx";
import {useTranslation} from "react-i18next";
@@ -411,7 +412,7 @@ export function HomepageAuth({
return (
<div
className={`w-[420px] max-w-full p-6 flex flex-col bg-[#18181b] border-2 border-[#303032] rounded-md ${className || ''}`}
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md ${className || ''}`}
{...props}
>
{dbError && (
@@ -686,51 +687,27 @@ export function HomepageAuth({
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<Label htmlFor="new-password">{t('auth.newPassword')}</Label>
<div className="relative">
<Input
id="new-password"
type={visibility.resetNew ? "text" : "password"}
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200 pr-10"
value={newPassword}
onChange={e => setNewPassword(e.target.value)}
disabled={resetLoading}
autoComplete="new-password"
/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => toggleVisibility('resetNew')}
className="absolute right-0 top-0 h-full px-3 py-2"
>
{visibility.resetNew ? <EyeOff className="h-4 w-4 text-muted-foreground" /> : <Eye className="h-4 w-4 text-muted-foreground" />}
</Button>
</div>
<PasswordInput
id="new-password"
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={newPassword}
onChange={e => setNewPassword(e.target.value)}
disabled={resetLoading}
autoComplete="new-password"
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="confirm-password">{t('auth.confirmNewPassword')}</Label>
<div className="relative">
<Input
id="confirm-password"
type={visibility.resetConfirm ? "text" : "password"}
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200 pr-10"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
disabled={resetLoading}
autoComplete="new-password"
/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => toggleVisibility('resetConfirm')}
className="absolute right-0 top-0 h-full px-3 py-2"
>
{visibility.resetConfirm ? <EyeOff className="h-4 w-4 text-muted-foreground" /> : <Eye className="h-4 w-4 text-muted-foreground" />}
</Button>
</div>
<PasswordInput
id="confirm-password"
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
disabled={resetLoading}
autoComplete="new-password"
/>
</div>
<Button
type="button"
@@ -775,48 +752,24 @@ export function HomepageAuth({
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="password">{t('common.password')}</Label>
<div className="relative">
<Input
id="password"
type={visibility.password ? "text" : "password"}
required
className="h-11 text-base pr-10"
value={password}
onChange={e => setPassword(e.target.value)}
disabled={loading || internalLoggedIn}/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => toggleVisibility('password')}
className="absolute right-0 top-0 h-full px-3 py-2"
>
{visibility.password ? <EyeOff className="h-4 w-4 text-muted-foreground" /> : <Eye className="h-4 w-4 text-muted-foreground" />}
</Button>
</div>
<PasswordInput
id="password"
required
className="h-11 text-base"
value={password}
onChange={e => setPassword(e.target.value)}
disabled={loading || internalLoggedIn}/>
</div>
{tab === "signup" && (
<div className="flex flex-col gap-2">
<Label htmlFor="signup-confirm-password">{t('common.confirmPassword')}</Label>
<div className="relative">
<Input
id="signup-confirm-password"
type={visibility.signupConfirm ? "text" : "password"}
required
className="h-11 text-base pr-10"
value={signupConfirmPassword}
onChange={e => setSignupConfirmPassword(e.target.value)}
disabled={loading || internalLoggedIn}/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => toggleVisibility('signupConfirm')}
className="absolute right-0 top-0 h-full px-3 py-2"
>
{visibility.signupConfirm ? <EyeOff className="h-4 w-4 text-muted-foreground" /> : <Eye className="h-4 w-4 text-muted-foreground" />}
</Button>
</div>
<PasswordInput
id="signup-confirm-password"
required
className="h-11 text-base"
value={signupConfirmPassword}
onChange={e => setSignupConfirmPassword(e.target.value)}
disabled={loading || internalLoggedIn}/>
</div>
)}
<Button type="submit" className="w-full h-11 mt-2 text-base font-semibold"
@@ -839,7 +792,7 @@ export function HomepageAuth({
</form>
)}
<div className="mt-6 pt-4 border-t border-[#303032]">
<div className="mt-6 pt-4 border-t border-dark-border">
<div className="flex items-center justify-between">
<div>
<Label className="text-sm text-muted-foreground">{t('common.language')}</Label>

View File

@@ -90,14 +90,14 @@ export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) {
};
return (
<div className="w-[400px] h-[600px] flex flex-col border-2 border-[#303032] rounded-lg bg-[#18181b] p-4 shadow-lg">
<div className="w-[400px] h-[600px] flex flex-col border-2 border-dark-border rounded-lg bg-dark-bg p-4 shadow-lg">
<div>
<h3 className="text-lg font-bold mb-3 text-white">{t('common.updatesAndReleases')}</h3>
<Separator className="p-0.25 mt-3 mb-3 bg-[#303032]"/>
<Separator className="p-0.25 mt-3 mb-3 bg-dark-border"/>
{versionInfo && versionInfo.status === 'requires_update' && (
<Alert className="bg-[#0e0e10] border-[#303032] text-white">
<Alert className="bg-dark-bg-darker border-dark-border text-white">
<AlertTitle className="text-white">{t('common.updateAvailable')}</AlertTitle>
<AlertDescription className="text-gray-300">
{t('common.newVersionAvailable', { version: versionInfo.version })}
@@ -107,7 +107,7 @@ export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) {
</div>
{versionInfo && versionInfo.status === 'requires_update' && (
<Separator className="p-0.25 mt-3 mb-3 bg-[#303032]"/>
<Separator className="p-0.25 mt-3 mb-3 bg-dark-border"/>
)}
<div className="flex-1 overflow-y-auto space-y-3 pr-2">
@@ -127,7 +127,7 @@ export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) {
{releases?.items.map((release) => (
<div
key={release.id}
className="border border-[#303032] rounded-lg p-3 hover:bg-[#0e0e10] transition-colors cursor-pointer bg-[#0e0e10]/50"
className="border border-dark-border rounded-lg p-3 hover:bg-dark-bg-darker transition-colors cursor-pointer bg-dark-bg-darker/50"
onClick={() => window.open(release.link, '_blank')}
>
<div className="flex items-start justify-between mb-2">
@@ -159,7 +159,7 @@ export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) {
))}
{releases && releases.items.length === 0 && !loading && (
<Alert className="bg-[#0e0e10] border-[#303032] text-gray-300">
<Alert className="bg-dark-bg-darker border-dark-border text-gray-300">
<AlertTitle className="text-gray-300">{t('common.noReleases')}</AlertTitle>
<AlertDescription className="text-gray-400">
{t('common.noReleasesFound')}

View File

@@ -14,7 +14,7 @@ interface TerminalViewProps {
}
export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactElement {
const {tabs, currentTab, allSplitScreenTab} = useTabs() as any;
const {tabs, currentTab, allSplitScreenTab, removeTab} = useTabs() as any;
const {state: sidebarState} = useSidebar();
const terminalTabs = tabs.filter((tab: any) => tab.type === 'terminal' || tab.type === 'server' || tab.type === 'file_manager');
@@ -141,7 +141,7 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
}
return (
<div style={{position: 'absolute', inset: 0, zIndex: 1}}>
<div className="absolute inset-0 z-[1]">
{terminalTabs.map((t: any) => {
const hasStyle = !!styles[t.id];
const isVisible = hasStyle || (allSplitScreenTab.length === 0 && t.id === currentTab);
@@ -155,7 +155,7 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
const effectiveVisible = isVisible && ready;
return (
<div key={t.id} style={finalStyle}>
<div className="absolute inset-0 rounded-md bg-[#18181b]">
<div className="absolute inset-0 rounded-md bg-dark-bg">
{t.type === 'terminal' ? (
<Terminal
ref={t.terminalRef}
@@ -164,6 +164,7 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
title={t.title}
showTitle={false}
splitScreen={allSplitScreenTab.length > 0}
onClose={() => removeTab(t.id)}
/>
) : t.type === 'server' ? (
<ServerView
@@ -177,6 +178,7 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
<FileManager
embedded
initialHost={t.hostConfig}
onClose={() => removeTab(t.id)}
/>
)}
</div>
@@ -193,7 +195,7 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
variant="ghost"
onClick={onClick}
aria-label="Reset split sizes"
className="absolute top-0 right-0 h-[28px] w-[28px] !rounded-none border-l-1 border-b-1 border-[#222224] bg-[#1b1b1e] hover:bg-[#232327] text-white flex items-center justify-center p-0"
className="absolute top-0 right-0 h-[28px] w-[28px] !rounded-none border-l-1 border-b-1 border-dark-border-panel bg-dark-bg-panel hover:bg-dark-bg-panel-hover text-white flex items-center justify-center p-0"
>
<RefreshCcw className="h-4 w-4"/>
</Button>
@@ -210,41 +212,21 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
const layoutTabs = [mainTab, ...splitTabs.filter((t: any) => t && t.id !== (mainTab && (mainTab as any).id))].filter(Boolean) as any[];
if (allSplitScreenTab.length === 0) return null;
const handleStyle = {pointerEvents: 'auto', zIndex: 12, background: '#303032'} as React.CSSProperties;
const handleStyle = {pointerEvents: 'auto', zIndex: 12, background: 'var(--color-dark-border)'} as React.CSSProperties;
const commonGroupProps = {onLayout: scheduleMeasureAndFit, onResize: scheduleMeasureAndFit} as any;
if (layoutTabs.length === 2) {
const [a, b] = layoutTabs as any[];
return (
<div style={{position: 'absolute', inset: 0, zIndex: 10, pointerEvents: 'none'}}>
<div className="absolute inset-0 z-[10] pointer-events-none">
<ResizablePrimitive.PanelGroup key={resetKey} direction="horizontal"
className="h-full w-full" {...commonGroupProps}>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
id={`panel-${a.id}`} order={1}>
<div ref={el => {
panelRefs.current[String(a.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
background: 'transparent',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>{a.title}</div>
}} className="h-full w-full flex flex-col bg-transparent relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">{a.title}</div>
</div>
</ResizablePanel>
<ResizableHandle style={handleStyle}/>
@@ -252,28 +234,8 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
id={`panel-${b.id}`} order={2}>
<div ref={el => {
panelRefs.current[String(b.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
background: 'transparent',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>
}} className="h-full w-full flex flex-col bg-transparent relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{b.title}
<ResetButton onClick={handleReset}/>
</div>
@@ -286,7 +248,7 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
if (layoutTabs.length === 3) {
const [a, b, c] = layoutTabs as any[];
return (
<div style={{position: 'absolute', inset: 0, zIndex: 10, pointerEvents: 'none'}}>
<div className="absolute inset-0 z-[10] pointer-events-none">
<ResizablePrimitive.PanelGroup key={resetKey} direction="vertical" className="h-full w-full"
id="main-vertical" {...commonGroupProps}>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
@@ -297,27 +259,8 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
id={`panel-${a.id}`} order={1}>
<div ref={el => {
panelRefs.current[String(a.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>{a.title}</div>
}} className="h-full w-full flex flex-col relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">{a.title}</div>
</div>
</ResizablePanel>
<ResizableHandle style={handleStyle}/>
@@ -325,27 +268,8 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
id={`panel-${b.id}`} order={2}>
<div ref={el => {
panelRefs.current[String(b.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>
}} className="h-full w-full flex flex-col relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{b.title}
<ResetButton onClick={handleReset}/>
</div>
@@ -358,27 +282,8 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
id="bottom-panel" order={2}>
<div ref={el => {
panelRefs.current[String(c.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>{c.title}</div>
}} className="h-full w-full flex flex-col relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">{c.title}</div>
</div>
</ResizablePanel>
</ResizablePrimitive.PanelGroup>
@@ -388,66 +293,28 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
if (layoutTabs.length === 4) {
const [a, b, c, d] = layoutTabs as any[];
return (
<div style={{position: 'absolute', inset: 0, zIndex: 10, pointerEvents: 'none'}}>
<div className="absolute inset-0 z-[10] pointer-events-none">
<ResizablePrimitive.PanelGroup key={resetKey} direction="vertical" className="h-full w-full"
id="main-vertical" {...commonGroupProps}>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
id="top-panel" order={1}>
<ResizablePanelGroup key={`top-${resetKey}`} direction="horizontal"
className="h-full w-full" id="top-horizontal" {...commonGroupProps}>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h_full w_full"
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
id={`panel-${a.id}`} order={1}>
<div ref={el => {
panelRefs.current[String(a.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>{a.title}</div>
}} className="h-full w-full flex flex-col relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">{a.title}</div>
</div>
</ResizablePanel>
<ResizableHandle style={handleStyle}/>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h_full w_full"
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
id={`panel-${b.id}`} order={2}>
<div ref={el => {
panelRefs.current[String(b.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>
}} className="h-full w-full flex flex-col relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">
{b.title}
<ResetButton onClick={handleReset}/>
</div>
@@ -456,63 +323,25 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
</ResizablePanelGroup>
</ResizablePanel>
<ResizableHandle style={handleStyle}/>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h_full w_full"
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
id="bottom-panel" order={2}>
<ResizablePanelGroup key={`bottom-${resetKey}`} direction="horizontal"
className="h-full w-full" id="bottom-horizontal" {...commonGroupProps}>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h_full w_full"
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
id={`panel-${c.id}`} order={1}>
<div ref={el => {
panelRefs.current[String(c.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>{c.title}</div>
}} className="h-full w-full flex flex-col relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">{c.title}</div>
</div>
</ResizablePanel>
<ResizableHandle style={handleStyle}/>
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h_full w_full"
<ResizablePanel defaultSize={50} minSize={20} className="!overflow-hidden h-full w-full"
id={`panel-${d.id}`} order={2}>
<div ref={el => {
panelRefs.current[String(d.id)] = el;
}} style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}>
<div style={{
background: '#1b1b1e',
color: '#fff',
fontSize: 13,
height: HEADER_H,
lineHeight: `${HEADER_H}px`,
padding: '0 10px',
borderBottom: '1px solid #222224',
letterSpacing: 1,
margin: 0,
pointerEvents: 'auto',
zIndex: 11,
position: 'relative'
}}>{d.title}</div>
}} className="h-full w-full flex flex-col relative">
<div className="bg-dark-bg-panel text-white text-[13px] h-[28px] leading-[28px] px-[10px] border-b border-dark-border-panel tracking-[1px] m-0 pointer-events-auto z-[11] relative">{d.title}</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
@@ -535,10 +364,9 @@ export function AppView({isTopbarOpen = true}: TerminalViewProps): React.ReactEl
return (
<div
ref={containerRef}
className="border-2 border-[#303032] rounded-lg overflow-hidden overflow-x-hidden"
className="border-2 border-dark-border rounded-lg overflow-hidden overflow-x-hidden relative"
style={{
position: 'relative',
background: (isFileManager && !isSplitScreen) ? '#09090b' : '#18181b',
background: (isFileManager && !isSplitScreen) ? 'var(--color-dark-bg-darkest)' : 'var(--color-dark-bg)',
marginLeft: leftMarginPx,
marginRight: 17,
marginTop: topMarginPx,

View File

@@ -43,9 +43,8 @@ export function FolderCard({folderName, hosts, isFirst, isLast}: FolderCardProps
};
return (
<div className="bg-[#0e0e10] border-2 border-[#303032] rounded-lg overflow-hidden"
style={{padding: '0', margin: '0'}}>
<div className={`px-4 py-3 relative ${isExpanded ? 'border-b-2' : ''} bg-[#131316]`}>
<div className="bg-dark-bg-darker border-2 border-dark-border rounded-lg overflow-hidden p-0 m-0">
<div className={`px-4 py-3 relative ${isExpanded ? 'border-b-2' : ''} bg-dark-bg-header`}>
<div className="flex gap-2 pr-10">
<div className="flex-shrink-0 flex items-center">
<Folder size={16} strokeWidth={3}/>

View File

@@ -58,13 +58,13 @@ export function Host({host}: HostProps): React.ReactElement {
{host.name || host.ip}
</p>
<ButtonGroup className="flex-shrink-0">
<Button variant="outline" className="!px-2 border-1 border-[#303032]" onClick={handleServerClick}>
<Button variant="outline" className="!px-2 border-1 border-dark-border" onClick={handleServerClick}>
<Server/>
</Button>
{host.enableTerminal && (
<Button
variant="outline"
className="!px-2 border-1 border-[#303032]"
className="!px-2 border-1 border-dark-border"
onClick={handleTerminalClick}
>
<Terminal/>
@@ -75,7 +75,7 @@ export function Host({host}: HostProps): React.ReactElement {
{hasTags && (
<div className="flex flex-wrap items-center gap-2 mt-1">
{tags.map((tag: string) => (
<div key={tag} className="bg-[#18181b] border-1 border-[#303032] pl-2 pr-2 rounded-[10px]">
<div key={tag} className="bg-dark-bg border-1 border-dark-border pl-2 pr-2 rounded-[10px]">
<p className="text-sm">{tag}</p>
</div>
))}

View File

@@ -34,6 +34,7 @@ import {
} from "@/components/ui/sheet.tsx";
import {Checkbox} from "@/components/ui/checkbox.tsx";
import {Input} from "@/components/ui/input.tsx";
import {PasswordInput} from "@/components/ui/password-input.tsx";
import {Label} from "@/components/ui/label.tsx";
import {Button} from "@/components/ui/button.tsx";
import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert.tsx";
@@ -316,7 +317,7 @@ export function LeftSidebar({
<Separator className="p-0.25"/>
<SidebarContent>
<SidebarGroup className="!m-0 !p-0 !-mb-2">
<Button className="m-2 flex flex-row font-semibold border-2 !border-[#303032]" variant="outline"
<Button className="m-2 flex flex-row font-semibold border-2 !border-dark-border" variant="outline"
onClick={openSshManagerTab} disabled={!!sshManagerTab || isSplitScreenActive}
title={sshManagerTab ? t('interface.sshManagerAlreadyOpen') : isSplitScreenActive ? t('interface.disabledDuringSplitScreen') : undefined}>
<HardDrive strokeWidth="2.5"/>
@@ -325,12 +326,12 @@ export function LeftSidebar({
</SidebarGroup>
<Separator className="p-0.25"/>
<SidebarGroup className="flex flex-col gap-y-2 !-mt-2">
<div className="!bg-[#222225] rounded-lg">
<div className="!bg-dark-bg-input rounded-lg">
<Input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder={t('placeholders.searchHostsAny')}
className="w-full h-8 text-sm border-2 !bg-[#222225] border-[#303032] rounded-md"
className="w-full h-8 text-sm border-2 !bg-dark-bg-input border-dark-border rounded-md"
autoComplete="off"
/>
</div>
@@ -371,7 +372,6 @@ export function LeftSidebar({
<DropdownMenuTrigger asChild>
<SidebarMenuButton
className="data-[state=open]:opacity-90 w-full"
style={{width: '100%'}}
disabled={disabled}
>
<User2/> {username ? username : t('common.logout')}
@@ -437,40 +437,28 @@ export function LeftSidebar({
{!isSidebarOpen && (
<div
onClick={() => setIsSidebarOpen(true)}
className="absolute top-0 left-0 w-[10px] h-full bg-[#18181b] cursor-pointer z-20 flex items-center justify-center rounded-tr-md rounded-br-md">
className="absolute top-0 left-0 w-[10px] h-full bg-dark-bg cursor-pointer z-20 flex items-center justify-center rounded-tr-md rounded-br-md">
<ChevronRight size={10}/>
</div>
)}
{deleteAccountOpen && (
<div
className="fixed inset-0 z-[999999] flex"
className="fixed top-0 left-0 right-0 bottom-0 z-[999999] pointer-events-auto isolate"
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 999999,
pointerEvents: 'auto',
isolation: 'isolate',
transform: 'translateZ(0)',
willChange: 'z-index'
}}
>
<div
className="w-[400px] h-full bg-[#18181b] border-r-2 border-[#303032] flex flex-col shadow-2xl"
className="w-[400px] h-full bg-dark-bg border-r-2 border-dark-border flex flex-col shadow-2xl relative isolate z-[9999999]"
style={{
backgroundColor: '#18181b',
boxShadow: '4px 0 20px rgba(0, 0, 0, 0.5)',
zIndex: 9999999,
position: 'relative',
isolation: 'isolate',
transform: 'translateZ(0)'
}}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between p-4 border-b border-[#303032]">
<div className="flex items-center justify-between p-4 border-b border-dark-border">
<h2 className="text-lg font-semibold text-white">{t('leftSidebar.deleteAccount')}</h2>
<Button
variant="outline"
@@ -510,9 +498,8 @@ export function LeftSidebar({
<form onSubmit={handleDeleteAccount} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="delete-password">{t('leftSidebar.confirmPassword')}</Label>
<Input
<PasswordInput
id="delete-password"
type="password"
value={deletePassword}
onChange={(e) => setDeletePassword(e.target.value)}
placeholder={t('placeholders.confirmPassword')}
@@ -553,7 +540,7 @@ export function LeftSidebar({
setDeletePassword("");
setDeleteError(null);
}}
style={{cursor: 'pointer'}}
className="cursor-pointer"
/>
</div>
)}

View File

@@ -43,7 +43,7 @@ export function Tab({
return (
<Button
variant="outline"
className={`!px-2 border-1 border-[#303032] ${isActive ? '!bg-[#1d1d1f] !text-white !border-[#2d2d30]' : ''}`}
className={`!px-2 border-1 border-dark-border ${isActive ? '!bg-dark-bg-active !text-white !border-dark-border-active' : ''}`}
onClick={onActivate}
disabled={disableActivate}
>
@@ -59,7 +59,7 @@ export function Tab({
<ButtonGroup>
<Button
variant="outline"
className={`!px-2 border-1 border-[#303032] ${isActive ? '!bg-[#1d1d1f] !text-white !border-[#2d2d30]' : ''}`}
className={`!px-2 border-1 border-dark-border ${isActive ? '!bg-dark-bg-active !text-white !border-dark-border-active' : ''}`}
onClick={onActivate}
disabled={disableActivate}
>
@@ -70,7 +70,7 @@ export function Tab({
{canSplit && (
<Button
variant="outline"
className="!px-2 border-1 border-[#303032]"
className="!px-2 border-1 border-dark-border"
onClick={onSplit}
disabled={disableSplit}
title={disableSplit ? t('nav.cannotSplitTab') : t('nav.splitScreen')}
@@ -81,7 +81,7 @@ export function Tab({
{canClose && (
<Button
variant="outline"
className="!px-2 border-1 border-[#303032]"
className="!px-2 border-1 border-dark-border"
onClick={onClose}
disabled={disableClose}
>
@@ -97,7 +97,7 @@ export function Tab({
<ButtonGroup>
<Button
variant="outline"
className={`!px-2 border-1 border-[#303032] ${isActive ? '!bg-[#1d1d1f] !text-white !border-[#2d2d30]' : ''}`}
className={`!px-2 border-1 border-dark-border ${isActive ? '!bg-dark-bg-active !text-white !border-dark-border-active' : ''}`}
onClick={onActivate}
disabled={disableActivate}
>
@@ -105,7 +105,7 @@ export function Tab({
</Button>
<Button
variant="outline"
className="!px-2 border-1 border-[#303032]"
className="!px-2 border-1 border-dark-border"
onClick={onClose}
disabled={disableClose}
>
@@ -120,7 +120,7 @@ export function Tab({
<ButtonGroup>
<Button
variant="outline"
className={`!px-2 border-1 border-[#303032] ${isActive ? '!bg-[#1d1d1f] !text-white !border-[#2d2d30]' : ''}`}
className={`!px-2 border-1 border-dark-border ${isActive ? '!bg-dark-bg-active !text-white !border-dark-border-active' : ''}`}
onClick={onActivate}
disabled={disableActivate}
>
@@ -128,7 +128,7 @@ export function Tab({
</Button>
<Button
variant="outline"
className="!px-2 border-1 border-[#303032]"
className="!px-2 border-1 border-dark-border"
onClick={onClose}
disabled={disableClose}
>

View File

@@ -73,7 +73,7 @@ export function TabDropdown(): React.ReactElement {
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="w-[30px] h-[30px] border-[#303032]"
className="w-[30px] h-[30px] border-dark-border"
title={t('nav.tabNavigation', { defaultValue: 'Tab Navigation' })}
>
<ChevronDown className="h-4 w-4" />
@@ -81,7 +81,7 @@ export function TabDropdown(): React.ReactElement {
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-56 bg-[#18181b] border-[#303032] text-white"
className="w-56 bg-dark-bg border-dark-border text-white"
>
{tabs.map((tab) => {
const isActive = tab.id === currentTab;
@@ -91,8 +91,8 @@ export function TabDropdown(): React.ReactElement {
onClick={() => handleTabSwitch(tab.id)}
className={`flex items-center gap-2 cursor-pointer px-3 py-2 ${
isActive
? 'bg-[#1d1d1f] text-white'
: 'hover:bg-[#2d2d30] text-gray-300'
? 'bg-dark-bg-active text-white'
: 'hover:bg-dark-hover text-gray-300'
}`}
>
{getTabIcon(tab.type)}

View File

@@ -217,19 +217,15 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
return (
<div>
<div
className="fixed z-10 h-[50px] bg-[#18181b] border-2 border-[#303032] rounded-lg transition-all duration-200 ease-linear flex flex-row"
className="fixed z-10 h-[50px] bg-dark-bg border-2 border-dark-border rounded-lg transition-all duration-200 ease-linear flex flex-row transform-none m-0 p-0"
style={{
top: isTopbarOpen ? "0.5rem" : "-3rem",
left: leftPosition,
right: "17px",
position: "fixed",
transform: "none",
margin: "0",
padding: "0"
right: "17px"
}}
>
<div
className="h-full p-1 pr-2 border-r-2 border-[#303032] w-[calc(100%-6rem)] flex items-center overflow-x-auto overflow-y-hidden gap-2 thin-scrollbar">
className="h-full p-1 pr-2 border-r-2 border-dark-border w-[calc(100%-6rem)] flex items-center overflow-x-auto overflow-y-hidden gap-2 thin-scrollbar">
{tabs.map((tab: any) => {
const isActive = tab.id === currentTab;
const isSplit = Array.isArray(allSplitScreenTab) && allSplitScreenTab.includes(tab.id);
@@ -287,45 +283,32 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
{!isTopbarOpen && (
<div
onClick={() => setIsTopbarOpen(true)}
className="absolute top-0 left-0 w-full h-[10px] bg-[#18181b] cursor-pointer z-20 flex items-center justify-center rounded-bl-md rounded-br-md">
className="absolute top-0 left-0 w-full h-[10px] bg-dark-bg cursor-pointer z-20 flex items-center justify-center rounded-bl-md rounded-br-md">
<ChevronDown size={10}/>
</div>
)}
{toolsSheetOpen && (
<div
className="fixed inset-0 z-[999999] flex justify-end"
className="fixed top-0 left-0 right-0 bottom-0 z-[999999] flex justify-end pointer-events-auto isolate"
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 999999,
pointerEvents: 'auto',
isolation: 'isolate',
transform: 'translateZ(0)'
}}
>
<div
className="flex-1"
className="flex-1 cursor-pointer"
onClick={() => setToolsSheetOpen(false)}
style={{cursor: 'pointer'}}
/>
<div
className="w-[400px] h-full bg-[#18181b] border-l-2 border-[#303032] flex flex-col shadow-2xl"
className="w-[400px] h-full bg-dark-bg border-l-2 border-dark-border flex flex-col shadow-2xl relative isolate z-[999999]"
style={{
backgroundColor: '#18181b',
boxShadow: '-4px 0 20px rgba(0, 0, 0, 0.5)',
zIndex: 999999,
position: 'relative',
isolation: 'isolate',
transform: 'translateZ(0)'
}}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between p-4 border-b border-[#303032]">
<div className="flex items-center justify-between p-4 border-b border-dark-border">
<h2 className="text-lg font-semibold text-white">{t('sshTools.title')}</h2>
<Button
variant="outline"

View File

@@ -4,6 +4,7 @@ import React, {useState} from "react";
import {completePasswordReset, initiatePasswordReset, verifyPasswordResetCode} from "@/ui/main-axios.ts";
import {Label} from "@/components/ui/label.tsx";
import {Input} from "@/components/ui/input.tsx";
import {PasswordInput} from "@/components/ui/password-input.tsx";
import {Button} from "@/components/ui/button.tsx";
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert.tsx";
import {toast} from "sonner";
@@ -182,9 +183,8 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<Label htmlFor="new-password">{t('common.newPassword')}</Label>
<Input
<PasswordInput
id="new-password"
type="password"
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={newPassword}
@@ -195,9 +195,8 @@ export function PasswordReset({userInfo}: PasswordResetProps) {
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="confirm-password">{t('common.confirmPassword')}</Label>
<Input
<PasswordInput
id="confirm-password"
type="password"
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={confirmPassword}

View File

@@ -2,6 +2,7 @@ import React, { useState } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { Button } from "@/components/ui/button.tsx";
import { Input } from "@/components/ui/input.tsx";
import { PasswordInput } from "@/components/ui/password-input.tsx";
import { Label } from "@/components/ui/label.tsx";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx";
@@ -165,9 +166,8 @@ export function TOTPSetup({ isEnabled: initialEnabled, onStatusChange }: TOTPSet
<div className="space-y-2">
<Label htmlFor="disable-password">{t('auth.passwordOrTotpCode')}</Label>
<Input
<PasswordInput
id="disable-password"
type="password"
placeholder={t('placeholders.enterPassword')}
value={password}
onChange={(e) => setPassword(e.target.value)}
@@ -199,9 +199,8 @@ export function TOTPSetup({ isEnabled: initialEnabled, onStatusChange }: TOTPSet
<div className="space-y-2">
<Label htmlFor="backup-password">{t('auth.passwordOrTotpCode')}</Label>
<Input
<PasswordInput
id="backup-password"
type="password"
placeholder={t('placeholders.enterPassword')}
value={password}
onChange={(e) => setPassword(e.target.value)}

View File

@@ -97,9 +97,8 @@ export function UserProfile({isTopbarOpen = true}: UserProfileProps) {
}
return (
<div className="container max-w-4xl mx-auto p-6 overflow-y-auto" style={{
<div className="container max-w-4xl mx-auto p-6 overflow-y-auto transition-[margin-top] duration-300 ease-in-out" style={{
marginTop: isTopbarOpen ? '60px' : '0',
transition: 'margin-top 0.3s ease',
maxHeight: 'calc(100vh - 60px)'
}}>
<div className="mb-6">

View File

@@ -11,7 +11,7 @@ export function BottomNavbar({onSidebarOpenClick}: MenuProps) {
const {tabs, currentTab, setCurrentTab, removeTab} = useTabs();
return (
<div className="w-full h-[80px] bg-[#18181B] flex items-center p-2 gap-2">
<div className="w-full h-[80px] bg-dark-bg flex items-center p-2 gap-2">
<Button className="w-[40px] h-[40px] flex-shrink-0" variant="outline" onClick={onSidebarOpenClick}>
<Menu/>
</Button>
@@ -22,8 +22,8 @@ export function BottomNavbar({onSidebarOpenClick}: MenuProps) {
<Button
variant="outline"
className={cn(
"h-10 rounded-r-none !px-3 border-1 border-[#303032]",
tab.id === currentTab && '!bg-[#09090b] !text-white'
"h-10 rounded-r-none !px-3 border-1 border-dark-border",
tab.id === currentTab && '!bg-dark-bg-darkest !text-white'
)}
onClick={() => setCurrentTab(tab.id)}
>
@@ -32,7 +32,7 @@ export function BottomNavbar({onSidebarOpenClick}: MenuProps) {
</Button>
<Button
variant="outline"
className="h-10 rounded-l-none !px-2 border-1 border-[#303032]"
className="h-10 rounded-l-none !px-2 border-1 border-dark-border"
onClick={() => removeTab(tab.id)}
>
<X className="h-4 w-4"/>

View File

@@ -42,9 +42,8 @@ export function FolderCard({folderName, hosts, onHostConnect}: FolderCardProps):
};
return (
<div className="bg-[#0e0e10] border-2 border-[#303032] rounded-lg overflow-hidden"
style={{padding: '0', margin: '0'}}>
<div className={`px-4 py-3 relative ${isExpanded ? 'border-b-2' : ''} bg-[#131316]`}>
<div className="bg-dark-bg-darker border-2 border-dark-border rounded-lg overflow-hidden p-0 m-0">
<div className={`px-4 py-3 relative ${isExpanded ? 'border-b-2' : ''} bg-dark-bg-header`}>
<div className="flex gap-2 pr-10">
<div className="flex-shrink-0 flex items-center">
<Folder size={16} strokeWidth={3}/>

View File

@@ -85,7 +85,7 @@ export function Host({host, onHostConnect}: HostProps): React.ReactElement {
{host.enableTerminal && (
<Button
variant="outline"
className="!px-2 border-1 w-[60px] border-[#303032]"
className="!px-2 border-1 w-[60px] border-dark-border"
onClick={handleTerminalClick}
>
<Terminal/>
@@ -96,7 +96,7 @@ export function Host({host, onHostConnect}: HostProps): React.ReactElement {
{hasTags && (
<div className="flex flex-wrap items-center gap-2 mt-1">
{tags.map((tag: string) => (
<div key={tag} className="bg-[#18181b] border-1 border-[#303032] pl-2 pr-2 rounded-[10px]">
<div key={tag} className="bg-dark-bg border-1 border-dark-border pl-2 pr-2 rounded-[10px]">
<p className="text-sm">{tag}</p>
</div>
))}

View File

@@ -153,12 +153,12 @@ export function LeftSidebar({isSidebarOpen, setIsSidebarOpen, onHostConnect, dis
</SidebarHeader>
<Separator/>
<SidebarContent className="px-2 py-2">
<div className="!bg-[#222225] rounded-lg mb-2">
<div className="!bg-dark-bg-input rounded-lg mb-2">
<Input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder={t('placeholders.searchHostsAny')}
className="w-full h-8 text-sm border-2 !bg-[#222225] border-[#303032] rounded-md"
className="w-full h-8 text-sm border-2 !bg-dark-bg-input border-dark-border rounded-md"
autoComplete="off"
/>
</div>
@@ -196,7 +196,6 @@ export function LeftSidebar({isSidebarOpen, setIsSidebarOpen, onHostConnect, dis
<DropdownMenuTrigger asChild>
<SidebarMenuButton
className="data-[state=open]:opacity-90 w-full"
style={{width: '100%'}}
disabled={disabled}
>
<User2/> {username ? username : t('common.logout')}

View File

@@ -152,7 +152,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
scrollback: 10000,
fontSize: 14,
fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace',
theme: {background: '#09090b', foreground: '#f7f7f7'},
theme: {background: 'var(--color-dark-bg-darkest)', foreground: '#f7f7f7'},
allowTransparency: true,
convertEol: true,
windowsMode: false,
@@ -265,8 +265,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
return (
<div
ref={xtermRef}
className="h-full w-full m-1"
style={{opacity: visible && isVisible ? 1 : 0, overflow: 'hidden'}}
className={`h-full w-full m-1 transition-opacity duration-200 ${visible && isVisible ? 'opacity-100' : 'opacity-0'} overflow-hidden`}
/>
);
});

View File

@@ -401,7 +401,7 @@ export function HomepageAuth({
return (
<div
className={`w-full max-w-md flex flex-col bg-[#18181b] ${className || ''}`}
className={`w-full max-w-md flex flex-col bg-dark-bg ${className || ''}`}
{...props}
>
{dbError && (
@@ -676,9 +676,8 @@ export function HomepageAuth({
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<Label htmlFor="new-password">{t('auth.newPassword')}</Label>
<Input
<PasswordInput
id="new-password"
type="password"
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={newPassword}
@@ -690,9 +689,8 @@ export function HomepageAuth({
<div className="flex flex-col gap-2">
<Label
htmlFor="confirm-password">{t('auth.confirmNewPassword')}</Label>
<Input
<PasswordInput
id="confirm-password"
type="password"
required
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={confirmPassword}
@@ -744,14 +742,14 @@ export function HomepageAuth({
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="password">{t('common.password')}</Label>
<PasswordInput id="password" type="password" required className="h-55 text-base"
<PasswordInput id="password" required className="h-55 text-base"
value={password} onChange={e => setPassword(e.target.value)}
disabled={loading || internalLoggedIn}/>
</div>
{tab === "signup" && (
<div className="flex flex-col gap-2">
<Label htmlFor="signup-confirm-password">{t('common.confirmPassword')}</Label>
<PasswordInput id="signup-confirm-password" type="password" required
<PasswordInput id="signup-confirm-password" required
className="h-11 text-base"
value={signupConfirmPassword}
onChange={e => setSignupConfirmPassword(e.target.value)}
@@ -778,7 +776,7 @@ export function HomepageAuth({
</form>
)}
<div className="mt-6 pt-4 border-t border-[#303032]">
<div className="mt-6 pt-4 border-t border-dark-border">
<div className="flex items-center justify-between">
<div>
<Label className="text-sm text-muted-foreground">{t('common.language')}</Label>

View File

@@ -17,7 +17,7 @@ function getCookie(name: string) {
const AppContent: FC = () => {
const {t} = useTranslation();
const {tabs, currentTab, getTab} = useTabs();
const {tabs, currentTab, getTab, removeTab} = useTabs();
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
const [ready, setReady] = React.useState(true);
@@ -108,7 +108,7 @@ const AppContent: FC = () => {
if (authLoading) {
return (
<div className="h-screen w-screen flex items-center justify-center bg-[#09090b]">
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg-darkest">
<p className="text-white">{t('common.loading')}</p>
</div>
)
@@ -116,7 +116,7 @@ const AppContent: FC = () => {
if (!isAuthenticated) {
return (
<div className="h-screen w-screen flex items-center justify-center bg-[#18181b] p-4">
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg p-4">
<HomepageAuth
setLoggedIn={setIsAuthenticated}
setIsAdmin={setIsAdmin}
@@ -135,21 +135,18 @@ const AppContent: FC = () => {
}
return (
<div className="h-screen w-screen flex flex-col bg-[#09090b] overflow-y-hidden overflow-x-hidden relative">
<div className="h-screen w-screen flex flex-col bg-dark-bg-darkest overflow-y-hidden overflow-x-hidden relative">
<div className="flex-1 min-h-0 relative">
{tabs.map(tab => (
<div
key={tab.id}
className="absolute inset-0 mb-2"
style={{
visibility: tab.id === currentTab ? 'visible' : 'hidden',
opacity: ready ? 1 : 0,
}}
className={`absolute inset-0 mb-2 ${tab.id === currentTab ? 'visible' : 'invisible'} ${ready ? 'opacity-100' : 'opacity-0'}`}
>
<Terminal
ref={tab.terminalRef}
hostConfig={tab.hostConfig}
isVisible={tab.id === currentTab}
onClose={() => removeTab(tab.id)}
/>
</div>
))}

View File

@@ -11,7 +11,7 @@ export function BottomNavbar({onSidebarOpenClick}: MenuProps) {
const {tabs, currentTab, setCurrentTab, removeTab} = useTabs();
return (
<div className="w-full h-[50px] bg-[#18181B] items-center p-1">
<div className="w-full h-[50px] bg-dark-bg items-center p-1">
<div className="flex gap-2 !mb-0.5">
<Button className="w-[40px] h-[40px] flex-shrink-0" variant="outline" onClick={onSidebarOpenClick}>
<Menu/>
@@ -23,8 +23,8 @@ export function BottomNavbar({onSidebarOpenClick}: MenuProps) {
<Button
variant="outline"
className={cn(
"h-10 rounded-r-none !px-3 border-1 border-[#303032]",
tab.id === currentTab && '!bg-[#09090b] !text-white'
"h-10 rounded-r-none !px-3 border-1 border-dark-border",
tab.id === currentTab && '!bg-dark-bg-darkest !text-white'
)}
onClick={() => setCurrentTab(tab.id)}
>
@@ -33,7 +33,7 @@ export function BottomNavbar({onSidebarOpenClick}: MenuProps) {
</Button>
<Button
variant="outline"
className="h-10 rounded-l-none !px-2 border-1 border-[#303032]"
className="h-10 rounded-l-none !px-2 border-1 border-dark-border"
onClick={() => removeTab(tab.id)}
>
<X className="h-4 w-4"/>

View File

@@ -42,9 +42,8 @@ export function FolderCard({folderName, hosts, onHostConnect}: FolderCardProps):
};
return (
<div className="bg-[#0e0e10] border-2 border-[#303032] rounded-lg overflow-hidden"
style={{padding: '0', margin: '0'}}>
<div className={`px-4 py-3 relative ${isExpanded ? 'border-b-2' : ''} bg-[#131316]`}>
<div className="bg-dark-bg-darker border-2 border-dark-border rounded-lg overflow-hidden p-0 m-0">
<div className={`px-4 py-3 relative ${isExpanded ? 'border-b-2' : ''} bg-dark-bg-header`}>
<div className="flex gap-2 pr-10">
<div className="flex-shrink-0 flex items-center">
<Folder size={16} strokeWidth={3}/>

View File

@@ -85,7 +85,7 @@ export function Host({host, onHostConnect}: HostProps): React.ReactElement {
{host.enableTerminal && (
<Button
variant="outline"
className="!px-2 border-1 w-[60px] border-[#303032]"
className="!px-2 border-1 w-[60px] border-dark-border"
onClick={handleTerminalClick}
>
<Terminal/>
@@ -96,7 +96,7 @@ export function Host({host, onHostConnect}: HostProps): React.ReactElement {
{hasTags && (
<div className="flex flex-wrap items-center gap-2 mt-1">
{tags.map((tag: string) => (
<div key={tag} className="bg-[#18181b] border-1 border-[#303032] pl-2 pr-2 rounded-[10px]">
<div key={tag} className="bg-dark-bg border-1 border-dark-border pl-2 pr-2 rounded-[10px]">
<p className="text-sm">{tag}</p>
</div>
))}

View File

@@ -153,12 +153,12 @@ export function LeftSidebar({isSidebarOpen, setIsSidebarOpen, onHostConnect, dis
</SidebarHeader>
<Separator/>
<SidebarContent className="px-2 py-2">
<div className="!bg-[#222225] rounded-lg">
<div className="!bg-dark-bg-input rounded-lg">
<Input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder={t('placeholders.searchHostsAny')}
className="w-full h-8 text-sm border-2 !bg-[#222225] border-[#303032] rounded-md"
className="w-full h-8 text-sm border-2 !bg-dark-bg-input border-dark-border rounded-md"
autoComplete="off"
/>
</div>
@@ -196,7 +196,6 @@ export function LeftSidebar({isSidebarOpen, setIsSidebarOpen, onHostConnect, dis
<DropdownMenuTrigger asChild>
<SidebarMenuButton
className="data-[state=open]:opacity-90 w-full"
style={{width: '100%'}}
disabled={disabled}
>
<User2/> {username ? username : t('common.logout')}