Status LED
Dieser Commit ist enthalten in:
@ -17,7 +17,8 @@
|
|||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(mkdir:*)",
|
"Bash(mkdir:*)",
|
||||||
"Bash(chmod:*)",
|
"Bash(chmod:*)",
|
||||||
"Bash(build.bat)"
|
"Bash(build.bat)",
|
||||||
|
"Bash(find:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
# ClaudeProjectManager-main
|
# ClaudeProjectManager
|
||||||
|
|
||||||
*This README was automatically generated by Claude Project Manager*
|
*This README was automatically generated by Claude Project Manager*
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
- **Path**: `C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main`
|
- **Path**: `C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main`
|
||||||
- **Files**: 227 files
|
- **Files**: 232 files
|
||||||
- **Size**: 77.0 MB
|
- **Size**: 78.4 MB
|
||||||
- **Last Modified**: 2025-07-08 08:19
|
- **Last Modified**: 2025-07-08 11:17
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
@ -197,3 +197,4 @@ This project is managed with Claude Project Manager. To work with this project:
|
|||||||
- README updated on 2025-07-07 22:12:28
|
- README updated on 2025-07-07 22:12:28
|
||||||
- README updated on 2025-07-07 22:34:45
|
- README updated on 2025-07-07 22:34:45
|
||||||
- README updated on 2025-07-08 08:19:16
|
- README updated on 2025-07-08 08:19:16
|
||||||
|
- README updated on 2025-07-08 11:17:58
|
||||||
|
|||||||
51
activity_server_cmd_fix.md
Normale Datei
51
activity_server_cmd_fix.md
Normale Datei
@ -0,0 +1,51 @@
|
|||||||
|
# Activity Server CMD Connection Fix
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
Users had to manually navigate to `/home/claude-dev/cpm-activity-server` after connecting via SSH when using the Activity Server CMD button.
|
||||||
|
|
||||||
|
## Solution Implemented
|
||||||
|
|
||||||
|
### 1. Enhanced VPS Connection Module
|
||||||
|
Modified `vps_connection.py` to create a more robust `create_activity_server_script()` method that:
|
||||||
|
- Detects available tools (PowerShell, plink, WSL, ssh)
|
||||||
|
- Attempts automatic directory change using multiple methods
|
||||||
|
- Provides clear fallback instructions when automation isn't possible
|
||||||
|
|
||||||
|
### 2. Project Tile Updates
|
||||||
|
Modified `gui/project_tile.py` to:
|
||||||
|
- Enable CMD button for both VPS Server and Activity Server tiles
|
||||||
|
- Use the VPS connection module for generating connection scripts
|
||||||
|
- Properly handle different project types
|
||||||
|
|
||||||
|
### 3. Connection Methods (in order of preference)
|
||||||
|
|
||||||
|
#### PowerShell + plink
|
||||||
|
- Uses PowerShell to better control plink execution
|
||||||
|
- Attempts to run commands after authentication
|
||||||
|
- Falls back to interactive mode with instructions
|
||||||
|
|
||||||
|
#### plink with batch commands
|
||||||
|
- Tries to execute `cd /home/claude-dev/cpm-activity-server && exec bash -l`
|
||||||
|
- If that fails, provides interactive session with clear instructions
|
||||||
|
|
||||||
|
#### WSL + sshpass
|
||||||
|
- Most reliable method when available
|
||||||
|
- Automatically changes directory and starts claude
|
||||||
|
- Uses inline bash commands for better control
|
||||||
|
|
||||||
|
#### Standard SSH fallback
|
||||||
|
- Clear instructions displayed for manual navigation
|
||||||
|
- Password shown for easy copy/paste
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
1. Click the "CMD" button on the Activity Server tile
|
||||||
|
2. The script will attempt to connect and automatically navigate to the correct directory
|
||||||
|
3. If automatic navigation fails, clear instructions are displayed
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
- `/vps_connection.py` - Enhanced `create_activity_server_script()` method
|
||||||
|
- `/gui/project_tile.py` - Updated CMD button logic and connection handling
|
||||||
|
|
||||||
|
## Additional Files Created
|
||||||
|
- `/activity_server_connect.ps1` - PowerShell script for enhanced connection (optional)
|
||||||
|
- `/setup_activity_server_profile.sh` - Server-side profile setup script (optional)
|
||||||
103
activity_server_connect.ps1
Normale Datei
103
activity_server_connect.ps1
Normale Datei
@ -0,0 +1,103 @@
|
|||||||
|
# PowerShell script for Activity Server connection with automatic directory change
|
||||||
|
|
||||||
|
$Host.UI.RawUI.WindowTitle = "CPM Activity Server Connection"
|
||||||
|
|
||||||
|
Write-Host "================================================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host " CPM Activity Server Connection" -ForegroundColor Cyan
|
||||||
|
Write-Host "================================================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Server: 91.99.192.14"
|
||||||
|
Write-Host "Username: claude-dev"
|
||||||
|
Write-Host "Target Directory: /home/claude-dev/cpm-activity-server"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Check if plink is available
|
||||||
|
$plinkPath = Get-Command plink -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($plinkPath) {
|
||||||
|
Write-Host "Using PuTTY plink for connection..." -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Create a temporary expect-like script using PowerShell
|
||||||
|
Write-Host "Attempting automatic connection with directory change..." -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# First attempt: Try to use plink with a command
|
||||||
|
$plinkArgs = @(
|
||||||
|
"-ssh",
|
||||||
|
"-l", "claude-dev",
|
||||||
|
"-pw", "z0E1Al}q2H?Yqd!O",
|
||||||
|
"-t",
|
||||||
|
"91.99.192.14",
|
||||||
|
"cd /home/claude-dev/cpm-activity-server && echo 'Successfully changed to Activity Server directory' && echo '' && bash -l"
|
||||||
|
)
|
||||||
|
|
||||||
|
$plinkProcess = Start-Process -FilePath "plink" -ArgumentList $plinkArgs -PassThru -Wait
|
||||||
|
|
||||||
|
if ($plinkProcess.ExitCode -ne 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Automatic directory change failed. Starting interactive session..." -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "================================================================================" -ForegroundColor Red
|
||||||
|
Write-Host "IMPORTANT: After login, please run these commands:" -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " cd /home/claude-dev/cpm-activity-server" -ForegroundColor White
|
||||||
|
Write-Host " claude" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "================================================================================" -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Interactive session
|
||||||
|
& plink -ssh -l claude-dev -pw "z0E1Al}q2H?Yqd!O" -t 91.99.192.14
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# Check for WSL
|
||||||
|
$wslPath = Get-Command wsl -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($wslPath) {
|
||||||
|
Write-Host "Using WSL for connection..." -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Use WSL with sshpass
|
||||||
|
$wslCommand = @"
|
||||||
|
if command -v sshpass >/dev/null 2>&1; then
|
||||||
|
echo 'Using sshpass for automatic login...'
|
||||||
|
sshpass -p 'z0E1Al}q2H?Yqd!O' ssh -o StrictHostKeyChecking=no -t claude-dev@91.99.192.14 'cd /home/claude-dev/cpm-activity-server && echo "Successfully changed to Activity Server directory" && echo && claude || bash -l'
|
||||||
|
else
|
||||||
|
echo 'Manual password entry required.'
|
||||||
|
echo 'Password: z0E1Al}q2H?Yqd!O'
|
||||||
|
echo ''
|
||||||
|
echo 'After login, run: cd /home/claude-dev/cpm-activity-server && claude'
|
||||||
|
echo ''
|
||||||
|
ssh -o StrictHostKeyChecking=no claude-dev@91.99.192.14
|
||||||
|
fi
|
||||||
|
"@
|
||||||
|
|
||||||
|
& wsl bash -c $wslCommand
|
||||||
|
} else {
|
||||||
|
# Fallback to SSH
|
||||||
|
Write-Host "No automated tools found. Using standard SSH..." -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "================================================================================" -ForegroundColor Red
|
||||||
|
Write-Host "Connection Instructions:" -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "1. Enter this password when prompted: z0E1Al}q2H?Yqd!O" -ForegroundColor White
|
||||||
|
Write-Host " (You can right-click to paste in most terminals)" -ForegroundColor Gray
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "2. After successful login, run these commands:" -ForegroundColor White
|
||||||
|
Write-Host " cd /home/claude-dev/cpm-activity-server" -ForegroundColor Cyan
|
||||||
|
Write-Host " claude" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "================================================================================" -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Press any key to continue..."
|
||||||
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
|
|
||||||
|
& ssh claude-dev@91.99.192.14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Connection closed." -ForegroundColor Yellow
|
||||||
|
Write-Host "Press any key to exit..."
|
||||||
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
@ -5,7 +5,7 @@
|
|||||||
"name": "VPS Server",
|
"name": "VPS Server",
|
||||||
"path": "claude-dev@91.99.192.14",
|
"path": "claude-dev@91.99.192.14",
|
||||||
"created_at": "2025-07-01T20:14:48.308074",
|
"created_at": "2025-07-01T20:14:48.308074",
|
||||||
"last_accessed": "2025-07-07T22:17:53.963308",
|
"last_accessed": "2025-07-08T11:14:14.271201",
|
||||||
"readme_path": "claude-dev@91.99.192.14\\CLAUDE_PROJECT_README.md",
|
"readme_path": "claude-dev@91.99.192.14\\CLAUDE_PROJECT_README.md",
|
||||||
"description": "Remote VPS Server with Claude",
|
"description": "Remote VPS Server with Claude",
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -48,15 +48,30 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "66c0f4bb-c560-43a6-a4c5-a8ecf8b73919",
|
"id": "66c0f4bb-c560-43a6-a4c5-a8ecf8b73919",
|
||||||
"name": "ClaudeProjectManager-main",
|
"name": "ClaudeProjectManager",
|
||||||
"path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main",
|
"path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main",
|
||||||
"created_at": "2025-07-07T21:38:23.820122",
|
"created_at": "2025-07-07T21:38:23.820122",
|
||||||
"last_accessed": "2025-07-08T08:19:16.600227",
|
"last_accessed": "2025-07-08T11:17:58.404487",
|
||||||
"readme_path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main\\CLAUDE_PROJECT_README.md",
|
"readme_path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main\\CLAUDE_PROJECT_README.md",
|
||||||
"description": "",
|
"description": "",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"gitea_repo": null
|
"gitea_repo": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "activity-server-permanent",
|
||||||
|
"name": "Activity Server",
|
||||||
|
"path": "/home/claude-dev/cpm-activity-server",
|
||||||
|
"created_at": "2025-07-08T12:12:14.492170",
|
||||||
|
"last_accessed": "2025-07-08T12:12:48.006792",
|
||||||
|
"readme_path": "/home/claude-dev/cpm-activity-server\\CLAUDE_PROJECT_README.md",
|
||||||
|
"description": "CPM Activity Server",
|
||||||
|
"tags": [
|
||||||
|
"activity",
|
||||||
|
"server",
|
||||||
|
"cpm"
|
||||||
|
],
|
||||||
|
"gitea_repo": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_updated": "2025-07-08T08:19:16.600227"
|
"last_updated": "2025-07-08T12:12:48.006792"
|
||||||
}
|
}
|
||||||
@ -60,4 +60,4 @@ This VPS server provides:
|
|||||||
|
|
||||||
## Connection Log
|
## Connection Log
|
||||||
|
|
||||||
- README generated on 2025-07-07 22:17:53
|
- README generated on 2025-07-08 11:14:14
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from typing import Optional, Callable, List, Dict
|
|||||||
from gui.styles import COLORS, FONTS
|
from gui.styles import COLORS, FONTS
|
||||||
from src.gitea.repository_manager import RepositoryManager
|
from src.gitea.repository_manager import RepositoryManager
|
||||||
from src.gitea.gitea_client import GiteaClient
|
from src.gitea.gitea_client import GiteaClient
|
||||||
|
import json
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -156,6 +157,22 @@ class GiteaExplorer(ctk.CTkFrame):
|
|||||||
text_color=COLORS['text_secondary']
|
text_color=COLORS['text_secondary']
|
||||||
)
|
)
|
||||||
self.status_label.pack(pady=5)
|
self.status_label.pack(pady=5)
|
||||||
|
|
||||||
|
def _get_clone_directory(self) -> Path:
|
||||||
|
"""Get clone directory from settings or use default"""
|
||||||
|
try:
|
||||||
|
settings_file = Path.home() / ".claude_project_manager" / "ui_settings.json"
|
||||||
|
if settings_file.exists():
|
||||||
|
with open(settings_file, 'r') as f:
|
||||||
|
settings = json.load(f)
|
||||||
|
clone_dir = settings.get("gitea_clone_directory")
|
||||||
|
if clone_dir:
|
||||||
|
return Path(clone_dir)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not load clone directory from settings: {e}")
|
||||||
|
|
||||||
|
# Return default if not found in settings
|
||||||
|
return Path.home() / "GiteaRepos"
|
||||||
|
|
||||||
def refresh_repositories(self):
|
def refresh_repositories(self):
|
||||||
"""Refresh repository list from Gitea - only IntelSight"""
|
"""Refresh repository list from Gitea - only IntelSight"""
|
||||||
@ -276,7 +293,8 @@ class GiteaExplorer(ctk.CTkFrame):
|
|||||||
name_label.pack(side="left", fill="x", expand=True)
|
name_label.pack(side="left", fill="x", expand=True)
|
||||||
|
|
||||||
# Clone/Status indicator
|
# Clone/Status indicator
|
||||||
local_path = Path.home() / "GiteaRepos" / repo['name']
|
clone_dir = self._get_clone_directory()
|
||||||
|
local_path = clone_dir / repo['name']
|
||||||
if local_path.exists():
|
if local_path.exists():
|
||||||
status_label = ctk.CTkLabel(
|
status_label = ctk.CTkLabel(
|
||||||
top_row,
|
top_row,
|
||||||
|
|||||||
@ -96,6 +96,9 @@ class MainWindow:
|
|||||||
# Start periodic status check after a delay
|
# Start periodic status check after a delay
|
||||||
self.root.after(30000, self.check_process_status) # Start after 30 seconds
|
self.root.after(30000, self.check_process_status) # Start after 30 seconds
|
||||||
|
|
||||||
|
# Check service status immediately on start
|
||||||
|
self.check_service_status()
|
||||||
|
|
||||||
# Initialize activity sync
|
# Initialize activity sync
|
||||||
self.init_activity_sync()
|
self.init_activity_sync()
|
||||||
|
|
||||||
@ -460,6 +463,9 @@ class MainWindow:
|
|||||||
elif project.id == "vps-docker-permanent":
|
elif project.id == "vps-docker-permanent":
|
||||||
# Handle VPS Docker
|
# Handle VPS Docker
|
||||||
self.open_vps_docker()
|
self.open_vps_docker()
|
||||||
|
elif project.id == "activity-server-permanent":
|
||||||
|
# Handle Activity Server
|
||||||
|
self.open_activity_server()
|
||||||
else:
|
else:
|
||||||
# Check if already running
|
# Check if already running
|
||||||
if self.process_tracker.is_running(project.id):
|
if self.process_tracker.is_running(project.id):
|
||||||
@ -641,6 +647,51 @@ class MainWindow:
|
|||||||
|
|
||||||
# Monitor the process
|
# Monitor the process
|
||||||
self.monitor_process("vps-docker-permanent", process)
|
self.monitor_process("vps-docker-permanent", process)
|
||||||
|
|
||||||
|
def open_activity_server(self):
|
||||||
|
"""Open Activity Server connection"""
|
||||||
|
# Check if already running
|
||||||
|
if self.process_tracker.is_running("activity-server-permanent"):
|
||||||
|
self.update_status("Activity Server connection already active", error=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create connection script for Activity Server
|
||||||
|
script = self.vps_connection.create_activity_server_script()
|
||||||
|
import tempfile
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f:
|
||||||
|
f.write(script)
|
||||||
|
script_path = f.name
|
||||||
|
|
||||||
|
# Launch terminal
|
||||||
|
import subprocess
|
||||||
|
if os.path.exists(r"C:\Windows\System32\wt.exe"):
|
||||||
|
process = subprocess.Popen([
|
||||||
|
"wt.exe", "-w", "0", "new-tab",
|
||||||
|
"--title", "CPM Activity Server",
|
||||||
|
"--", "cmd", "/c", script_path
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
process = subprocess.Popen(['cmd', '/c', 'start', 'CPM Activity Server', script_path])
|
||||||
|
|
||||||
|
# Track the process
|
||||||
|
self.process_manager.processes["activity-server-permanent"] = process
|
||||||
|
self.process_manager.save_process_data()
|
||||||
|
self.process_tracker.set_running("activity-server-permanent")
|
||||||
|
|
||||||
|
# Update tile status immediately
|
||||||
|
if "activity-server-permanent" in self.project_tiles:
|
||||||
|
self.project_tiles["activity-server-permanent"].update_status(True)
|
||||||
|
|
||||||
|
self.update_status("Connected to Activity Server")
|
||||||
|
|
||||||
|
# Update Activity Server project
|
||||||
|
activity_project = self.project_manager.get_project("activity-server-permanent")
|
||||||
|
if activity_project:
|
||||||
|
activity_project.update_last_accessed()
|
||||||
|
self.project_manager.save_projects()
|
||||||
|
|
||||||
|
# Monitor the process
|
||||||
|
self.monitor_process("activity-server-permanent", process)
|
||||||
|
|
||||||
def open_readme(self, project: Project):
|
def open_readme(self, project: Project):
|
||||||
"""Open project README"""
|
"""Open project README"""
|
||||||
@ -1200,6 +1251,54 @@ class MainWindow:
|
|||||||
# Schedule next check in 30 seconds (less frequent)
|
# Schedule next check in 30 seconds (less frequent)
|
||||||
self.root.after(30000, self.check_process_status)
|
self.root.after(30000, self.check_process_status)
|
||||||
|
|
||||||
|
# Also check service status
|
||||||
|
self.check_service_status()
|
||||||
|
|
||||||
|
def check_service_status(self):
|
||||||
|
"""Check status of Activity Server and Admin Panel"""
|
||||||
|
import threading
|
||||||
|
|
||||||
|
def check_services():
|
||||||
|
# Check Activity Server
|
||||||
|
activity_status = self.check_activity_server_status()
|
||||||
|
if "activity-server-permanent" in self.project_tiles:
|
||||||
|
self.root.after(0, lambda: self.project_tiles["activity-server-permanent"].update_service_status(activity_status))
|
||||||
|
|
||||||
|
# Check Admin Panel
|
||||||
|
admin_status = self.check_admin_panel_status()
|
||||||
|
if "admin-panel-permanent" in self.project_tiles:
|
||||||
|
self.root.after(0, lambda: self.project_tiles["admin-panel-permanent"].update_service_status(admin_status))
|
||||||
|
|
||||||
|
# Run in background thread
|
||||||
|
threading.Thread(target=check_services, daemon=True).start()
|
||||||
|
|
||||||
|
# Schedule next check in 60 seconds
|
||||||
|
self.root.after(60000, self.check_service_status)
|
||||||
|
|
||||||
|
def check_activity_server_status(self) -> bool:
|
||||||
|
"""Check if Activity Server is running"""
|
||||||
|
import requests
|
||||||
|
try:
|
||||||
|
# Activity Server runs on port 3001
|
||||||
|
# Check /api/activities endpoint - returns 401 when running (needs auth)
|
||||||
|
response = requests.get("http://91.99.192.14:3001/api/activities", timeout=5)
|
||||||
|
# 401 means server is running but needs authentication
|
||||||
|
return response.status_code in [200, 401, 403]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_admin_panel_status(self) -> bool:
|
||||||
|
"""Check if Admin Panel is accessible"""
|
||||||
|
import requests
|
||||||
|
try:
|
||||||
|
# Admin Panel runs on port 80 (not 8082)
|
||||||
|
response = requests.get("http://91.99.192.14:80/", timeout=5)
|
||||||
|
return response.status_code in [200, 301, 302] # OK or redirects
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
def open_gitea_window(self):
|
def open_gitea_window(self):
|
||||||
"""Open Gitea integration window"""
|
"""Open Gitea integration window"""
|
||||||
# Create Gitea window if not already open
|
# Create Gitea window if not already open
|
||||||
|
|||||||
@ -68,6 +68,19 @@ class ProjectTile(ctk.CTkFrame):
|
|||||||
title_row = ctk.CTkFrame(container, fg_color="transparent")
|
title_row = ctk.CTkFrame(container, fg_color="transparent")
|
||||||
title_row.pack(fill="x", pady=(0, 5))
|
title_row.pack(fill="x", pady=(0, 5))
|
||||||
|
|
||||||
|
# Service status indicator for Admin Panel and Activity Server
|
||||||
|
if self.project.id in ["admin-panel-permanent", "activity-server-permanent"]:
|
||||||
|
self.service_status_indicator = ctk.CTkLabel(
|
||||||
|
title_row,
|
||||||
|
text="⚫", # Gray circle by default
|
||||||
|
font=('Segoe UI', 10),
|
||||||
|
text_color=COLORS['text_dim'],
|
||||||
|
width=20
|
||||||
|
)
|
||||||
|
self.service_status_indicator.pack(side="left", padx=(0, 5))
|
||||||
|
# Initialize tooltip
|
||||||
|
self._update_service_tooltip("Wird geprüft...")
|
||||||
|
|
||||||
# Activity indicator (initially hidden)
|
# Activity indicator (initially hidden)
|
||||||
self.activity_indicator = ctk.CTkLabel(
|
self.activity_indicator = ctk.CTkLabel(
|
||||||
title_row,
|
title_row,
|
||||||
@ -209,8 +222,8 @@ class ProjectTile(ctk.CTkFrame):
|
|||||||
)
|
)
|
||||||
self.open_button.pack(side="left", padx=(0, 5))
|
self.open_button.pack(side="left", padx=(0, 5))
|
||||||
|
|
||||||
# CMD button only for main VPS Server tile (not Admin Panel or Docker)
|
# CMD button for VPS Server and Activity Server
|
||||||
if self.is_vps and self.project.id == "vps-permanent":
|
if self.project.id in ["vps-permanent", "activity-server-permanent"]:
|
||||||
self.cmd_button = ctk.CTkButton(
|
self.cmd_button = ctk.CTkButton(
|
||||||
button_frame,
|
button_frame,
|
||||||
text="CMD",
|
text="CMD",
|
||||||
@ -220,8 +233,8 @@ class ProjectTile(ctk.CTkFrame):
|
|||||||
)
|
)
|
||||||
self.cmd_button.pack(side="left", padx=(0, 5))
|
self.cmd_button.pack(side="left", padx=(0, 5))
|
||||||
|
|
||||||
# Gitea button (not for VPS)
|
# Gitea button and Activity button (not for VPS and not for Activity Server)
|
||||||
if not self.is_vps:
|
if not self.is_vps and self.project.id != "activity-server-permanent":
|
||||||
self.gitea_button = ctk.CTkButton(
|
self.gitea_button = ctk.CTkButton(
|
||||||
button_frame,
|
button_frame,
|
||||||
text="Gitea ▼",
|
text="Gitea ▼",
|
||||||
@ -246,8 +259,8 @@ class ProjectTile(ctk.CTkFrame):
|
|||||||
if not activity_service.is_configured() or not activity_service.connected:
|
if not activity_service.is_configured() or not activity_service.connected:
|
||||||
self.activity_button.configure(state="normal", text_color=COLORS['text_dim'])
|
self.activity_button.configure(state="normal", text_color=COLORS['text_dim'])
|
||||||
|
|
||||||
# Delete button (not for VPS) - keep it in first row
|
# Delete button (not for VPS and not for Activity Server) - keep it in first row
|
||||||
if not self.is_vps and self.on_delete:
|
if not self.is_vps and self.on_delete and self.project.id != "activity-server-permanent":
|
||||||
self.delete_button = ctk.CTkButton(
|
self.delete_button = ctk.CTkButton(
|
||||||
button_frame,
|
button_frame,
|
||||||
text="✕",
|
text="✕",
|
||||||
@ -259,8 +272,8 @@ class ProjectTile(ctk.CTkFrame):
|
|||||||
)
|
)
|
||||||
self.delete_button.pack(side="right")
|
self.delete_button.pack(side="right")
|
||||||
|
|
||||||
# Second row - Open Explorer button (only for non-VPS tiles)
|
# Second row - Open Explorer button (only for non-VPS tiles and not for Activity Server)
|
||||||
if not self.is_vps:
|
if not self.is_vps and self.project.id != "activity-server-permanent":
|
||||||
explorer_frame = ctk.CTkFrame(container, fg_color="transparent")
|
explorer_frame = ctk.CTkFrame(container, fg_color="transparent")
|
||||||
explorer_frame.pack(fill="x", pady=(5, 0))
|
explorer_frame.pack(fill="x", pady=(5, 0))
|
||||||
|
|
||||||
@ -416,7 +429,41 @@ class ProjectTile(ctk.CTkFrame):
|
|||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
# Create a temporary batch file for Windows
|
# Create a temporary batch file for Windows
|
||||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f:
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f:
|
||||||
batch_content = f'''@echo off
|
# Different content based on which tile's CMD button was pressed
|
||||||
|
if self.project.id == "activity-server-permanent":
|
||||||
|
batch_content = f'''@echo off
|
||||||
|
echo Connecting to CPM Activity Server...
|
||||||
|
echo.
|
||||||
|
echo Target Directory: /home/claude-dev/cpm-activity-server
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Try using plink if available (PuTTY command line)
|
||||||
|
where plink >nul 2>&1
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo Using PuTTY plink for connection...
|
||||||
|
echo.
|
||||||
|
echo After connection, please run:
|
||||||
|
echo cd /home/claude-dev/cpm-activity-server
|
||||||
|
echo.
|
||||||
|
plink -ssh -l {ssh_user} -pw "{ssh_pass}" {ssh_host}
|
||||||
|
) else (
|
||||||
|
echo PuTTY plink not found. Trying ssh with manual password entry...
|
||||||
|
echo.
|
||||||
|
echo Password: {ssh_pass}
|
||||||
|
echo.
|
||||||
|
echo Please copy the password above and paste it when prompted.
|
||||||
|
echo.
|
||||||
|
echo After login, run: cd /home/claude-dev/cpm-activity-server
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
ssh {ssh_user}@{ssh_host}
|
||||||
|
)
|
||||||
|
|
||||||
|
pause
|
||||||
|
'''
|
||||||
|
else:
|
||||||
|
# Regular VPS Server connection
|
||||||
|
batch_content = f'''@echo off
|
||||||
echo Connecting to VPS Server...
|
echo Connecting to VPS Server...
|
||||||
echo.
|
echo.
|
||||||
echo Please wait while establishing connection...
|
echo Please wait while establishing connection...
|
||||||
@ -715,6 +762,71 @@ pause
|
|||||||
# Update activity button if exists
|
# Update activity button if exists
|
||||||
if hasattr(self, 'activity_button'):
|
if hasattr(self, 'activity_button'):
|
||||||
self.activity_button.configure(text="▶")
|
self.activity_button.configure(text="▶")
|
||||||
|
|
||||||
|
def update_service_status(self, is_online: bool):
|
||||||
|
"""Update service status indicator"""
|
||||||
|
if hasattr(self, 'service_status_indicator'):
|
||||||
|
if is_online:
|
||||||
|
self.service_status_indicator.configure(
|
||||||
|
text="🟢",
|
||||||
|
text_color=COLORS['accent_success']
|
||||||
|
)
|
||||||
|
self._update_service_tooltip("Online")
|
||||||
|
else:
|
||||||
|
self.service_status_indicator.configure(
|
||||||
|
text="🔴",
|
||||||
|
text_color=COLORS['accent_error']
|
||||||
|
)
|
||||||
|
self._update_service_tooltip("Offline")
|
||||||
|
|
||||||
|
def _update_service_tooltip(self, status: str):
|
||||||
|
"""Update tooltip for service status"""
|
||||||
|
if hasattr(self, 'service_status_indicator'):
|
||||||
|
# Store status for tooltip
|
||||||
|
self.service_status = status
|
||||||
|
|
||||||
|
# Bind hover events if not already bound
|
||||||
|
if not hasattr(self, '_service_tooltip_bound'):
|
||||||
|
self.service_status_indicator.bind("<Enter>", self._show_service_tooltip)
|
||||||
|
self.service_status_indicator.bind("<Leave>", self._hide_service_tooltip)
|
||||||
|
self._service_tooltip_bound = True
|
||||||
|
|
||||||
|
def _show_service_tooltip(self, event):
|
||||||
|
"""Show service status tooltip"""
|
||||||
|
if hasattr(self, 'service_tooltip'):
|
||||||
|
self.service_tooltip.destroy()
|
||||||
|
|
||||||
|
self.service_tooltip = ctk.CTkToplevel(self)
|
||||||
|
self.service_tooltip.wm_overrideredirect(True)
|
||||||
|
self.service_tooltip.configure(fg_color=COLORS['bg_secondary'])
|
||||||
|
|
||||||
|
# Get status text
|
||||||
|
status_text = getattr(self, 'service_status', 'Wird geprüft...')
|
||||||
|
if status_text == "Online":
|
||||||
|
display_text = "✓ Service ist online"
|
||||||
|
elif status_text == "Offline":
|
||||||
|
display_text = "✗ Service ist offline"
|
||||||
|
else:
|
||||||
|
display_text = "⟳ Status wird geprüft..."
|
||||||
|
|
||||||
|
label = ctk.CTkLabel(
|
||||||
|
self.service_tooltip,
|
||||||
|
text=display_text,
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_primary'],
|
||||||
|
fg_color=COLORS['bg_secondary']
|
||||||
|
)
|
||||||
|
label.pack(padx=8, pady=5)
|
||||||
|
|
||||||
|
# Position tooltip
|
||||||
|
x = self.service_status_indicator.winfo_rootx()
|
||||||
|
y = self.service_status_indicator.winfo_rooty() + 25
|
||||||
|
self.service_tooltip.geometry(f"+{x}+{y}")
|
||||||
|
|
||||||
|
def _hide_service_tooltip(self, event):
|
||||||
|
"""Hide service status tooltip"""
|
||||||
|
if hasattr(self, 'service_tooltip'):
|
||||||
|
self.service_tooltip.destroy()
|
||||||
|
|
||||||
def _show_activity_tooltip(self, user_name: str):
|
def _show_activity_tooltip(self, user_name: str):
|
||||||
"""Show tooltip with active user name"""
|
"""Show tooltip with active user name"""
|
||||||
|
|||||||
@ -8,6 +8,8 @@ from gui.styles import COLORS, FONTS
|
|||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from tkinter import filedialog
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class SettingsDialog(ctk.CTkToplevel):
|
class SettingsDialog(ctk.CTkToplevel):
|
||||||
@ -19,7 +21,7 @@ class SettingsDialog(ctk.CTkToplevel):
|
|||||||
|
|
||||||
# Window setup
|
# Window setup
|
||||||
self.title("Einstellungen")
|
self.title("Einstellungen")
|
||||||
self.minsize(500, 450)
|
self.minsize(650, 550)
|
||||||
self.resizable(True, True)
|
self.resizable(True, True)
|
||||||
|
|
||||||
# Make modal
|
# Make modal
|
||||||
@ -56,96 +58,31 @@ class SettingsDialog(ctk.CTkToplevel):
|
|||||||
)
|
)
|
||||||
title_label.pack(pady=(0, 20))
|
title_label.pack(pady=(0, 20))
|
||||||
|
|
||||||
# Activity Server Section
|
# Tab view
|
||||||
activity_section = ctk.CTkFrame(main_frame, fg_color=COLORS['bg_secondary'])
|
self.tabview = ctk.CTkTabview(
|
||||||
activity_section.pack(fill="x", pady=(0, 15))
|
main_frame,
|
||||||
|
fg_color=COLORS['bg_secondary'],
|
||||||
activity_header = ctk.CTkLabel(
|
segmented_button_fg_color=COLORS['bg_tile'],
|
||||||
activity_section,
|
segmented_button_selected_color=COLORS['accent_primary'],
|
||||||
text="👥 Team-Aktivität Server",
|
segmented_button_selected_hover_color=COLORS['accent_hover'],
|
||||||
font=FONTS['body'],
|
segmented_button_unselected_color=COLORS['bg_tile'],
|
||||||
text_color=COLORS['text_primary']
|
segmented_button_unselected_hover_color=COLORS['bg_tile_hover'],
|
||||||
)
|
|
||||||
activity_header.pack(anchor="w", padx=15, pady=(10, 5))
|
|
||||||
|
|
||||||
# Server URL
|
|
||||||
url_label = ctk.CTkLabel(
|
|
||||||
activity_section,
|
|
||||||
text="Server URL:",
|
|
||||||
font=FONTS['small'],
|
|
||||||
text_color=COLORS['text_secondary']
|
|
||||||
)
|
|
||||||
url_label.pack(anchor="w", padx=30, pady=(5, 0))
|
|
||||||
|
|
||||||
self.server_url_var = ctk.StringVar(value=self.settings.get("activity_server_url", "http://91.99.192.14:3001"))
|
|
||||||
self.server_url_entry = ctk.CTkEntry(
|
|
||||||
activity_section,
|
|
||||||
textvariable=self.server_url_var,
|
|
||||||
width=300,
|
|
||||||
fg_color=COLORS['bg_primary']
|
|
||||||
)
|
|
||||||
self.server_url_entry.pack(padx=30, pady=(0, 5))
|
|
||||||
|
|
||||||
# API Key
|
|
||||||
api_label = ctk.CTkLabel(
|
|
||||||
activity_section,
|
|
||||||
text="API Key:",
|
|
||||||
font=FONTS['small'],
|
|
||||||
text_color=COLORS['text_secondary']
|
|
||||||
)
|
|
||||||
api_label.pack(anchor="w", padx=30, pady=(5, 0))
|
|
||||||
|
|
||||||
self.api_key_var = ctk.StringVar(value=self.settings.get("activity_api_key", ""))
|
|
||||||
self.api_key_entry = ctk.CTkEntry(
|
|
||||||
activity_section,
|
|
||||||
textvariable=self.api_key_var,
|
|
||||||
width=300,
|
|
||||||
fg_color=COLORS['bg_primary'],
|
|
||||||
show="*"
|
|
||||||
)
|
|
||||||
self.api_key_entry.pack(padx=30, pady=(0, 5))
|
|
||||||
|
|
||||||
# User Name
|
|
||||||
user_label = ctk.CTkLabel(
|
|
||||||
activity_section,
|
|
||||||
text="Benutzername:",
|
|
||||||
font=FONTS['small'],
|
|
||||||
text_color=COLORS['text_secondary']
|
|
||||||
)
|
|
||||||
user_label.pack(anchor="w", padx=30, pady=(5, 0))
|
|
||||||
|
|
||||||
self.user_name_var = ctk.StringVar(value=self.settings.get("activity_user_name", ""))
|
|
||||||
self.user_name_entry = ctk.CTkEntry(
|
|
||||||
activity_section,
|
|
||||||
textvariable=self.user_name_var,
|
|
||||||
width=300,
|
|
||||||
fg_color=COLORS['bg_primary']
|
|
||||||
)
|
|
||||||
self.user_name_entry.pack(padx=30, pady=(0, 5))
|
|
||||||
|
|
||||||
# Test connection button
|
|
||||||
test_btn = ctk.CTkButton(
|
|
||||||
activity_section,
|
|
||||||
text="Verbindung testen",
|
|
||||||
command=self.test_activity_connection,
|
|
||||||
fg_color=COLORS['bg_tile'],
|
|
||||||
hover_color=COLORS['bg_tile_hover'],
|
|
||||||
text_color=COLORS['text_primary'],
|
text_color=COLORS['text_primary'],
|
||||||
width=150
|
text_color_disabled=COLORS['text_dim']
|
||||||
)
|
)
|
||||||
test_btn.pack(pady=(5, 10))
|
self.tabview.pack(fill="both", expand=True)
|
||||||
|
|
||||||
# Status label
|
# Add tabs
|
||||||
self.connection_status_label = ctk.CTkLabel(
|
self.tabview.add("Allgemein")
|
||||||
activity_section,
|
self.tabview.add("Gitea")
|
||||||
text="",
|
self.tabview.add("Team-Aktivität")
|
||||||
font=FONTS['small'],
|
|
||||||
text_color=COLORS['text_secondary'],
|
|
||||||
height=20
|
|
||||||
)
|
|
||||||
self.connection_status_label.pack(pady=(0, 10))
|
|
||||||
|
|
||||||
# Buttons
|
# Setup each tab
|
||||||
|
self.setup_general_tab()
|
||||||
|
self.setup_gitea_tab()
|
||||||
|
self.setup_activity_tab()
|
||||||
|
|
||||||
|
# Buttons (at bottom of main frame)
|
||||||
button_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
|
button_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
|
||||||
button_frame.pack(fill="x", pady=(20, 0))
|
button_frame.pack(fill="x", pady=(20, 0))
|
||||||
|
|
||||||
@ -184,6 +121,264 @@ class SettingsDialog(ctk.CTkToplevel):
|
|||||||
)
|
)
|
||||||
save_btn.pack(side="right", padx=(5, 0))
|
save_btn.pack(side="right", padx=(5, 0))
|
||||||
|
|
||||||
|
def setup_general_tab(self):
|
||||||
|
"""Setup general settings tab"""
|
||||||
|
tab = self.tabview.tab("Allgemein")
|
||||||
|
|
||||||
|
# Container with padding
|
||||||
|
container = ctk.CTkFrame(tab, fg_color="transparent")
|
||||||
|
container.pack(fill="both", expand=True, padx=20, pady=20)
|
||||||
|
|
||||||
|
# Placeholder for future general settings
|
||||||
|
info_label = ctk.CTkLabel(
|
||||||
|
container,
|
||||||
|
text="Allgemeine Einstellungen werden hier angezeigt.\n(Aktuell keine verfügbar)",
|
||||||
|
font=FONTS['body'],
|
||||||
|
text_color=COLORS['text_dim']
|
||||||
|
)
|
||||||
|
info_label.pack(pady=50)
|
||||||
|
|
||||||
|
def setup_gitea_tab(self):
|
||||||
|
"""Setup Gitea settings tab"""
|
||||||
|
tab = self.tabview.tab("Gitea")
|
||||||
|
|
||||||
|
# Scrollable container
|
||||||
|
container = ctk.CTkScrollableFrame(tab, fg_color="transparent")
|
||||||
|
container.pack(fill="both", expand=True, padx=20, pady=20)
|
||||||
|
|
||||||
|
# Clone Directory Section
|
||||||
|
clone_section = ctk.CTkFrame(container, fg_color=COLORS['bg_tile'])
|
||||||
|
clone_section.pack(fill="x", pady=(0, 15))
|
||||||
|
|
||||||
|
clone_header = ctk.CTkLabel(
|
||||||
|
clone_section,
|
||||||
|
text="📁 Clone-Verzeichnis",
|
||||||
|
font=FONTS['body'],
|
||||||
|
text_color=COLORS['text_primary']
|
||||||
|
)
|
||||||
|
clone_header.pack(anchor="w", padx=15, pady=(10, 5))
|
||||||
|
|
||||||
|
# Clone directory path
|
||||||
|
clone_path_frame = ctk.CTkFrame(clone_section, fg_color="transparent")
|
||||||
|
clone_path_frame.pack(fill="x", padx=30, pady=(5, 10))
|
||||||
|
|
||||||
|
default_clone_dir = str(Path.home() / "GiteaRepos")
|
||||||
|
self.clone_dir_var = ctk.StringVar(value=self.settings.get("gitea_clone_directory", default_clone_dir))
|
||||||
|
|
||||||
|
self.clone_dir_entry = ctk.CTkEntry(
|
||||||
|
clone_path_frame,
|
||||||
|
textvariable=self.clone_dir_var,
|
||||||
|
width=350,
|
||||||
|
fg_color=COLORS['bg_primary']
|
||||||
|
)
|
||||||
|
self.clone_dir_entry.pack(side="left", padx=(0, 10))
|
||||||
|
|
||||||
|
browse_btn = ctk.CTkButton(
|
||||||
|
clone_path_frame,
|
||||||
|
text="Durchsuchen...",
|
||||||
|
command=self.browse_clone_directory,
|
||||||
|
fg_color=COLORS['bg_secondary'],
|
||||||
|
hover_color=COLORS['bg_tile_hover'],
|
||||||
|
text_color=COLORS['text_primary'],
|
||||||
|
width=100
|
||||||
|
)
|
||||||
|
browse_btn.pack(side="left")
|
||||||
|
|
||||||
|
# Info label
|
||||||
|
info_label = ctk.CTkLabel(
|
||||||
|
clone_section,
|
||||||
|
text="Standardverzeichnis für geklonte Repositories",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_dim']
|
||||||
|
)
|
||||||
|
info_label.pack(anchor="w", padx=30, pady=(0, 10))
|
||||||
|
|
||||||
|
# Server Configuration Section
|
||||||
|
server_section = ctk.CTkFrame(container, fg_color=COLORS['bg_tile'])
|
||||||
|
server_section.pack(fill="x", pady=(0, 15))
|
||||||
|
|
||||||
|
server_header = ctk.CTkLabel(
|
||||||
|
server_section,
|
||||||
|
text="🌐 Gitea Server",
|
||||||
|
font=FONTS['body'],
|
||||||
|
text_color=COLORS['text_primary']
|
||||||
|
)
|
||||||
|
server_header.pack(anchor="w", padx=15, pady=(10, 5))
|
||||||
|
|
||||||
|
# Server URL
|
||||||
|
url_label = ctk.CTkLabel(
|
||||||
|
server_section,
|
||||||
|
text="Server URL:",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary']
|
||||||
|
)
|
||||||
|
url_label.pack(anchor="w", padx=30, pady=(5, 0))
|
||||||
|
|
||||||
|
self.gitea_url_var = ctk.StringVar(value=self.settings.get("gitea_server_url", "https://gitea-undso.intelsight.de"))
|
||||||
|
self.gitea_url_entry = ctk.CTkEntry(
|
||||||
|
server_section,
|
||||||
|
textvariable=self.gitea_url_var,
|
||||||
|
width=350,
|
||||||
|
fg_color=COLORS['bg_primary']
|
||||||
|
)
|
||||||
|
self.gitea_url_entry.pack(padx=30, pady=(0, 5))
|
||||||
|
|
||||||
|
# API Token
|
||||||
|
token_label = ctk.CTkLabel(
|
||||||
|
server_section,
|
||||||
|
text="API Token:",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary']
|
||||||
|
)
|
||||||
|
token_label.pack(anchor="w", padx=30, pady=(5, 0))
|
||||||
|
|
||||||
|
self.gitea_token_var = ctk.StringVar(value=self.settings.get("gitea_api_token", ""))
|
||||||
|
self.gitea_token_entry = ctk.CTkEntry(
|
||||||
|
server_section,
|
||||||
|
textvariable=self.gitea_token_var,
|
||||||
|
width=350,
|
||||||
|
fg_color=COLORS['bg_primary'],
|
||||||
|
show="*"
|
||||||
|
)
|
||||||
|
self.gitea_token_entry.pack(padx=30, pady=(0, 5))
|
||||||
|
|
||||||
|
# Username
|
||||||
|
user_label = ctk.CTkLabel(
|
||||||
|
server_section,
|
||||||
|
text="Benutzername:",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary']
|
||||||
|
)
|
||||||
|
user_label.pack(anchor="w", padx=30, pady=(5, 0))
|
||||||
|
|
||||||
|
self.gitea_user_var = ctk.StringVar(value=self.settings.get("gitea_username", ""))
|
||||||
|
self.gitea_user_entry = ctk.CTkEntry(
|
||||||
|
server_section,
|
||||||
|
textvariable=self.gitea_user_var,
|
||||||
|
width=350,
|
||||||
|
fg_color=COLORS['bg_primary']
|
||||||
|
)
|
||||||
|
self.gitea_user_entry.pack(padx=30, pady=(0, 10))
|
||||||
|
|
||||||
|
# Test connection button
|
||||||
|
test_btn = ctk.CTkButton(
|
||||||
|
server_section,
|
||||||
|
text="Verbindung testen",
|
||||||
|
command=self.test_gitea_connection,
|
||||||
|
fg_color=COLORS['bg_secondary'],
|
||||||
|
hover_color=COLORS['bg_tile_hover'],
|
||||||
|
text_color=COLORS['text_primary'],
|
||||||
|
width=150
|
||||||
|
)
|
||||||
|
test_btn.pack(pady=(5, 10))
|
||||||
|
|
||||||
|
# Status label
|
||||||
|
self.gitea_status_label = ctk.CTkLabel(
|
||||||
|
server_section,
|
||||||
|
text="",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary'],
|
||||||
|
height=20
|
||||||
|
)
|
||||||
|
self.gitea_status_label.pack(pady=(0, 10))
|
||||||
|
|
||||||
|
def setup_activity_tab(self):
|
||||||
|
"""Setup activity server settings tab"""
|
||||||
|
tab = self.tabview.tab("Team-Aktivität")
|
||||||
|
|
||||||
|
# Container with padding
|
||||||
|
container = ctk.CTkFrame(tab, fg_color="transparent")
|
||||||
|
container.pack(fill="both", expand=True, padx=20, pady=20)
|
||||||
|
|
||||||
|
# Activity Server Section
|
||||||
|
activity_section = ctk.CTkFrame(container, fg_color=COLORS['bg_tile'])
|
||||||
|
activity_section.pack(fill="x", pady=(0, 15))
|
||||||
|
|
||||||
|
activity_header = ctk.CTkLabel(
|
||||||
|
activity_section,
|
||||||
|
text="👥 Team-Aktivität Server",
|
||||||
|
font=FONTS['body'],
|
||||||
|
text_color=COLORS['text_primary']
|
||||||
|
)
|
||||||
|
activity_header.pack(anchor="w", padx=15, pady=(10, 5))
|
||||||
|
|
||||||
|
# Server URL
|
||||||
|
url_label = ctk.CTkLabel(
|
||||||
|
activity_section,
|
||||||
|
text="Server URL:",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary']
|
||||||
|
)
|
||||||
|
url_label.pack(anchor="w", padx=30, pady=(5, 0))
|
||||||
|
|
||||||
|
self.server_url_var = ctk.StringVar(value=self.settings.get("activity_server_url", "http://91.99.192.14:3001"))
|
||||||
|
self.server_url_entry = ctk.CTkEntry(
|
||||||
|
activity_section,
|
||||||
|
textvariable=self.server_url_var,
|
||||||
|
width=350,
|
||||||
|
fg_color=COLORS['bg_primary']
|
||||||
|
)
|
||||||
|
self.server_url_entry.pack(padx=30, pady=(0, 5))
|
||||||
|
|
||||||
|
# API Key
|
||||||
|
api_label = ctk.CTkLabel(
|
||||||
|
activity_section,
|
||||||
|
text="API Key:",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary']
|
||||||
|
)
|
||||||
|
api_label.pack(anchor="w", padx=30, pady=(5, 0))
|
||||||
|
|
||||||
|
self.api_key_var = ctk.StringVar(value=self.settings.get("activity_api_key", ""))
|
||||||
|
self.api_key_entry = ctk.CTkEntry(
|
||||||
|
activity_section,
|
||||||
|
textvariable=self.api_key_var,
|
||||||
|
width=350,
|
||||||
|
fg_color=COLORS['bg_primary'],
|
||||||
|
show="*"
|
||||||
|
)
|
||||||
|
self.api_key_entry.pack(padx=30, pady=(0, 5))
|
||||||
|
|
||||||
|
# User Name
|
||||||
|
user_label = ctk.CTkLabel(
|
||||||
|
activity_section,
|
||||||
|
text="Benutzername:",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary']
|
||||||
|
)
|
||||||
|
user_label.pack(anchor="w", padx=30, pady=(5, 0))
|
||||||
|
|
||||||
|
self.user_name_var = ctk.StringVar(value=self.settings.get("activity_user_name", ""))
|
||||||
|
self.user_name_entry = ctk.CTkEntry(
|
||||||
|
activity_section,
|
||||||
|
textvariable=self.user_name_var,
|
||||||
|
width=350,
|
||||||
|
fg_color=COLORS['bg_primary']
|
||||||
|
)
|
||||||
|
self.user_name_entry.pack(padx=30, pady=(0, 5))
|
||||||
|
|
||||||
|
# Test connection button
|
||||||
|
test_btn = ctk.CTkButton(
|
||||||
|
activity_section,
|
||||||
|
text="Verbindung testen",
|
||||||
|
command=self.test_activity_connection,
|
||||||
|
fg_color=COLORS['bg_secondary'],
|
||||||
|
hover_color=COLORS['bg_tile_hover'],
|
||||||
|
text_color=COLORS['text_primary'],
|
||||||
|
width=150
|
||||||
|
)
|
||||||
|
test_btn.pack(pady=(5, 10))
|
||||||
|
|
||||||
|
# Status label
|
||||||
|
self.connection_status_label = ctk.CTkLabel(
|
||||||
|
activity_section,
|
||||||
|
text="",
|
||||||
|
font=FONTS['small'],
|
||||||
|
text_color=COLORS['text_secondary'],
|
||||||
|
height=20
|
||||||
|
)
|
||||||
|
self.connection_status_label.pack(pady=(0, 10))
|
||||||
|
|
||||||
def load_settings(self):
|
def load_settings(self):
|
||||||
"""Load settings from file"""
|
"""Load settings from file"""
|
||||||
try:
|
try:
|
||||||
@ -205,49 +400,141 @@ class SettingsDialog(ctk.CTkToplevel):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to save UI settings: {e}")
|
logger.error(f"Failed to save UI settings: {e}")
|
||||||
|
|
||||||
|
def browse_clone_directory(self):
|
||||||
|
"""Browse for clone directory"""
|
||||||
|
current_dir = self.clone_dir_var.get()
|
||||||
|
if not os.path.exists(current_dir):
|
||||||
|
current_dir = str(Path.home())
|
||||||
|
|
||||||
|
directory = filedialog.askdirectory(
|
||||||
|
title="Clone-Verzeichnis auswählen",
|
||||||
|
initialdir=current_dir,
|
||||||
|
parent=self
|
||||||
|
)
|
||||||
|
|
||||||
|
if directory:
|
||||||
|
self.clone_dir_var.set(directory)
|
||||||
|
logger.info(f"Selected clone directory: {directory}")
|
||||||
|
|
||||||
|
def test_gitea_connection(self):
|
||||||
|
"""Test connection to Gitea server"""
|
||||||
|
import requests
|
||||||
|
|
||||||
|
server_url = self.gitea_url_var.get().strip()
|
||||||
|
api_token = self.gitea_token_var.get().strip()
|
||||||
|
|
||||||
|
if not server_url:
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text="⚠️ Bitte Server URL eingeben",
|
||||||
|
text_color=COLORS['accent_warning']
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text="🔄 Teste Verbindung...",
|
||||||
|
text_color=COLORS['text_secondary']
|
||||||
|
)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to connect to the Gitea API
|
||||||
|
headers = {}
|
||||||
|
if api_token:
|
||||||
|
headers["Authorization"] = f"token {api_token}"
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
f"{server_url}/api/v1/version",
|
||||||
|
timeout=5,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
version = response.json().get('version', 'Unknown')
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text=f"✅ Verbindung erfolgreich! (Gitea {version})",
|
||||||
|
text_color=COLORS['accent_success']
|
||||||
|
)
|
||||||
|
logger.info(f"Gitea connection successful: {server_url}, version: {version}")
|
||||||
|
else:
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text=f"❌ Server antwortet mit Status {response.status_code}",
|
||||||
|
text_color=COLORS['accent_error']
|
||||||
|
)
|
||||||
|
logger.warning(f"Gitea server returned status {response.status_code}")
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text="❌ Server nicht erreichbar",
|
||||||
|
text_color=COLORS['accent_error']
|
||||||
|
)
|
||||||
|
logger.error(f"Gitea server not reachable: {server_url}")
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text="❌ Verbindung Timeout",
|
||||||
|
text_color=COLORS['accent_error']
|
||||||
|
)
|
||||||
|
logger.error(f"Gitea server connection timeout: {server_url}")
|
||||||
|
except Exception as e:
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text=f"❌ Fehler: {str(e)}",
|
||||||
|
text_color=COLORS['accent_error']
|
||||||
|
)
|
||||||
|
logger.error(f"Gitea server connection error: {e}")
|
||||||
|
|
||||||
def save_settings_only(self):
|
def save_settings_only(self):
|
||||||
"""Save settings without applying to service or closing dialog"""
|
"""Save settings without applying to service or closing dialog"""
|
||||||
# Get values
|
# Get activity values
|
||||||
server_url = self.server_url_var.get().strip()
|
server_url = self.server_url_var.get().strip()
|
||||||
api_key = self.api_key_var.get().strip()
|
api_key = self.api_key_var.get().strip()
|
||||||
user_name = self.user_name_var.get().strip()
|
user_name = self.user_name_var.get().strip()
|
||||||
|
|
||||||
|
# Get Gitea values
|
||||||
|
gitea_url = self.gitea_url_var.get().strip()
|
||||||
|
gitea_token = self.gitea_token_var.get().strip()
|
||||||
|
gitea_user = self.gitea_user_var.get().strip()
|
||||||
|
clone_dir = self.clone_dir_var.get().strip()
|
||||||
|
|
||||||
# Update settings
|
# Update settings
|
||||||
self.settings["activity_server_url"] = server_url
|
self.settings["activity_server_url"] = server_url
|
||||||
self.settings["activity_api_key"] = api_key
|
self.settings["activity_api_key"] = api_key
|
||||||
self.settings["activity_user_name"] = user_name
|
self.settings["activity_user_name"] = user_name
|
||||||
|
|
||||||
|
self.settings["gitea_server_url"] = gitea_url
|
||||||
|
self.settings["gitea_api_token"] = gitea_token
|
||||||
|
self.settings["gitea_username"] = gitea_user
|
||||||
|
self.settings["gitea_clone_directory"] = clone_dir
|
||||||
|
|
||||||
self.save_settings()
|
self.save_settings()
|
||||||
|
|
||||||
# Show confirmation
|
# Show confirmation on the active tab
|
||||||
self.connection_status_label.configure(
|
current_tab = self.tabview.get()
|
||||||
text="✅ Einstellungen gespeichert!",
|
if current_tab == "Team-Aktivität":
|
||||||
text_color=COLORS['accent_success']
|
self.connection_status_label.configure(
|
||||||
)
|
text="✅ Einstellungen gespeichert!",
|
||||||
logger.info("Settings saved successfully")
|
text_color=COLORS['accent_success']
|
||||||
|
)
|
||||||
|
self.after(2000, lambda: self.connection_status_label.configure(text=""))
|
||||||
|
elif current_tab == "Gitea":
|
||||||
|
self.gitea_status_label.configure(
|
||||||
|
text="✅ Einstellungen gespeichert!",
|
||||||
|
text_color=COLORS['accent_success']
|
||||||
|
)
|
||||||
|
self.after(2000, lambda: self.gitea_status_label.configure(text=""))
|
||||||
|
|
||||||
# Clear status after 2 seconds
|
logger.info("Settings saved successfully")
|
||||||
self.after(2000, lambda: self.connection_status_label.configure(text=""))
|
|
||||||
|
|
||||||
def apply_settings(self):
|
def apply_settings(self):
|
||||||
"""Apply the selected settings"""
|
"""Apply the selected settings"""
|
||||||
import uuid
|
import uuid
|
||||||
from services.activity_sync import activity_service
|
from services.activity_sync import activity_service
|
||||||
|
|
||||||
# Get values
|
# Save all settings first
|
||||||
server_url = self.server_url_var.get().strip()
|
self.save_settings_only()
|
||||||
api_key = self.api_key_var.get().strip()
|
|
||||||
user_name = self.user_name_var.get().strip()
|
|
||||||
|
|
||||||
# Update settings
|
# Apply activity service settings
|
||||||
self.settings["activity_server_url"] = server_url
|
activity_service.server_url = self.settings.get("activity_server_url", "")
|
||||||
self.settings["activity_api_key"] = api_key
|
activity_service.api_key = self.settings.get("activity_api_key", "")
|
||||||
self.settings["activity_user_name"] = user_name
|
activity_service.user_name = self.settings.get("activity_user_name", "")
|
||||||
self.save_settings()
|
|
||||||
|
|
||||||
# Update activity service
|
|
||||||
activity_service.server_url = server_url
|
|
||||||
activity_service.api_key = api_key
|
|
||||||
activity_service.user_name = user_name
|
|
||||||
activity_service.user_id = self.settings.get("activity_user_id", str(uuid.uuid4()))
|
activity_service.user_id = self.settings.get("activity_user_id", str(uuid.uuid4()))
|
||||||
|
|
||||||
# Save user ID if newly generated
|
# Save user ID if newly generated
|
||||||
@ -267,9 +554,65 @@ class SettingsDialog(ctk.CTkToplevel):
|
|||||||
|
|
||||||
logger.info("Activity server settings applied")
|
logger.info("Activity server settings applied")
|
||||||
|
|
||||||
|
# Apply Gitea settings to config
|
||||||
|
self.update_gitea_config()
|
||||||
|
|
||||||
# Close dialog
|
# Close dialog
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
def update_gitea_config(self):
|
||||||
|
"""Update Gitea configuration in the application"""
|
||||||
|
try:
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from src.gitea.gitea_client import gitea_config
|
||||||
|
from src.gitea.git_operations import git_ops
|
||||||
|
|
||||||
|
# Update Gitea config
|
||||||
|
if hasattr(gitea_config, 'base_url'):
|
||||||
|
gitea_config.base_url = self.settings.get("gitea_server_url", gitea_config.base_url)
|
||||||
|
if hasattr(gitea_config, 'api_token'):
|
||||||
|
gitea_config.api_token = self.settings.get("gitea_api_token", gitea_config.api_token)
|
||||||
|
if hasattr(gitea_config, 'username'):
|
||||||
|
gitea_config.username = self.settings.get("gitea_username", gitea_config.username)
|
||||||
|
|
||||||
|
# Update clone directory
|
||||||
|
clone_dir = self.settings.get("gitea_clone_directory")
|
||||||
|
if clone_dir:
|
||||||
|
# Re-initialize git_ops to use new settings
|
||||||
|
from src.gitea.git_operations import init_git_ops
|
||||||
|
init_git_ops()
|
||||||
|
|
||||||
|
# Now update the clone directory
|
||||||
|
if hasattr(git_ops, 'default_clone_dir'):
|
||||||
|
git_ops.default_clone_dir = Path(clone_dir)
|
||||||
|
logger.info(f"Updated git_ops clone directory to: {clone_dir}")
|
||||||
|
|
||||||
|
# Update all existing RepositoryManager instances
|
||||||
|
# This ensures that any git_ops instances in the application get the new directory
|
||||||
|
try:
|
||||||
|
# Update in main window if it exists
|
||||||
|
parent = self.master
|
||||||
|
if hasattr(parent, 'gitea_handler') and hasattr(parent.gitea_handler, 'repo_manager'):
|
||||||
|
if hasattr(parent.gitea_handler.repo_manager, 'git_ops'):
|
||||||
|
parent.gitea_handler.repo_manager.git_ops.default_clone_dir = Path(clone_dir)
|
||||||
|
logger.info("Updated main window's git_ops clone directory")
|
||||||
|
|
||||||
|
# Update in sidebar gitea explorer if it exists
|
||||||
|
if hasattr(parent, 'sidebar') and hasattr(parent.sidebar, 'gitea_explorer'):
|
||||||
|
if hasattr(parent.sidebar.gitea_explorer, 'repo_manager'):
|
||||||
|
if hasattr(parent.sidebar.gitea_explorer.repo_manager, 'git_ops'):
|
||||||
|
parent.sidebar.gitea_explorer.repo_manager.git_ops.default_clone_dir = Path(clone_dir)
|
||||||
|
logger.info("Updated sidebar's git_ops clone directory")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not update all git_ops instances: {e}")
|
||||||
|
|
||||||
|
logger.info("Gitea configuration updated")
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
logger.warning(f"Could not update Gitea config: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating Gitea config: {e}")
|
||||||
|
|
||||||
def test_activity_connection(self):
|
def test_activity_connection(self):
|
||||||
"""Test connection to activity server"""
|
"""Test connection to activity server"""
|
||||||
import requests
|
import requests
|
||||||
|
|||||||
@ -58,10 +58,12 @@ class ProjectManager:
|
|||||||
self.projects: Dict[str, Project] = {}
|
self.projects: Dict[str, Project] = {}
|
||||||
self.vps_project = None
|
self.vps_project = None
|
||||||
self.admin_panel_project = None
|
self.admin_panel_project = None
|
||||||
|
self.activity_server_project = None
|
||||||
self._ensure_data_dir()
|
self._ensure_data_dir()
|
||||||
self.load_projects()
|
self.load_projects()
|
||||||
self._initialize_vps_project()
|
self._initialize_vps_project()
|
||||||
self._initialize_admin_panel_project()
|
self._initialize_admin_panel_project()
|
||||||
|
self._initialize_activity_server_project()
|
||||||
|
|
||||||
def _ensure_data_dir(self):
|
def _ensure_data_dir(self):
|
||||||
"""Ensure data directory exists"""
|
"""Ensure data directory exists"""
|
||||||
@ -101,6 +103,22 @@ class ProjectManager:
|
|||||||
else:
|
else:
|
||||||
self.admin_panel_project = self.projects[admin_id]
|
self.admin_panel_project = self.projects[admin_id]
|
||||||
|
|
||||||
|
def _initialize_activity_server_project(self):
|
||||||
|
"""Initialize the permanent Activity Server project"""
|
||||||
|
activity_id = "activity-server-permanent"
|
||||||
|
if activity_id not in self.projects:
|
||||||
|
self.activity_server_project = Project(
|
||||||
|
name="Activity Server",
|
||||||
|
path="/home/claude-dev/cpm-activity-server",
|
||||||
|
project_id=activity_id
|
||||||
|
)
|
||||||
|
self.activity_server_project.description = "CPM Activity Server"
|
||||||
|
self.activity_server_project.tags = ["activity", "server", "cpm"]
|
||||||
|
self.projects[activity_id] = self.activity_server_project
|
||||||
|
self.save_projects()
|
||||||
|
else:
|
||||||
|
self.activity_server_project = self.projects[activity_id]
|
||||||
|
|
||||||
def load_projects(self):
|
def load_projects(self):
|
||||||
"""Load projects from JSON file"""
|
"""Load projects from JSON file"""
|
||||||
logger.info("Loading projects from file")
|
logger.info("Loading projects from file")
|
||||||
@ -162,12 +180,13 @@ class ProjectManager:
|
|||||||
def get_all_projects(self) -> List[Project]:
|
def get_all_projects(self) -> List[Project]:
|
||||||
"""Get all projects sorted alphabetically by name"""
|
"""Get all projects sorted alphabetically by name"""
|
||||||
projects = list(self.projects.values())
|
projects = list(self.projects.values())
|
||||||
# Sort alphabetically, but keep VPS and Admin Panel first
|
# Sort alphabetically, but keep VPS projects first
|
||||||
vps = [p for p in projects if p.id == "vps-permanent"]
|
vps = [p for p in projects if p.id == "vps-permanent"]
|
||||||
admin = [p for p in projects if p.id == "admin-panel-permanent"]
|
admin = [p for p in projects if p.id == "admin-panel-permanent"]
|
||||||
others = [p for p in projects if p.id not in ["vps-permanent", "admin-panel-permanent"]]
|
activity = [p for p in projects if p.id == "activity-server-permanent"]
|
||||||
|
others = [p for p in projects if p.id not in ["vps-permanent", "admin-panel-permanent", "activity-server-permanent"]]
|
||||||
others.sort(key=lambda p: p.name.lower()) # Sort alphabetically by name (case-insensitive)
|
others.sort(key=lambda p: p.name.lower()) # Sort alphabetically by name (case-insensitive)
|
||||||
return vps + admin + others
|
return vps + admin + activity + others
|
||||||
|
|
||||||
def update_project(self, project_id: str, **kwargs):
|
def update_project(self, project_id: str, **kwargs):
|
||||||
"""Update project properties"""
|
"""Update project properties"""
|
||||||
|
|||||||
72
setup_activity_server_profile.sh
Normale Datei
72
setup_activity_server_profile.sh
Normale Datei
@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Setup script to configure automatic directory change for Activity Server SSH connections
|
||||||
|
|
||||||
|
echo "Activity Server Profile Setup Script"
|
||||||
|
echo "===================================="
|
||||||
|
echo ""
|
||||||
|
echo "This script will configure the server to automatically navigate to"
|
||||||
|
echo "/home/claude-dev/cpm-activity-server when connecting via SSH."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# SSH connection details
|
||||||
|
SSH_HOST="91.99.192.14"
|
||||||
|
SSH_USER="claude-dev"
|
||||||
|
SSH_PASS="z0E1Al}q2H?Yqd!O"
|
||||||
|
|
||||||
|
# Create the profile modification script
|
||||||
|
cat << 'PROFILE_SCRIPT' > /tmp/setup_profile.sh
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Add to .bashrc to detect SSH connections and change directory
|
||||||
|
echo "" >> ~/.bashrc
|
||||||
|
echo "# Auto-navigate to Activity Server directory for SSH connections" >> ~/.bashrc
|
||||||
|
echo "if [ -n \"\$SSH_CLIENT\" ] || [ -n \"\$SSH_TTY\" ]; then" >> ~/.bashrc
|
||||||
|
echo " # Check if we're in an SSH session" >> ~/.bashrc
|
||||||
|
echo " if [ -d \"/home/claude-dev/cpm-activity-server\" ]; then" >> ~/.bashrc
|
||||||
|
echo " cd /home/claude-dev/cpm-activity-server" >> ~/.bashrc
|
||||||
|
echo " echo \"Automatically changed to Activity Server directory\"" >> ~/.bashrc
|
||||||
|
echo " echo \"\"" >> ~/.bashrc
|
||||||
|
echo " fi" >> ~/.bashrc
|
||||||
|
echo "fi" >> ~/.bashrc
|
||||||
|
|
||||||
|
echo "Profile updated successfully!"
|
||||||
|
PROFILE_SCRIPT
|
||||||
|
|
||||||
|
# Make the script executable
|
||||||
|
chmod +x /tmp/setup_profile.sh
|
||||||
|
|
||||||
|
echo "Connecting to server to apply configuration..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Try different methods to connect and run the script
|
||||||
|
if command -v sshpass >/dev/null 2>&1; then
|
||||||
|
echo "Using sshpass..."
|
||||||
|
sshpass -p "$SSH_PASS" ssh -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST 'bash -s' < /tmp/setup_profile.sh
|
||||||
|
elif command -v expect >/dev/null 2>&1; then
|
||||||
|
echo "Using expect..."
|
||||||
|
expect << EOF
|
||||||
|
spawn ssh -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST
|
||||||
|
expect "password:"
|
||||||
|
send "$SSH_PASS\r"
|
||||||
|
expect "$ "
|
||||||
|
send "bash < /tmp/setup_profile.sh\r"
|
||||||
|
expect "$ "
|
||||||
|
send "exit\r"
|
||||||
|
expect eof
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
echo "Neither sshpass nor expect found."
|
||||||
|
echo ""
|
||||||
|
echo "Please manually run the following on the server:"
|
||||||
|
echo "1. SSH to $SSH_USER@$SSH_HOST"
|
||||||
|
echo "2. Run the commands in /tmp/setup_profile.sh"
|
||||||
|
echo ""
|
||||||
|
echo "Or install sshpass: sudo apt-get install sshpass"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -f /tmp/setup_profile.sh
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Setup complete! Future SSH connections will automatically"
|
||||||
|
echo "navigate to the Activity Server directory."
|
||||||
@ -8,6 +8,7 @@ import tkinter as tk
|
|||||||
from tkinter import filedialog
|
from tkinter import filedialog
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
|
import json
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GitCredentials:
|
class GitCredentials:
|
||||||
@ -25,7 +26,30 @@ class GitOperationsManager:
|
|||||||
def __init__(self, base_url: str, token: str, username: str = "claude-project-manager"):
|
def __init__(self, base_url: str, token: str, username: str = "claude-project-manager"):
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
self.credentials = GitCredentials(username=username, token=token)
|
self.credentials = GitCredentials(username=username, token=token)
|
||||||
self.default_clone_dir = Path.home() / "GiteaRepos"
|
self.default_clone_dir = self._get_clone_directory()
|
||||||
|
|
||||||
|
def _get_clone_directory(self) -> Path:
|
||||||
|
"""Get clone directory from settings or use default"""
|
||||||
|
try:
|
||||||
|
settings_file = Path.home() / ".claude_project_manager" / "ui_settings.json"
|
||||||
|
if settings_file.exists():
|
||||||
|
with open(settings_file, 'r') as f:
|
||||||
|
settings = json.load(f)
|
||||||
|
clone_dir = settings.get("gitea_clone_directory")
|
||||||
|
if clone_dir:
|
||||||
|
logger.info(f"Loaded clone directory from settings: {clone_dir}")
|
||||||
|
return Path(clone_dir)
|
||||||
|
else:
|
||||||
|
logger.info("No gitea_clone_directory in settings")
|
||||||
|
else:
|
||||||
|
logger.info("Settings file does not exist")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not load clone directory from settings: {e}")
|
||||||
|
|
||||||
|
# Return default if not found in settings
|
||||||
|
default_dir = Path.home() / "GiteaRepos"
|
||||||
|
logger.info(f"Using default clone directory: {default_dir}")
|
||||||
|
return default_dir
|
||||||
|
|
||||||
def select_directory(self, title: str = "Select Clone Directory") -> Optional[Path]:
|
def select_directory(self, title: str = "Select Clone Directory") -> Optional[Path]:
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
@ -95,7 +119,10 @@ class GitOperationsManager:
|
|||||||
if clone_dir is None:
|
if clone_dir is None:
|
||||||
# Use default clone directory if none specified
|
# Use default clone directory if none specified
|
||||||
clone_dir = self.default_clone_dir
|
clone_dir = self.default_clone_dir
|
||||||
|
logger.info(f"Using default clone directory: {clone_dir}")
|
||||||
clone_dir.mkdir(parents=True, exist_ok=True)
|
clone_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
else:
|
||||||
|
logger.info(f"Using provided clone directory: {clone_dir}")
|
||||||
|
|
||||||
repo_path = f"{owner}/{repo}"
|
repo_path = f"{owner}/{repo}"
|
||||||
remote_url = self.credentials.get_remote_url(self.base_url, repo_path)
|
remote_url = self.credentials.get_remote_url(self.base_url, repo_path)
|
||||||
@ -616,4 +643,25 @@ class GitOperationsManager:
|
|||||||
cmd = ["git", "add", file_path]
|
cmd = ["git", "add", file_path]
|
||||||
self._run_git_command(cmd, cwd=repo_path)
|
self._run_git_command(cmd, cwd=repo_path)
|
||||||
|
|
||||||
return True, "Dateien wurden zu Git LFS migriert. Bitte committen Sie die Änderungen."
|
return True, "Dateien wurden zu Git LFS migriert. Bitte committen Sie die Änderungen."
|
||||||
|
|
||||||
|
# Create global instance function
|
||||||
|
def create_git_ops():
|
||||||
|
"""Create a new GitOperationsManager instance with current config"""
|
||||||
|
from src.gitea.gitea_client import gitea_config
|
||||||
|
return GitOperationsManager(
|
||||||
|
base_url=gitea_config.base_url,
|
||||||
|
token=gitea_config.api_token,
|
||||||
|
username=gitea_config.username
|
||||||
|
)
|
||||||
|
|
||||||
|
# Global instance
|
||||||
|
git_ops = None
|
||||||
|
|
||||||
|
def init_git_ops():
|
||||||
|
"""Initialize the global git_ops instance"""
|
||||||
|
global git_ops
|
||||||
|
git_ops = create_git_ops()
|
||||||
|
|
||||||
|
# Initialize on import
|
||||||
|
init_git_ops()
|
||||||
@ -4,6 +4,7 @@ from typing import Dict, List, Optional, Any
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -15,6 +16,30 @@ class GiteaConfig:
|
|||||||
api_version: str = "v1"
|
api_version: str = "v1"
|
||||||
username: str = "StuXn3t"
|
username: str = "StuXn3t"
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
"""Load settings from config file if available"""
|
||||||
|
self.load_from_settings()
|
||||||
|
|
||||||
|
def load_from_settings(self):
|
||||||
|
"""Load Gitea settings from UI settings file"""
|
||||||
|
try:
|
||||||
|
settings_file = Path.home() / ".claude_project_manager" / "ui_settings.json"
|
||||||
|
if settings_file.exists():
|
||||||
|
with open(settings_file, 'r') as f:
|
||||||
|
settings = json.load(f)
|
||||||
|
|
||||||
|
# Override with saved settings if available
|
||||||
|
if "gitea_server_url" in settings and settings["gitea_server_url"]:
|
||||||
|
self.base_url = settings["gitea_server_url"]
|
||||||
|
if "gitea_api_token" in settings and settings["gitea_api_token"]:
|
||||||
|
self.api_token = settings["gitea_api_token"]
|
||||||
|
if "gitea_username" in settings and settings["gitea_username"]:
|
||||||
|
self.username = settings["gitea_username"]
|
||||||
|
|
||||||
|
logger.info(f"Loaded Gitea settings from config file")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not load Gitea settings from config: {e}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_url(self) -> str:
|
def api_url(self) -> str:
|
||||||
return f"{self.base_url}/api/{self.api_version}"
|
return f"{self.base_url}/api/{self.api_version}"
|
||||||
@ -204,4 +229,10 @@ class GiteaClient:
|
|||||||
"sha": sha,
|
"sha": sha,
|
||||||
"branch": branch
|
"branch": branch
|
||||||
}
|
}
|
||||||
return self._request("DELETE", f"repos/{owner}/{repo}/contents/{filepath}", json=data)
|
return self._request("DELETE", f"repos/{owner}/{repo}/contents/{filepath}", json=data)
|
||||||
|
|
||||||
|
# Global config instance
|
||||||
|
gitea_config = GiteaConfig()
|
||||||
|
|
||||||
|
# Default client instance
|
||||||
|
client = GiteaClient(gitea_config)
|
||||||
@ -322,6 +322,96 @@ wsl bash {bash_wsl_path}
|
|||||||
|
|
||||||
REM Clean up temp file
|
REM Clean up temp file
|
||||||
del "{bash_path}" 2>nul
|
del "{bash_path}" 2>nul
|
||||||
|
"""
|
||||||
|
return script_content
|
||||||
|
|
||||||
|
def create_activity_server_script(self) -> str:
|
||||||
|
"""Create a script for SSH connection to Activity Server"""
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
logger.info("Creating Activity Server connection script")
|
||||||
|
|
||||||
|
# Create a robust script with multiple approaches
|
||||||
|
script_content = f"""@echo off
|
||||||
|
cls
|
||||||
|
echo ================================================================================
|
||||||
|
echo CPM Activity Server Connection
|
||||||
|
echo ================================================================================
|
||||||
|
echo.
|
||||||
|
echo Server: {self.server}
|
||||||
|
echo Username: {self.username}
|
||||||
|
echo Target Directory: /home/claude-dev/cpm-activity-server
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Check if PowerShell is available and use it for better plink integration
|
||||||
|
where powershell >nul 2>&1
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo Using PowerShell for enhanced connection...
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "& {{$plinkPath = Get-Command plink -ErrorAction SilentlyContinue; if ($plinkPath) {{ Write-Host 'Connecting via plink...' -ForegroundColor Green; Write-Host ''; $process = Start-Process -FilePath 'plink' -ArgumentList '-ssh', '-l', '{self.username}', '-pw', '{self.password}', '-t', '{self.server}', 'cd /home/claude-dev/cpm-activity-server && echo \"Successfully changed to Activity Server directory\" && echo && bash -l' -PassThru -NoNewWindow -Wait; if ($process.ExitCode -ne 0) {{ Write-Host ''; Write-Host 'Automatic directory change failed. Starting interactive session...' -ForegroundColor Yellow; Write-Host ''; Write-Host 'IMPORTANT: After login, run these commands:' -ForegroundColor Red; Write-Host ' cd /home/claude-dev/cpm-activity-server' -ForegroundColor White; Write-Host ' claude' -ForegroundColor White; Write-Host ''; & plink -ssh -l {self.username} -pw '{self.password}' -t {self.server} }} }} else {{ Write-Host 'Plink not found, trying standard SSH...' -ForegroundColor Yellow; Write-Host 'Password: {self.password}'; Write-Host 'After login, run: cd /home/claude-dev/cpm-activity-server && claude'; & ssh {self.username}@{self.server} }} }}"
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
REM First, try the plink approach with RemoteCommand
|
||||||
|
where plink >nul 2>&1
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo Using PuTTY plink for connection...
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Try plink with direct command execution
|
||||||
|
echo Attempting automatic directory change...
|
||||||
|
plink -batch -ssh -l {self.username} -pw "{self.password}" {self.server} "cd /home/claude-dev/cpm-activity-server && exec bash -l"
|
||||||
|
|
||||||
|
REM If that didn't work, try interactive mode
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
echo.
|
||||||
|
echo Direct command execution failed. Starting interactive session...
|
||||||
|
echo.
|
||||||
|
echo ================================================================================
|
||||||
|
echo IMPORTANT: After login, please run these commands:
|
||||||
|
echo.
|
||||||
|
echo cd /home/claude-dev/cpm-activity-server
|
||||||
|
echo claude
|
||||||
|
echo.
|
||||||
|
echo ================================================================================
|
||||||
|
echo.
|
||||||
|
plink -ssh -l {self.username} -pw "{self.password}" -t {self.server}
|
||||||
|
)
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check if we have WSL available
|
||||||
|
where wsl >nul 2>&1
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo Using WSL for connection...
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Create inline bash command for WSL
|
||||||
|
wsl bash -c "echo 'Connecting to Activity Server...' && if command -v sshpass >/dev/null 2>&1; then sshpass -p '{self.password}' ssh -o StrictHostKeyChecking=no -t {self.username}@{self.server} 'cd /home/claude-dev/cpm-activity-server && echo \"Successfully changed to Activity Server directory\" && echo && claude || bash -l'; else echo 'Manual password entry required.' && echo 'Password: {self.password}' && echo && echo 'After login, run: cd /home/claude-dev/cpm-activity-server && claude' && echo && ssh -o StrictHostKeyChecking=no {self.username}@{self.server}; fi"
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Fallback to standard SSH
|
||||||
|
echo No automated tools found. Using standard SSH...
|
||||||
|
echo.
|
||||||
|
echo ================================================================================
|
||||||
|
echo Connection Instructions:
|
||||||
|
echo.
|
||||||
|
echo 1. Enter this password when prompted: {self.password}
|
||||||
|
echo (You can right-click to paste in most terminals)
|
||||||
|
echo.
|
||||||
|
echo 2. After successful login, run these commands:
|
||||||
|
echo cd /home/claude-dev/cpm-activity-server
|
||||||
|
echo claude
|
||||||
|
echo.
|
||||||
|
echo ================================================================================
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
ssh {self.username}@{self.server}
|
||||||
|
|
||||||
|
:end
|
||||||
|
echo.
|
||||||
|
echo Connection closed.
|
||||||
|
pause
|
||||||
"""
|
"""
|
||||||
return script_content
|
return script_content
|
||||||
|
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren