Dieser Commit ist enthalten in:
Claude Project Manager
2025-07-10 14:54:38 +02:00
Ursprung 6112313a91
Commit ac9993d704
6 geänderte Dateien mit 237 neuen und 114 gelöschten Zeilen

Datei anzeigen

@ -20,7 +20,8 @@
"Bash(build.bat)", "Bash(build.bat)",
"Bash(find:*)", "Bash(find:*)",
"Bash(pip3 list:*)", "Bash(pip3 list:*)",
"Bash(curl:*)" "Bash(curl:*)",
"Bash(jq:*)"
], ],
"deny": [] "deny": []
} }

Datei anzeigen

@ -5,9 +5,9 @@
## Project Overview ## Project Overview
- **Path**: `C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main` - **Path**: `C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main`
- **Files**: 81 files - **Files**: 84 files
- **Size**: 1.8 MB - **Size**: 4.4 MB
- **Last Modified**: 2025-07-09 22:31 - **Last Modified**: 2025-07-10 13:52
## Technology Stack ## Technology Stack
@ -61,7 +61,8 @@ logs/
│ ├── cpm_20250709_215939.log │ ├── cpm_20250709_215939.log
│ ├── cpm_20250709_220833.log │ ├── cpm_20250709_220833.log
│ ├── cpm_20250709_222800.log │ ├── cpm_20250709_222800.log
── cpm_20250709_222952.log ── cpm_20250709_222952.log
│ └── cpm_20250709_223933.log
scripts/ scripts/
│ ├── check_lfs_status.bat │ ├── check_lfs_status.bat
│ ├── fix_large_files.bat │ ├── fix_large_files.bat
@ -168,3 +169,4 @@ This project is managed with Claude Project Manager. To work with this project:
- README updated on 2025-07-09 21:31:18 - README updated on 2025-07-09 21:31:18
- README updated on 2025-07-09 22:12:45 - README updated on 2025-07-09 22:12:45
- README updated on 2025-07-09 22:31:20 - README updated on 2025-07-09 22:31:20
- README updated on 2025-07-10 13:52:48

Datei anzeigen

@ -5,7 +5,7 @@
"name": "VPS Server", "name": "VPS Server",
"path": "claude-dev@91.99.192.14", "path": "claude-dev@91.99.192.14",
"created_at": "2025-07-01T20:14:48.308074", "created_at": "2025-07-01T20:14:48.308074",
"last_accessed": "2025-07-09T22:15:03.835828", "last_accessed": "2025-07-10T13:54:21.595494",
"readme_path": "claude-dev@91.99.192.14\\CLAUDE_PROJECT_README.md", "readme_path": "claude-dev@91.99.192.14\\CLAUDE_PROJECT_README.md",
"description": "Remote VPS Server with Claude", "description": "Remote VPS Server with Claude",
"tags": [ "tags": [
@ -51,7 +51,7 @@
"name": "ClaudeProjectManager", "name": "ClaudeProjectManager",
"path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main", "path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main",
"created_at": "2025-07-07T21:38:23.820122", "created_at": "2025-07-07T21:38:23.820122",
"last_accessed": "2025-07-09T22:31:20.779365", "last_accessed": "2025-07-10T13:52:48.621815",
"readme_path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main\\CLAUDE_PROJECT_README.md", "readme_path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main\\CLAUDE_PROJECT_README.md",
"description": "", "description": "",
"tags": [], "tags": [],
@ -84,5 +84,5 @@
"gitea_repo": null "gitea_repo": null
} }
], ],
"last_updated": "2025-07-09T22:31:20.779365" "last_updated": "2025-07-10T13:54:21.595494"
} }

Datei anzeigen

@ -60,4 +60,4 @@ This VPS server provides:
## Connection Log ## Connection Log
- README generated on 2025-07-09 22:15:03 - README generated on 2025-07-10 13:54:21

Datei anzeigen

@ -8,6 +8,7 @@ from tkinter import filedialog, messagebox
import os import os
import threading import threading
import subprocess import subprocess
import time
from typing import Optional from typing import Optional
from PIL import Image from PIL import Image
@ -59,6 +60,14 @@ class MainWindow:
self.user_interacting = False self.user_interacting = False
self.pending_updates = [] self.pending_updates = []
# Performance optimization: Activity tracking
self._last_activities_hash = None
self._last_display_state = {}
self._verbose_logging = False # Set to True to enable debug logging
self._activity_update_interval = 5000 # Default 5 seconds
self._idle_update_interval = 10000 # 10 seconds when idle
self._last_user_interaction = time.time()
# Create main window # Create main window
self.root = ctk.CTk() self.root = ctk.CTk()
self.root.title(WINDOW_CONFIG['title']) self.root.title(WINDOW_CONFIG['title'])
@ -354,74 +363,119 @@ class MainWindow:
self._differential_update(projects) self._differential_update(projects)
return return
# Full refresh - clear and rebuild # Start async refresh
for widget in self.flow_frame.winfo_children(): self._async_refresh_projects(projects)
widget.destroy()
self.project_tiles.clear()
# Calculate how many tiles can fit in a row def _async_refresh_projects(self, projects):
"""Asynchronously refresh projects to avoid UI freeze"""
# Clear existing widgets in background
def clear_widgets():
for widget in self.flow_frame.winfo_children():
widget.destroy()
self.project_tiles.clear()
# Continue with creating tiles
self._create_tiles_batch(projects, 0)
# Schedule widget clearing
self.root.after(1, clear_widgets)
def _create_tiles_batch(self, projects, start_index, batch_size=3):
"""Create project tiles in batches to keep UI responsive"""
if start_index == 0:
# Initialize on first batch
self._init_tile_creation(projects)
vps_ids = ["vps-permanent", "admin-panel-permanent", "vps-docker-permanent", "activity-server-permanent"]
# Process VPS tiles first if we're at the beginning
if start_index == 0:
# Create VPS tiles
vps_tiles = []
for vps_id in vps_ids:
vps_project = next((p for p in projects if p.id == vps_id), None)
if vps_project:
vps_tiles.append(vps_project)
# Create VPS tiles
for vps_project in vps_tiles:
self._add_tile_to_flow(vps_project, is_vps=True)
# Add separator after VPS tiles
if vps_tiles:
self._add_separator()
# Schedule local projects creation
self.root.after(10, lambda: self._create_tiles_batch(projects, 1, batch_size))
return
# Get local projects only
local_projects = [p for p in projects if p.id not in vps_ids]
# Calculate batch range
batch_start = start_index - 1 # Adjust for VPS tiles being handled separately
batch_end = min(batch_start + batch_size, len(local_projects))
# Create tiles for this batch
for i in range(batch_start, batch_end):
self._add_tile_to_flow(local_projects[i], is_vps=False)
# Check if we have more projects to process
if batch_end < len(local_projects):
# Schedule next batch
self.root.after(10, lambda: self._create_tiles_batch(projects, batch_end + 1, batch_size))
else:
# All projects processed, add the "Add Project" tile
self._add_tile_to_flow(None, is_add_tile=True)
self.update_status("Projects refreshed")
def _init_tile_creation(self, projects):
"""Initialize tile creation state"""
# Calculate tiles per row
window_width = self.scroll_container.winfo_width() if self.scroll_container.winfo_width() > 1 else WINDOW_CONFIG['width'] - 60 window_width = self.scroll_container.winfo_width() if self.scroll_container.winfo_width() > 1 else WINDOW_CONFIG['width'] - 60
tile_width = TILE_SIZE['width'] tile_width = TILE_SIZE['width']
tile_margin = 10 tile_margin = 10
tiles_per_row = max(1, window_width // (tile_width + tile_margin * 2)) self._tiles_per_row = max(1, window_width // (tile_width + tile_margin * 2))
# Create row frames # Initialize row tracking
current_row_frame = None self._current_row_frame = None
tiles_in_current_row = 0 self._tiles_in_current_row = 0
# Helper function to create a new row def _create_new_row(self):
def create_new_row(): """Create a new row frame"""
nonlocal current_row_frame, tiles_in_current_row self._current_row_frame = ctk.CTkFrame(self.flow_frame, fg_color="transparent")
current_row_frame = ctk.CTkFrame(self.flow_frame, fg_color="transparent") self._current_row_frame.pack(fill="x", pady=5)
current_row_frame.pack(fill="x", pady=5) self._tiles_in_current_row = 0
tiles_in_current_row = 0
# Start with first row def _add_tile_to_flow(self, project, is_vps=False, is_add_tile=False):
create_new_row() """Add a single tile to the flow layout"""
# Check if we need a new row
if self._current_row_frame is None or self._tiles_in_current_row >= self._tiles_per_row:
self._create_new_row()
# Add VPS tile first if is_add_tile:
vps_project = next((p for p in projects if p.id == "vps-permanent"), None) # Create add project tile
if vps_project: self.create_add_tile_flow(self._current_row_frame)
self.create_project_tile_flow(vps_project, current_row_frame, is_vps=True) else:
tiles_in_current_row += 1 # Create project tile
self.create_project_tile_flow(project, self._current_row_frame, is_vps=is_vps)
# Add Admin Panel tile second self._tiles_in_current_row += 1
admin_project = next((p for p in projects if p.id == "admin-panel-permanent"), None)
if admin_project:
if tiles_in_current_row >= tiles_per_row:
create_new_row()
self.create_project_tile_flow(admin_project, current_row_frame, is_vps=True)
tiles_in_current_row += 1
# Add VPS Docker tile third def _add_separator(self):
vps_docker_project = next((p for p in projects if p.id == "vps-docker-permanent"), None) """Add separator between VPS and local projects"""
if vps_docker_project: # Add separator line
if tiles_in_current_row >= tiles_per_row:
create_new_row()
self.create_project_tile_flow(vps_docker_project, current_row_frame, is_vps=True)
tiles_in_current_row += 1
# Add Activity Server tile fourth
activity_project = next((p for p in projects if p.id == "activity-server-permanent"), None)
if activity_project:
if tiles_in_current_row >= tiles_per_row:
create_new_row()
self.create_project_tile_flow(activity_project, current_row_frame, is_vps=True)
tiles_in_current_row += 1
# Add separator line between VPS tiles and local projects
separator_frame = ctk.CTkFrame(self.flow_frame, fg_color="transparent") separator_frame = ctk.CTkFrame(self.flow_frame, fg_color="transparent")
separator_frame.pack(fill="x", pady=15) separator_frame.pack(fill="x", pady=15)
# Use a more visible separator
separator_line = ctk.CTkFrame( separator_line = ctk.CTkFrame(
separator_frame, separator_frame,
height=2, # Thicker line height=2,
fg_color=COLORS['accent_secondary'] # More visible blue-gray color fg_color=COLORS['accent_secondary']
) )
separator_line.pack(fill="x", padx=20) separator_line.pack(fill="x", padx=20)
# Label for local projects section # Label for local projects
local_label = ctk.CTkLabel( local_label = ctk.CTkLabel(
self.flow_frame, self.flow_frame,
text="Lokale Projekte", text="Lokale Projekte",
@ -430,23 +484,9 @@ class MainWindow:
) )
local_label.pack(pady=(0, 10)) local_label.pack(pady=(0, 10))
# Start new row for local projects # Reset row for local projects
create_new_row() self._current_row_frame = None
self._tiles_in_current_row = 0
# Add local project tiles
for project in projects:
if project.id not in vps_ids:
if tiles_in_current_row >= tiles_per_row:
create_new_row()
self.create_project_tile_flow(project, current_row_frame)
tiles_in_current_row += 1
# Add "Add Project" tile
if tiles_in_current_row >= tiles_per_row:
create_new_row()
self.create_add_tile_flow(current_row_frame)
self.update_status("Projects refreshed")
def create_project_tile(self, project: Project, row: int, col: int, is_vps: bool = False): def create_project_tile(self, project: Project, row: int, col: int, is_vps: bool = False):
"""Create a project tile (legacy grid method)""" """Create a project tile (legacy grid method)"""
@ -1216,10 +1256,14 @@ class MainWindow:
self.root.bind_all("<<ComboboxSelected>>", self._on_dropdown_select) self.root.bind_all("<<ComboboxSelected>>", self._on_dropdown_select)
self.root.bind_all("<FocusIn>", self._on_focus_in) self.root.bind_all("<FocusIn>", self._on_focus_in)
self.root.bind_all("<FocusOut>", self._on_focus_out) self.root.bind_all("<FocusOut>", self._on_focus_out)
self.root.bind_all("<Key>", self._on_key_press)
self.root.bind_all("<Motion>", self._on_mouse_motion)
self._last_mouse_position = (0, 0)
def _on_click_start(self, event): def _on_click_start(self, event):
"""Track start of user interaction""" """Track start of user interaction"""
self.user_interacting = True self.user_interacting = True
self._last_user_interaction = time.time()
def _on_click_end(self, event): def _on_click_end(self, event):
"""Track end of user interaction""" """Track end of user interaction"""
@ -1229,17 +1273,33 @@ class MainWindow:
def _on_dropdown_select(self, event): def _on_dropdown_select(self, event):
"""Handle dropdown selection""" """Handle dropdown selection"""
self.user_interacting = True self.user_interacting = True
self._last_user_interaction = time.time()
self.root.after(500, self._check_pending_updates) self.root.after(500, self._check_pending_updates)
def _on_focus_in(self, event): def _on_focus_in(self, event):
"""Track focus events""" """Track focus events"""
if isinstance(event.widget, (ctk.CTkComboBox, ctk.CTkOptionMenu)): if isinstance(event.widget, (ctk.CTkComboBox, ctk.CTkOptionMenu)):
self.user_interacting = True self.user_interacting = True
self._last_user_interaction = time.time()
def _on_focus_out(self, event): def _on_focus_out(self, event):
"""Track focus loss""" """Track focus loss"""
self.root.after(200, self._check_pending_updates) self.root.after(200, self._check_pending_updates)
def _on_key_press(self, event):
"""Track keyboard activity"""
self._last_user_interaction = time.time()
def _on_mouse_motion(self, event):
"""Track mouse movement"""
# Only update if mouse has moved significantly to avoid constant updates
if hasattr(self, '_last_mouse_position'):
dx = abs(event.x_root - self._last_mouse_position[0])
dy = abs(event.y_root - self._last_mouse_position[1])
if dx > 10 or dy > 10: # Significant movement
self._last_user_interaction = time.time()
self._last_mouse_position = (event.x_root, event.y_root)
def _check_pending_updates(self): def _check_pending_updates(self):
"""Process pending updates after interaction ends""" """Process pending updates after interaction ends"""
self.user_interacting = False self.user_interacting = False
@ -3115,18 +3175,21 @@ class MainWindow:
"""Update activity display in status bar""" """Update activity display in status bar"""
from services.activity_sync import activity_service from services.activity_sync import activity_service
# Cache current state to avoid unnecessary UI updates
if not hasattr(self, '_last_display_state'):
self._last_display_state = {'own_led': '', 'own_label': '', 'team_label': ''}
new_state = {'own_led': '', 'own_label': '', 'team_label': ''}
# Update own activity indicator (yellow LED) # Update own activity indicator (yellow LED)
own_activities = activity_service.get_all_current_activities() own_activities = activity_service.get_all_current_activities()
if own_activities: if own_activities:
self.own_activity_led.configure(text="🟡") new_state['own_led'] = "🟡"
activity_count = len(own_activities) activity_count = len(own_activities)
if activity_count == 1: if activity_count == 1:
self.own_activity_label.configure(text=f"{own_activities[0]['projectName']}") new_state['own_label'] = f"{own_activities[0]['projectName']}"
else: else:
self.own_activity_label.configure(text=f"{activity_count} Projekte aktiv") new_state['own_label'] = f"{activity_count} Projekte aktiv"
else:
self.own_activity_led.configure(text="")
self.own_activity_label.configure(text="")
# Update team activity indicator (blue LED) - count unique users, not activities # Update team activity indicator (blue LED) - count unique users, not activities
unique_users = set() unique_users = set()
@ -3138,11 +3201,20 @@ class MainWindow:
count = len(unique_users) count = len(unique_users)
if count > 0: if count > 0:
self.activity_label.configure(text=f"🔵 {count} Teammitglieder aktiv") new_state['team_label'] = f"🔵 {count} Teammitglieder aktiv"
else:
self.activity_label.configure(text="")
# Update project tiles # Only update UI elements that have changed
if new_state['own_led'] != self._last_display_state.get('own_led', ''):
self.own_activity_led.configure(text=new_state['own_led'])
if new_state['own_label'] != self._last_display_state.get('own_label', ''):
self.own_activity_label.configure(text=new_state['own_label'])
if new_state['team_label'] != self._last_display_state.get('team_label', ''):
self.activity_label.configure(text=new_state['team_label'])
self._last_display_state = new_state
# Update project tiles only if activities have changed
# The tiles themselves will handle their own change detection
for project_id, tile in self.project_tiles.items(): for project_id, tile in self.project_tiles.items():
if hasattr(tile, 'check_activity'): if hasattr(tile, 'check_activity'):
tile.check_activity() tile.check_activity()
@ -3152,17 +3224,47 @@ class MainWindow:
from services.activity_sync import activity_service from services.activity_sync import activity_service
from utils.logger import logger from utils.logger import logger
logger.debug("Periodic activity status update triggered") # Only log debug messages in verbose mode to reduce spam
if hasattr(self, '_verbose_logging') and self._verbose_logging:
logger.debug("Periodic activity status update triggered")
if activity_service.connected: if activity_service.connected:
# Update display with current activities # Check if activities have actually changed
logger.debug(f"Updating activity display with {len(activity_service.activities)} activities") current_hash = self._compute_activities_hash(activity_service.activities)
self.update_activity_display(activity_service.activities)
else: # Only update if activities have changed
logger.debug("Activity service not connected, skipping update") if not hasattr(self, '_last_activities_hash') or self._last_activities_hash != current_hash:
self._last_activities_hash = current_hash
if hasattr(self, '_verbose_logging') and self._verbose_logging:
logger.debug(f"Activities changed, updating display with {len(activity_service.activities)} activities")
self.update_activity_display(activity_service.activities)
# Adaptive update interval based on user activity
current_time = time.time()
time_since_interaction = current_time - self._last_user_interaction
# Use shorter interval if user recently interacted, longer if idle
if time_since_interaction < 60: # Active in last minute
interval = self._activity_update_interval # 5 seconds
else: # Idle for more than a minute
interval = self._idle_update_interval # 10 seconds
# Schedule next update # Schedule next update
self.root.after(5000, self.update_activity_status) # Update every 5 seconds self.root.after(interval, self.update_activity_status)
def _compute_activities_hash(self, activities):
"""Compute a hash of activities to detect changes"""
# Create a sorted tuple of relevant activity data
activity_data = []
for activity in activities:
activity_data.append((
activity.get('userId'),
activity.get('projectName'),
activity.get('isActive', False)
))
# Sort to ensure consistent ordering
activity_data.sort()
return hash(tuple(activity_data))
def _show_own_activity_tooltip(self, event=None): def _show_own_activity_tooltip(self, event=None):
"""Show tooltip with own active projects""" """Show tooltip with own active projects"""
@ -3369,6 +3471,19 @@ class MainWindow:
tkinter.Tk.report_callback_exception = handle_tk_error tkinter.Tk.report_callback_exception = handle_tk_error
def set_verbose_logging(self, enabled: bool):
"""Enable or disable verbose activity logging"""
self._verbose_logging = enabled
from utils.logger import logger
logger.info(f"Verbose activity logging: {'enabled' if enabled else 'disabled'}")
def set_activity_update_intervals(self, active_interval: int = 5000, idle_interval: int = 10000):
"""Set the update intervals for activity checking"""
self._activity_update_interval = active_interval
self._idle_update_interval = idle_interval
from utils.logger import logger
logger.info(f"Activity update intervals set - Active: {active_interval}ms, Idle: {idle_interval}ms")
def run(self): def run(self):
"""Run the application""" """Run the application"""
self.root.mainloop() self.root.mainloop()

Datei anzeigen

@ -879,13 +879,13 @@ pause
"""Check if this project has active users""" """Check if this project has active users"""
from services.activity_sync import activity_service from services.activity_sync import activity_service
logger.debug(f"check_activity called for project: {self.project.name}") # Cache the previous state to avoid unnecessary updates
if not hasattr(self, '_last_activity_state'):
self._last_activity_state = {'active': False, 'users': '', 'is_own': False}
# First check if this is our own current activity # First check if this is our own current activity
is_own_current = activity_service.is_project_active_for_user(self.project.name) is_own_current = activity_service.is_project_active_for_user(self.project.name)
logger.debug(f"Current activity check - is_own_current: {is_own_current}")
# Get all activities for this project from server # Get all activities for this project from server
active_users = [] active_users = []
is_own_activity = False is_own_activity = False
@ -908,25 +908,30 @@ pause
else: else:
has_other_users = True has_other_users = True
logger.debug(f"Server activities - active_users: {active_users}, is_own_activity: {is_own_activity}, has_other_users: {has_other_users}")
# If we have a local current activity, ensure it's included # If we have a local current activity, ensure it's included
if is_own_current: if is_own_current:
is_own_activity = True is_own_activity = True
if activity_service.user_name not in active_users: if activity_service.user_name not in active_users:
active_users.append(activity_service.user_name) active_users.append(activity_service.user_name)
logger.debug(f"Added local user to active_users: {activity_service.user_name}")
if active_users: # Determine new state
# Show indicator with all active users new_state = {
user_text = ", ".join(active_users) 'active': bool(active_users),
# If both own and others are active, show as others (orange) to indicate collaboration 'users': ', '.join(active_users) if active_users else '',
final_is_own = is_own_activity and not has_other_users 'is_own': is_own_activity and not has_other_users if active_users else False
logger.info(f"Updating activity status for {self.project.name}: active=True, users={user_text}, is_own={final_is_own}") }
self.update_activity_status(True, user_text, final_is_own)
else: # Only update UI if state has changed
logger.info(f"Updating activity status for {self.project.name}: active=False") if new_state != self._last_activity_state:
self.update_activity_status(False) self._last_activity_state = new_state
if new_state['active']:
# Only log significant changes (not every periodic check)
logger.info(f"Activity status changed for {self.project.name}: active=True, users={new_state['users']}, is_own={new_state['is_own']}")
self.update_activity_status(True, new_state['users'], new_state['is_own'])
else:
logger.info(f"Activity status changed for {self.project.name}: active=False")
self.update_activity_status(False)
def _start_activity_animation(self): def _start_activity_animation(self):
"""Start animated border for team activity""" """Start animated border for team activity"""