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(find:*)",
"Bash(pip3 list:*)",
"Bash(curl:*)"
"Bash(curl:*)",
"Bash(jq:*)"
],
"deny": []
}

Datei anzeigen

@ -5,9 +5,9 @@
## Project Overview
- **Path**: `C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main`
- **Files**: 81 files
- **Size**: 1.8 MB
- **Last Modified**: 2025-07-09 22:31
- **Files**: 84 files
- **Size**: 4.4 MB
- **Last Modified**: 2025-07-10 13:52
## Technology Stack
@ -61,7 +61,8 @@ logs/
│ ├── cpm_20250709_215939.log
│ ├── cpm_20250709_220833.log
│ ├── cpm_20250709_222800.log
── cpm_20250709_222952.log
── cpm_20250709_222952.log
│ └── cpm_20250709_223933.log
scripts/
│ ├── check_lfs_status.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 22:12:45
- 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",
"path": "claude-dev@91.99.192.14",
"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",
"description": "Remote VPS Server with Claude",
"tags": [
@ -51,7 +51,7 @@
"name": "ClaudeProjectManager",
"path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main",
"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",
"description": "",
"tags": [],
@ -84,5 +84,5 @@
"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
- 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 threading
import subprocess
import time
from typing import Optional
from PIL import Image
@ -59,6 +60,14 @@ class MainWindow:
self.user_interacting = False
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
self.root = ctk.CTk()
self.root.title(WINDOW_CONFIG['title'])
@ -354,74 +363,119 @@ class MainWindow:
self._differential_update(projects)
return
# Full refresh - clear and rebuild
for widget in self.flow_frame.winfo_children():
widget.destroy()
self.project_tiles.clear()
# Start async refresh
self._async_refresh_projects(projects)
# 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
tile_width = TILE_SIZE['width']
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
current_row_frame = None
tiles_in_current_row = 0
# Initialize row tracking
self._current_row_frame = None
self._tiles_in_current_row = 0
def _create_new_row(self):
"""Create a new row frame"""
self._current_row_frame = ctk.CTkFrame(self.flow_frame, fg_color="transparent")
self._current_row_frame.pack(fill="x", pady=5)
self._tiles_in_current_row = 0
def _add_tile_to_flow(self, project, is_vps=False, is_add_tile=False):
"""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()
# Helper function to create a new row
def create_new_row():
nonlocal current_row_frame, tiles_in_current_row
current_row_frame = ctk.CTkFrame(self.flow_frame, fg_color="transparent")
current_row_frame.pack(fill="x", pady=5)
tiles_in_current_row = 0
if is_add_tile:
# Create add project tile
self.create_add_tile_flow(self._current_row_frame)
else:
# Create project tile
self.create_project_tile_flow(project, self._current_row_frame, is_vps=is_vps)
# Start with first row
create_new_row()
# Add VPS tile first
vps_project = next((p for p in projects if p.id == "vps-permanent"), None)
if vps_project:
self.create_project_tile_flow(vps_project, current_row_frame, is_vps=True)
tiles_in_current_row += 1
# Add Admin Panel tile second
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
vps_docker_project = next((p for p in projects if p.id == "vps-docker-permanent"), None)
if vps_docker_project:
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
self._tiles_in_current_row += 1
def _add_separator(self):
"""Add separator between VPS and local projects"""
# Add separator line
separator_frame = ctk.CTkFrame(self.flow_frame, fg_color="transparent")
separator_frame.pack(fill="x", pady=15)
# Use a more visible separator
separator_line = ctk.CTkFrame(
separator_frame,
height=2, # Thicker line
fg_color=COLORS['accent_secondary'] # More visible blue-gray color
separator_frame,
height=2,
fg_color=COLORS['accent_secondary']
)
separator_line.pack(fill="x", padx=20)
# Label for local projects section
# Label for local projects
local_label = ctk.CTkLabel(
self.flow_frame,
text="Lokale Projekte",
@ -430,23 +484,9 @@ class MainWindow:
)
local_label.pack(pady=(0, 10))
# Start new row for local projects
create_new_row()
# 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")
# Reset row for local projects
self._current_row_frame = None
self._tiles_in_current_row = 0
def create_project_tile(self, project: Project, row: int, col: int, is_vps: bool = False):
"""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("<FocusIn>", self._on_focus_in)
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):
"""Track start of user interaction"""
self.user_interacting = True
self._last_user_interaction = time.time()
def _on_click_end(self, event):
"""Track end of user interaction"""
@ -1229,16 +1273,32 @@ class MainWindow:
def _on_dropdown_select(self, event):
"""Handle dropdown selection"""
self.user_interacting = True
self._last_user_interaction = time.time()
self.root.after(500, self._check_pending_updates)
def _on_focus_in(self, event):
"""Track focus events"""
if isinstance(event.widget, (ctk.CTkComboBox, ctk.CTkOptionMenu)):
self.user_interacting = True
self._last_user_interaction = time.time()
def _on_focus_out(self, event):
"""Track focus loss"""
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):
"""Process pending updates after interaction ends"""
@ -3115,18 +3175,21 @@ class MainWindow:
"""Update activity display in status bar"""
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)
own_activities = activity_service.get_all_current_activities()
if own_activities:
self.own_activity_led.configure(text="🟡")
new_state['own_led'] = "🟡"
activity_count = len(own_activities)
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:
self.own_activity_label.configure(text=f"{activity_count} Projekte aktiv")
else:
self.own_activity_led.configure(text="")
self.own_activity_label.configure(text="")
new_state['own_label'] = f"{activity_count} Projekte aktiv"
# Update team activity indicator (blue LED) - count unique users, not activities
unique_users = set()
@ -3138,11 +3201,20 @@ class MainWindow:
count = len(unique_users)
if count > 0:
self.activity_label.configure(text=f"🔵 {count} Teammitglieder aktiv")
else:
self.activity_label.configure(text="")
# Update project tiles
new_state['team_label'] = f"🔵 {count} Teammitglieder aktiv"
# 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():
if hasattr(tile, 'check_activity'):
tile.check_activity()
@ -3152,17 +3224,47 @@ class MainWindow:
from services.activity_sync import activity_service
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:
# Update display with current activities
logger.debug(f"Updating activity display with {len(activity_service.activities)} activities")
self.update_activity_display(activity_service.activities)
else:
logger.debug("Activity service not connected, skipping update")
# Check if activities have actually changed
current_hash = self._compute_activities_hash(activity_service.activities)
# Only update if activities have changed
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
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):
"""Show tooltip with own active projects"""
@ -3369,6 +3471,19 @@ class MainWindow:
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):
"""Run the application"""
self.root.mainloop()

Datei anzeigen

@ -879,13 +879,13 @@ pause
"""Check if this project has active users"""
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
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
active_users = []
is_own_activity = False
@ -908,25 +908,30 @@ pause
else:
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 is_own_current:
is_own_activity = True
if activity_service.user_name not in active_users:
active_users.append(activity_service.user_name)
logger.debug(f"Added local user to active_users: {activity_service.user_name}")
if active_users:
# Show indicator with all active users
user_text = ", ".join(active_users)
# If both own and others are active, show as others (orange) to indicate collaboration
final_is_own = is_own_activity and not has_other_users
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:
logger.info(f"Updating activity status for {self.project.name}: active=False")
self.update_activity_status(False)
# Determine new state
new_state = {
'active': bool(active_users),
'users': ', '.join(active_users) if active_users else '',
'is_own': is_own_activity and not has_other_users if active_users else False
}
# Only update UI if state has changed
if new_state != self._last_activity_state:
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):
"""Start animated border for team activity"""