v1.6.0 (#221)
* Add documentation in Chinese language (#160) * Update file naming and structure for mobile support * Add conditional desktop/mobile rendering * Mobile terminal * Fix overwritten i18n (#161) * Add comprehensive Chinese internationalization support - Implemented i18n framework with react-i18next for multi-language support - Added Chinese (zh) and English (en) translation files with comprehensive coverage - Localized Admin interface, authentication flows, and error messages - Translated FileManager operations and UI elements - Updated HomepageAuth component with localized authentication messages - Localized LeftSidebar navigation and host management - Added language switcher component (shown after login only) - Configured default language as English with Chinese as secondary option - Localized TOTPSetup two-factor authentication interface - Updated Docker build to include translation files - Achieved 95%+ UI localization coverage across core components Co-Authored-By: Claude <noreply@anthropic.com> * Extend Chinese localization coverage to Host Manager components - Added comprehensive translations for HostManagerHostViewer component - Localized all host management UI text including import/export features - Translated error messages and confirmation dialogs for host operations - Added translations for HostManagerHostEditor validation messages - Localized connection details, organization settings, and form labels - Fixed syntax error in FileManagerOperations component - Achieved near-complete localization of SSH host management interface - Updated placeholders and tooltips for better user guidance Co-Authored-By: Claude <noreply@anthropic.com> * Complete comprehensive Chinese localization for Termix - Added full localization support for Tunnel components (connected/disconnected states, retry messages) - Localized all tunnel status messages and connection errors - Added translations for port forwarding UI elements - Verified Server, TopNavbar, and Tab components already have complete i18n support - Achieved 99%+ localization coverage across entire application - All core UI components now fully support Chinese and English languages This completes the comprehensive internationalization effort for the Termix SSH management platform. Co-Authored-By: Claude <noreply@anthropic.com> * Localize additional Host Manager components and authentication settings - Added translations for all authentication options (Password, Key, SSH Private Key) - Localized form labels in HostManagerHostEditor (Pin Connection, Enable Terminal/Tunnel/FileManager) - Translated Upload/Update Key button states - Localized Host Viewer and Add/Edit Host tab labels - Added Chinese translations for all host management settings - Fixed duplicate translation keys in JSON files Co-Authored-By: Claude <noreply@anthropic.com> * Extend localization coverage to UI components and common strings - Added comprehensive common translations (online/offline, success/error, etc.) - Localized status indicator component with all status states - Updated FileManagerLeftSidebar toast messages for rename/delete operations - Added translations for UI elements (close, toggle sidebar, etc.) - Expanded placeholder translations for form inputs - Added Chinese translations for all new common strings - Improved consistency across component status messages Co-Authored-By: Claude <noreply@anthropic.com> * Complete Chinese localization for remaining UI components - Add comprehensive Chinese translations for Host Manager component - Translate all form labels, buttons, and descriptions - Add translations for SSH configuration warnings and instructions - Localize tunnel connection settings and port forwarding options - Localize SSH Tools panel - Translate key recording functionality - Add translations for settings and configuration options - Translate homepage welcome messages and navigation elements - Add Chinese translations for login success messages - Localize "Updates & Releases" section title - Translate sidebar "Host Manager" button - Fix translation key display issues - Remove duplicate translation keys in both language files - Ensure all components properly reference translation keys - Fix hosts.tunnelConnections key mapping This completes the full Chinese localization of the Termix application, achieving near 100% UI translation coverage while maintaining English as the default language. * Complete final Chinese localization for Host Manager tunnel configuration - Add Chinese translations for authentication UI elements - Translate "Authentication", "Password", and "Key" tab labels - Localize SSH private key and key password fields - Add translations for key type selector - Localize tunnel connection configuration descriptions - Translate retry attempts and retry interval descriptions - Add dynamic tunnel forwarding description with port parameters - Localize endpoint SSH configuration labels - Fix missing translation keys - Add "upload" translation for file upload button - Ensure all FormLabel and FormDescription elements use translation keys This completes the comprehensive Chinese localization of the entire Termix application, achieving 100% UI translation coverage. * Fix PR feedback: Improve Profile section translations and UX - Fixed password reset translations in Profile section - Moved language selector from TopNavbar to Profile page - Added profile.selectPreferredLanguage translation key - Improved user experience for language preferences * Apply critical OIDC and notification system fixes while preserving i18n - Merge OIDC authentication fixes from3877e90: * Enhanced JWKS discovery mechanism with multiple backup URLs * Better support for non-standard OIDC providers (Authentik, etc.) * Improved error handling for "Failed to get user information" - Migrate to unified Sonner toast notification system: * Replace custom success/error state management * Remove redundant alert state variables * Consistent user feedback across all components - Improve code quality and function naming conventions - PRESERVE all existing i18n functionality and Chinese translations 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> * Fix OIDC errors for "Failed to get user information" * Fix OIDC errors for "Failed to get user information" * Fix spelling error * Migrate everything to alert system, update user.ts for OIDC updates. * Fix OIDC errors for "Failed to get user information" * Fix OIDC errors for "Failed to get user information" * Fix spelling error * Migrate everything to alert system, update user.ts for OIDC updates. * Update env * Fix users.ts and schema for override * Convert web app to Electron desktop application - Add Electron main process with developer tools support - Create preload script for secure context bridge - Configure electron-builder for packaging - Update Vite config for Electron compatibility (base: './') - Add environment variable support for API host configuration - Fix i18n to use relative paths for Electron file protocol - Restore multi-port backend architecture (8081-8085) - Add enhanced backend startup script with port checking - Update package.json with Electron dependencies and build scripts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Complete Electron desktop application implementation - Add backend auto-start functionality in main process - Fix authentication token storage for Electron environment - Implement localStorage-based token management in Electron - Add proper Electron environment detection via preload script - Fix WebSocket connections for terminal functionality - Resolve font file loading issues in packaged application - Update API endpoints to work with backend auto-start - Streamline build scripts with unified electron:package command - Fix better-sqlite3 native module compatibility issues - Ensure all services start automatically in production mode 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove releases folder from git and force Desktop UI. * Improve mobile support with half-baked custom keyboard * Fix API routing * Upgrade mobile keyboard with more keys. * Add cross-platform support and clean up obsolete files - Add electron-packager scripts for Windows, macOS, and Linux - Include universal architecture support for macOS - Add electron:package:all for building all platforms - Remove obsolete start-backend.sh script (replaced by Electron auto-start) - Improve ignore patterns to exclude repo-images folder - Add platform-specific icon configurations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix build system by removing electron-builder dependency - Remove electron-builder and @electron/rebuild packages to resolve build errors - Clean up package.json scripts that depend on electron-builder - Fix merge conflict markers in AdminSettings.tsx and PasswordReset.tsx - All build commands now work correctly: - npm run build (frontend + backend) - npm run build:frontend - npm run build:backend - npm run electron:package (using electron-packager) The build system is now stable and functional without signing requirements. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com> * Mobile UI improvement * Electron dev (#185) * Add comprehensive Chinese internationalization support - Implemented i18n framework with react-i18next for multi-language support - Added Chinese (zh) and English (en) translation files with comprehensive coverage - Localized Admin interface, authentication flows, and error messages - Translated FileManager operations and UI elements - Updated HomepageAuth component with localized authentication messages - Localized LeftSidebar navigation and host management - Added language switcher component (shown after login only) - Configured default language as English with Chinese as secondary option - Localized TOTPSetup two-factor authentication interface - Updated Docker build to include translation files - Achieved 95%+ UI localization coverage across core components Co-Authored-By: Claude <noreply@anthropic.com> * Extend Chinese localization coverage to Host Manager components - Added comprehensive translations for HostManagerHostViewer component - Localized all host management UI text including import/export features - Translated error messages and confirmation dialogs for host operations - Added translations for HostManagerHostEditor validation messages - Localized connection details, organization settings, and form labels - Fixed syntax error in FileManagerOperations component - Achieved near-complete localization of SSH host management interface - Updated placeholders and tooltips for better user guidance Co-Authored-By: Claude <noreply@anthropic.com> * Complete comprehensive Chinese localization for Termix - Added full localization support for Tunnel components (connected/disconnected states, retry messages) - Localized all tunnel status messages and connection errors - Added translations for port forwarding UI elements - Verified Server, TopNavbar, and Tab components already have complete i18n support - Achieved 99%+ localization coverage across entire application - All core UI components now fully support Chinese and English languages This completes the comprehensive internationalization effort for the Termix SSH management platform. Co-Authored-By: Claude <noreply@anthropic.com> * Localize additional Host Manager components and authentication settings - Added translations for all authentication options (Password, Key, SSH Private Key) - Localized form labels in HostManagerHostEditor (Pin Connection, Enable Terminal/Tunnel/FileManager) - Translated Upload/Update Key button states - Localized Host Viewer and Add/Edit Host tab labels - Added Chinese translations for all host management settings - Fixed duplicate translation keys in JSON files Co-Authored-By: Claude <noreply@anthropic.com> * Extend localization coverage to UI components and common strings - Added comprehensive common translations (online/offline, success/error, etc.) - Localized status indicator component with all status states - Updated FileManagerLeftSidebar toast messages for rename/delete operations - Added translations for UI elements (close, toggle sidebar, etc.) - Expanded placeholder translations for form inputs - Added Chinese translations for all new common strings - Improved consistency across component status messages Co-Authored-By: Claude <noreply@anthropic.com> * Complete Chinese localization for remaining UI components - Add comprehensive Chinese translations for Host Manager component - Translate all form labels, buttons, and descriptions - Add translations for SSH configuration warnings and instructions - Localize tunnel connection settings and port forwarding options - Localize SSH Tools panel - Translate key recording functionality - Add translations for settings and configuration options - Translate homepage welcome messages and navigation elements - Add Chinese translations for login success messages - Localize "Updates & Releases" section title - Translate sidebar "Host Manager" button - Fix translation key display issues - Remove duplicate translation keys in both language files - Ensure all components properly reference translation keys - Fix hosts.tunnelConnections key mapping This completes the full Chinese localization of the Termix application, achieving near 100% UI translation coverage while maintaining English as the default language. * Complete final Chinese localization for Host Manager tunnel configuration - Add Chinese translations for authentication UI elements - Translate "Authentication", "Password", and "Key" tab labels - Localize SSH private key and key password fields - Add translations for key type selector - Localize tunnel connection configuration descriptions - Translate retry attempts and retry interval descriptions - Add dynamic tunnel forwarding description with port parameters - Localize endpoint SSH configuration labels - Fix missing translation keys - Add "upload" translation for file upload button - Ensure all FormLabel and FormDescription elements use translation keys This completes the comprehensive Chinese localization of the entire Termix application, achieving 100% UI translation coverage. * Fix PR feedback: Improve Profile section translations and UX - Fixed password reset translations in Profile section - Moved language selector from TopNavbar to Profile page - Added profile.selectPreferredLanguage translation key - Improved user experience for language preferences * Apply critical OIDC and notification system fixes while preserving i18n - Merge OIDC authentication fixes from3877e90: * Enhanced JWKS discovery mechanism with multiple backup URLs * Better support for non-standard OIDC providers (Authentik, etc.) * Improved error handling for "Failed to get user information" - Migrate to unified Sonner toast notification system: * Replace custom success/error state management * Remove redundant alert state variables * Consistent user feedback across all components - Improve code quality and function naming conventions - PRESERVE all existing i18n functionality and Chinese translations 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> * Fix OIDC errors for "Failed to get user information" * Fix OIDC errors for "Failed to get user information" * Fix spelling error * Migrate everything to alert system, update user.ts for OIDC updates. * Fix OIDC errors for "Failed to get user information" * Fix OIDC errors for "Failed to get user information" * Fix spelling error * Migrate everything to alert system, update user.ts for OIDC updates. * Update env * Fix users.ts and schema for override * Convert web app to Electron desktop application - Add Electron main process with developer tools support - Create preload script for secure context bridge - Configure electron-builder for packaging - Update Vite config for Electron compatibility (base: './') - Add environment variable support for API host configuration - Fix i18n to use relative paths for Electron file protocol - Restore multi-port backend architecture (8081-8085) - Add enhanced backend startup script with port checking - Update package.json with Electron dependencies and build scripts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Complete Electron desktop application implementation - Add backend auto-start functionality in main process - Fix authentication token storage for Electron environment - Implement localStorage-based token management in Electron - Add proper Electron environment detection via preload script - Fix WebSocket connections for terminal functionality - Resolve font file loading issues in packaged application - Update API endpoints to work with backend auto-start - Streamline build scripts with unified electron:package command - Fix better-sqlite3 native module compatibility issues - Ensure all services start automatically in production mode 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove releases folder from git and force Desktop UI. * Improve mobile support with half-baked custom keyboard * Fix API routing * Upgrade mobile keyboard with more keys. * Add cross-platform support and clean up obsolete files - Add electron-packager scripts for Windows, macOS, and Linux - Include universal architecture support for macOS - Add electron:package:all for building all platforms - Remove obsolete start-backend.sh script (replaced by Electron auto-start) - Improve ignore patterns to exclude repo-images folder - Add platform-specific icon configurations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix build system by removing electron-builder dependency - Remove electron-builder and @electron/rebuild packages to resolve build errors - Clean up package.json scripts that depend on electron-builder - Fix merge conflict markers in AdminSettings.tsx and PasswordReset.tsx - All build commands now work correctly: - npm run build (frontend + backend) - npm run build:frontend - npm run build:backend - npm run electron:package (using electron-packager) The build system is now stable and functional without signing requirements. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com> Co-authored-by: Karmaa <88517757+LukeGus@users.noreply.github.com> * Add navigation and hardcoded hosts * Update mobile sidebar to use API, add auth and tab system to mobile. * Update sidebar state * Mobile support (#190) * Add vibration to keyboard * Fix keyboard keys * Fix keyboard keys * Fix keyboard keys * Rename files, improve keyboard usability * Improve keyboard view and fix various issues with it * Add mobile chinese translation * Disable OS keyboard from appearing * Fix fit addon not resizing with "more" on keyboard * Disable OS keyboard on terminal load * Merge Luke and Zac * feat: add export option for ssh hosts (#173) (#187) * Update issue templates * feat: add export JSON option for SSH hosts (#173) --------- Co-authored-by: Karmaa <88517757+LukeGus@users.noreply.github.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com> * feat(profile): display version number from .env in profile menu (#182) * feat(profile): display version number from .env in profile menu * Update version checking process --------- Co-authored-by: LukeGus <bugattiguy527@gmail.com> * Add pretier * feat(auth): Add password visibility toggle to auth forms (#166) * added hide and unhide password button * Undo admin settings changes --------- Co-authored-by: LukeGus <bugattiguy527@gmail.com> * Re-added password input * Remove encrpytion, improve logging and merge interfaces. * Improve logging (backend and frontend) and added dedicde OIDC clear * feat: Added option to paste private key (#203) * Improve logging frontend/backend, fix host form being reversed. * Improve logging more, fix credentials sync issues, migrate more to be toasts * Improve logging more, fix credentials sync issues, migrate more to be toasts * More error to toast migration * Remove more inline styles and run npm updates * Update homepage appearing over everything and terminal incorrect bg * Improved server stat generation and UI by caching and supporting more platforms * Update mobile app with the same stat changes and remove rate limiting * Put user profle in its own tab, add code rabbit support * Improve code rabbit yaml * Update chinese translation and fix z indexs causing delay to hide * Bump vite from 7.1.3 to 7.1.5 (#204) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.3 to 7.1.5. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v7.1.5/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 7.1.5 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update read me * Update electron builder and fix mobile terminal background * Update logo, move translations, update electron building. * Remove backend from electon, switching to server manager * Add electron server configurator * Fix backend builder on Dockerfile * Fix langauge file for Dockerfile * Fix architecture issues in Dockerfile * Fix architecture issues in Dockerfile * Fix architecture issues in Dockerfile * Fix backend building for docker image * Add electron builder * Fix node starting in entrypoint and remove release from electron build * Remove double packaing in electron build * Fix folder nesting for electron gbuilder * Fix native module docker build (better-sql and bcrypt) * Fix api routes and missing translations and improve reconnection for terminals * Update read me for new installation method * Update CONTRIBUTING.md with color scheme * Fix terrminal not closing afer 3 tries * Fix electronm api routing, fikx ssh not connecting, and OIDC redirect errors * Fix more electron API issues (ssh/oidc), make server manager force API check, and login saving. * Add electron API routes * Fix more electron APi routes and issues * Hide admin settings on electron and fix server manager URl verification * Hide admin settings on electron and fix server manager URl verification * Fix admin setting visiblity on electron * Add links to docs in respective places * Migrate all getCookies to use main-axios. * Migrate all isElectron to use main-axios. * Clean up backend files * Clean up frontend files and read me translations * Run prettier * Fix terminal in web, and update translations and prep for release. * Update API to work on devs and remove random letter * Run prettier * Update read me for release * Update read me for release * Fixed delete issue (ready for release) * Ensure retention days for artifact upload are set --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: starry <115192496+sky22333@users.noreply.github.com> Co-authored-by: ZacharyZcR <PayasoNorahC@protonmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: Shivam Kumar <155747305+maishivamhoo123@users.noreply.github.com> Co-authored-by: Abhilash Gandhamalla <150357125+AbhilashG12@users.noreply.github.com> Co-authored-by: jedi04 <78037206+jedi04@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit was merged in pull request #221.
This commit is contained in:
478
src/ui/Desktop/Apps/Server/Server.tsx
Normal file
478
src/ui/Desktop/Apps/Server/Server.tsx
Normal file
@@ -0,0 +1,478 @@
|
||||
import React from "react";
|
||||
import { useSidebar } from "@/components/ui/sidebar.tsx";
|
||||
import { Status, StatusIndicator } from "@/components/ui/shadcn-io/status";
|
||||
import { Separator } from "@/components/ui/separator.tsx";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
import { Progress } from "@/components/ui/progress.tsx";
|
||||
import { Cpu, HardDrive, MemoryStick } from "lucide-react";
|
||||
import { Tunnel } from "@/ui/Desktop/Apps/Tunnel/Tunnel.tsx";
|
||||
import {
|
||||
getServerStatusById,
|
||||
getServerMetricsById,
|
||||
type ServerMetrics,
|
||||
} from "@/ui/main-axios.ts";
|
||||
import { useTabs } from "@/ui/Desktop/Navigation/Tabs/TabContext.tsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface ServerProps {
|
||||
hostConfig?: any;
|
||||
title?: string;
|
||||
isVisible?: boolean;
|
||||
isTopbarOpen?: boolean;
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
export function Server({
|
||||
hostConfig,
|
||||
title,
|
||||
isVisible = true,
|
||||
isTopbarOpen = true,
|
||||
embedded = false,
|
||||
}: ServerProps): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const { state: sidebarState } = useSidebar();
|
||||
const { addTab, tabs } = useTabs() as any;
|
||||
const [serverStatus, setServerStatus] = React.useState<"online" | "offline">(
|
||||
"offline",
|
||||
);
|
||||
const [metrics, setMetrics] = React.useState<ServerMetrics | null>(null);
|
||||
const [currentHostConfig, setCurrentHostConfig] = React.useState(hostConfig);
|
||||
const [isLoadingMetrics, setIsLoadingMetrics] = React.useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setCurrentHostConfig(hostConfig);
|
||||
}, [hostConfig]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchLatestHostConfig = async () => {
|
||||
if (hostConfig?.id) {
|
||||
try {
|
||||
const { getSSHHosts } = await import("@/ui/main-axios.ts");
|
||||
const hosts = await getSSHHosts();
|
||||
const updatedHost = hosts.find((h) => h.id === hostConfig.id);
|
||||
if (updatedHost) {
|
||||
setCurrentHostConfig(updatedHost);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(t("serverStats.failedToFetchHostConfig"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchLatestHostConfig();
|
||||
|
||||
const handleHostsChanged = async () => {
|
||||
if (hostConfig?.id) {
|
||||
try {
|
||||
const { getSSHHosts } = await import("@/ui/main-axios.ts");
|
||||
const hosts = await getSSHHosts();
|
||||
const updatedHost = hosts.find((h) => h.id === hostConfig.id);
|
||||
if (updatedHost) {
|
||||
setCurrentHostConfig(updatedHost);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(t("serverStats.failedToFetchHostConfig"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("ssh-hosts:changed", handleHostsChanged);
|
||||
return () =>
|
||||
window.removeEventListener("ssh-hosts:changed", handleHostsChanged);
|
||||
}, [hostConfig?.id]);
|
||||
|
||||
React.useEffect(() => {
|
||||
let cancelled = false;
|
||||
let intervalId: number | undefined;
|
||||
|
||||
const fetchStatus = async () => {
|
||||
try {
|
||||
const res = await getServerStatusById(currentHostConfig?.id);
|
||||
if (!cancelled) {
|
||||
setServerStatus(res?.status === "online" ? "online" : "offline");
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (!cancelled) {
|
||||
if (error?.response?.status === 503) {
|
||||
setServerStatus("offline");
|
||||
} else if (error?.response?.status === 504) {
|
||||
setServerStatus("offline");
|
||||
} else if (error?.response?.status === 404) {
|
||||
setServerStatus("offline");
|
||||
} else {
|
||||
setServerStatus("offline");
|
||||
}
|
||||
toast.error(t("serverStats.failedToFetchStatus"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMetrics = async () => {
|
||||
if (!currentHostConfig?.id) return;
|
||||
try {
|
||||
setIsLoadingMetrics(true);
|
||||
const data = await getServerMetricsById(currentHostConfig.id);
|
||||
if (!cancelled) {
|
||||
setMetrics(data);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
setMetrics(null);
|
||||
toast.error(t("serverStats.failedToFetchMetrics"));
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsLoadingMetrics(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (currentHostConfig?.id && isVisible) {
|
||||
fetchStatus();
|
||||
fetchMetrics();
|
||||
intervalId = window.setInterval(() => {
|
||||
if (isVisible) {
|
||||
fetchStatus();
|
||||
fetchMetrics();
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (intervalId) window.clearInterval(intervalId);
|
||||
};
|
||||
}, [currentHostConfig?.id, isVisible]);
|
||||
|
||||
const topMarginPx = isTopbarOpen ? 74 : 16;
|
||||
const leftMarginPx = sidebarState === "collapsed" ? 16 : 8;
|
||||
const bottomMarginPx = 8;
|
||||
|
||||
const isFileManagerAlreadyOpen = React.useMemo(() => {
|
||||
if (!currentHostConfig) return false;
|
||||
return tabs.some(
|
||||
(tab: any) =>
|
||||
tab.type === "file_manager" &&
|
||||
tab.hostConfig?.id === currentHostConfig.id,
|
||||
);
|
||||
}, [tabs, currentHostConfig]);
|
||||
|
||||
const wrapperStyle: React.CSSProperties = embedded
|
||||
? { opacity: isVisible ? 1 : 0, height: "100%", width: "100%" }
|
||||
: {
|
||||
opacity: isVisible ? 1 : 0,
|
||||
marginLeft: leftMarginPx,
|
||||
marginRight: 17,
|
||||
marginTop: topMarginPx,
|
||||
marginBottom: bottomMarginPx,
|
||||
height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`,
|
||||
};
|
||||
|
||||
const containerClass = embedded
|
||||
? "h-full w-full text-white overflow-hidden bg-transparent"
|
||||
: "bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden";
|
||||
|
||||
return (
|
||||
<div style={wrapperStyle} className={containerClass}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Top Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between px-4 pt-3 pb-3 gap-3">
|
||||
<div className="flex items-center gap-4 min-w-0">
|
||||
<div className="min-w-0">
|
||||
<h1 className="font-bold text-lg truncate">
|
||||
{currentHostConfig?.folder} / {title}
|
||||
</h1>
|
||||
</div>
|
||||
<Status
|
||||
status={serverStatus}
|
||||
className="!bg-transparent !p-0.75 flex-shrink-0"
|
||||
>
|
||||
<StatusIndicator />
|
||||
</Status>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={isRefreshing}
|
||||
onClick={async () => {
|
||||
if (currentHostConfig?.id) {
|
||||
try {
|
||||
setIsRefreshing(true);
|
||||
const res = await getServerStatusById(currentHostConfig.id);
|
||||
setServerStatus(
|
||||
res?.status === "online" ? "online" : "offline",
|
||||
);
|
||||
const data = await getServerMetricsById(
|
||||
currentHostConfig.id,
|
||||
);
|
||||
setMetrics(data);
|
||||
} catch (error: any) {
|
||||
if (error?.response?.status === 503) {
|
||||
setServerStatus("offline");
|
||||
} else if (error?.response?.status === 504) {
|
||||
setServerStatus("offline");
|
||||
} else if (error?.response?.status === 404) {
|
||||
setServerStatus("offline");
|
||||
} else {
|
||||
setServerStatus("offline");
|
||||
}
|
||||
setMetrics(null);
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}
|
||||
}}
|
||||
title={t("serverStats.refreshStatusAndMetrics")}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"></div>
|
||||
{t("serverStats.refreshing")}
|
||||
</div>
|
||||
) : (
|
||||
t("serverStats.refreshStatus")
|
||||
)}
|
||||
</Button>
|
||||
{currentHostConfig?.enableFileManager && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="font-semibold"
|
||||
disabled={isFileManagerAlreadyOpen}
|
||||
title={
|
||||
isFileManagerAlreadyOpen
|
||||
? t("serverStats.fileManagerAlreadyOpen")
|
||||
: t("serverStats.openFileManager")
|
||||
}
|
||||
onClick={() => {
|
||||
if (!currentHostConfig || isFileManagerAlreadyOpen) return;
|
||||
const titleBase =
|
||||
currentHostConfig?.name &&
|
||||
currentHostConfig.name.trim() !== ""
|
||||
? currentHostConfig.name.trim()
|
||||
: `${currentHostConfig.username}@${currentHostConfig.ip}`;
|
||||
addTab({
|
||||
type: "file_manager",
|
||||
title: titleBase,
|
||||
hostConfig: currentHostConfig,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("nav.fileManager")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="p-0.25 w-full" />
|
||||
|
||||
{/* Stats */}
|
||||
<div className="rounded-lg border-2 border-dark-border m-3 bg-dark-bg-darker p-4">
|
||||
{isLoadingMetrics && !metrics ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-6 h-6 border-2 border-blue-400 border-t-transparent rounded-full animate-spin"></div>
|
||||
<span className="text-gray-300">
|
||||
{t("serverStats.loadingMetrics")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : !metrics && serverStatus === "offline" ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 mx-auto mb-3 rounded-full bg-red-500/20 flex items-center justify-center">
|
||||
<div className="w-6 h-6 border-2 border-red-400 rounded-full"></div>
|
||||
</div>
|
||||
<p className="text-gray-300 mb-1">
|
||||
{t("serverStats.serverOffline")}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("serverStats.cannotFetchMetrics")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 lg:gap-6">
|
||||
{/* CPU Stats */}
|
||||
<div className="space-y-3 p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Cpu className="h-5 w-5 text-blue-400" />
|
||||
<h3 className="font-semibold text-lg text-white">
|
||||
{t("serverStats.cpuUsage")}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-300">
|
||||
{(() => {
|
||||
const pct = metrics?.cpu?.percent;
|
||||
const cores = metrics?.cpu?.cores;
|
||||
const pctText =
|
||||
typeof pct === "number" ? `${pct}%` : "N/A";
|
||||
const coresText =
|
||||
typeof cores === "number"
|
||||
? t("serverStats.cpuCores", { count: cores })
|
||||
: t("serverStats.naCpus");
|
||||
return `${pctText} ${t("serverStats.of")} ${coresText}`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Progress
|
||||
value={
|
||||
typeof metrics?.cpu?.percent === "number"
|
||||
? metrics!.cpu!.percent!
|
||||
: 0
|
||||
}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-gray-500">
|
||||
{metrics?.cpu?.load
|
||||
? `Load: ${metrics.cpu.load[0].toFixed(2)}, ${metrics.cpu.load[1].toFixed(2)}, ${metrics.cpu.load[2].toFixed(2)}`
|
||||
: "Load: N/A"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Memory Stats */}
|
||||
<div className="space-y-3 p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<MemoryStick className="h-5 w-5 text-green-400" />
|
||||
<h3 className="font-semibold text-lg text-white">
|
||||
{t("serverStats.memoryUsage")}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-300">
|
||||
{(() => {
|
||||
const pct = metrics?.memory?.percent;
|
||||
const used = metrics?.memory?.usedGiB;
|
||||
const total = metrics?.memory?.totalGiB;
|
||||
const pctText =
|
||||
typeof pct === "number" ? `${pct}%` : "N/A";
|
||||
const usedText =
|
||||
typeof used === "number"
|
||||
? `${used.toFixed(1)} GiB`
|
||||
: "N/A";
|
||||
const totalText =
|
||||
typeof total === "number"
|
||||
? `${total.toFixed(1)} GiB`
|
||||
: "N/A";
|
||||
return `${pctText} (${usedText} ${t("serverStats.of")} ${totalText})`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Progress
|
||||
value={
|
||||
typeof metrics?.memory?.percent === "number"
|
||||
? metrics!.memory!.percent!
|
||||
: 0
|
||||
}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-gray-500">
|
||||
{(() => {
|
||||
const used = metrics?.memory?.usedGiB;
|
||||
const total = metrics?.memory?.totalGiB;
|
||||
const free =
|
||||
typeof used === "number" && typeof total === "number"
|
||||
? (total - used).toFixed(1)
|
||||
: "N/A";
|
||||
return `Free: ${free} GiB`;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Disk Stats */}
|
||||
<div className="space-y-3 p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<HardDrive className="h-5 w-5 text-orange-400" />
|
||||
<h3 className="font-semibold text-lg text-white">
|
||||
{t("serverStats.rootStorageSpace")}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-300">
|
||||
{(() => {
|
||||
const pct = metrics?.disk?.percent;
|
||||
const used = metrics?.disk?.usedHuman;
|
||||
const total = metrics?.disk?.totalHuman;
|
||||
const pctText =
|
||||
typeof pct === "number" ? `${pct}%` : "N/A";
|
||||
const usedText = used ?? "N/A";
|
||||
const totalText = total ?? "N/A";
|
||||
return `${pctText} (${usedText} ${t("serverStats.of")} ${totalText})`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Progress
|
||||
value={
|
||||
typeof metrics?.disk?.percent === "number"
|
||||
? metrics!.disk!.percent!
|
||||
: 0
|
||||
}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-gray-500">
|
||||
{(() => {
|
||||
const used = metrics?.disk?.usedHuman;
|
||||
const total = metrics?.disk?.totalHuman;
|
||||
return used && total
|
||||
? `Available: ${total}`
|
||||
: "Available: N/A";
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SSH Tunnels */}
|
||||
{currentHostConfig?.tunnelConnections &&
|
||||
currentHostConfig.tunnelConnections.length > 0 && (
|
||||
<div 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>
|
||||
)}
|
||||
|
||||
<p className="px-4 pt-2 pb-2 text-sm text-gray-500">
|
||||
{t("serverStats.feedbackMessage")}{" "}
|
||||
<a
|
||||
href="https://github.com/LukeGus/Termix/issues/new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user