feat: Enhanced security, UI improvements, and animations (#432)

* fix: Remove empty catch blocks and add error logging

* refactor: Modularize server stats widget collectors

* feat: Add i18n support for terminal customization and login stats

- Add comprehensive terminal customization translations (60+ keys) for appearance, behavior, and advanced settings across all 4 languages
- Add SSH login statistics translations
- Update HostManagerEditor to use i18n for all terminal customization UI elements
- Update LoginStatsWidget to use i18n for all UI text
- Add missing logger imports in backend files for improved debugging

* feat: Add keyboard shortcut enhancements with Kbd component

- Add shadcn kbd component for displaying keyboard shortcuts
- Enhance file manager context menu to display shortcuts with Kbd component
- Add 5 new keyboard shortcuts to file manager:
  - Ctrl+D: Download selected files
  - Ctrl+N: Create new file
  - Ctrl+Shift+N: Create new folder
  - Ctrl+U: Upload files
  - Enter: Open/run selected file
- Add keyboard shortcut hints to command palette footer
- Create helper function to parse and render keyboard shortcuts

* feat: Add i18n support for command palette

- Add commandPalette translation section with 22 keys to all 4 languages
- Update CommandPalette component to use i18n for all UI text
- Translate search placeholder, group headings, menu items, and shortcut hints
- Support multilingual command palette interface

* feat: Add smooth transitions and animations to UI

- Add fade-in/fade-out transition to command palette (200ms)
- Add scale animation to command palette on open/close
- Add smooth popup animation to context menu (150ms)
- Add visual feedback for file selection with ring effect
- Add hover scale effect to file grid items
- Add transition-all to list view items for consistent behavior
- Zero JavaScript overhead, pure CSS transitions
- All animations under 200ms for instant feel

* feat: Add button active state and dashboard card animations

- Add active:scale-95 to all buttons for tactile click feedback
- Add hover border effect to dashboard cards (150ms transition)
- Add pulse animation to dashboard loading states
- Pure CSS transitions with zero JavaScript overhead
- Improves enterprise-level feel of UI

* feat: Add smooth macOS-style page transitions

- Add fullscreen crossfade transition for login/logout (300ms fade-out + 400ms fade-in)
- Add slide-in-from-right animation for all page switches (Dashboard, Terminal, SSH Manager, Admin, Profile)
- Fix TypeScript compilation by adding esModuleInterop to tsconfig.node.json
- Pass handleLogout from DesktopApp to LeftSidebar for consistent transition behavior

All page transitions now use Tailwind animate-in utilities with 300ms duration for smooth, native-feeling UX

* fix: Add key prop to force animation re-trigger on tab switch

Each page container now has key={currentTab} to ensure React unmounts and remounts the element on every tab switch, properly triggering the slide-in animation

* revert: Remove page transition animations

Page switching animations were not noticeable enough and felt unnecessary.
Keep only the login/logout fullscreen crossfade transitions which provide clear visual feedback for authentication state changes

* feat: Add ripple effect to login/logout transitions

Add three-layer expanding ripple animation during fadeOut phase:
- Ripples expand from screen center using primary theme color
- Each layer has staggered delay (0ms, 150ms, 300ms) for wave effect
- Ripples fade out as they expand to create elegant visual feedback
- Uses pure CSS keyframe animation, no external libraries

Total animation: 800ms ripple + 300ms screen fade

* feat: Add smooth TERMIX logo animation to transitions

Changes:
- Extend transition duration from 300ms/400ms to 800ms/600ms for more elegant feel
- Reduce ripple intensity from /20,/15,/10 to /8,/5 for subtlety
- Slow down ripple animation from 0.8s to 2s with cubic-bezier easing
- Add centered TERMIX logo with monospace font and subtitle
- Logo fades in from 80% scale, holds, then fades out at 110% scale
- Total effect: 1.2s logo animation synced with 2s ripple waves

Creates a premium, branded transition experience

* feat: Enhance transition animation with premium details

Timing adjustments:
- Extend fadeOut from 800ms to 1200ms
- Extend fadeIn from 600ms to 800ms
- Slow background fade to 700ms for elegance

Visual enhancements:
- Add 4-layer ripple waves (10%, 7%, 5%, 3% opacity) with staggered delays
- Ripple animation extended to 2.5s with refined opacity curve
- Logo blur effect: starts at 8px, sharpens to 0px, exits at 4px
- Logo glow effect: triple-layer text-shadow using primary theme color
- Increase logo size from text-6xl to text-7xl
- Subtitle delayed fade-in from bottom with smooth slide animation

Creates a cinematic, polished brand experience

* feat: Redesign login page with split-screen cinematic layout

Major redesign of authentication page:

Left Side (40% width):
- Full-height gradient background using primary theme color
- Large TERMIX logo with glow effect
- Subtitle and tagline
- Infinite animated ripple waves (3 layers)
- Hidden on mobile, shows brand identity

Right Side (60% width):
- Centered glassmorphism card with backdrop blur
- Refined tab switcher with pill-style active state
- Enlarged title with gradient text effect
- Added welcome subtitles for better UX
- Card slides in from bottom on load
- All existing functionality preserved

Visual enhancements:
- Tab navigation: segmented control style in muted container
- Active tab: white background with subtle shadow
- Smooth 200ms transitions on all interactions
- Card: rounded-2xl, shadow-xl, semi-transparent border

Creates premium, modern login experience matching transition animations

* feat: Update login page theme colors and add i18n support

- Changed login page gradient from blue to match dark theme colors
- Updated ripple effects to use theme primary color
- Added i18n translation keys for login page (auth.tagline, auth.description, auth.welcomeBack, auth.createAccount, auth.continueExternal)
- Updated all language files (en, zh, de, ru, pt-BR) with new translations
- Fixed TypeScript compilation issues by clearing build cache

* refactor: Use shadcn Tabs component and fix modal styling

- Replace custom tab navigation with shadcn Tabs component
- Restore border-2 border-dark-border for modal consistency
- Remove circular icon from login success message
- Simplify authentication success display

* refactor: Remove ripple effects and gradient from login page

- Remove animated ripple background effects
- Remove gradient background, use solid color (bg-dark-bg-darker)
- Remove text-shadow glow effect from logo
- Simplify brand showcase to clean, minimal design

* feat: Add decorative slash and remove subtitle from login page

- Add decorative slash divider with gradient lines below TERMIX logo
- Remove subtitle text (welcomeBack and createAccount)
- Simplify page title to show only the main heading

* feat: Add diagonal line pattern background to login page

- Replace decorative slash with subtle diagonal line pattern background
- Use repeating-linear-gradient at 45deg angle
- Set very low opacity (0.03) for subtle effect
- Pattern uses theme primary color

* fix: Display diagonal line pattern on login background

- Combine background color and pattern in single style attribute
- Use white semi-transparent lines (rgba 0.03 opacity)
- 45deg angle, 35px spacing, 2px width
- Remove separate overlay div to ensure pattern visibility

* security: Fix user enumeration vulnerability in login

- Unify error messages for invalid username and incorrect password
- Both return 401 status with 'Invalid username or password'
- Prevent attackers from enumerating valid usernames
- Maintain detailed logging for debugging purposes
- Changed from 404 'User not found' to generic auth failure message

* security: Add login rate limiting to prevent brute force attacks

- Implement LoginRateLimiter with IP and username-based tracking
- Block after 5 failed attempts within 15 minutes
- Lock account/IP for 15 minutes after threshold
- Automatic cleanup of expired entries every 5 minutes
- Track remaining attempts in logs for monitoring
- Return 429 status with remaining time on rate limit
- Reset counters on successful login
- Dual protection: both IP-based and username-based limits
This commit was merged in pull request #432.
This commit is contained in:
ZacharyZcR
2025-11-09 09:48:32 +08:00
committed by GitHub
parent b43e98073f
commit 5fc2ec3dc0
55 changed files with 2081 additions and 649 deletions

View File

@@ -167,7 +167,9 @@ export function HostManagerEditor({
setFolders(uniqueFolders);
setSshConfigurations(uniqueConfigurations);
} catch {}
} catch (error) {
console.error("Host manager operation failed:", error);
}
};
fetchData();
@@ -196,7 +198,9 @@ export function HostManagerEditor({
setFolders(uniqueFolders);
setSshConfigurations(uniqueConfigurations);
} catch {}
} catch (error) {
console.error("Host manager operation failed:", error);
}
};
window.addEventListener("credentials:changed", handleCredentialChange);
@@ -262,9 +266,18 @@ export function HostManagerEditor({
"uptime",
"processes",
"system",
"login_stats",
]),
)
.default(["cpu", "memory", "disk", "network", "uptime", "system"]),
.default([
"cpu",
"memory",
"disk",
"network",
"uptime",
"system",
"login_stats",
]),
statusCheckEnabled: z.boolean().default(true),
statusCheckInterval: z.number().min(5).max(3600).default(30),
metricsEnabled: z.boolean().default(true),
@@ -278,6 +291,7 @@ export function HostManagerEditor({
"network",
"uptime",
"system",
"login_stats",
],
statusCheckEnabled: true,
statusCheckInterval: 30,
@@ -1399,15 +1413,15 @@ export function HostManagerEditor({
</AlertDescription>
</Alert>
<h1 className="text-xl font-semibold mt-7">
Terminal Customization
{t("hosts.terminalCustomization")}
</h1>
<Accordion type="multiple" className="w-full">
<AccordionItem value="appearance">
<AccordionTrigger>Appearance</AccordionTrigger>
<AccordionTrigger>{t("hosts.appearance")}</AccordionTrigger>
<AccordionContent className="space-y-4 pt-4">
<div className="space-y-2">
<label className="text-sm font-medium">
Theme Preview
{t("hosts.themePreview")}
</label>
<TerminalPreview
theme={form.watch("terminalConfig.theme")}
@@ -1431,14 +1445,14 @@ export function HostManagerEditor({
name="terminalConfig.theme"
render={({ field }) => (
<FormItem>
<FormLabel>Theme</FormLabel>
<FormLabel>{t("hosts.theme")}</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select theme" />
<SelectValue placeholder={t("hosts.selectTheme")} />
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -1452,7 +1466,7 @@ export function HostManagerEditor({
</SelectContent>
</Select>
<FormDescription>
Choose a color theme for the terminal
{t("hosts.chooseColorTheme")}
</FormDescription>
</FormItem>
)}
@@ -1463,14 +1477,14 @@ export function HostManagerEditor({
name="terminalConfig.fontFamily"
render={({ field }) => (
<FormItem>
<FormLabel>Font Family</FormLabel>
<FormLabel>{t("hosts.fontFamily")}</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select font" />
<SelectValue placeholder={t("hosts.selectFont")} />
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -1485,7 +1499,7 @@ export function HostManagerEditor({
</SelectContent>
</Select>
<FormDescription>
Select the font to use in the terminal
{t("hosts.selectFontDesc")}
</FormDescription>
</FormItem>
)}
@@ -1496,7 +1510,7 @@ export function HostManagerEditor({
name="terminalConfig.fontSize"
render={({ field }) => (
<FormItem>
<FormLabel>Font Size: {field.value}px</FormLabel>
<FormLabel>{t("hosts.fontSizeValue", { value: field.value })}</FormLabel>
<FormControl>
<Slider
min={8}
@@ -1509,7 +1523,7 @@ export function HostManagerEditor({
/>
</FormControl>
<FormDescription>
Adjust the terminal font size
{t("hosts.adjustFontSize")}
</FormDescription>
</FormItem>
)}
@@ -1521,7 +1535,7 @@ export function HostManagerEditor({
render={({ field }) => (
<FormItem>
<FormLabel>
Letter Spacing: {field.value}px
{t("hosts.letterSpacingValue", { value: field.value })}
</FormLabel>
<FormControl>
<Slider
@@ -1535,7 +1549,7 @@ export function HostManagerEditor({
/>
</FormControl>
<FormDescription>
Adjust spacing between characters
{t("hosts.adjustLetterSpacing")}
</FormDescription>
</FormItem>
)}
@@ -1546,7 +1560,7 @@ export function HostManagerEditor({
name="terminalConfig.lineHeight"
render={({ field }) => (
<FormItem>
<FormLabel>Line Height: {field.value}</FormLabel>
<FormLabel>{t("hosts.lineHeightValue", { value: field.value })}</FormLabel>
<FormControl>
<Slider
min={1}
@@ -1559,7 +1573,7 @@ export function HostManagerEditor({
/>
</FormControl>
<FormDescription>
Adjust spacing between lines
{t("hosts.adjustLineHeight")}
</FormDescription>
</FormItem>
)}
@@ -1570,26 +1584,26 @@ export function HostManagerEditor({
name="terminalConfig.cursorStyle"
render={({ field }) => (
<FormItem>
<FormLabel>Cursor Style</FormLabel>
<FormLabel>{t("hosts.cursorStyle")}</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select cursor style" />
<SelectValue placeholder={t("hosts.selectCursorStyle")} />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="block">Block</SelectItem>
<SelectItem value="block">{t("hosts.cursorStyleBlock")}</SelectItem>
<SelectItem value="underline">
Underline
{t("hosts.cursorStyleUnderline")}
</SelectItem>
<SelectItem value="bar">Bar</SelectItem>
<SelectItem value="bar">{t("hosts.cursorStyleBar")}</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Choose the cursor appearance
{t("hosts.chooseCursorAppearance")}
</FormDescription>
</FormItem>
)}
@@ -1601,9 +1615,9 @@ export function HostManagerEditor({
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<FormLabel>Cursor Blink</FormLabel>
<FormLabel>{t("hosts.cursorBlink")}</FormLabel>
<FormDescription>
Enable cursor blinking animation
{t("hosts.enableCursorBlink")}
</FormDescription>
</div>
<FormControl>
@@ -1619,7 +1633,7 @@ export function HostManagerEditor({
</AccordionItem>
<AccordionItem value="behavior">
<AccordionTrigger>Behavior</AccordionTrigger>
<AccordionTrigger>{t("hosts.behavior")}</AccordionTrigger>
<AccordionContent className="space-y-4 pt-4">
<FormField
control={form.control}
@@ -1627,7 +1641,7 @@ export function HostManagerEditor({
render={({ field }) => (
<FormItem>
<FormLabel>
Scrollback Buffer: {field.value} lines
{t("hosts.scrollbackBufferValue", { value: field.value })}
</FormLabel>
<FormControl>
<Slider
@@ -1641,7 +1655,7 @@ export function HostManagerEditor({
/>
</FormControl>
<FormDescription>
Number of lines to keep in scrollback history
{t("hosts.scrollbackBufferDesc")}
</FormDescription>
</FormItem>
)}
@@ -1652,30 +1666,25 @@ export function HostManagerEditor({
name="terminalConfig.bellStyle"
render={({ field }) => (
<FormItem>
<FormLabel>Bell Style</FormLabel>
<FormLabel>{t("hosts.bellStyle")}</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select bell style" />
<SelectValue placeholder={t("hosts.selectBellStyle")} />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value="sound">Sound</SelectItem>
<SelectItem value="visual">Visual</SelectItem>
<SelectItem value="both">Both</SelectItem>
<SelectItem value="none">{t("hosts.bellStyleNone")}</SelectItem>
<SelectItem value="sound">{t("hosts.bellStyleSound")}</SelectItem>
<SelectItem value="visual">{t("hosts.bellStyleVisual")}</SelectItem>
<SelectItem value="both">{t("hosts.bellStyleBoth")}</SelectItem>
</SelectContent>
</Select>
<FormDescription>
How to handle terminal bell (BEL character,
\x07). Programs trigger this when completing
tasks, encountering errors, or for
notifications. "Sound" plays an audio beep,
"Visual" flashes the screen briefly, "Both" does
both, "None" disables bell alerts.
{t("hosts.bellStyleDesc")}
</FormDescription>
</FormItem>
)}
@@ -1687,9 +1696,9 @@ export function HostManagerEditor({
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<FormLabel>Right Click Selects Word</FormLabel>
<FormLabel>{t("hosts.rightClickSelectsWord")}</FormLabel>
<FormDescription>
Right-clicking selects the word under cursor
{t("hosts.rightClickSelectsWordDesc")}
</FormDescription>
</div>
<FormControl>
@@ -1707,24 +1716,24 @@ export function HostManagerEditor({
name="terminalConfig.fastScrollModifier"
render={({ field }) => (
<FormItem>
<FormLabel>Fast Scroll Modifier</FormLabel>
<FormLabel>{t("hosts.fastScrollModifier")}</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select modifier" />
<SelectValue placeholder={t("hosts.selectModifier")} />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="alt">Alt</SelectItem>
<SelectItem value="ctrl">Ctrl</SelectItem>
<SelectItem value="shift">Shift</SelectItem>
<SelectItem value="alt">{t("hosts.modifierAlt")}</SelectItem>
<SelectItem value="ctrl">{t("hosts.modifierCtrl")}</SelectItem>
<SelectItem value="shift">{t("hosts.modifierShift")}</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Modifier key for fast scrolling
{t("hosts.fastScrollModifierDesc")}
</FormDescription>
</FormItem>
)}
@@ -1736,7 +1745,7 @@ export function HostManagerEditor({
render={({ field }) => (
<FormItem>
<FormLabel>
Fast Scroll Sensitivity: {field.value}
{t("hosts.fastScrollSensitivityValue", { value: field.value })}
</FormLabel>
<FormControl>
<Slider
@@ -1750,7 +1759,7 @@ export function HostManagerEditor({
/>
</FormControl>
<FormDescription>
Scroll speed multiplier when modifier is held
{t("hosts.fastScrollSensitivityDesc")}
</FormDescription>
</FormItem>
)}
@@ -1762,7 +1771,7 @@ export function HostManagerEditor({
render={({ field }) => (
<FormItem>
<FormLabel>
Minimum Contrast Ratio: {field.value}
{t("hosts.minimumContrastRatioValue", { value: field.value })}
</FormLabel>
<FormControl>
<Slider
@@ -1776,8 +1785,7 @@ export function HostManagerEditor({
/>
</FormControl>
<FormDescription>
Automatically adjust colors for better
readability
{t("hosts.minimumContrastRatioDesc")}
</FormDescription>
</FormItem>
)}
@@ -1786,7 +1794,7 @@ export function HostManagerEditor({
</AccordionItem>
<AccordionItem value="advanced">
<AccordionTrigger>Advanced</AccordionTrigger>
<AccordionTrigger>{t("hosts.advanced")}</AccordionTrigger>
<AccordionContent className="space-y-4 pt-4">
<FormField
control={form.control}
@@ -1794,10 +1802,9 @@ export function HostManagerEditor({
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<FormLabel>SSH Agent Forwarding</FormLabel>
<FormLabel>{t("hosts.sshAgentForwarding")}</FormLabel>
<FormDescription>
Forward SSH authentication agent to remote
host
{t("hosts.sshAgentForwardingDesc")}
</FormDescription>
</div>
<FormControl>
@@ -1815,27 +1822,27 @@ export function HostManagerEditor({
name="terminalConfig.backspaceMode"
render={({ field }) => (
<FormItem>
<FormLabel>Backspace Mode</FormLabel>
<FormLabel>{t("hosts.backspaceMode")}</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select backspace mode" />
<SelectValue placeholder={t("hosts.selectBackspaceMode")} />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="normal">
Normal (DEL)
{t("hosts.backspaceModeNormal")}
</SelectItem>
<SelectItem value="control-h">
Control-H (^H)
{t("hosts.backspaceModeControlH")}
</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Backspace key behavior for compatibility
{t("hosts.backspaceModeDesc")}
</FormDescription>
</FormItem>
)}
@@ -1846,7 +1853,7 @@ export function HostManagerEditor({
name="terminalConfig.startupSnippetId"
render={({ field }) => (
<FormItem>
<FormLabel>Startup Snippet</FormLabel>
<FormLabel>{t("hosts.startupSnippet")}</FormLabel>
<Select
onValueChange={(value) => {
field.onChange(
@@ -1858,13 +1865,13 @@ export function HostManagerEditor({
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select snippet" />
<SelectValue placeholder={t("hosts.selectSnippet")} />
</SelectTrigger>
</FormControl>
<SelectContent>
<div className="px-2 pb-2 sticky top-0 bg-popover z-10">
<Input
placeholder="Search snippets..."
placeholder={t("hosts.searchSnippets")}
value={snippetSearch}
onChange={(e) =>
setSnippetSearch(e.target.value)
@@ -1875,7 +1882,7 @@ export function HostManagerEditor({
/>
</div>
<div className="max-h-[200px] overflow-y-auto">
<SelectItem value="none">None</SelectItem>
<SelectItem value="none">{t("hosts.snippetNone")}</SelectItem>
{snippets
.filter((snippet) =>
snippet.name
@@ -2657,6 +2664,7 @@ export function HostManagerEditor({
"uptime",
"processes",
"system",
"login_stats",
] as const
).map((widget) => (
<div
@@ -2696,6 +2704,8 @@ export function HostManagerEditor({
t("serverStats.processes")}
{widget === "system" &&
t("serverStats.systemInfo")}
{widget === "login_stats" &&
t("serverStats.loginStats")}
</label>
</div>
))}