Update logo, move translations, update electron building.

This commit is contained in:
LukeGus
2025-09-10 19:18:06 -05:00
parent 9d84469ea3
commit 86671a670d
35 changed files with 2386 additions and 832 deletions

View File

@@ -23,8 +23,7 @@ Run the following commands:
```sh
npm run dev
npx tsc -p tsconfig.node.json
node ./dist/backend/starter.js
npm run dev:backend
```
This will start the backend and the frontend Vite server. You can access Termix by going to `http://localhost:5174/`.
@@ -40,11 +39,11 @@ This will start the backend and the frontend Vite server. You can access Termix
3. **Make your changes**: Implement your feature, fix, or improvement.
4. **Commit your changes**:
```sh
git commit -m "Add feature: my new feature"
git commit -m "Feature request my new feature"
```
5. **Push to your fork**:
```sh
git push origin feature/my-new-feature
git push origin feature/my-feature-request
```
6. **Open a pull request**: Go to the original repository and create a PR with a clear description.
@@ -54,3 +53,8 @@ This will start the backend and the frontend Vite server. You can access Termix
- Place all API routes in the `main-axios.ts` file. Updating the `openapi.json` is unneeded.
- Include meaningful commit messages.
- Link related issues when applicable.
- `MobileApp.tsx` renders when the users screen width is less than 768px, otherwise it loads the usual `DesktopApp.tsx`.
## 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.

View File

@@ -1,35 +0,0 @@
import { execSync } from 'child_process';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Check if we're in a path with spaces
const currentPath = process.cwd();
if (currentPath.includes(' ')) {
console.log('⚠️ Warning: Project path contains spaces which may cause issues with native modules.');
console.log('Current path:', currentPath);
console.log('Consider moving the project to a path without spaces for better compatibility.');
console.log('');
}
// Set environment variables to help with native module compilation
process.env.npm_config_cache = path.join(process.cwd(), 'node_modules', '.cache');
process.env.npm_config_tmp = path.join(process.cwd(), 'node_modules', '.tmp');
console.log('Building Electron application...');
// Skip better-sqlite3 rebuild due to path issues
console.log('Skipping better-sqlite3 rebuild due to path space issues...');
console.log('Note: Using existing better-sqlite3 installation.');
// Run the electron-builder
try {
execSync('npx electron-builder', { stdio: 'inherit' });
console.log('✅ Electron build completed successfully!');
} catch (error) {
console.error('❌ Electron build failed:', error.message);
process.exit(1);
}

View File

@@ -16,40 +16,28 @@
"!eslint.config.js"
],
"extraMetadata": {
"main": "electron/main-simple.cjs"
"main": "electron/main.cjs"
},
"buildDependenciesFromSource": false,
"nodeGypRebuild": false,
"npmRebuild": false,
"mac": {
"category": "public.app-category.developer-tools",
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
]
},
"win": {
"target": "nsis",
"forceCodeSigning": false
"icon": "public/icon.ico",
"executableName": "Termix"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"artifactName": "${productName}-Setup-${version}.${ext}"
"artifactName": "${productName}-Setup-${version}.${ext}",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Termix",
"uninstallDisplayName": "Termix"
},
"linux": {
"category": "Development",
"target": [
{
"target": "AppImage",
"arch": ["x64"]
}
]
"icon": "public/icon.png",
"category": "Development"
}
}

View File

@@ -1,236 +0,0 @@
const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
// 全局变量
let mainWindow = null;
let backendProcess = null;
// 开发环境检测
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
// 启动后端服务
function startBackendServer() {
if (backendProcess) {
console.log('Backend server already running');
return;
}
// 在打包环境中,后端文件在 resources/app/dist/backend/backend/ 目录下
const backendPath = isDev
? path.join(__dirname, '../dist/backend/starter.js')
: path.join(process.resourcesPath, 'app', 'dist', 'backend', 'backend', 'starter.js');
console.log('Starting backend server from:', backendPath);
console.log('Working directory:', process.cwd());
// 设置环境变量
const env = {
...process.env,
NODE_ENV: 'production',
DATA_PATH: app.getPath('userData'),
DB_PATH: path.join(app.getPath('userData'), 'database.db'),
VERSION: app.getVersion()
};
// 检查文件是否存在
const fs = require('fs');
if (!fs.existsSync(backendPath)) {
console.error('Backend file not found at:', backendPath);
return;
}
console.log('Backend file exists, starting process...');
console.log('Environment variables:', env);
backendProcess = spawn('node', [backendPath], {
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
cwd: isDev ? path.join(__dirname, '..') : process.resourcesPath,
env: env
});
backendProcess.stdout.on('data', (data) => {
console.log('Backend stdout:', data.toString());
});
backendProcess.stderr.on('data', (data) => {
console.error('Backend stderr:', data.toString());
});
backendProcess.on('close', (code) => {
console.log(`Backend process exited with code ${code}`);
backendProcess = null;
});
backendProcess.on('error', (error) => {
console.error('Failed to start backend process:', error);
console.error('Error details:', error.message);
console.error('Error code:', error.code);
backendProcess = null;
});
// 等待一下看看进程是否启动成功
setTimeout(() => {
if (backendProcess && !backendProcess.killed) {
console.log('Backend process appears to be running');
} else {
console.error('Backend process failed to start or died immediately');
}
}, 1000);
}
// 停止后端服务
function stopBackendServer() {
if (backendProcess) {
console.log('Stopping backend server...');
backendProcess.kill();
backendProcess = null;
}
}
// 防止多开
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
}
// 创建主窗口
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
title: 'Termix',
icon: path.join(__dirname, '..', 'public', 'icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload-simple.cjs'),
webSecurity: !isDev
},
show: false,
});
// 创建应用菜单(包含开发者工具快捷键)
const template = [
{
label: 'View',
submenu: [
{
label: 'Toggle Developer Tools',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click: () => {
mainWindow.webContents.toggleDevTools();
}
},
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: () => {
mainWindow.webContents.reload();
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
// 加载应用
if (isDev) {
// 开发环境:连接到 Vite 开发服务器
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
// 生产环境:加载构建后的文件
mainWindow.loadFile(path.join(__dirname, '..', 'dist', 'index.html'));
// 生产环境也启用开发者工具以便调试
mainWindow.webContents.openDevTools();
}
// 窗口准备好后显示
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// 处理窗口关闭事件
mainWindow.on('closed', () => {
mainWindow = null;
});
// 处理外部链接
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
}
// IPC 通信处理
ipcMain.handle('get-app-version', () => {
return app.getVersion();
});
ipcMain.handle('get-platform', () => {
return process.platform;
});
ipcMain.handle('get-backend-port', () => {
return 8081; // 后端服务端口
});
ipcMain.handle('restart-backend', async () => {
try {
stopBackendServer();
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
startBackendServer();
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// 应用事件处理
app.whenReady().then(async () => {
// 启动后端服务
startBackendServer();
// 等待后端服务启动
await new Promise(resolve => setTimeout(resolve, 2000));
createWindow();
});
app.on('window-all-closed', () => {
stopBackendServer();
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
} else if (mainWindow) {
mainWindow.show();
}
});
// 处理未捕获的异常
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

View File

@@ -1,52 +1,24 @@
const { app, BrowserWindow, Menu, Tray, shell, ipcMain, dialog } = require('electron');
const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const fs = require('fs');
// 动态导入可能有 ESM 问题的模块
let portfinder;
let Store;
let autoUpdater;
try {
portfinder = require('portfinder');
Store = require('electron-store');
const updaterModule = require('electron-updater');
autoUpdater = updaterModule.autoUpdater;
} catch (error) {
console.error('Error loading modules:', error);
// 提供后备方案
portfinder = {
getPortPromise: async () => 18080 + Math.floor(Math.random() * 100)
};
Store = class {
constructor() { this.data = {}; }
get(key, defaultValue) { return this.data[key] || defaultValue; }
set(key, value) { this.data[key] = value; }
};
}
// 初始化配置存储
const store = new Store();
// 全局变量
// Global variables
let mainWindow = null;
let backendProcess = null;
let tray = null;
let backendPort = null;
let isQuitting = false;
// 开发环境检测
// Development environment detection
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
// 防止多开
const gotTheLock = app.requestSingleInstanceLock();
const BACKEND_CONFIG = {
port: 18080
};
// Prevent multiple instances
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', () => {
// 如果用户试图运行第二个实例,我们应该聚焦我们的窗口
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
@@ -54,217 +26,43 @@ if (!gotTheLock) {
});
}
// 后端进程管理类
class BackendManager {
constructor() {
this.process = null;
this.port = null;
this.retryCount = 0;
this.maxRetries = 3;
this.isStarting = false;
this.healthCheckInterval = null;
}
async findAvailablePort() {
portfinder.basePort = store.get('backend.port', 18080);
// Simple backend management
async function startBackend() {
try {
const port = await portfinder.getPortPromise();
this.port = port;
return port;
} catch (error) {
console.error('Error finding available port:', error);
throw error;
}
}
console.log('Starting backend...');
async start() {
if (this.isStarting || this.process) {
console.log('Backend already starting or running');
return;
}
// Set up environment
process.env.NODE_ENV = 'production';
process.env.PORT = BACKEND_CONFIG.port.toString();
process.env.DATA_PATH = app.getPath('userData');
process.env.DB_PATH = path.join(app.getPath('userData'), 'database.db');
process.env.VERSION = app.getVersion();
this.isStarting = true;
// Get backend path
const backendPath = isDev
? path.join(__dirname, '..', 'dist', 'backend', 'starter.js')
: path.join(process.resourcesPath, 'dist', 'backend', 'starter.js');
try {
// 查找可用端口
await this.findAvailablePort();
console.log(`Starting backend on port ${this.port}`);
console.log('Loading backend from:', backendPath);
// 确定后端可执行文件路径
let backendPath;
if (isDev) {
// 开发环境:使用 node 运行构建后的 JS
backendPath = path.join(__dirname, '..', 'dist', 'backend', 'starter.js');
} else {
// 生产环境:使用打包后的后端
backendPath = path.join(process.resourcesPath, 'backend', 'starter.js');
}
// 确保后端文件存在
if (!fs.existsSync(backendPath)) {
throw new Error(`Backend file not found at ${backendPath}`);
console.error('Backend file not found at:', backendPath);
return null;
}
// 设置环境变量
const env = {
...process.env,
PORT: this.port.toString(),
NODE_ENV: isDev ? 'development' : 'production',
DATA_PATH: app.getPath('userData'),
DB_PATH: path.join(app.getPath('userData'), 'database.db'),
};
// Load and start the backend
const { startServer } = await import(backendPath);
backendPort = await startServer();
console.log('Backend started successfully on port:', backendPort);
return backendPort;
// 启动后端进程
if (isDev) {
this.process = spawn('node', [backendPath], {
env,
cwd: path.join(__dirname, '..'),
stdio: ['ignore', 'pipe', 'pipe']
});
} else {
this.process = spawn('node', [backendPath], {
env,
cwd: process.resourcesPath,
stdio: ['ignore', 'pipe', 'pipe']
});
}
// 监听后端输出
this.process.stdout.on('data', (data) => {
console.log(`Backend stdout: ${data}`);
// 向渲染进程发送日志
if (mainWindow) {
mainWindow.webContents.send('backend-log', data.toString());
}
});
this.process.stderr.on('data', (data) => {
console.error(`Backend stderr: ${data}`);
if (mainWindow) {
mainWindow.webContents.send('backend-error', data.toString());
}
});
// 监听后端进程退出
this.process.on('exit', (code) => {
console.log(`Backend process exited with code ${code}`);
this.process = null;
this.isStarting = false;
// 如果不是正在退出且退出码不为0尝试重启
if (!isQuitting && code !== 0 && this.retryCount < this.maxRetries) {
this.retryCount++;
console.log(`Attempting to restart backend (retry ${this.retryCount}/${this.maxRetries})`);
setTimeout(() => this.start(), 2000);
}
});
// 等待后端启动
await this.waitForBackend();
// 启动健康检查
this.startHealthCheck();
// 更新全局端口变量
backendPort = this.port;
// 通知渲染进程
if (mainWindow) {
mainWindow.webContents.send('backend-started', { port: this.port });
}
this.isStarting = false;
this.retryCount = 0;
return this.port;
} catch (error) {
console.error('Failed to start backend:', error);
this.isStarting = false;
throw error;
return null;
}
}
async waitForBackend() {
const maxWaitTime = 30000; // 30秒
const checkInterval = 500; // 每500ms检查一次
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
try {
// 尝试连接后端健康检查端点
const response = await fetch(`http://127.0.0.1:${this.port}/health`);
if (response.ok) {
console.log('Backend is ready');
return;
}
} catch (error) {
// 继续等待
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
throw new Error('Backend failed to start within timeout period');
}
startHealthCheck() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
this.healthCheckInterval = setInterval(async () => {
if (!this.process) return;
try {
const response = await fetch(`http://127.0.0.1:${this.port}/health`);
if (!response.ok) {
console.error('Backend health check failed');
// 可以在这里触发重启逻辑
}
} catch (error) {
console.error('Backend health check error:', error);
}
}, 10000); // 每10秒检查一次
}
stop() {
return new Promise((resolve) => {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = null;
}
if (!this.process) {
resolve();
return;
}
console.log('Stopping backend process...');
// 设置超时强制杀死
const killTimeout = setTimeout(() => {
if (this.process) {
console.log('Force killing backend process');
this.process.kill('SIGKILL');
}
}, 5000);
this.process.on('exit', () => {
clearTimeout(killTimeout);
this.process = null;
console.log('Backend process stopped');
resolve();
});
// 优雅关闭
this.process.kill('SIGTERM');
});
}
}
// 创建后端管理器实例
const backendManager = new BackendManager();
// 创建主窗口
// Create main window
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
@@ -272,44 +70,51 @@ function createWindow() {
minWidth: 800,
minHeight: 600,
title: 'Termix',
icon: path.join(__dirname, '..', 'public', 'icon.png'),
icon: isDev
? path.join(__dirname, '..', 'public', 'icon.png')
: path.join(process.resourcesPath, 'public', 'icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.cjs'),
webSecurity: !isDev
},
show: false, // 先不显示,等加载完成
show: false
});
// 移除默认菜单栏(Windows/Linux
// Remove menu bar on Windows/Linux
if (process.platform !== 'darwin') {
mainWindow.setMenuBarVisibility(false);
}
// 加载应用
// Load application
if (isDev) {
// 开发环境:连接到 Vite 开发服务器
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
// 生产环境:加载构建后的文件
mainWindow.loadFile(path.join(__dirname, '..', 'dist', 'index.html'));
// Load from dist folder
const indexPath = path.join(__dirname, '..', 'dist', 'index.html');
console.log('Loading frontend from:', indexPath);
mainWindow.loadFile(indexPath);
}
// 窗口准备好后显示
// Show window when ready
mainWindow.once('ready-to-show', () => {
console.log('Window ready to show');
mainWindow.show();
});
// 处理窗口关闭事件
// Handle load errors
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
console.error('Failed to load:', errorCode, errorDescription, validatedURL);
});
mainWindow.webContents.on('did-finish-load', () => {
console.log('Frontend loaded successfully');
});
// Handle window close
mainWindow.on('close', (event) => {
if (!isQuitting && process.platform === 'darwin') {
// macOS隐藏窗口而不是退出
event.preventDefault();
mainWindow.hide();
} else if (!isQuitting && store.get('minimizeToTray', true)) {
// Windows/Linux最小化到托盘
if (process.platform === 'darwin') {
event.preventDefault();
mainWindow.hide();
}
@@ -319,53 +124,14 @@ function createWindow() {
mainWindow = null;
});
// 处理外部链接
// Handle external links
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
}
// 创建系统托盘
function createTray() {
if (process.platform === 'darwin') return; // macOS 不需要托盘
const iconPath = path.join(__dirname, '..', 'public', 'icon.png');
tray = new Tray(iconPath);
const contextMenu = Menu.buildFromTemplate([
{
label: '显示',
click: () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
}
},
{ type: 'separator' },
{
label: '退出',
click: () => {
isQuitting = true;
app.quit();
}
}
]);
tray.setToolTip('Termix');
tray.setContextMenu(contextMenu);
// 双击托盘图标显示窗口
tray.on('double-click', () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
});
}
// IPC 通信处理
// IPC handlers
ipcMain.handle('get-backend-port', () => {
return backendPort;
});
@@ -380,61 +146,29 @@ ipcMain.handle('get-platform', () => {
ipcMain.handle('restart-backend', async () => {
try {
await backendManager.stop();
await backendManager.start();
return { success: true, port: backendManager.port };
backendPort = await startBackend();
return { success: true, port: backendPort };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('show-save-dialog', async (event, options) => {
const result = await dialog.showSaveDialog(mainWindow, options);
return result;
});
ipcMain.handle('show-open-dialog', async (event, options) => {
const result = await dialog.showOpenDialog(mainWindow, options);
return result;
});
// 自动更新
if (!isDev && autoUpdater) {
try {
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-available', () => {
if (mainWindow) {
mainWindow.webContents.send('update-available');
}
});
autoUpdater.on('update-downloaded', () => {
if (mainWindow) {
mainWindow.webContents.send('update-downloaded');
}
});
} catch (error) {
console.log('Auto-updater not available:', error);
}
}
// 应用事件处理
// App event handlers
app.whenReady().then(async () => {
try {
// 启动后端
await backendManager.start();
// 创建窗口
// Create window immediately for fast startup
createWindow();
// 创建托盘
createTray();
} catch (error) {
console.error('Failed to initialize application:', error);
dialog.showErrorBox('启动失败', `无法启动应用: ${error.message}`);
app.quit();
// Start backend in background (non-blocking)
try {
backendPort = await startBackend();
if (backendPort) {
console.log('Backend started successfully on port:', backendPort);
}
} catch (error) {
console.error('Backend failed to start:', error);
}
console.log('Termix started successfully');
});
app.on('window-all-closed', () => {
@@ -451,15 +185,13 @@ app.on('activate', () => {
}
});
app.on('before-quit', async () => {
isQuitting = true;
await backendManager.stop();
app.on('before-quit', () => {
console.log('App is quitting...');
});
// 处理未捕获的异常
// Error handling
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
dialog.showErrorBox('未捕获的异常', error.message);
});
process.on('unhandledRejection', (reason, promise) => {

View File

@@ -1,24 +0,0 @@
const { contextBridge, ipcRenderer } = require('electron');
// 暴露简化的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 获取应用版本
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
// 获取平台信息
getPlatform: () => ipcRenderer.invoke('get-platform'),
// 获取后端端口
getBackendPort: () => ipcRenderer.invoke('get-backend-port'),
// 重启后端服务
restartBackend: () => ipcRenderer.invoke('restart-backend'),
// 环境检测
isElectron: true,
isDev: process.env.NODE_ENV === 'development',
});
// 添加一个标识,让渲染进程知道这是 Electron 环境
// 在上下文隔离环境中,使用 contextBridge 暴露
contextBridge.exposeInMainWorld('IS_ELECTRON', true);

View File

@@ -1,54 +0,0 @@
const { contextBridge, ipcRenderer } = require('electron');
// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 获取后端端口
getBackendPort: () => ipcRenderer.invoke('get-backend-port'),
// 获取应用版本
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
// 获取平台信息
getPlatform: () => ipcRenderer.invoke('get-platform'),
// 重启后端
restartBackend: () => ipcRenderer.invoke('restart-backend'),
// 文件对话框
showSaveDialog: (options) => ipcRenderer.invoke('show-save-dialog', options),
showOpenDialog: (options) => ipcRenderer.invoke('show-open-dialog', options),
// 监听后端事件
onBackendStarted: (callback) => {
ipcRenderer.on('backend-started', (event, data) => callback(data));
},
onBackendLog: (callback) => {
ipcRenderer.on('backend-log', (event, data) => callback(data));
},
onBackendError: (callback) => {
ipcRenderer.on('backend-error', (event, data) => callback(data));
},
// 监听更新事件
onUpdateAvailable: (callback) => {
ipcRenderer.on('update-available', () => callback());
},
onUpdateDownloaded: (callback) => {
ipcRenderer.on('update-downloaded', () => callback());
},
// 移除事件监听器
removeAllListeners: (channel) => {
ipcRenderer.removeAllListeners(channel);
},
// 环境检测
isElectron: true,
isDev: process.env.NODE_ENV === 'development',
});
// 添加一个标识,让渲染进程知道这是 Electron 环境
window.IS_ELECTRON = true;

2261
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,20 +4,19 @@
"version": "1.6.0",
"description": "A web-based server management platform with SSH terminal, tunneling, and file editing capabilities",
"author": "Karmaa",
"main": "electron/main-simple.cjs",
"main": "electron/main.cjs",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build:backend": "tsc -p tsconfig.node.json",
"build": "vite build && tsc -p tsconfig.node.json",
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js",
"lint": "eslint .",
"preview": "vite preview",
"electron": "electron .",
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"",
"electron:build": "npm run build && npm run build:backend && electron-builder --dir",
"electron:build-win": "npm run build && npm run build:backend && electron-builder --win --dir",
"electron:pack": "npm run build && npm run build:backend && electron-packager . Termix --platform=all --arch=x64,arm64 --out=release --overwrite"
"build:win-portable": "npm run build && electron-builder --win --dir",
"build:win-installer": "npm run build && electron-builder --win --publish=never",
"build:linux-portable": "npm run build && electron-builder --linux --dir"
},
"dependencies": {
"@hookform/resolvers": "^5.1.1",
@@ -108,6 +107,7 @@
"concurrently": "^9.2.1",
"electron": "^38.0.0",
"electron-builder": "^26.0.12",
"electron-icon-builder": "^2.0.1",
"electron-packager": "^17.1.2",
"eslint": "^9.34.0",
"eslint-plugin-react-hooks": "^5.2.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 168 KiB

BIN
public/icon.icns Normal file

Binary file not shown.

BIN
public/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 B

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
public/icons/1024x1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
public/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
public/icons/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

BIN
public/icons/24x24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

BIN
public/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
public/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

BIN
public/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/icons/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/icons/icon.icns Normal file

Binary file not shown.

BIN
public/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

View File

@@ -2,11 +2,13 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpApi from 'i18next-http-backend';
// Import translation files directly
import enTranslation from '../locales/en/translation.json';
import zhTranslation from '../locales/zh/translation.json';
// Initialize i18n
i18n
.use(HttpApi) // Load translations using http
.use(LanguageDetector) // Detect user language
.use(initReactI18next) // Pass i18n instance to react-i18next
.init({
@@ -23,9 +25,14 @@ i18n
checkWhitelist: true,
},
// Backend options
backend: {
loadPath: './locales/{{lng}}/translation.json',
// Resources - load translations directly
resources: {
en: {
translation: enTranslation
},
zh: {
translation: zhTranslation
}
},
interpolation: {

View File

@@ -1,10 +1,10 @@
import React, { useState, useEffect, useRef } from 'react';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { FormControl, FormItem, FormLabel } from "@/components/ui/form";
import { getCredentials } from '@/ui/main-axios';
import { Button } from "@/components/ui/button.tsx";
import { Input } from "@/components/ui/input.tsx";
import { FormControl, FormItem, FormLabel } from "@/components/ui/form.tsx";
import { getCredentials } from '@/ui/main-axios.ts';
import { useTranslation } from "react-i18next";
import type { Credential } from '../types/index.js';
import type { Credential } from '../../../../types';
interface CredentialSelectorProps {
value?: number | null;

View File

@@ -23,7 +23,7 @@ import {Alert, AlertDescription} from "@/components/ui/alert.tsx";
import {toast} from "sonner";
import {createSSHHost, updateSSHHost, getSSHHosts, getCredentials} from '@/ui/main-axios.ts';
import {useTranslation} from "react-i18next";
import {CredentialSelector} from "@/components/CredentialSelector.tsx";
import {CredentialSelector} from "@/ui/Desktop/Apps/Credentials/CredentialSelector.tsx";
interface SSHHost {
id: number;

View File

@@ -7,7 +7,7 @@ import {PasswordInput} from "@/components/ui/password-input.tsx";
import {Label} from "@/components/ui/label.tsx";
import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert.tsx";
import {useTranslation} from "react-i18next";
import {LanguageSwitcher} from "@/components/LanguageSwitcher.tsx";
import {LanguageSwitcher} from "@/ui/Desktop/User/LanguageSwitcher.tsx";
import {
registerUser,
loginUser,

View File

@@ -7,7 +7,7 @@ import {
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
} from '@/components/ui/select.tsx';
import { Globe } from 'lucide-react';
const languages = [

View File

@@ -13,7 +13,7 @@ import {getVersionInfo} from "@/ui/main-axios.ts";
import {toast} from "sonner";
import {PasswordReset} from "@/ui/Desktop/User/PasswordReset.tsx";
import {useTranslation} from "react-i18next";
import {LanguageSwitcher} from "@/components/LanguageSwitcher.tsx";
import {LanguageSwitcher} from "@/ui/Desktop/User/LanguageSwitcher.tsx";
import {useSidebar} from "@/components/ui/sidebar.tsx";

View File

@@ -5,7 +5,7 @@ import {Input} from "@/components/ui/input.tsx";
import {Label} from "@/components/ui/label.tsx";
import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert.tsx";
import {useTranslation} from "react-i18next";
import {LanguageSwitcher} from "@/components/LanguageSwitcher.tsx";
import {LanguageSwitcher} from "@/ui/Desktop/User/LanguageSwitcher.tsx";
import {
registerUser,
loginUser,