Dieser Commit ist enthalten in:
Claude Project Manager
2025-07-08 13:13:46 +02:00
Ursprung d1667f9e0d
Commit 5f32daf3a4
15 geänderte Dateien mit 1139 neuen und 136 gelöschten Zeilen

Datei anzeigen

@ -17,7 +17,8 @@
"Bash(git commit:*)",
"Bash(mkdir:*)",
"Bash(chmod:*)",
"Bash(build.bat)"
"Bash(build.bat)",
"Bash(find:*)"
],
"deny": []
}

Datei anzeigen

@ -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
Datei anzeigen

@ -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
Datei anzeigen

@ -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")

Datei anzeigen

@ -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"
}

Datei anzeigen

@ -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

Datei anzeigen

@ -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,

Datei anzeigen

@ -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

Datei anzeigen

@ -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"""

Datei anzeigen

@ -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

Datei anzeigen

@ -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"""

Datei anzeigen

@ -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."

Datei anzeigen

@ -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()

Datei anzeigen

@ -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)

Datei anzeigen

@ -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