v1.6.0 #221

Merged
LukeGus merged 74 commits from dev-1.6.0 into main 2025-09-12 19:42:00 +00:00
11 changed files with 5585 additions and 50 deletions
Showing only changes of commit 9d84469ea3 - Show all commits

View File

@@ -1,25 +0,0 @@
{
"permissions": {
"allow": [
"Read(/C:\\Users\\29037\\WebstormProjects\\Termix\\docker/**)",
"Bash(git fetch:*)",
"Bash(git pull:*)",
"Bash(git checkout:*)",
"Bash(git add:*)",
"Bash(grep:*)",
"Bash(git push:*)",
"Bash(git branch:*)",
"Bash(npm run build:*)",
"Bash(npm install)",
"Bash(npm run electron:build:*)",
"Bash(npm uninstall:*)",
"Bash(git remote set-url:*)",
"Bash(npm run dev:backend:*)",
"Bash(taskkill:*)",
"Bash(node:*)",
"WebFetch(domain:ui.shadcn.com)"
],
"deny": [],
"ask": []
}
}

1
.gitignore vendored
View File

@@ -24,3 +24,4 @@ dist-ssr
*.sw? *.sw?
/db/ /db/
/release/ /release/
/.claude/

35
build-electron.js Normal file
View File

@@ -0,0 +1,35 @@
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

@@ -6,18 +6,23 @@
}, },
"files": [ "files": [
"dist/**/*", "dist/**/*",
"electron/**/*" "electron/**/*",
"public/**/*",
"!**/node_modules/**/*",
"!src/**/*",
"!*.md",
"!tsconfig*.json",
"!vite.config.ts",
"!eslint.config.js"
], ],
"extraMetadata": { "extraMetadata": {
"main": "electron/main-simple.cjs" "main": "electron/main-simple.cjs"
}, },
"buildDependenciesFromSource": false,
"nodeGypRebuild": false,
"npmRebuild": false,
"mac": { "mac": {
"category": "public.app-category.developer-tools", "category": "public.app-category.developer-tools",
"icon": "public/icon.icns",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"target": [ "target": [
{ {
"target": "dmg", "target": "dmg",
@@ -31,7 +36,7 @@
}, },
"win": { "win": {
"target": "nsis", "target": "nsis",
"icon": "public/icon.ico" "forceCodeSigning": false
}, },
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,

View File

@@ -16,27 +16,68 @@ function startBackendServer() {
return; return;
} }
const backendPath = path.join(__dirname, '../dist/backend/starter.js'); // 在打包环境中,后端文件在 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('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], { backendProcess = spawn('node', [backendPath], {
stdio: ['ignore', 'pipe', 'pipe'], stdio: ['ignore', 'pipe', 'pipe'],
detached: false, detached: false,
cwd: path.join(__dirname, '..') // Set working directory to app root cwd: isDev ? path.join(__dirname, '..') : process.resourcesPath,
env: env
}); });
backendProcess.stdout.on('data', (data) => { backendProcess.stdout.on('data', (data) => {
console.log('Backend:', data.toString()); console.log('Backend stdout:', data.toString());
}); });
backendProcess.stderr.on('data', (data) => { backendProcess.stderr.on('data', (data) => {
console.error('Backend Error:', data.toString()); console.error('Backend stderr:', data.toString());
}); });
backendProcess.on('close', (code) => { backendProcess.on('close', (code) => {
console.log(`Backend process exited with code ${code}`); console.log(`Backend process exited with code ${code}`);
backendProcess = null; 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);
} }
// 停止后端服务 // 停止后端服务
@@ -144,12 +185,29 @@ ipcMain.handle('get-platform', () => {
return process.platform; return process.platform;
}); });
// 应用事件处理 ipcMain.handle('get-backend-port', () => {
app.whenReady().then(() => { return 8081; // 后端服务端口
// 在生产环境启动后端服务 });
if (!isDev) {
ipcMain.handle('restart-backend', async () => {
try {
stopBackendServer();
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
startBackendServer(); 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(); createWindow();
}); });

View File

@@ -8,6 +8,12 @@ contextBridge.exposeInMainWorld('electronAPI', {
// 获取平台信息 // 获取平台信息
getPlatform: () => ipcRenderer.invoke('get-platform'), getPlatform: () => ipcRenderer.invoke('get-platform'),
// 获取后端端口
getBackendPort: () => ipcRenderer.invoke('get-backend-port'),
// 重启后端服务
restartBackend: () => ipcRenderer.invoke('restart-backend'),
// 环境检测 // 环境检测
isElectron: true, isElectron: true,
isDev: process.env.NODE_ENV === 'development', isDev: process.env.NODE_ENV === 'development',

5449
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,10 @@
{ {
"name": "termix", "name": "termix",
"private": true, "private": true,
"version": "0.0.0", "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",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -9,7 +12,12 @@
"build:backend": "tsc -p tsconfig.node.json", "build:backend": "tsc -p tsconfig.node.json",
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js", "dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "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"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.1.1", "@hookform/resolvers": "^5.1.1",
@@ -97,6 +105,10 @@
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@vitejs/plugin-react-swc": "^3.10.2", "@vitejs/plugin-react-swc": "^3.10.2",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"concurrently": "^9.2.1",
"electron": "^38.0.0",
"electron-builder": "^26.0.12",
"electron-packager": "^17.1.2",
"eslint": "^9.34.0", "eslint": "^9.34.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
@@ -105,6 +117,7 @@
"tw-animate-css": "^1.3.5", "tw-animate-css": "^1.3.5",
"typescript": "~5.9.2", "typescript": "~5.9.2",
"typescript-eslint": "^8.40.0", "typescript-eslint": "^8.40.0",
"vite": "^7.1.5" "vite": "^7.1.5",
"wait-on": "^8.0.4"
} }
} }

View File

@@ -537,13 +537,12 @@ export function LeftSidebar({
</div> </div>
<div <div
className="flex-1" className="flex-1 cursor-pointer"
onClick={() => { onClick={() => {
setDeleteAccountOpen(false); setDeleteAccountOpen(false);
setDeletePassword(""); setDeletePassword("");
setDeleteError(null); setDeleteError(null);
}} }}
className="cursor-pointer"
/> />
</div> </div>
)} )}

View File

@@ -152,7 +152,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
scrollback: 10000, scrollback: 10000,
fontSize: 14, fontSize: 14,
fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace', fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace',
theme: {background: 'var(--color-dark-bg-darkest)', foreground: '#f7f7f7'}, theme: {background: '#09090b', foreground: '#f7f7f7'},
allowTransparency: true, allowTransparency: true,
convertEol: true, convertEol: true,
windowsMode: false, windowsMode: false,

View File

@@ -742,7 +742,7 @@ export function HomepageAuth({
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label htmlFor="password">{t('common.password')}</Label> <Label htmlFor="password">{t('common.password')}</Label>
<PasswordInput id="password" required className="h-55 text-base" <PasswordInput id="password" required className="h-11 text-base"
value={password} onChange={e => setPassword(e.target.value)} value={password} onChange={e => setPassword(e.target.value)}
disabled={loading || internalLoggedIn}/> disabled={loading || internalLoggedIn}/>
</div> </div>