diff --git a/dashboard/api.py b/dashboard/api.py index ebcc278..376a470 100644 --- a/dashboard/api.py +++ b/dashboard/api.py @@ -34,7 +34,6 @@ from constants import ( # noqa: E402 re-exported for tests VENV_PYTHON, WORKSPACE_DIR, ) -from handlers.agents import AgentsHandlers # noqa: E402 from handlers.cron import CronHandlers # noqa: E402 from handlers.eco import EcoHandlers # noqa: E402 from handlers.files import FilesHandlers # noqa: E402 @@ -54,7 +53,6 @@ class TaskBoardHandler( YoutubeHandlers, WorkspaceHandlers, CronHandlers, - AgentsHandlers, SimpleHTTPRequestHandler, ): """HTTP request handler — dispatches to handler-mixin methods.""" @@ -90,12 +88,8 @@ class TaskBoardHandler( self.send_json({'status': 'ok', 'time': _dt.now().isoformat()}) elif self.path == '/api/git' or self.path.startswith('/api/git?'): self.handle_git_status() - elif self.path == '/api/agents' or self.path.startswith('/api/agents?'): - self.handle_agents_status() elif self.path == '/api/cron' or self.path.startswith('/api/cron?'): self.handle_cron_status() - elif self.path == '/api/activity' or self.path.startswith('/api/activity?'): - self.handle_activity() elif self.path == '/api/habits': self.handle_habits_get() elif self.path.startswith('/api/files'): diff --git a/dashboard/handlers/agents.py b/dashboard/handlers/agents.py deleted file mode 100644 index 530fafd..0000000 --- a/dashboard/handlers/agents.py +++ /dev/null @@ -1,203 +0,0 @@ -"""LEGACY: /api/agents and /api/activity endpoints (clawdbot era). - -These read from ~/.clawdbot/ and shell out to the `clawdbot` CLI. -Scheduled for removal once the post-decommission work completes. -""" -import json -import subprocess -from datetime import datetime, timezone as dt_timezone -from pathlib import Path -from zoneinfo import ZoneInfo - -import constants - - -class AgentsHandlers: - """Mixin providing /api/agents and /api/activity (deprecated).""" - - def handle_agents_status(self): - """Get agents status — reads session files from ~/.clawdbot/agents.""" - try: - agents_config = [ - {'id': 'echo', 'name': 'Echo', 'emoji': '🌀'}, - {'id': 'echo-work', 'name': 'Work', 'emoji': '⚡'}, - {'id': 'echo-health', 'name': 'Health', 'emoji': '❤️'}, - {'id': 'echo-growth', 'name': 'Growth', 'emoji': '🪜'}, - {'id': 'echo-sprijin', 'name': 'Sprijin', 'emoji': '⭕'}, - {'id': 'echo-scout', 'name': 'Scout', 'emoji': '⚜️'}, - ] - - active_agents = set() - sessions_base = Path.home() / '.clawdbot' / 'agents' - - if sessions_base.exists(): - for agent_dir in sessions_base.iterdir(): - if agent_dir.is_dir(): - sessions_file = agent_dir / 'sessions' / 'sessions.json' - if sessions_file.exists(): - try: - data = json.loads(sessions_file.read_text()) - now = datetime.now().timestamp() * 1000 - for _key, sess in data.items(): - if isinstance(sess, dict): - last_active = sess.get('updatedAt', 0) - if now - last_active < 30 * 60 * 1000: - active_agents.add(agent_dir.name) - break - except Exception: - pass - - agents = [ - {**cfg, 'active': cfg['id'] in active_agents} - for cfg in agents_config - ] - self.send_json({'agents': agents}) - except Exception as e: - self.send_json({'error': str(e)}, 500) - - def handle_activity(self): - """Aggregate activity from multiple sources: cron jobs, git commits, file changes.""" - try: - activities = [] - bucharest = ZoneInfo('Europe/Bucharest') - workspace = constants.GIT_WORKSPACE - - # 1. Cron jobs ran today - try: - result = subprocess.run( - ['clawdbot', 'cron', 'list', '--json'], - capture_output=True, text=True, timeout=10, - ) - if result.returncode == 0: - cron_data = json.loads(result.stdout) - today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - today_start_ms = today_start.timestamp() * 1000 - - for job in cron_data.get('jobs', []): - state = job.get('state', {}) - last_run = state.get('lastRunAtMs', 0) - if last_run >= today_start_ms: - run_time = datetime.fromtimestamp(last_run / 1000, tz=dt_timezone.utc) - local_time = run_time.astimezone(bucharest) - activities.append({ - 'type': 'cron', - 'icon': 'clock', - 'text': f"Job: {job.get('name', 'unknown')}", - 'agent': job.get('agentId', 'echo'), - 'time': local_time.strftime('%H:%M'), - 'timestamp': last_run, - 'status': state.get('lastStatus', 'ok'), - }) - except Exception: - pass - - # 2. Git commits (last 24h) - try: - result = subprocess.run( - ['git', 'log', '--oneline', '--since=24 hours ago', '--format=%H|%s|%at'], - cwd=str(workspace), capture_output=True, text=True, timeout=10, - ) - if result.returncode == 0: - for line in result.stdout.strip().split('\n'): - if '|' in line: - parts = line.split('|') - if len(parts) >= 3: - commit_hash, message, timestamp = parts[0], parts[1], int(parts[2]) - commit_time = datetime.fromtimestamp(timestamp, tz=dt_timezone.utc) - local_time = commit_time.astimezone(bucharest) - activities.append({ - 'type': 'git', - 'icon': 'git-commit', - 'text': message[:60] + ('...' if len(message) > 60 else ''), - 'agent': 'git', - 'time': local_time.strftime('%H:%M'), - 'timestamp': timestamp * 1000, - 'commitHash': commit_hash[:8], - }) - except Exception: - pass - - # 2b. Git uncommitted files - try: - result = subprocess.run( - ['git', 'status', '--short'], - cwd=str(workspace), capture_output=True, text=True, timeout=10, - ) - if result.returncode == 0 and result.stdout.strip(): - for line in result.stdout.strip().split('\n'): - if len(line) >= 4: - status = line[:2] - filepath = line[2:].lstrip() - if not filepath: - continue - status_clean = status.strip() - status_labels = {'M': 'modificat', 'A': 'adăugat', 'D': 'șters', '??': 'nou', 'R': 'redenumit'} - status_label = status_labels.get(status_clean, status_clean) - activities.append({ - 'type': 'git-file', - 'icon': 'file-diff', - 'text': f"{filepath}", - 'agent': f"git ({status_label})", - 'time': 'acum', - 'timestamp': int(datetime.now().timestamp() * 1000), - 'path': filepath, - 'gitStatus': status_clean, - }) - except Exception: - pass - - # 3. Recent files in memory/kb/ (last 24h) - try: - kb_dir = workspace / 'kb' - cutoff = datetime.now().timestamp() - (24 * 3600) - for md_file in kb_dir.rglob('*.md'): - stat = md_file.stat() - if stat.st_mtime > cutoff: - file_time = datetime.fromtimestamp(stat.st_mtime, tz=dt_timezone.utc) - local_time = file_time.astimezone(bucharest) - rel_path = md_file.relative_to(workspace) - activities.append({ - 'type': 'file', - 'icon': 'file-text', - 'text': f"Fișier: {md_file.name}", - 'agent': str(rel_path.parent), - 'time': local_time.strftime('%H:%M'), - 'timestamp': int(stat.st_mtime * 1000), - 'path': str(rel_path), - }) - except Exception: - pass - - # 4. Tasks from tasks.json - try: - tasks_file = workspace / 'dashboard' / 'tasks.json' - if tasks_file.exists(): - tasks_data = json.loads(tasks_file.read_text()) - for col in tasks_data.get('columns', []): - for task in col.get('tasks', []): - ts_str = task.get('completed') or task.get('created', '') - if ts_str: - try: - ts = datetime.fromisoformat(ts_str.replace('Z', '+00:00')) - if ts.timestamp() > (datetime.now().timestamp() - 7 * 24 * 3600): - local_time = ts.astimezone(bucharest) - activities.append({ - 'type': 'task', - 'icon': 'check-circle' if task.get('completed') else 'circle', - 'text': task.get('title', ''), - 'agent': task.get('agent', 'Echo'), - 'time': local_time.strftime('%d %b %H:%M'), - 'timestamp': int(ts.timestamp() * 1000), - 'status': 'done' if task.get('completed') else col['id'], - }) - except Exception: - pass - except Exception: - pass - - activities.sort(key=lambda x: x.get('timestamp', 0), reverse=True) - activities = activities[:30] - - self.send_json({'activities': activities, 'total': len(activities)}) - except Exception as e: - self.send_json({'error': str(e)}, 500) diff --git a/dashboard/index.html b/dashboard/index.html index 5d6addf..d674a87 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -155,33 +155,6 @@ color: #818cf8; } - .status-section-icon.agents { - background: rgba(168, 85, 247, 0.15); - color: #a855f7; - } - - .agents-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: var(--space-2); - } - - .agent-chip { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - background: var(--bg-elevated); - border-radius: var(--radius-md); - font-size: var(--text-xs); - } - - .agent-chip .emoji { font-size: 14px; } - .agent-chip .name { font-weight: 500; color: var(--text-primary); } - .agent-chip .status { color: var(--text-muted); } - .agent-chip.active { background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); } - .agent-chip.active .status { color: #22c55e; } - .status-section-info { flex: 1; min-width: 0; @@ -426,121 +399,6 @@ overflow-y: auto; } - /* Activity panel */ - .activity-panel .panel-header { - background: linear-gradient(135deg, rgba(99, 102, 241, 0.15), rgba(139, 92, 246, 0.1)); - } - - .activity-panel .panel-title svg { - color: #8b5cf6; - } - - .activity-section { - margin-bottom: var(--space-4); - } - - .activity-section-title { - font-size: var(--text-xs); - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: var(--space-2); - display: flex; - align-items: center; - gap: var(--space-2); - } - - .activity-item { - display: flex; - align-items: flex-start; - gap: var(--space-3); - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-md); - margin-bottom: var(--space-1); - transition: background var(--transition-fast); - } - - .activity-item:hover { - background: var(--bg-elevated); - } - - .activity-icon { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - flex-shrink: 0; - } - - .activity-icon.done, .activity-icon.task { - background: rgba(34, 197, 94, 0.2); - color: #22c55e; - } - - .activity-icon.running { - background: rgba(59, 130, 246, 0.2); - color: #3b82f6; - } - - .activity-icon.cron { - background: rgba(168, 85, 247, 0.2); - color: #a855f7; - } - - .activity-icon.git { - background: rgba(249, 115, 22, 0.2); - color: #f97316; - } - - .activity-icon.git-file { - background: rgba(234, 179, 8, 0.2); - color: #eab308; - } - - .activity-icon.file { - background: rgba(20, 184, 166, 0.2); - color: #14b8a6; - } - - .activity-icon svg { - width: 14px; - height: 14px; - } - - .activity-type { - font-size: var(--text-xs); - padding: 2px 6px; - border-radius: 4px; - background: var(--bg-surface); - } - - .activity-content { - flex: 1; - min-width: 0; - } - - .activity-text { - font-size: var(--text-sm); - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .activity-meta { - font-size: var(--text-xs); - color: var(--text-muted); - display: flex; - gap: var(--space-2); - } - - .activity-agent { - color: var(--accent); - } - .progress-bar { height: 4px; background: var(--bg-elevated); @@ -1231,29 +1089,6 @@ - -
Se încarcă...
-Nicio activitate recentă
-${item.commitHash}` : ''}${item.text}