diff --git a/docker/nginx.conf b/docker/nginx.conf index 728aad3b..7afc334f 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -18,7 +18,7 @@ http { index index.html index.htm; } - location /users/ { + location ~ ^/users(/.*)?$ { proxy_pass http://127.0.0.1:8081; proxy_http_version 1.1; proxy_set_header Host $host; @@ -27,7 +27,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location /version/ { + location ~ ^/version(/.*)?$ { proxy_pass http://127.0.0.1:8081; proxy_http_version 1.1; proxy_set_header Host $host; @@ -36,7 +36,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location /releases/ { + location ~ ^/releases(/.*)?$ { proxy_pass http://127.0.0.1:8081; proxy_http_version 1.1; proxy_set_header Host $host; @@ -45,7 +45,16 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location /alerts/ { + location ~ ^/alerts(/.*)?$ { + proxy_pass http://127.0.0.1:8081; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~ ^/credentials(/.*)?$ { proxy_pass http://127.0.0.1:8081; proxy_http_version 1.1; proxy_set_header Host $host; @@ -129,7 +138,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location /status/ { + location ~ ^/status(/.*)?$ { proxy_pass http://127.0.0.1:8085; proxy_http_version 1.1; proxy_set_header Host $host; @@ -138,7 +147,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location /metrics/ { + location ~ ^/metrics(/.*)?$ { proxy_pass http://127.0.0.1:8085; proxy_http_version 1.1; proxy_set_header Host $host; diff --git a/electron/main.cjs b/electron/main.cjs index dd5f2c21..677b0df1 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -129,7 +129,7 @@ ipcMain.handle('save-server-config', (event, config) => { ipcMain.handle('test-server-connection', async (event, serverUrl) => { try { - const { default: fetch } = await import('node-fetch'); + const fetch = require('node-fetch'); // Try multiple endpoints to test the connection const testUrls = [ diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index a2817a37..a4a224d5 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -507,9 +507,11 @@ "unknownError": "Unknown error occurred", "messageParseError": "Failed to parse server message", "websocketError": "WebSocket connection error", + "connecting": "Connecting...", "reconnecting": "Reconnecting... ({{attempt}}/{{max}})", "reconnected": "Reconnected successfully", - "maxReconnectAttemptsReached": "Maximum reconnection attempts reached" + "maxReconnectAttemptsReached": "Maximum reconnection attempts reached", + "connectionTimeout": "Connection timeout" }, "fileManager": { "title": "File Manager", @@ -816,7 +818,6 @@ "oidcAuthFailed": "OIDC authentication failed", "noTokenReceived": "No token received from login", "invalidAuthUrl": "Invalid authorization URL received from backend", - "connectionTimeout": "Connection timeout", "invalidInput": "Invalid input", "requiredField": "This field is required", "minLength": "Minimum length is {{min}}", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 89330ab9..e500d3f2 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -58,9 +58,8 @@ "keyPassword": "密钥密码(可选)", "keyType": "密钥类型", "keyTypeRSA": "RSA", - "keyTypeECDSA": "ECDSA", + "keyTypeECDSA": "ECDSA", "keyTypeEd25519": "Ed25519", - "updateCredential": "更新凭据", "basicInfo": "基本信息", "authentication": "认证方式", "organization": "组织管理", @@ -106,7 +105,6 @@ "credentialSecuredDescription": "所有敏感数据均使用AES-256加密", "passwordAuthentication": "密码认证", "keyAuthentication": "密钥认证", - "keyType": "密钥类型", "securityReminder": "安全提醒", "securityReminderText": "请勿分享您的凭据。所有数据均已静态加密。", "hostsUsingCredential": "使用此凭据的主机", @@ -181,7 +179,7 @@ "warning": "警告", "info": "信息", "success": "成功", - "loading": "加载中", + "loading": "加载中...", "required": "必填", "optional": "可选", "clear": "清除", @@ -195,8 +193,7 @@ "updateAvailable": "有可用更新", "sshPath": "SSH 路径", "localPath": "本地路径", - "loading": "加载中...", - "noAuthCredentials": "此 SSH 主机没有可用的身份验证凭据", + "noAuthCredentials": "此 SSH 主机没有可用的认证凭据", "noReleases": "没有发布版本", "updatesAndReleases": "更新与发布", "newVersionAvailable": "有新版本 ({{version}}) 可用。", @@ -209,13 +206,10 @@ "resetPassword": "重置密码", "resetCode": "重置代码", "newPassword": "新密码", - "sshPath": "SSH 路径", - "localPath": "本地路径", "folder": "文件夹", "file": "文件", "renamedSuccessfully": "重命名成功", "deletedSuccessfully": "删除成功", - "noAuthCredentials": "此 SSH 主机没有可用的认证凭据", "noTunnelConnections": "没有配置隧道连接", "sshTools": "SSH 工具", "english": "英语", @@ -236,22 +230,15 @@ "edit": "编辑", "add": "添加", "search": "搜索", - "loading": "加载中...", - "error": "错误", - "success": "成功", - "warning": "警告", - "info": "信息", "confirm": "确认", "yes": "是", "no": "否", "ok": "确定", - "close": "关闭", "enabled": "已启用", "disabled": "已禁用", "important": "重要", "notEnabled": "未启用", "settingUp": "设置中...", - "back": "返回", "next": "下一步", "previous": "上一步", "refresh": "刷新", @@ -297,7 +284,7 @@ "userManagement": "用户管理", "makeAdmin": "设为管理员", "removeAdmin": "移除管理员", - "deleteUser": "删除用户", + "deleteUser": "删除用户 {{username}} 吗?此操作无法撤销。", "allowRegistration": "允许注册", "oidcSettings": "OIDC 设置", "clientId": "客户端 ID", @@ -346,7 +333,6 @@ "removeAdminStatus": "移除 {{username}} 的管理员权限吗?", "adminStatusRemoved": "已移除 {{username}} 的管理员权限", "failedToRemoveAdminStatus": "移除管理员权限失败", - "deleteUser": "删除用户 {{username}} 吗?此操作无法撤销。", "userDeletedSuccessfully": "用户 {{username}} 删除成功", "failedToDeleteUser": "删除用户失败", "overrideUserInfoUrl": "覆盖用户信息 URL(非必填)" @@ -380,7 +366,7 @@ "importError": "导入错误", "failedToImportJson": "导入 JSON 文件失败", "connectionDetails": "连接详情", - "organization": "组织", + "organization": "组织管理", "ipAddress": "IP 地址", "port": "端口", "name": "名称", @@ -395,16 +381,11 @@ "addHost": "添加主机", "editHost": "编辑主机", "deleteHost": "删除主机", - "hostName": "主机名", - "ipAddress": "IP 地址", - "port": "端口", "authType": "认证类型", "passwordAuth": "密码", "keyAuth": "SSH 密钥", "keyPassword": "密钥密码", "keyType": "密钥类型", - "folder": "文件夹", - "tags": "标签", "pin": "固定", "enableTerminal": "启用终端", "enableTunnel": "启用隧道", @@ -418,8 +399,6 @@ "connecting": "连接中...", "connectionFailed": "连接失败", "connectionSuccess": "连接成功", - "connectionDetails": "连接详情", - "organization": "组织管理", "addTags": "添加标签(空格添加)", "sourcePort": "源端口", "sourcePortDesc": "(源指通用标签页中的当前连接详情)", @@ -465,8 +444,6 @@ "credentialRequired": "使用凭证认证时需要选择凭证", "credentialDescription": "选择凭证将覆盖当前用户名并使用凭证的认证详细信息。", "sshPrivateKey": "SSH 私钥", - "keyPassword": "密钥密码", - "keyType": "密钥类型", "maxRetriesDescription": "隧道连接的最大重试次数。", "retryIntervalDescription": "重试尝试之间的等待时间。", "otherInstallMethods": "其他安装方法:", @@ -533,7 +510,18 @@ "error": "错误", "disconnected": "已断开连接", "connectionClosed": "连接已关闭", - "connectionError": "连接错误" + "connectionError": "连接错误", + "connected": "已连接", + "sshConnected": "SSH 连接已建立", + "authError": "认证失败:{{message}}", + "unknownError": "发生未知错误", + "messageParseError": "解析服务器消息失败", + "websocketError": "WebSocket 连接错误", + "connecting": "连接中...", + "reconnecting": "重新连接中... ({{attempt}}/{{max}})", + "reconnected": "重新连接成功", + "maxReconnectAttemptsReached": "已达到最大重连尝试次数", + "connectionTimeout": "连接超时" }, "fileManager": { "title": "文件管理器", @@ -580,16 +568,11 @@ "failedToRenameItem": "重命名项目失败", "upload": "上传", "download": "下载", - "newFile": "新建文件", - "newFolder": "新建文件夹", - "rename": "重命名", "delete": "删除", "permissions": "权限", "size": "大小", "modified": "修改时间", "path": "路径", - "fileName": "文件名", - "folderName": "文件夹名", "confirmDelete": "确定要删除 {{name}} 吗?", "uploadSuccess": "文件上传成功", "uploadFailed": "文件上传失败", @@ -609,10 +592,7 @@ "fileSavedSuccessfully": "文件保存成功", "saveTimeout": "保存操作超时。文件可能已成功保存,但操作用时过长。请检查 Docker 日志以确认。", "failedToSaveFile": "保存文件失败", - "folder": "文件夹", - "file": "文件", "deletedSuccessfully": "删除成功", - "failedToDeleteItem": "删除项目失败", "connectToServer": "连接到服务器", "selectServerToEdit": "从侧边栏选择服务器以开始编辑文件", "fileOperations": "文件操作", @@ -640,11 +620,11 @@ "tunnels": { "title": "SSH 隧道", "noSshTunnels": "没有 SSH 隧道", - "createFirstTunnelMessage": "您还没有创建任何 SSH 隧道。在主机管理器中配置隧道连接以开始使用。", + "createFirstTunnelMessage": "创建您的第一个 SSH 隧道以开始使用。使用 SSH 管理器添加具有隧道连接的主机。", "connected": "已连接", - "disconnected": "已断开", + "disconnected": "已断开连接", "connecting": "连接中...", - "disconnecting": "断开中...", + "disconnecting": "断开连接中...", "unknown": "未知", "error": "错误", "failed": "失败", @@ -680,17 +660,7 @@ "local": "本地", "remote": "远程", "dynamic": "动态", - "noSshTunnels": "没有 SSH 隧道", - "createFirstTunnelMessage": "创建您的第一个 SSH 隧道以开始使用。使用 SSH 管理器添加具有隧道连接的主机。", - "unknown": "未知", - "connected": "已连接", - "connecting": "连接中...", - "disconnecting": "断开连接中...", - "disconnected": "已断开连接", "portMapping": "端口 {{sourcePort}} → {{endpointHost}}:{{endpointPort}}", - "disconnect": "断开连接", - "connect": "连接", - "canceling": "取消中...", "endpointHostNotFound": "未找到端点主机" }, "serverStats": { @@ -700,7 +670,7 @@ "disk": "磁盘", "network": "网络", "uptime": "运行时间", - "loadAverage": "平均负载", + "loadAverage": "平均: {{avg1}}, {{avg5}}, {{avg15}}", "processes": "进程", "connections": "连接", "usage": "使用率", @@ -716,20 +686,20 @@ "cpuCores_one": "{{count}} 个 CPU", "cpuCores_other": "{{count}} 个 CPU", "naCpus": "N/A CPU", - "loadAverage": "平均: {{avg1}}, {{avg5}}, {{avg15}}", "loadAverageNA": "平均: N/A", "cpuUsage": "CPU 使用率", "memoryUsage": "内存使用率", "rootStorageSpace": "根目录存储空间", "of": "的", "feedbackMessage": "对服务器管理的下一步功能有想法?在这里分享吧", + "failedToFetchHostConfig": "获取主机配置失败", + "failedToFetchStatus": "获取服务器状态失败", + "failedToFetchMetrics": "获取服务器指标失败", "loadingMetrics": "正在加载指标...", "refreshing": "正在刷新...", "serverOffline": "服务器离线", "cannotFetchMetrics": "无法从离线服务器获取指标", - "load": "负载", - "free": "空闲", - "available": "可用" + "load": "负载" }, "auth": { "loginTitle": "登录 Termix", @@ -831,7 +801,6 @@ "oidcAuthFailed": "OIDC 认证失败", "noTokenReceived": "登录未收到令牌", "invalidAuthUrl": "从后端收到无效的授权 URL", - "connectionTimeout": "连接超时", "invalidInput": "输入无效", "requiredField": "此字段为必填项", "minLength": "最小长度为 {{min}}", @@ -876,6 +845,9 @@ "external": "外部 (OIDC)", "selectPreferredLanguage": "选择您的界面首选语言" }, + "user": { + "failedToLoadVersionInfo": "加载版本信息失败" + }, "placeholders": { "enterCode": "000000", "ipAddress": "127.0.0.1", @@ -953,7 +925,6 @@ "deleteItem": "删除项目", "createNewFile": "创建新文件", "createNewFolder": "创建新文件夹", - "deleteItem": "删除项目", "renameItem": "重命名项目", "clickToSelectFile": "点击选择文件", "noSshHosts": "没有 SSH 主机", @@ -1018,8 +989,6 @@ "updateKey": "更新密钥", "sshpassRequired": "密码认证需要 Sshpass", "sshServerConfigRequired": "需要 SSH 服务器配置", - "sshManagerAlreadyOpen": "SSH 管理器已打开", - "disabledDuringSplitScreen": "分屏期间禁用", "productionFolder": "生产环境", "databaseServer": "数据库服务器", "unknownError": "未知错误", diff --git a/src/ui/Desktop/Apps/Server/Server.tsx b/src/ui/Desktop/Apps/Server/Server.tsx index 3f872712..84d43f58 100644 --- a/src/ui/Desktop/Apps/Server/Server.tsx +++ b/src/ui/Desktop/Apps/Server/Server.tsx @@ -306,9 +306,6 @@ export function Server({ value={typeof metrics?.cpu?.percent === 'number' ? metrics!.cpu!.percent! : 0} className="h-2" /> - {typeof metrics?.cpu?.percent === 'number' && metrics.cpu.percent > 80 && ( -
- )}
@@ -347,9 +344,6 @@ export function Server({ value={typeof metrics?.memory?.percent === 'number' ? metrics!.memory!.percent! : 0} className="h-2" /> - {typeof metrics?.memory?.percent === 'number' && metrics.memory.percent > 85 && ( -
- )}
@@ -390,9 +384,6 @@ export function Server({ value={typeof metrics?.disk?.percent === 'number' ? metrics!.disk!.percent! : 0} className="h-2" /> - {typeof metrics?.disk?.percent === 'number' && metrics.disk.percent > 90 && ( -
- )}
diff --git a/src/ui/Desktop/Apps/Terminal/Terminal.tsx b/src/ui/Desktop/Apps/Terminal/Terminal.tsx index ab980ae3..5a448760 100644 --- a/src/ui/Desktop/Apps/Terminal/Terminal.tsx +++ b/src/ui/Desktop/Apps/Terminal/Terminal.tsx @@ -29,6 +29,7 @@ export const Terminal = forwardRef(function SSHTerminal( const pingIntervalRef = useRef(null); const [visible, setVisible] = useState(false); const [isConnected, setIsConnected] = useState(false); + const [isConnecting, setIsConnecting] = useState(false); const [connectionError, setConnectionError] = useState(null); const isVisibleRef = useRef(false); const reconnectTimeoutRef = useRef(null); @@ -36,7 +37,8 @@ export const Terminal = forwardRef(function SSHTerminal( const maxReconnectAttempts = 3; const isUnmountingRef = useRef(false); const shouldNotReconnectRef = useRef(false); - + const isReconnectingRef = useRef(false); + const connectionTimeoutRef = useRef(null); const lastSentSizeRef = useRef<{ cols: number; rows: number } | null>(null); const pendingSizeRef = useRef<{ cols: number; rows: number } | null>(null); @@ -76,6 +78,7 @@ export const Terminal = forwardRef(function SSHTerminal( disconnect: () => { isUnmountingRef.current = true; shouldNotReconnectRef.current = true; + isReconnectingRef.current = false; if (pingIntervalRef.current) { clearInterval(pingIntervalRef.current); pingIntervalRef.current = null; @@ -84,8 +87,13 @@ export const Terminal = forwardRef(function SSHTerminal( clearTimeout(reconnectTimeoutRef.current); reconnectTimeoutRef.current = null; } + if (connectionTimeoutRef.current) { + clearTimeout(connectionTimeoutRef.current); + connectionTimeoutRef.current = null; + } webSocketRef.current?.close(); setIsConnected(false); + setIsConnecting(false); // Clear connecting state }, fit: () => { fitAddonRef.current?.fit(); @@ -135,31 +143,54 @@ export const Terminal = forwardRef(function SSHTerminal( } function attemptReconnection() { - // Don't attempt reconnection if component is unmounting or if we shouldn't reconnect - if (isUnmountingRef.current || shouldNotReconnectRef.current) { + // Don't attempt reconnection if component is unmounting, shouldn't reconnect, or already reconnecting + if (isUnmountingRef.current || shouldNotReconnectRef.current || isReconnectingRef.current) { return; } + // Check if we've already reached max attempts if (reconnectAttempts.current >= maxReconnectAttempts) { toast.error(t('terminal.maxReconnectAttemptsReached')); return; } + // Set reconnecting flag to prevent multiple simultaneous attempts + isReconnectingRef.current = true; + + // Clear terminal immediately to prevent showing last line + if (terminal) { + terminal.clear(); + } + + // Increment attempt counter reconnectAttempts.current++; + + // Show toast with current attempt number 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) { + isReconnectingRef.current = false; return; } + + // Check if we haven't exceeded max attempts during the timeout + if (reconnectAttempts.current > maxReconnectAttempts) { + isReconnectingRef.current = false; + return; + } + if (terminal && hostConfig) { - // Clear terminal before reconnecting + // Ensure terminal is clear before reconnecting terminal.clear(); const cols = terminal.cols; const rows = terminal.rows; connectToHost(cols, rows); } + + // Reset reconnecting flag after attempting connection + isReconnectingRef.current = false; }, 2000 * reconnectAttempts.current); // Exponential backoff } @@ -187,6 +218,8 @@ export const Terminal = forwardRef(function SSHTerminal( wasDisconnectedBySSH.current = false; setConnectionError(null); shouldNotReconnectRef.current = false; // Reset reconnection flag + isReconnectingRef.current = false; // Reset reconnecting flag + setIsConnecting(true); // Set connecting state setupWebSocketListeners(ws, cols, rows); } @@ -195,12 +228,27 @@ export const Terminal = forwardRef(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 + // Don't set isConnected to true here - wait for actual SSH connection + // Don't show reconnected toast here - wait for actual connection confirmation + + // Set a timeout for SSH connection establishment + connectionTimeoutRef.current = setTimeout(() => { + if (!isConnected) { + // SSH connection didn't establish within timeout + // Clear terminal immediately when connection times out + if (terminal) { + terminal.clear(); + } + toast.error(t('terminal.connectionTimeout')); + if (webSocketRef.current) { + webSocketRef.current.close(); + } + // Attempt reconnection if this was a reconnection attempt + if (reconnectAttempts.current > 0) { + attemptReconnection(); + } + } + }, 10000); // 10 second timeout for SSH connection ws.send(JSON.stringify({type: 'connectToHost', data: {cols, rows, hostConfig}})); terminal.onData((data) => { @@ -250,6 +298,12 @@ export const Terminal = forwardRef(function SSHTerminal( errorMessage.toLowerCase().includes('network')) { toast.error(t('terminal.connectionError', { message: errorMessage })); setIsConnected(false); + // Clear terminal immediately when connection error occurs + if (terminal) { + terminal.clear(); + } + // Set connecting state immediately for reconnection + setIsConnecting(true); attemptReconnection(); return; } @@ -258,9 +312,28 @@ export const Terminal = forwardRef(function SSHTerminal( toast.error(t('terminal.error', { message: errorMessage })); } else if (msg.type === 'connected') { setIsConnected(true); + setIsConnecting(false); // Clear connecting state + // Clear connection timeout since SSH connection is established + if (connectionTimeoutRef.current) { + clearTimeout(connectionTimeoutRef.current); + connectionTimeoutRef.current = null; + } + // Show reconnected toast if this was a reconnection attempt + if (reconnectAttempts.current > 0) { + toast.success(t('terminal.reconnected')); + } + // Reset reconnection counter and flags on successful connection + reconnectAttempts.current = 0; + isReconnectingRef.current = false; } else if (msg.type === 'disconnected') { wasDisconnectedBySSH.current = true; setIsConnected(false); + // Clear terminal immediately when disconnected + if (terminal) { + terminal.clear(); + } + // Set connecting state immediately for reconnection + setIsConnecting(true); // Attempt reconnection for disconnections if (!isUnmountingRef.current && !shouldNotReconnectRef.current) { attemptReconnection(); @@ -273,6 +346,12 @@ export const Terminal = forwardRef(function SSHTerminal( ws.addEventListener('close', (event) => { setIsConnected(false); + // Clear terminal immediately when connection closes + if (terminal) { + terminal.clear(); + } + // Set connecting state immediately for reconnection + setIsConnecting(true); if (!wasDisconnectedBySSH.current && !isUnmountingRef.current && !shouldNotReconnectRef.current) { // Attempt reconnection for unexpected disconnections attemptReconnection(); @@ -282,6 +361,12 @@ export const Terminal = forwardRef(function SSHTerminal( ws.addEventListener('error', (event) => { setIsConnected(false); setConnectionError(t('terminal.websocketError')); + // Clear terminal immediately when WebSocket error occurs + if (terminal) { + terminal.clear(); + } + // Set connecting state immediately for reconnection + setIsConnecting(true); // Attempt reconnection for WebSocket errors if (!isUnmountingRef.current && !shouldNotReconnectRef.current) { attemptReconnection(); @@ -429,11 +514,14 @@ export const Terminal = forwardRef(function SSHTerminal( return () => { isUnmountingRef.current = true; shouldNotReconnectRef.current = true; + isReconnectingRef.current = false; + setIsConnecting(false); // Clear connecting state resizeObserver.disconnect(); element?.removeEventListener('contextmenu', handleContextMenu); if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current); if (resizeTimeout.current) clearTimeout(resizeTimeout.current); if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current); + if (connectionTimeoutRef.current) clearTimeout(connectionTimeoutRef.current); if (pingIntervalRef.current) { clearInterval(pingIntervalRef.current); pingIntervalRef.current = null; @@ -474,15 +562,28 @@ export const Terminal = forwardRef(function SSHTerminal( }, [splitScreen, isVisible, terminal]); return ( -
{ - if (terminal && !splitScreen) { - terminal.focus(); - } - }} - /> +
+ {/* Terminal */} +
{ + if (terminal && !splitScreen) { + terminal.focus(); + } + }} + /> + + {/* Connecting State */} + {isConnecting && ( +
+
+
+ {t('terminal.connecting')} +
+
+ )} +
); }); diff --git a/src/ui/Desktop/DesktopApp.tsx b/src/ui/Desktop/DesktopApp.tsx index b0b84231..8f4ef088 100644 --- a/src/ui/Desktop/DesktopApp.tsx +++ b/src/ui/Desktop/DesktopApp.tsx @@ -115,7 +115,6 @@ function AppContent() { isAuthenticated={isAuthenticated} authLoading={authLoading} onAuthSuccess={handleAuthSuccess} - isTopbarOpen={isTopbarOpen} />
)} diff --git a/src/ui/Desktop/Homepage/Homepage.tsx b/src/ui/Desktop/Homepage/Homepage.tsx index 8e4ea4f3..3ee99521 100644 --- a/src/ui/Desktop/Homepage/Homepage.tsx +++ b/src/ui/Desktop/Homepage/Homepage.tsx @@ -11,7 +11,6 @@ interface HomepageProps { isAuthenticated: boolean; authLoading: boolean; onAuthSuccess: (authData: { isAdmin: boolean; username: string | null; userId: string | null }) => void; - isTopbarOpen?: boolean; } function getCookie(name: string) { @@ -30,8 +29,7 @@ export function Homepage({ onSelectView, isAuthenticated, authLoading, - onAuthSuccess, - isTopbarOpen = true + onAuthSuccess }: HomepageProps): React.ReactElement { const {t} = useTranslation(); const [loggedIn, setLoggedIn] = useState(isAuthenticated); @@ -72,82 +70,64 @@ export function Homepage({ } }, [isAuthenticated]); - const topOffset = isTopbarOpen ? 66 : 0; - const topPadding = isTopbarOpen ? 66 : 0; return ( -
+
{!loggedIn ? ( -
- -
+ ) : ( -
-
-
- +
+
+ -
- -
- -
- -
- -
+
+ +
+ +
+ +
+
diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 0a612c3a..a6daa8c2 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -237,8 +237,13 @@ function createApiInstance(baseURL: string, serviceName: string = 'API'): AxiosI // Handle auth token clearing if (status === 401) { - document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; - localStorage.removeItem('jwt'); + const isElectron = (window as any).IS_ELECTRON === true || (window as any).electronAPI?.isElectron === true; + if (isElectron) { + localStorage.removeItem('jwt'); + } else { + document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + localStorage.removeItem('jwt'); + } } return Promise.reject(error);