Status LED
Dieser Commit ist enthalten in:
@ -17,7 +17,8 @@
|
||||
"Bash(git commit:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(chmod:*)",
|
||||
"Bash(build.bat)"
|
||||
"Bash(build.bat)",
|
||||
"Bash(find:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
# ClaudeProjectManager-main
|
||||
# ClaudeProjectManager
|
||||
|
||||
*This README was automatically generated by Claude Project Manager*
|
||||
|
||||
## Project Overview
|
||||
|
||||
- **Path**: `C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main`
|
||||
- **Files**: 227 files
|
||||
- **Size**: 77.0 MB
|
||||
- **Last Modified**: 2025-07-08 08:19
|
||||
- **Files**: 232 files
|
||||
- **Size**: 78.4 MB
|
||||
- **Last Modified**: 2025-07-08 11:17
|
||||
|
||||
## 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:34:45
|
||||
- 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",
|
||||
"path": "claude-dev@91.99.192.14",
|
||||
"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",
|
||||
"description": "Remote VPS Server with Claude",
|
||||
"tags": [
|
||||
@ -48,15 +48,30 @@
|
||||
},
|
||||
{
|
||||
"id": "66c0f4bb-c560-43a6-a4c5-a8ecf8b73919",
|
||||
"name": "ClaudeProjectManager-main",
|
||||
"name": "ClaudeProjectManager",
|
||||
"path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main",
|
||||
"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",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"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
|
||||
|
||||
- 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 src.gitea.repository_manager import RepositoryManager
|
||||
from src.gitea.gitea_client import GiteaClient
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -156,6 +157,22 @@ class GiteaExplorer(ctk.CTkFrame):
|
||||
text_color=COLORS['text_secondary']
|
||||
)
|
||||
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):
|
||||
"""Refresh repository list from Gitea - only IntelSight"""
|
||||
@ -276,7 +293,8 @@ class GiteaExplorer(ctk.CTkFrame):
|
||||
name_label.pack(side="left", fill="x", expand=True)
|
||||
|
||||
# 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():
|
||||
status_label = ctk.CTkLabel(
|
||||
top_row,
|
||||
|
||||
@ -96,6 +96,9 @@ class MainWindow:
|
||||
# Start periodic status check after a delay
|
||||
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
|
||||
self.init_activity_sync()
|
||||
|
||||
@ -460,6 +463,9 @@ class MainWindow:
|
||||
elif project.id == "vps-docker-permanent":
|
||||
# Handle VPS Docker
|
||||
self.open_vps_docker()
|
||||
elif project.id == "activity-server-permanent":
|
||||
# Handle Activity Server
|
||||
self.open_activity_server()
|
||||
else:
|
||||
# Check if already running
|
||||
if self.process_tracker.is_running(project.id):
|
||||
@ -641,6 +647,51 @@ class MainWindow:
|
||||
|
||||
# Monitor the 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):
|
||||
"""Open project README"""
|
||||
@ -1200,6 +1251,54 @@ class MainWindow:
|
||||
# Schedule next check in 30 seconds (less frequent)
|
||||
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):
|
||||
"""Open Gitea integration window"""
|
||||
# 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.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)
|
||||
self.activity_indicator = ctk.CTkLabel(
|
||||
title_row,
|
||||
@ -209,8 +222,8 @@ class ProjectTile(ctk.CTkFrame):
|
||||
)
|
||||
self.open_button.pack(side="left", padx=(0, 5))
|
||||
|
||||
# CMD button only for main VPS Server tile (not Admin Panel or Docker)
|
||||
if self.is_vps and self.project.id == "vps-permanent":
|
||||
# CMD button for VPS Server and Activity Server
|
||||
if self.project.id in ["vps-permanent", "activity-server-permanent"]:
|
||||
self.cmd_button = ctk.CTkButton(
|
||||
button_frame,
|
||||
text="CMD",
|
||||
@ -220,8 +233,8 @@ class ProjectTile(ctk.CTkFrame):
|
||||
)
|
||||
self.cmd_button.pack(side="left", padx=(0, 5))
|
||||
|
||||
# Gitea button (not for VPS)
|
||||
if not self.is_vps:
|
||||
# Gitea button and Activity button (not for VPS and not for Activity Server)
|
||||
if not self.is_vps and self.project.id != "activity-server-permanent":
|
||||
self.gitea_button = ctk.CTkButton(
|
||||
button_frame,
|
||||
text="Gitea ▼",
|
||||
@ -246,8 +259,8 @@ class ProjectTile(ctk.CTkFrame):
|
||||
if not activity_service.is_configured() or not activity_service.connected:
|
||||
self.activity_button.configure(state="normal", text_color=COLORS['text_dim'])
|
||||
|
||||
# Delete button (not for VPS) - keep it in first row
|
||||
if not self.is_vps and self.on_delete:
|
||||
# Delete button (not for VPS and not for Activity Server) - keep it in first row
|
||||
if not self.is_vps and self.on_delete and self.project.id != "activity-server-permanent":
|
||||
self.delete_button = ctk.CTkButton(
|
||||
button_frame,
|
||||
text="✕",
|
||||
@ -259,8 +272,8 @@ class ProjectTile(ctk.CTkFrame):
|
||||
)
|
||||
self.delete_button.pack(side="right")
|
||||
|
||||
# Second row - Open Explorer button (only for non-VPS tiles)
|
||||
if not self.is_vps:
|
||||
# Second row - Open Explorer button (only for non-VPS tiles and not for Activity Server)
|
||||
if not self.is_vps and self.project.id != "activity-server-permanent":
|
||||
explorer_frame = ctk.CTkFrame(container, fg_color="transparent")
|
||||
explorer_frame.pack(fill="x", pady=(5, 0))
|
||||
|
||||
@ -416,7 +429,41 @@ class ProjectTile(ctk.CTkFrame):
|
||||
if platform.system() == 'Windows':
|
||||
# Create a temporary batch file for Windows
|
||||
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.
|
||||
echo Please wait while establishing connection...
|
||||
@ -715,6 +762,71 @@ pause
|
||||
# Update activity button if exists
|
||||
if hasattr(self, 'activity_button'):
|
||||
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):
|
||||
"""Show tooltip with active user name"""
|
||||
|
||||
@ -8,6 +8,8 @@ from gui.styles import COLORS, FONTS
|
||||
from utils.logger import logger
|
||||
import json
|
||||
from pathlib import Path
|
||||
from tkinter import filedialog
|
||||
import os
|
||||
|
||||
|
||||
class SettingsDialog(ctk.CTkToplevel):
|
||||
@ -19,7 +21,7 @@ class SettingsDialog(ctk.CTkToplevel):
|
||||
|
||||
# Window setup
|
||||
self.title("Einstellungen")
|
||||
self.minsize(500, 450)
|
||||
self.minsize(650, 550)
|
||||
self.resizable(True, True)
|
||||
|
||||
# Make modal
|
||||
@ -56,96 +58,31 @@ class SettingsDialog(ctk.CTkToplevel):
|
||||
)
|
||||
title_label.pack(pady=(0, 20))
|
||||
|
||||
# Activity Server Section
|
||||
activity_section = ctk.CTkFrame(main_frame, fg_color=COLORS['bg_secondary'])
|
||||
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=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'],
|
||||
# Tab view
|
||||
self.tabview = ctk.CTkTabview(
|
||||
main_frame,
|
||||
fg_color=COLORS['bg_secondary'],
|
||||
segmented_button_fg_color=COLORS['bg_tile'],
|
||||
segmented_button_selected_color=COLORS['accent_primary'],
|
||||
segmented_button_selected_hover_color=COLORS['accent_hover'],
|
||||
segmented_button_unselected_color=COLORS['bg_tile'],
|
||||
segmented_button_unselected_hover_color=COLORS['bg_tile_hover'],
|
||||
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
|
||||
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))
|
||||
# Add tabs
|
||||
self.tabview.add("Allgemein")
|
||||
self.tabview.add("Gitea")
|
||||
self.tabview.add("Team-Aktivität")
|
||||
|
||||
# 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.pack(fill="x", pady=(20, 0))
|
||||
|
||||
@ -184,6 +121,264 @@ class SettingsDialog(ctk.CTkToplevel):
|
||||
)
|
||||
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):
|
||||
"""Load settings from file"""
|
||||
try:
|
||||
@ -205,49 +400,141 @@ class SettingsDialog(ctk.CTkToplevel):
|
||||
except Exception as 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):
|
||||
"""Save settings without applying to service or closing dialog"""
|
||||
# Get values
|
||||
# Get activity values
|
||||
server_url = self.server_url_var.get().strip()
|
||||
api_key = self.api_key_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
|
||||
self.settings["activity_server_url"] = server_url
|
||||
self.settings["activity_api_key"] = api_key
|
||||
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()
|
||||
|
||||
# Show confirmation
|
||||
self.connection_status_label.configure(
|
||||
text="✅ Einstellungen gespeichert!",
|
||||
text_color=COLORS['accent_success']
|
||||
)
|
||||
logger.info("Settings saved successfully")
|
||||
# Show confirmation on the active tab
|
||||
current_tab = self.tabview.get()
|
||||
if current_tab == "Team-Aktivität":
|
||||
self.connection_status_label.configure(
|
||||
text="✅ Einstellungen gespeichert!",
|
||||
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
|
||||
self.after(2000, lambda: self.connection_status_label.configure(text=""))
|
||||
logger.info("Settings saved successfully")
|
||||
|
||||
def apply_settings(self):
|
||||
"""Apply the selected settings"""
|
||||
import uuid
|
||||
from services.activity_sync import activity_service
|
||||
|
||||
# Get values
|
||||
server_url = self.server_url_var.get().strip()
|
||||
api_key = self.api_key_var.get().strip()
|
||||
user_name = self.user_name_var.get().strip()
|
||||
# Save all settings first
|
||||
self.save_settings_only()
|
||||
|
||||
# Update settings
|
||||
self.settings["activity_server_url"] = server_url
|
||||
self.settings["activity_api_key"] = api_key
|
||||
self.settings["activity_user_name"] = 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
|
||||
# Apply activity service settings
|
||||
activity_service.server_url = self.settings.get("activity_server_url", "")
|
||||
activity_service.api_key = self.settings.get("activity_api_key", "")
|
||||
activity_service.user_name = self.settings.get("activity_user_name", "")
|
||||
activity_service.user_id = self.settings.get("activity_user_id", str(uuid.uuid4()))
|
||||
|
||||
# Save user ID if newly generated
|
||||
@ -267,9 +554,65 @@ class SettingsDialog(ctk.CTkToplevel):
|
||||
|
||||
logger.info("Activity server settings applied")
|
||||
|
||||
# Apply Gitea settings to config
|
||||
self.update_gitea_config()
|
||||
|
||||
# Close dialog
|
||||
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):
|
||||
"""Test connection to activity server"""
|
||||
import requests
|
||||
|
||||
@ -58,10 +58,12 @@ class ProjectManager:
|
||||
self.projects: Dict[str, Project] = {}
|
||||
self.vps_project = None
|
||||
self.admin_panel_project = None
|
||||
self.activity_server_project = None
|
||||
self._ensure_data_dir()
|
||||
self.load_projects()
|
||||
self._initialize_vps_project()
|
||||
self._initialize_admin_panel_project()
|
||||
self._initialize_activity_server_project()
|
||||
|
||||
def _ensure_data_dir(self):
|
||||
"""Ensure data directory exists"""
|
||||
@ -101,6 +103,22 @@ class ProjectManager:
|
||||
else:
|
||||
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):
|
||||
"""Load projects from JSON file"""
|
||||
logger.info("Loading projects from file")
|
||||
@ -162,12 +180,13 @@ class ProjectManager:
|
||||
def get_all_projects(self) -> List[Project]:
|
||||
"""Get all projects sorted alphabetically by name"""
|
||||
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"]
|
||||
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)
|
||||
return vps + admin + others
|
||||
return vps + admin + activity + others
|
||||
|
||||
def update_project(self, project_id: str, **kwargs):
|
||||
"""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 datetime import datetime
|
||||
from utils.logger import logger
|
||||
import json
|
||||
|
||||
@dataclass
|
||||
class GitCredentials:
|
||||
@ -25,7 +26,30 @@ class GitOperationsManager:
|
||||
def __init__(self, base_url: str, token: str, username: str = "claude-project-manager"):
|
||||
self.base_url = base_url
|
||||
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]:
|
||||
root = tk.Tk()
|
||||
@ -95,7 +119,10 @@ class GitOperationsManager:
|
||||
if clone_dir is None:
|
||||
# Use default clone directory if none specified
|
||||
clone_dir = self.default_clone_dir
|
||||
logger.info(f"Using default clone directory: {clone_dir}")
|
||||
clone_dir.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
logger.info(f"Using provided clone directory: {clone_dir}")
|
||||
|
||||
repo_path = f"{owner}/{repo}"
|
||||
remote_url = self.credentials.get_remote_url(self.base_url, repo_path)
|
||||
@ -616,4 +643,25 @@ class GitOperationsManager:
|
||||
cmd = ["git", "add", file_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 datetime import datetime
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -15,6 +16,30 @@ class GiteaConfig:
|
||||
api_version: str = "v1"
|
||||
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
|
||||
def api_url(self) -> str:
|
||||
return f"{self.base_url}/api/{self.api_version}"
|
||||
@ -204,4 +229,10 @@ class GiteaClient:
|
||||
"sha": sha,
|
||||
"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
|
||||
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
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren