228 Zeilen
9.0 KiB
Python
228 Zeilen
9.0 KiB
Python
"""
|
|
Project Manager Module
|
|
Handles project storage, loading, and management
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from datetime import datetime
|
|
from typing import List, Dict, Optional
|
|
import uuid
|
|
from utils.logger import logger
|
|
|
|
class Project:
|
|
def __init__(self, name: str, path: str, project_id: str = None):
|
|
self.id = project_id or str(uuid.uuid4())
|
|
self.name = name
|
|
self.path = path
|
|
self.created_at = datetime.now().isoformat()
|
|
self.last_accessed = datetime.now().isoformat()
|
|
self.readme_path = os.path.join(path, "CLAUDE_PROJECT_README.md")
|
|
self.description = ""
|
|
self.tags = []
|
|
self.gitea_repo = None # Format: "owner/repo_name" or None if not linked
|
|
|
|
def to_dict(self) -> Dict:
|
|
"""Convert project to dictionary for JSON storage"""
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'path': self.path,
|
|
'created_at': self.created_at,
|
|
'last_accessed': self.last_accessed,
|
|
'readme_path': self.readme_path,
|
|
'description': self.description,
|
|
'tags': self.tags,
|
|
'gitea_repo': self.gitea_repo
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict) -> 'Project':
|
|
"""Create project from dictionary"""
|
|
project = cls(data['name'], data['path'], data['id'])
|
|
project.created_at = data.get('created_at', datetime.now().isoformat())
|
|
project.last_accessed = data.get('last_accessed', datetime.now().isoformat())
|
|
project.readme_path = data.get('readme_path', os.path.join(data['path'], "CLAUDE_PROJECT_README.md"))
|
|
project.description = data.get('description', '')
|
|
project.tags = data.get('tags', [])
|
|
project.gitea_repo = data.get('gitea_repo', None)
|
|
return project
|
|
|
|
def update_last_accessed(self):
|
|
"""Update last accessed timestamp"""
|
|
self.last_accessed = datetime.now().isoformat()
|
|
|
|
class ProjectManager:
|
|
def __init__(self, data_file: str = "data/projects.json"):
|
|
self.data_file = data_file
|
|
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"""
|
|
data_dir = os.path.dirname(self.data_file)
|
|
if data_dir and not os.path.exists(data_dir):
|
|
os.makedirs(data_dir)
|
|
|
|
def _initialize_vps_project(self):
|
|
"""Initialize the permanent VPS project"""
|
|
vps_id = "vps-permanent"
|
|
if vps_id not in self.projects:
|
|
self.vps_project = Project(
|
|
name="VPS Server",
|
|
path="claude-dev@91.99.192.14",
|
|
project_id=vps_id
|
|
)
|
|
self.vps_project.description = "Remote VPS Server with Claude"
|
|
self.vps_project.tags = ["vps", "remote", "server"]
|
|
self.projects[vps_id] = self.vps_project
|
|
self.save_projects()
|
|
else:
|
|
self.vps_project = self.projects[vps_id]
|
|
|
|
def _initialize_admin_panel_project(self):
|
|
"""Initialize the permanent Admin Panel project"""
|
|
admin_id = "admin-panel-permanent"
|
|
if admin_id not in self.projects:
|
|
self.admin_panel_project = Project(
|
|
name="Admin Panel",
|
|
path="/opt/v2-Docker",
|
|
project_id=admin_id
|
|
)
|
|
self.admin_panel_project.description = "V2 Docker Admin Panel"
|
|
self.admin_panel_project.tags = ["admin", "docker", "v2"]
|
|
self.projects[admin_id] = self.admin_panel_project
|
|
self.save_projects()
|
|
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")
|
|
if os.path.exists(self.data_file):
|
|
try:
|
|
with open(self.data_file, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
for proj_data in data.get('projects', []):
|
|
project = Project.from_dict(proj_data)
|
|
self.projects[project.id] = project
|
|
except Exception as e:
|
|
print(f"Error loading projects: {e}")
|
|
self.projects = {}
|
|
|
|
def save_projects(self):
|
|
"""Save projects to JSON file"""
|
|
logger.debug("Saving projects to file")
|
|
try:
|
|
data = {
|
|
'projects': [proj.to_dict() for proj in self.projects.values()],
|
|
'last_updated': datetime.now().isoformat()
|
|
}
|
|
with open(self.data_file, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
except Exception as e:
|
|
print(f"Error saving projects: {e}")
|
|
|
|
def add_project(self, name: str, path: str) -> Project:
|
|
"""Add new project"""
|
|
logger.info(f"Adding project: {name} at {path}")
|
|
# Check if project with same path already exists
|
|
for project in self.projects.values():
|
|
if project.path == path and project.id != "vps-permanent":
|
|
# Update existing project
|
|
project.name = name
|
|
project.update_last_accessed()
|
|
self.save_projects()
|
|
return project
|
|
|
|
# Create new project
|
|
project = Project(name, path)
|
|
self.projects[project.id] = project
|
|
self.save_projects()
|
|
return project
|
|
|
|
def remove_project(self, project_id: str) -> bool:
|
|
"""Remove project by ID"""
|
|
logger.info(f"Removing project with ID: {project_id}")
|
|
if project_id in self.projects and project_id not in ["vps-permanent", "admin-panel-permanent"]:
|
|
del self.projects[project_id]
|
|
self.save_projects()
|
|
return True
|
|
return False
|
|
|
|
def get_project(self, project_id: str) -> Optional[Project]:
|
|
"""Get project by ID"""
|
|
return self.projects.get(project_id)
|
|
|
|
def get_all_projects(self) -> List[Project]:
|
|
"""Get all projects sorted alphabetically by name"""
|
|
projects = list(self.projects.values())
|
|
# 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"]
|
|
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 + activity + others
|
|
|
|
def update_project(self, project_id: str, **kwargs):
|
|
"""Update project properties"""
|
|
project = self.get_project(project_id)
|
|
if project:
|
|
for key, value in kwargs.items():
|
|
if hasattr(project, key):
|
|
setattr(project, key, value)
|
|
self.save_projects()
|
|
|
|
def search_projects(self, query: str) -> List[Project]:
|
|
"""Search projects by name, path, or tags"""
|
|
query = query.lower()
|
|
results = []
|
|
for project in self.projects.values():
|
|
if (query in project.name.lower() or
|
|
query in project.path.lower() or
|
|
any(query in tag.lower() for tag in project.tags)):
|
|
results.append(project)
|
|
return results
|
|
|
|
# Test the module
|
|
if __name__ == "__main__":
|
|
# Create manager
|
|
manager = ProjectManager("test_projects.json")
|
|
|
|
# Add test projects
|
|
proj1 = manager.add_project("Test Project 1", "C:\\Users\\test\\project1")
|
|
proj2 = manager.add_project("Test Project 2", "C:\\Users\\test\\project2")
|
|
|
|
# List all projects
|
|
print("All projects:")
|
|
for project in manager.get_all_projects():
|
|
print(f"- {project.name} ({project.path})")
|
|
|
|
# Clean up test file
|
|
import os
|
|
if os.path.exists("test_projects.json"):
|
|
os.remove("test_projects.json") |