diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index 6daaadd9..a0ce9a75 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -1600,6 +1600,15 @@
"commandAutocompleteDesc": "Enable Tab key autocomplete suggestions for terminal commands based on your command history",
"defaultSnippetFoldersCollapsed": "Collapse Snippet Folders by Default",
"defaultSnippetFoldersCollapsedDesc": "When enabled, all snippet folders will be collapsed when you open the snippets tab",
+ "showHostTags": "Show Host Tags",
+ "showHostTagsDesc": "Display tags under each host in the sidebar. Disable to hide all tags.",
+ "account": "Account",
+ "appearance": "Appearance",
+ "languageLocalization": "Language & Localization",
+ "fileManagerSettings": "File Manager",
+ "terminalSettings": "Terminal",
+ "hostSidebarSettings": "Host & Sidebar",
+ "snippetsSettings": "Snippets",
"currentPassword": "Current Password",
"passwordChangedSuccess": "Password changed successfully! Please log in again.",
"failedToChangePassword": "Failed to change password. Please check your current password and try again."
@@ -1642,7 +1651,7 @@
"searchHosts": "Search hosts by name, username, IP, folder, tags...",
"enterPassword": "Enter your password",
"totpCode": "6-digit TOTP code",
- "searchHostsAny": "Search hosts by any info...",
+ "searchHostsAny": "Search hosts (try: tag:prod, user:root, ip:192.168)...",
"confirmPassword": "Enter your password to confirm",
"typeHere": "Type here",
"fileName": "Enter file name (e.g., example.txt)",
diff --git a/src/ui/desktop/apps/dashboard/Dashboard.tsx b/src/ui/desktop/apps/dashboard/Dashboard.tsx
index 61a72bfa..9f019d9b 100644
--- a/src/ui/desktop/apps/dashboard/Dashboard.tsx
+++ b/src/ui/desktop/apps/dashboard/Dashboard.tsx
@@ -616,23 +616,35 @@ export function Dashboard({
{t("dashboard.noRecentActivity")}
) : (
- recentActivity.map((item) => (
-
- ))
+ recentActivity
+ .filter((item, index, array) => {
+ // Always show the first item
+ if (index === 0) return true;
+
+ // Show if different from previous item (by hostId and type)
+ const prevItem = array[index - 1];
+ return !(
+ item.hostId === prevItem.hostId &&
+ item.type === prevItem.type
+ );
+ })
+ .map((item) => (
+
+ ))
)}
diff --git a/src/ui/desktop/apps/file-manager/FileManager.tsx b/src/ui/desktop/apps/file-manager/FileManager.tsx
index d45dde8e..4c698d2a 100644
--- a/src/ui/desktop/apps/file-manager/FileManager.tsx
+++ b/src/ui/desktop/apps/file-manager/FileManager.tsx
@@ -336,6 +336,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
credentialId: currentHost.credentialId,
userId: currentHost.userId,
forceKeyboardInteractive: currentHost.forceKeyboardInteractive,
+ jumpHosts: currentHost.jumpHosts,
useSocks5: currentHost.useSocks5,
socks5Host: currentHost.socks5Host,
socks5Port: currentHost.socks5Port,
@@ -774,6 +775,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
sshKey: currentHost.key,
keyPassword: currentHost.keyPassword,
credentialId: currentHost.credentialId,
+ jumpHosts: currentHost.jumpHosts,
useSocks5: currentHost.useSocks5,
socks5Host: currentHost.socks5Host,
socks5Port: currentHost.socks5Port,
@@ -1325,6 +1327,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
authType: currentHost.authType,
credentialId: currentHost.credentialId,
userId: currentHost.userId,
+ jumpHosts: currentHost.jumpHosts,
useSocks5: currentHost.useSocks5,
socks5Host: currentHost.socks5Host,
socks5Port: currentHost.socks5Port,
@@ -1482,6 +1485,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
authType: credentials.password ? "password" : "key",
credentialId: currentHost.credentialId,
userId: currentHost.userId,
+ jumpHosts: currentHost.jumpHosts,
useSocks5: currentHost.useSocks5,
socks5Host: currentHost.socks5Host,
socks5Port: currentHost.socks5Port,
diff --git a/src/ui/desktop/apps/file-manager/components/TerminalWindow.tsx b/src/ui/desktop/apps/file-manager/components/TerminalWindow.tsx
index 84e3191e..5dc47dfa 100644
--- a/src/ui/desktop/apps/file-manager/components/TerminalWindow.tsx
+++ b/src/ui/desktop/apps/file-manager/components/TerminalWindow.tsx
@@ -60,6 +60,15 @@ export function TerminalWindow({
const handleMaximize = () => {
maximizeWindow(windowId);
+ // Trigger resize after maximize/restore
+ if (resizeTimeoutRef.current) {
+ clearTimeout(resizeTimeoutRef.current);
+ }
+ resizeTimeoutRef.current = setTimeout(() => {
+ if (terminalRef.current?.fit) {
+ terminalRef.current.fit();
+ }
+ }, 150);
};
const handleFocus = () => {
diff --git a/src/ui/desktop/apps/host-manager/HostManagerEditor.tsx b/src/ui/desktop/apps/host-manager/HostManagerEditor.tsx
index c5ccbbde..5cc3af24 100644
--- a/src/ui/desktop/apps/host-manager/HostManagerEditor.tsx
+++ b/src/ui/desktop/apps/host-manager/HostManagerEditor.tsx
@@ -219,6 +219,7 @@ function QuickActionItem({
placeholder={t("hosts.quickActionName")}
value={quickAction.name}
onChange={(e) => onUpdate(e.target.value, quickAction.snippetId)}
+ onBlur={(e) => onUpdate(e.target.value.trim(), quickAction.snippetId)}
className="flex-1"
/>
@@ -1196,6 +1197,10 @@ export function HostManagerEditor({
field.ref(e);
ipInputRef.current = e;
}}
+ onBlur={(e) => {
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
@@ -1238,6 +1243,10 @@ export function HostManagerEditor({
placeholder={t("placeholders.username")}
disabled={shouldDisable}
{...field}
+ onBlur={(e) => {
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
@@ -1259,6 +1268,10 @@ export function HostManagerEditor({
{
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
@@ -1283,6 +1296,10 @@ export function HostManagerEditor({
field.onChange(e);
setFolderDropdownOpen(true);
}}
+ onBlur={(e) => {
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
{folderDropdownOpen && filteredFolders.length > 0 && (
@@ -1878,6 +1895,10 @@ export function HostManagerEditor({
{
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
@@ -1927,6 +1948,10 @@ export function HostManagerEditor({
{
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
@@ -2047,6 +2072,23 @@ export function HostManagerEditor({
newChain,
);
}}
+ onBlur={(e) => {
+ const currentChain =
+ form.watch(
+ "socks5ProxyChain",
+ ) || [];
+ const newChain = [
+ ...currentChain,
+ ];
+ newChain[index] = {
+ ...newChain[index],
+ host: e.target.value.trim(),
+ };
+ form.setValue(
+ "socks5ProxyChain",
+ newChain,
+ );
+ }}
/>
@@ -2142,6 +2184,23 @@ export function HostManagerEditor({
newChain,
);
}}
+ onBlur={(e) => {
+ const currentChain =
+ form.watch(
+ "socks5ProxyChain",
+ ) || [];
+ const newChain = [
+ ...currentChain,
+ ];
+ newChain[index] = {
+ ...newChain[index],
+ username: e.target.value.trim(),
+ };
+ form.setValue(
+ "socks5ProxyChain",
+ newChain,
+ );
+ }}
/>
@@ -2694,6 +2753,10 @@ export function HostManagerEditor({
{
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
@@ -2769,6 +2832,10 @@ export function HostManagerEditor({
{
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
@@ -2780,7 +2847,14 @@ export function HostManagerEditor({
render={({ field }) => (
-
+ {
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
+ />
)}
@@ -3057,6 +3131,10 @@ export function HostManagerEditor({
}),
);
}}
+ onBlur={(e) => {
+ endpointHostField.onChange(e.target.value.trim());
+ endpointHostField.onBlur();
+ }}
/>
{sshConfigDropdownOpen[index] &&
@@ -3244,6 +3322,10 @@ export function HostManagerEditor({
{
+ field.onChange(e.target.value.trim());
+ field.onBlur();
+ }}
/>
diff --git a/src/ui/desktop/apps/host-manager/HostManagerViewer.tsx b/src/ui/desktop/apps/host-manager/HostManagerViewer.tsx
index 91beed2e..70f2b136 100644
--- a/src/ui/desktop/apps/host-manager/HostManagerViewer.tsx
+++ b/src/ui/desktop/apps/host-manager/HostManagerViewer.tsx
@@ -48,8 +48,6 @@ import {
Pencil,
FolderMinus,
Copy,
- Activity,
- Clock,
Palette,
Trash,
Cloud,
@@ -63,6 +61,8 @@ import {
FolderOpen,
Share2,
Users,
+ ArrowDownUp,
+ Container,
} from "lucide-react";
import type {
SSHHost,
@@ -583,46 +583,6 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
}
};
- const getMonitoringStatus = (host: SSHHost) => {
- try {
- const statsConfig = host.statsConfig
- ? JSON.parse(host.statsConfig)
- : DEFAULT_STATS_CONFIG;
-
- const formatInterval = (seconds: number): string => {
- if (seconds >= 60) {
- const minutes = Math.round(seconds / 60);
- return `${minutes}m`;
- }
- return `${seconds}s`;
- };
-
- const statusEnabled = statsConfig.statusCheckEnabled !== false;
- const metricsEnabled = statsConfig.metricsEnabled !== false;
- const statusInterval = statusEnabled
- ? formatInterval(statsConfig.statusCheckInterval || 30)
- : null;
- const metricsInterval = metricsEnabled
- ? formatInterval(statsConfig.metricsInterval || 30)
- : null;
-
- return {
- statusEnabled,
- metricsEnabled,
- statusInterval,
- metricsInterval,
- bothDisabled: !statusEnabled && !metricsEnabled,
- };
- } catch {
- return {
- statusEnabled: true,
- metricsEnabled: true,
- statusInterval: "30s",
- metricsInterval: "30s",
- bothDisabled: false,
- };
- }
- };
const filteredAndSortedHosts = useMemo(() => {
let filtered = hosts;
@@ -1419,48 +1379,15 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
{t("hosts.fileManagerBadge")}
)}
-
- {(() => {
- const monitoringStatus =
- getMonitoringStatus(host);
-
- if (monitoringStatus.bothDisabled) {
- return (
-
-
- {t("hosts.monitoringDisabledBadge")}
-
- );
- }
-
- return (
- <>
- {monitoringStatus.statusEnabled && (
-
-
- {t("hosts.statusMonitoring")}:{" "}
- {monitoringStatus.statusInterval}
-
- )}
- {monitoringStatus.metricsEnabled && (
-
-
- {t("hosts.metricsMonitoring")}:{" "}
- {monitoringStatus.metricsInterval}
-
- )}
- >
- );
- })()}
+ {host.enableDocker && (
+
+
+ Docker
+
+ )}
@@ -1519,6 +1446,60 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
)}
+ {host.enableTunnel && (
+
+
+
+
+
+ Open Tunnels
+
+
+ )}
+ {host.enableDocker && (
+
+
+
+
+
+ Open Docker
+
+
+ )}