diff --git a/.coderabbit.yaml b/.coderabbit.yaml
index 9a0cefda..e210dea1 100644
--- a/.coderabbit.yaml
+++ b/.coderabbit.yaml
@@ -119,7 +119,7 @@ reviews:
- Identify and fix potential null/undefined access errors
- Fix improper event handling and memory leaks
- Resolve improper state management and data flow issues
-
+
- path: "**/backend/**/*.{ts,js}"
instructions: |
Review backend code for Termix server management platform. Key considerations:
@@ -167,7 +167,7 @@ reviews:
- Implement proper health checks and status endpoints
Highlight any security vulnerabilities, performance issues, or architectural deviations.
-
+
- path: "**/components/**/*.{ts,tsx}"
instructions: |
Review UI components for Termix server management platform. Key considerations:
@@ -207,7 +207,7 @@ reviews:
- Use proper tunnel status and management UI
Highlight any UI/UX issues, accessibility problems, or performance concerns.
-
+
- path: "**/types/**/*.{ts,js}"
instructions: |
Review type definitions for Termix server management platform. Key considerations:
@@ -237,7 +237,7 @@ reviews:
- Use proper type assertions and casting
Highlight any type safety issues, missing types, or type inconsistencies.
-
+
- path: "**/hooks/**/*.{ts,tsx}"
instructions: |
Review custom hooks for Termix server management platform. Key considerations:
@@ -285,7 +285,7 @@ reviews:
- Fix improper error handling in custom hooks
Highlight any hook design issues, performance problems, or reusability concerns.
-
+
- path: "**/lib/**/*.{ts,js}"
instructions: |
Review utility libraries and helper functions for Termix server management platform. Key considerations:
@@ -337,7 +337,7 @@ reviews:
- Resolve improper configuration and environment variable handling
Highlight any utility design issues, performance problems, or security concerns.
-
+
- path: "**/main-axios.ts"
instructions: |
Review main-axios.ts API client configuration for Termix server management platform. Key considerations:
@@ -405,7 +405,7 @@ reviews:
- Identify and fix potential security vulnerabilities in API handling
Highlight any API design issues, error handling problems, or security concerns.
-
+
- path: "**/electron/**/*.{ts,js,cjs}"
instructions: |
Review Electron application code for Termix server management platform. Key considerations:
@@ -443,7 +443,7 @@ reviews:
- Identify and fix potential security vulnerabilities in Electron setup
Highlight any Electron-specific issues, security vulnerabilities, or performance problems.
-
+
- path: "**/docker/**/*"
instructions: |
Review Docker configuration files for Termix server management platform. Key considerations:
@@ -505,7 +505,7 @@ reviews:
- Use proper visual aids and diagrams where appropriate
Highlight any documentation issues, inaccuracies, or missing information.
-
+
- path: "**/index.css"
instructions: |
Review index.css styling configuration for Termix server management platform. Key considerations:
diff --git a/.env b/.env
index 3b7d55ee..6f985423 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,2 @@
VERSION=1.6.0
-VITE_API_HOST=localhost
-CREDENTIAL_ENCRYPTION_KEY=98fbfabe84b125db7cbbb5168eb584aaecc2f3779a2aaa955c57bdd305071a84
\ No newline at end of file
+VITE_API_HOST=localhost
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ceda9f68..da7949ca 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,4 +1,4 @@
-# Contributing
+_# Contributing
## Prerequisites
@@ -26,6 +26,7 @@ npm run dev
npm run dev:backend
```
+a
This will start the backend and the frontend Vite server. You can access Termix by going to `http://localhost:5174/`.
## Contributing
@@ -59,43 +60,48 @@ This will start the backend and the frontend Vite server. You can access Termix
## Color Scheme
### Background Colors
-| CSS Variable | Color Value | Usage | Description |
-|--------------|-------------|-------|-------------|
-| `--color-dark-bg` | `#18181b` | Main dark background | Primary dark background color |
-| `--color-dark-bg-darker` | `#0e0e10` | Darker backgrounds | Darker variant for panels and containers |
-| `--color-dark-bg-darkest` | `#09090b` | Darkest backgrounds | Darkest background (terminal) |
-| `--color-dark-bg-light` | `#141416` | Light dark backgrounds | Lighter variant of dark background |
-| `--color-dark-bg-very-light` | `#101014` | Very light dark backgrounds | Very light variant of dark background |
-| `--color-dark-bg-panel` | `#1b1b1e` | Panel backgrounds | Background for panels and cards |
-| `--color-dark-bg-panel-hover` | `#232327` | Panel hover states | Background for panels on hover |
+
+| CSS Variable | Color Value | Usage | Description |
+|-------------------------------|-------------|-----------------------------|------------------------------------------|
+| `--color-dark-bg` | `#18181b` | Main dark background | Primary dark background color |
+| `--color-dark-bg-darker` | `#0e0e10` | Darker backgrounds | Darker variant for panels and containers |
+| `--color-dark-bg-darkest` | `#09090b` | Darkest backgrounds | Darkest background (terminal) |
+| `--color-dark-bg-light` | `#141416` | Light dark backgrounds | Lighter variant of dark background |
+| `--color-dark-bg-very-light` | `#101014` | Very light dark backgrounds | Very light variant of dark background |
+| `--color-dark-bg-panel` | `#1b1b1e` | Panel backgrounds | Background for panels and cards |
+| `--color-dark-bg-panel-hover` | `#232327` | Panel hover states | Background for panels on hover |
### Element-Specific Backgrounds
-| CSS Variable | Color Value | Usage | Description |
-|--------------|-------------|-------|-------------|
-| `--color-dark-bg-input` | `#222225` | Input fields | Background for input fields and form elements |
-| `--color-dark-bg-button` | `#23232a` | Button backgrounds | Background for buttons and clickable elements |
-| `--color-dark-bg-active` | `#1d1d1f` | Active states | Background for active/selected elements |
-| `--color-dark-bg-header` | `#131316` | Header backgrounds | Background for headers and navigation bars |
+
+| CSS Variable | Color Value | Usage | Description |
+|--------------------------|-------------|--------------------|-----------------------------------------------|
+| `--color-dark-bg-input` | `#222225` | Input fields | Background for input fields and form elements |
+| `--color-dark-bg-button` | `#23232a` | Button backgrounds | Background for buttons and clickable elements |
+| `--color-dark-bg-active` | `#1d1d1f` | Active states | Background for active/selected elements |
+| `--color-dark-bg-header` | `#131316` | Header backgrounds | Background for headers and navigation bars |
### Border Colors
-| CSS Variable | Color Value | Usage | Description |
-|--------------|-------------|-------|-------------|
-| `--color-dark-border` | `#303032` | Default borders | Standard border color |
-| `--color-dark-border-active` | `#2d2d30` | Active borders | Border color for active elements |
-| `--color-dark-border-hover` | `#434345` | Hover borders | Border color on hover states |
-| `--color-dark-border-light` | `#5a5a5d` | Light borders | Lighter border color for subtle elements |
-| `--color-dark-border-medium` | `#373739` | Medium borders | Medium weight border color |
-| `--color-dark-border-panel` | `#222224` | Panel borders | Border color for panels and cards |
+
+| CSS Variable | Color Value | Usage | Description |
+|------------------------------|-------------|-----------------|------------------------------------------|
+| `--color-dark-border` | `#303032` | Default borders | Standard border color |
+| `--color-dark-border-active` | `#2d2d30` | Active borders | Border color for active elements |
+| `--color-dark-border-hover` | `#434345` | Hover borders | Border color on hover states |
+| `--color-dark-border-light` | `#5a5a5d` | Light borders | Lighter border color for subtle elements |
+| `--color-dark-border-medium` | `#373739` | Medium borders | Medium weight border color |
+| `--color-dark-border-panel` | `#222224` | Panel borders | Border color for panels and cards |
### Interactive States
-| CSS Variable | Color Value | Usage | Description |
-|--------------|-------------|-------|-------------|
-| `--color-dark-hover` | `#2d2d30` | Hover states | Background color for hover effects |
-| `--color-dark-active` | `#2a2a2c` | Active states | Background color for active elements |
-| `--color-dark-pressed` | `#1a1a1c` | Pressed states | Background color for pressed/clicked elements |
-| `--color-dark-hover-alt` | `#2a2a2d` | Alternative hover | Alternative hover state color |
+| CSS Variable | Color Value | Usage | Description |
+|--------------------------|-------------|-------------------|-----------------------------------------------|
+| `--color-dark-hover` | `#2d2d30` | Hover states | Background color for hover effects |
+| `--color-dark-active` | `#2a2a2c` | Active states | Background color for active elements |
+| `--color-dark-pressed` | `#1a1a1c` | Pressed states | Background color for pressed/clicked elements |
+| `--color-dark-hover-alt` | `#2a2a2d` | Alternative hover | Alternative hover state color |
## Support
-If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues) repo.
\ No newline at end of file
+If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support
+channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues)
+repo.
\ No newline at end of file
diff --git a/README-CN.md b/README-CN.md
index 54611470..39400868 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -1,7 +1,7 @@
-# Repo Stats
+# 仓库统计
-# License
-根据 Apache 2.0 许可证发布。更多信息请参见 LICENSE。
+# 许可证
+根据 Apache 2.0 许可证发布。更多信息请参见 LICENSE。
\ No newline at end of file
diff --git a/README.md b/README.md
index d1c2d9a2..dcbd7a8c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# Repo Stats
+
English |
中文
@@ -9,7 +10,9 @@


+
#### Top Technologies
+
[](#)
[](#)
[](#)
@@ -35,24 +38,34 @@ If you would like, you can support the project here!\
-Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a web-based solution for managing your servers and infrastructure through a single, intuitive interface. Termix offers SSH terminal access, SSH tunneling capabilities, and remote file editing, with many more tools to come.
+Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a web-based
+solution for managing your servers and infrastructure through a single, intuitive interface. Termix offers SSH terminal
+access, SSH tunneling capabilities, and remote file editing, with many more tools to come.
# Features
+
- **SSH Terminal Access** - Full-featured terminal with split-screen support (up to 4 panels) and tab system
- **SSH Tunnel Management** - Create and manage SSH tunnels with automatic reconnection and health monitoring
-- **Remote File Editor** - Edit files directly on remote servers with syntax highlighting, file management features (uploading, removing, renaming, deleting files)
+- **Remote File Editor** - Edit files directly on remote servers with syntax highlighting, file management features (
+ uploading, removing, renaming, deleting files)
- **SSH Host Manager** - Save, organize, and manage your SSH connections with tags and folders
- **Server Stats** - View CPU, memory, and HDD usage on any SSH server
- **User Authentication** - Secure user management with admin controls and OIDC and 2FA (TOTP) support
- **Modern UI** - Clean mobile friendly (in progress) interface built with React, Tailwind CSS, and Shadcn
- **Languages** - Built-in support for English and Chinese
-- **Improved Platform Support** - Now includes an installable Electron app (in progress) for desktop, with a dedicated mobile app also planned.
+- **Improved Platform Support** - Now includes an installable Electron app (in progress) for desktop, with a dedicated
+ mobile app also planned.
# Planned Features
-See [Projects](https://github.com/users/LukeGus/projects/3). If you are looking to contribute, see [Contributing](https://github.com/LukeGus/Termix/blob/main/CONTRIBUTING.md),
+
+See [Projects](https://github.com/users/LukeGus/projects/3). If you are looking to contribute,
+see [Contributing](https://github.com/LukeGus/Termix/blob/main/CONTRIBUTING.md),
# Installation
-Visit the Termix [Docs](https://docs.termix.site/install) for more information on how to install Termix. Otherwise, view a sample docker-compose file here:
+
+Visit the Termix [Docs](https://docs.termix.site/install) for more information on how to install Termix. Otherwise, view
+a sample docker-compose file here:
+
```yaml
services:
termix:
@@ -70,10 +83,16 @@ volumes:
termix-data:
driver: local
```
-Pre-built binaries are now available for download, including a Windows installer/portable app and a Linux portable app (built with Electron). See [Docs](http://localhost:5174/install#pre-built-binaries) for details. A native iOS/Android app is planned.
+
+Pre-built binaries are now available for download, including a Windows installer/portable app and a Linux portable app (
+built with Electron). See [Docs](http://localhost:5174/install#pre-built-binaries) for details. A native iOS/Android app
+is planned.
# Support
-If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues) repo.
+
+If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support
+channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues)
+repo.
# Show-off
@@ -95,4 +114,5 @@ If you need help with Termix, you can join the [Discord](https://discord.gg/jVQG
# License
+
Distributed under the Apache License Version 2.0. See LICENSE for more information.
diff --git a/src/hooks/use-confirmation.ts b/src/hooks/use-confirmation.ts
index cbbcf531..de250e0b 100644
--- a/src/hooks/use-confirmation.ts
+++ b/src/hooks/use-confirmation.ts
@@ -1,5 +1,5 @@
-import { useState } from 'react';
-import { toast } from 'sonner';
+import {useState} from 'react';
+import {toast} from 'sonner';
interface ConfirmationOptions {
title: string;
@@ -35,11 +35,10 @@ export function useConfirmation() {
setOnConfirm(null);
};
- // For simple confirmations, we can use a toast with action
const confirmWithToast = (message: string, callback: () => void, variant: 'default' | 'destructive' = 'default') => {
const actionText = variant === 'destructive' ? 'Delete' : 'Confirm';
const cancelText = 'Cancel';
-
+
toast(message, {
action: {
label: actionText,
@@ -47,9 +46,10 @@ export function useConfirmation() {
},
cancel: {
label: cancelText,
- onClick: () => {}
+ onClick: () => {
+ }
},
- duration: 10000, // Longer duration for confirmations
+ duration: 10000,
className: variant === 'destructive' ? 'border-red-500' : ''
});
};
diff --git a/src/hooks/use-mobile.ts b/src/hooks/use-mobile.ts
index 2b0fe1df..81e9106f 100644
--- a/src/hooks/use-mobile.ts
+++ b/src/hooks/use-mobile.ts
@@ -3,17 +3,17 @@ import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
+ const [isMobile, setIsMobile] = React.useState(undefined)
- React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
- const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener("change", onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener("change", onChange)
+ }, [])
- return !!isMobile
+ return !!isMobile
}
diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts
index 2a472cc2..5b0274b5 100644
--- a/src/i18n/i18n.ts
+++ b/src/i18n/i18n.ts
@@ -1,47 +1,42 @@
-// i18n configuration for multi-language support
import i18n from 'i18next';
-import { initReactI18next } from 'react-i18next';
+import {initReactI18next} from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
-// Import translation files directly
import enTranslation from '../locales/en/translation.json';
import zhTranslation from '../locales/zh/translation.json';
-// Initialize i18n
i18n
- .use(LanguageDetector) // Detect user language
- .use(initReactI18next) // Pass i18n instance to react-i18next
- .init({
- supportedLngs: ['en', 'zh'], // Supported languages
- fallbackLng: 'en', // Fallback language
- debug: false,
-
- // Detection options - disabled to always use English by default
- detection: {
- order: ['localStorage', 'cookie'], // Only check user's saved preference
- caches: ['localStorage', 'cookie'],
- lookupLocalStorage: 'i18nextLng',
- lookupCookie: 'i18nextLng',
- checkWhitelist: true,
- },
-
- // Resources - load translations directly
- resources: {
- en: {
- translation: enTranslation
- },
- zh: {
- translation: zhTranslation
- }
- },
-
- interpolation: {
- escapeValue: false, // React already escapes values
- },
-
- react: {
- useSuspense: false, // Disable suspense for SSR compatibility
- },
- });
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ supportedLngs: ['en', 'zh'],
+ fallbackLng: 'en',
+ debug: false,
+
+ detection: {
+ order: ['localStorage', 'cookie'],
+ caches: ['localStorage', 'cookie'],
+ lookupLocalStorage: 'i18nextLng',
+ lookupCookie: 'i18nextLng',
+ checkWhitelist: true,
+ },
+
+ resources: {
+ en: {
+ translation: enTranslation
+ },
+ zh: {
+ translation: zhTranslation
+ }
+ },
+
+ interpolation: {
+ escapeValue: false,
+ },
+
+ react: {
+ useSuspense: false,
+ },
+ });
export default i18n;
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index 7ddaea28..73d25630 100644
--- a/src/index.css
+++ b/src/index.css
@@ -180,24 +180,24 @@
}
.thin-scrollbar::-webkit-scrollbar {
- width: 6px;
- height: 6px;
+ width: 6px;
+ height: 6px;
}
.thin-scrollbar::-webkit-scrollbar-track {
- background: #18181b;
+ background: #18181b;
}
.thin-scrollbar::-webkit-scrollbar-thumb {
- background: #434345;
- border-radius: 3px;
+ background: #434345;
+ border-radius: 3px;
}
.thin-scrollbar::-webkit-scrollbar-thumb:hover {
- background: #5a5a5d;
+ background: #5a5a5d;
}
.thin-scrollbar {
- scrollbar-width: thin;
- scrollbar-color: #434345 #18181b;
+ scrollbar-width: thin;
+ scrollbar-color: #434345 #18181b;
}
\ No newline at end of file
diff --git a/src/lib/frontend-logger.ts b/src/lib/frontend-logger.ts
index a6193301..ea647378 100644
--- a/src/lib/frontend-logger.ts
+++ b/src/lib/frontend-logger.ts
@@ -1,8 +1,3 @@
-/**
- * Frontend Logger - A comprehensive logging utility for the frontend
- * Enhanced with better formatting, readability, and request/response grouping
- */
-
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
export interface LogContext {
@@ -21,6 +16,7 @@ export interface LogContext {
retryCount?: number;
errorCode?: string;
errorMessage?: string;
+
[key: string]: any;
}
@@ -46,7 +42,7 @@ class FrontendLogger {
const timestamp = this.getTimeStamp();
const levelTag = this.getLevelTag(level);
const serviceTag = this.getServiceTag();
-
+
let contextStr = '';
if (context && this.isDevelopment) {
const contextParts = [];
@@ -58,7 +54,7 @@ class FrontendLogger {
if (context.responseTime) contextParts.push(`${context.responseTime}ms`);
if (context.status) contextParts.push(`status:${context.status}`);
if (context.errorCode) contextParts.push(`code:${context.errorCode}`);
-
+
if (contextParts.length > 0) {
contextStr = ` (${contextParts.join(', ')})`;
}
@@ -91,9 +87,9 @@ class FrontendLogger {
private log(level: LogLevel, message: string, context?: LogContext, error?: unknown): void {
if (!this.shouldLog(level)) return;
-
+
const formattedMessage = this.formatMessage(level, message, context);
-
+
switch (level) {
case 'debug':
console.debug(formattedMessage);
@@ -136,60 +132,58 @@ class FrontendLogger {
this.log('success', message, context);
}
- // Convenience methods for common operations
api(message: string, context?: LogContext): void {
- this.info(`API: ${message}`, { ...context, operation: 'api' });
+ this.info(`API: ${message}`, {...context, operation: 'api'});
}
request(message: string, context?: LogContext): void {
- this.info(`REQUEST: ${message}`, { ...context, operation: 'request' });
+ this.info(`REQUEST: ${message}`, {...context, operation: 'request'});
}
response(message: string, context?: LogContext): void {
- this.info(`RESPONSE: ${message}`, { ...context, operation: 'response' });
+ this.info(`RESPONSE: ${message}`, {...context, operation: 'response'});
}
auth(message: string, context?: LogContext): void {
- this.info(`AUTH: ${message}`, { ...context, operation: 'auth' });
+ this.info(`AUTH: ${message}`, {...context, operation: 'auth'});
}
ssh(message: string, context?: LogContext): void {
- this.info(`SSH: ${message}`, { ...context, operation: 'ssh' });
+ this.info(`SSH: ${message}`, {...context, operation: 'ssh'});
}
tunnel(message: string, context?: LogContext): void {
- this.info(`TUNNEL: ${message}`, { ...context, operation: 'tunnel' });
+ this.info(`TUNNEL: ${message}`, {...context, operation: 'tunnel'});
}
file(message: string, context?: LogContext): void {
- this.info(`FILE: ${message}`, { ...context, operation: 'file' });
+ this.info(`FILE: ${message}`, {...context, operation: 'file'});
}
connection(message: string, context?: LogContext): void {
- this.info(`CONNECTION: ${message}`, { ...context, operation: 'connection' });
+ this.info(`CONNECTION: ${message}`, {...context, operation: 'connection'});
}
disconnect(message: string, context?: LogContext): void {
- this.info(`DISCONNECT: ${message}`, { ...context, operation: 'disconnect' });
+ this.info(`DISCONNECT: ${message}`, {...context, operation: 'disconnect'});
}
retry(message: string, context?: LogContext): void {
- this.warn(`RETRY: ${message}`, { ...context, operation: 'retry' });
+ this.warn(`RETRY: ${message}`, {...context, operation: 'retry'});
}
performance(message: string, context?: LogContext): void {
- this.info(`PERFORMANCE: ${message}`, { ...context, operation: 'performance' });
+ this.info(`PERFORMANCE: ${message}`, {...context, operation: 'performance'});
}
security(message: string, context?: LogContext): void {
- this.warn(`SECURITY: ${message}`, { ...context, operation: 'security' });
+ this.warn(`SECURITY: ${message}`, {...context, operation: 'security'});
}
- // Enhanced API request/response logging methods
requestStart(method: string, url: string, context?: LogContext): void {
const cleanUrl = this.sanitizeUrl(url);
const shortUrl = this.getShortUrl(cleanUrl);
-
+
console.group(`🚀 ${method.toUpperCase()} ${shortUrl}`);
this.request(`→ Starting request to ${cleanUrl}`, {
...context,
@@ -203,7 +197,7 @@ class FrontendLogger {
const shortUrl = this.getShortUrl(cleanUrl);
const statusIcon = this.getStatusIcon(status);
const performanceIcon = this.getPerformanceIcon(responseTime);
-
+
this.response(`← ${statusIcon} ${status} ${performanceIcon} ${responseTime}ms`, {
...context,
method: method.toUpperCase(),
@@ -218,7 +212,7 @@ class FrontendLogger {
const cleanUrl = this.sanitizeUrl(url);
const shortUrl = this.getShortUrl(cleanUrl);
const statusIcon = this.getStatusIcon(status);
-
+
this.error(`← ${statusIcon} ${status} ${errorMessage}`, undefined, {
...context,
method: method.toUpperCase(),
@@ -233,7 +227,7 @@ class FrontendLogger {
networkError(method: string, url: string, errorMessage: string, context?: LogContext): void {
const cleanUrl = this.sanitizeUrl(url);
const shortUrl = this.getShortUrl(cleanUrl);
-
+
this.error(`🌐 Network Error: ${errorMessage}`, undefined, {
...context,
method: method.toUpperCase(),
@@ -247,7 +241,7 @@ class FrontendLogger {
authError(method: string, url: string, context?: LogContext): void {
const cleanUrl = this.sanitizeUrl(url);
const shortUrl = this.getShortUrl(cleanUrl);
-
+
this.security(`🔐 Authentication Required`, {
...context,
method: method.toUpperCase(),
@@ -260,7 +254,7 @@ class FrontendLogger {
retryAttempt(method: string, url: string, attempt: number, maxAttempts: number, context?: LogContext): void {
const cleanUrl = this.sanitizeUrl(url);
const shortUrl = this.getShortUrl(cleanUrl);
-
+
this.retry(`🔄 Retry ${attempt}/${maxAttempts}`, {
...context,
method: method.toUpperCase(),
@@ -269,25 +263,22 @@ class FrontendLogger {
});
}
- // Enhanced logging for API operations
apiOperation(operation: string, details: string, context?: LogContext): void {
- this.info(`🔧 ${operation}: ${details}`, { ...context, operation: 'api_operation' });
+ this.info(`🔧 ${operation}: ${details}`, {...context, operation: 'api_operation'});
}
- // Log request summary for better debugging
requestSummary(method: string, url: string, status: number, responseTime: number, context?: LogContext): void {
const cleanUrl = this.sanitizeUrl(url);
const shortUrl = this.getShortUrl(cleanUrl);
const statusIcon = this.getStatusIcon(status);
const performanceIcon = this.getPerformanceIcon(responseTime);
-
- console.log(`%c📊 ${method} ${shortUrl} ${statusIcon} ${status} ${performanceIcon} ${responseTime}ms`,
- 'color: #666; font-style: italic; font-size: 0.9em;',
+
+ console.log(`%c📊 ${method} ${shortUrl} ${statusIcon} ${status} ${performanceIcon} ${responseTime}ms`,
+ 'color: #666; font-style: italic; font-size: 0.9em;',
context
);
}
- // New helper methods for better formatting
private getShortUrl(url: string): string {
try {
const urlObj = new URL(url);
@@ -316,10 +307,8 @@ class FrontendLogger {
}
private sanitizeUrl(url: string): string {
- // Remove sensitive information from URLs for logging
try {
const urlObj = new URL(url);
- // Remove query parameters that might contain sensitive data
if (urlObj.searchParams.has('password') || urlObj.searchParams.has('token')) {
urlObj.search = '';
}
@@ -330,7 +319,6 @@ class FrontendLogger {
}
}
-// Service-specific loggers
export const apiLogger = new FrontendLogger('API', '🌐', '#3b82f6');
export const authLogger = new FrontendLogger('AUTH', '🔐', '#dc2626');
export const sshLogger = new FrontendLogger('SSH', '🖥️', '#1e3a8a');
@@ -339,5 +327,4 @@ export const fileLogger = new FrontendLogger('FILE', '📁', '#1e3a8a');
export const statsLogger = new FrontendLogger('STATS', '📊', '#22c55e');
export const systemLogger = new FrontendLogger('SYSTEM', '🚀', '#1e3a8a');
-// Default logger for general use
export const logger = systemLogger;
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index bd0c391d..d1a4eb4e 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,6 +1,6 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
+import {clsx, type ClassValue} from "clsx"
+import {twMerge} from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs))
}
diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index cd8aceb7..86c59980 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -10,7 +10,7 @@
"deleteCredential": "Delete Credential",
"updateCredential": "Update Credential",
"credentialName": "Credential Name",
- "credentialDescription": "Description",
+ "credentialDescription": "Description",
"username": "Username",
"searchCredentials": "Search credentials...",
"selectFolder": "Select Folder",
@@ -42,7 +42,7 @@
"credentialsCount": "{{count}} credentials",
"refresh": "Refresh",
"passwordRequired": "Password is required",
- "sshKeyRequired": "SSH key is required",
+ "sshKeyRequired": "SSH key is required",
"credentialAddedSuccessfully": "Credential \"{{name}}\" added successfully",
"general": "General",
"description": "Description",
@@ -57,7 +57,7 @@
"keyPassword": "Key Password (optional)",
"keyType": "Key Type",
"keyTypeRSA": "RSA",
- "keyTypeECDSA": "ECDSA",
+ "keyTypeECDSA": "ECDSA",
"keyTypeEd25519": "Ed25519",
"updateCredential": "Update Credential",
"basicInfo": "Basic Info",
@@ -224,7 +224,7 @@
"register": "Register",
"username": "Username",
"password": "Password",
- "version" : "Version",
+ "version": "Version",
"confirmPassword": "Confirm Password",
"back": "Back",
"email": "Email",
diff --git a/src/main.tsx b/src/main.tsx
index 13d7ff12..b8bdaf0f 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -2,7 +2,7 @@ import {StrictMode, useEffect, useState, useRef} from 'react'
import {createRoot} from 'react-dom/client'
import './index.css'
import DesktopApp from './ui/Desktop/DesktopApp.tsx'
-import { MobileApp } from './ui/Mobile/MobileApp.tsx'
+import {MobileApp} from './ui/Mobile/MobileApp.tsx'
import {ThemeProvider} from "@/components/theme-provider"
import './i18n/i18n'
import {isElectron} from './ui/main-axios.ts'
@@ -54,10 +54,10 @@ function RootApp() {
const width = useWindowWidth();
const isMobile = width < 768;
if (isElectron()) {
- return ;
+ return ;
}
- return isMobile ? : ;
+ return isMobile ? : ;
}
createRoot(document.getElementById('root')!).render(
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts
deleted file mode 100644
index f2176d2e..00000000
--- a/src/types/electron.d.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-interface ElectronAPI {
- getAppVersion: () => Promise;
- getPlatform: () => Promise;
- getServerConfig: () => Promise<{ serverUrl: string; lastUpdated: string } | null>;
- saveServerConfig: (config: { serverUrl: string; lastUpdated: string }) => Promise<{ success: boolean; error?: string }>;
- testServerConnection: (serverUrl: string) => Promise<{ success: boolean; error?: string; status?: number }>;
- showSaveDialog: (options: any) => Promise;
- showOpenDialog: (options: any) => Promise;
- onUpdateAvailable: (callback: () => void) => void;
- onUpdateDownloaded: (callback: () => void) => void;
- removeAllListeners: (channel: string) => void;
- isElectron: boolean;
- isDev: boolean;
- invoke: (channel: string, ...args: any[]) => Promise;
-}
-
-interface Window {
- electronAPI?: ElectronAPI;
- IS_ELECTRON?: boolean;
-}
\ No newline at end of file
diff --git a/src/types/index.ts b/src/types/index.ts
index 302bed98..9232f3f5 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -4,7 +4,7 @@
// This file contains all shared interfaces and types used across the application
// to avoid duplication and ensure consistency.
-import type { Client } from 'ssh2';
+import type {Client} from 'ssh2';
// ============================================================================
// SSH HOST TYPES
diff --git a/src/ui/Desktop/Admin/AdminSettings.tsx b/src/ui/Desktop/Admin/AdminSettings.tsx
index 73fdf0d8..e7a93ce3 100644
--- a/src/ui/Desktop/Admin/AdminSettings.tsx
+++ b/src/ui/Desktop/Admin/AdminSettings.tsx
@@ -74,24 +74,19 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
React.useEffect(() => {
const jwt = getCookie("jwt");
if (!jwt) return;
-
- // Check if we're in Electron and have a server configured
+
if (isElectron()) {
- // In Electron, check if we have a configured server
const serverUrl = (window as any).configuredServerUrl;
if (!serverUrl) {
- console.log('No server configured in Electron, skipping API calls');
return;
}
}
-
+
getOIDCConfig()
.then(res => {
if (res) setOidcConfig(res);
})
.catch((err) => {
- console.error('Failed to fetch OIDC config:', err);
- // Only show error if it's not a "no server configured" error
if (!err.message?.includes('No server configured')) {
toast.error(t('admin.failedToFetchOidcConfig'));
}
@@ -100,15 +95,13 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
}, []);
React.useEffect(() => {
- // Check if we're in Electron and have a server configured
if (isElectron()) {
const serverUrl = (window as any).configuredServerUrl;
if (!serverUrl) {
- console.log('No server configured in Electron, skipping registration status check');
return;
}
}
-
+
getRegistrationAllowed()
.then(res => {
if (typeof res?.allowed === 'boolean') {
@@ -116,8 +109,6 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
}
})
.catch((err) => {
- console.error('Failed to fetch registration status:', err);
- // Only show error if it's not a "no server configured" error
if (!err.message?.includes('No server configured')) {
toast.error(t('admin.failedToFetchRegistrationStatus'));
}
@@ -127,23 +118,19 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
const fetchUsers = async () => {
const jwt = getCookie("jwt");
if (!jwt) return;
-
- // Check if we're in Electron and have a server configured
+
if (isElectron()) {
const serverUrl = (window as any).configuredServerUrl;
if (!serverUrl) {
- console.log('No server configured in Electron, skipping user fetch');
return;
}
}
-
+
setUsersLoading(true);
try {
const response = await getUserList();
setUsers(response.users);
} catch (err) {
- console.error('Failed to fetch users:', err);
- // Only show error if it's not a "no server configured" error
if (!err.message?.includes('No server configured')) {
toast.error(t('admin.failedToFetchUsers'));
}
@@ -171,7 +158,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
const required = ['client_id', 'client_secret', 'issuer_url', 'authorization_url', 'token_url'];
const missing = required.filter(f => !oidcConfig[f as keyof typeof oidcConfig]);
if (missing.length > 0) {
- setOidcError(t('admin.missingRequiredFields', { fields: missing.join(', ') }));
+ setOidcError(t('admin.missingRequiredFields', {fields: missing.join(', ')}));
setOidcLoading(false);
return;
}
@@ -199,7 +186,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
const jwt = getCookie("jwt");
try {
await makeUserAdmin(newAdminUsername.trim());
- toast.success(t('admin.userIsNowAdmin', { username: newAdminUsername }));
+ toast.success(t('admin.userIsNowAdmin', {username: newAdminUsername}));
setNewAdminUsername("");
fetchUsers();
} catch (err: any) {
@@ -211,15 +198,14 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
const handleRemoveAdminStatus = async (username: string) => {
confirmWithToast(
- t('admin.removeAdminStatus', { username }),
+ t('admin.removeAdminStatus', {username}),
async () => {
const jwt = getCookie("jwt");
try {
await removeAdminStatus(username);
- toast.success(t('admin.adminStatusRemoved', { username }));
+ toast.success(t('admin.adminStatusRemoved', {username}));
fetchUsers();
} catch (err: any) {
- console.error('Failed to remove admin status:', err);
toast.error(t('admin.failedToRemoveAdminStatus'));
}
}
@@ -228,15 +214,14 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
const handleDeleteUser = async (username: string) => {
confirmWithToast(
- t('admin.deleteUser', { username }),
+ t('admin.deleteUser', {username}),
async () => {
const jwt = getCookie("jwt");
try {
await deleteUser(username);
- toast.success(t('admin.userDeletedSuccessfully', { username }));
+ toast.success(t('admin.userDeletedSuccessfully', {username}));
fetchUsers();
} catch (err: any) {
- console.error('Failed to delete user:', err);
toast.error(t('admin.failedToDeleteUser'));
}
},
@@ -301,9 +286,9 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.