#!/usr/bin/env python3 import json import os import socket import sys import time import urllib.parse from datetime import datetime from http.server import HTTPServer, BaseHTTPRequestHandler from socketserver import ThreadingMixIn class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): """Handle requests in a separate thread.""" daemon_threads = True class ControlPanelHandler(BaseHTTPRequestHandler): def __init__(self, *args, **kwargs): self.data_dir = "/home/wdjones/.openclaw/workspace/projects/control-panel/data" super().__init__(*args, **kwargs) def do_GET(self): try: self.handle_get() except Exception as e: self.send_error(500, f"Internal error: {e}") def do_POST(self): try: self.handle_post() except Exception as e: self.send_error(500, f"Internal error: {e}") def handle_get(self): if self.path == '/': self.serve_dashboard() elif self.path == '/accounts': self.serve_accounts() elif self.path == '/api-keys': self.serve_api_keys() elif self.path == '/services': self.serve_services() elif self.path == '/budget': self.serve_budget() elif self.path == '/activity': self.serve_activity() elif self.path == '/todos': self.serve_todos() else: self.send_error(404, "Not found") def handle_post(self): content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length).decode('utf-8') form_data = urllib.parse.parse_qs(post_data) if self.path == '/accounts': self.handle_accounts_post(form_data) elif self.path == '/api-keys': self.handle_api_keys_post(form_data) elif self.path == '/budget': self.handle_budget_post(form_data) elif self.path == '/todos': self.handle_todos_post(form_data) else: self.send_error(404, "Not found") def load_data(self, filename): filepath = os.path.join(self.data_dir, filename) if os.path.exists(filepath): with open(filepath, 'r') as f: return json.load(f) return [] def save_data(self, filename, data): os.makedirs(self.data_dir, exist_ok=True) filepath = os.path.join(self.data_dir, filename) with open(filepath, 'w') as f: json.dump(data, f, indent=2) def log_activity(self, action, details=""): activity = self.load_data('activity.json') entry = { "timestamp": datetime.now().isoformat(), "action": action, "details": details } activity.insert(0, entry) # Latest first activity = activity[:100] # Keep last 100 entries self.save_data('activity.json', activity) def check_service_health(self, port): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(2) result = sock.connect_ex(('localhost', port)) sock.close() return result == 0 except: return False def get_base_template(self, title, content): return f""" {title} - Case Control Panel
{content}
""" def serve_dashboard(self): accounts = self.load_data('accounts.json') api_keys = self.load_data('api-keys.json') budget = self.load_data('budget.json') todos = self.load_data('todos.json') # Calculate stats total_accounts = len(accounts) active_accounts = len([a for a in accounts if a.get('status') == 'active']) total_api_keys = len(api_keys) pending_todos = len([t for t in todos if t.get('status') == 'pending']) monthly_spend = sum([b.get('amount', 0) for b in budget if b.get('type') == 'spending' and b.get('timestamp', '').startswith(datetime.now().strftime('%Y-%m'))]) content = f"""
{total_accounts}
Total Accounts
{active_accounts}
Active Services
{total_api_keys}
API Keys
{pending_todos}
⚡ Actions Required
Quick Actions
Manage Accounts Manage API Keys Add Budget Entry Check Services
""" html = self.get_base_template("Dashboard", content) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_accounts(self): accounts = self.load_data('accounts.json') accounts_table = "" for i, account in enumerate(accounts): status_class = "status-active" if account.get('status') == 'active' else "status-inactive" login_btn = f'Login' if account.get('url') else "" accounts_table += f""" {account.get('service', 'N/A')} {account.get('url', 'N/A')} {account.get('username', 'N/A')} {account.get('status', 'unknown')} {account.get('last_accessed', 'Never')} {account.get('notes', '')} {login_btn} """ content = f"""
Account Management
{accounts_table}
Service URL Username/Email Status Last Accessed Notes Actions
""" html = self.get_base_template("Accounts", content) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_api_keys(self): api_keys = self.load_data('api-keys.json') keys_table = "" for key in api_keys: masked_key = f'' + \ ('*' * len(key.get('key', ''))) + '' keys_table += f""" {key.get('service', 'N/A')} {key.get('name', 'N/A')} {masked_key} {key.get('created', 'N/A')} {key.get('expires', 'Never')} {key.get('usage', 0)} """ content = f"""
API Key Management
{keys_table}
Service Name Key (click to reveal) Created Expires Usage Count
""" html = self.get_base_template("API Keys", content) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_services(self): services = [ {"name": "Feed Hunter Portal", "port": 8888, "path": ""}, {"name": "Chrome Debug", "port": 9222, "path": ""}, {"name": "OpenClaw Gateway", "port": 18789, "path": ""}, {"name": "Case Control Panel", "port": 8000, "path": ""}, ] services_table = "" for service in services: is_healthy = self.check_service_health(service["port"]) status = "Running" if is_healthy else "Stopped" status_class = "status-active" if is_healthy else "status-inactive" url = f"http://localhost:{service['port']}{service['path']}" link = f'{service["name"]}' services_table += f""" {link} {service['port']} {status} N/A """ content = f"""
Running Services
{services_table}
Service Name Port Status Uptime
""" html = self.get_base_template("Services", content) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_budget(self): budget = self.load_data('budget.json') # Calculate totals total_balance = sum([b.get('amount', 0) for b in budget if b.get('type') == 'deposit']) - \ sum([b.get('amount', 0) for b in budget if b.get('type') in ['withdrawal', 'spending']]) current_month = datetime.now().strftime('%Y-%m') monthly_spending = sum([b.get('amount', 0) for b in budget if b.get('type') == 'spending' and b.get('timestamp', '').startswith(current_month)]) budget_table = "" for entry in sorted(budget, key=lambda x: x.get('timestamp', ''), reverse=True)[:50]: amount_str = f"${entry.get('amount', 0):.2f}" if entry.get('type') == 'deposit': amount_str = f"+{amount_str}" elif entry.get('type') in ['withdrawal', 'spending']: amount_str = f"-{amount_str}" budget_table += f""" {entry.get('timestamp', 'N/A')} {entry.get('type', 'N/A')} {entry.get('service', 'General')} {amount_str} {entry.get('description', '')} """ content = f"""
${total_balance:.2f}
Total Balance
${monthly_spending:.2f}
Monthly Spending
Budget Management
{budget_table}
Date Type Service Amount Description
""" html = self.get_base_template("Budget", content) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_activity(self): activity = self.load_data('activity.json') activity_list = "" for entry in activity: activity_list += f"""
{entry.get('timestamp', 'N/A')}
{entry.get('action', 'N/A')}
{entry.get('details', '')}
""" content = f"""
Activity Log
{activity_list if activity_list else '

No activity recorded yet.

'}
""" html = self.get_base_template("Activity", content) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_todos(self): todos = self.load_data('todos.json') pending = [t for t in todos if t.get('status') == 'pending'] done = [t for t in todos if t.get('status') == 'done'] priority_colors = {'high': '#f85149', 'medium': '#d29922', 'low': '#8b949e'} category_icons = {'dns': '🌐', 'account': '🔑', 'config': '⚙️', 'install': '📦', 'other': '📋'} pending_html = "" for i, t in enumerate(pending): pc = priority_colors.get(t.get('priority', 'medium'), '#d29922') icon = category_icons.get(t.get('category', 'other'), '📋') steps = "" if t.get('steps'): steps_list = "".join(f"
  • {s}
  • " for s in t['steps']) steps = f'
    Steps:
      {steps_list}
    ' pending_html += f"""
    {icon} {t.get('title','Untitled')} ● {t.get('priority','medium').upper()}
    {t.get('description','')}
    {steps}
    Added {t.get('created','?')} by {t.get('source','unknown')}
    """ done_html = "" for t in done[:10]: done_html += f"""
    {t.get('title','')} completed {t.get('completed','')}
    """ content = f"""
    {len(pending)}
    Pending Actions
    {len(done)}
    Completed
    ⚡ Action Required
    {pending_html if pending_html else '

    Nothing pending — all clear! 🎉

    '}
    Recently Completed
    {done_html if done_html else '

    Nothing completed yet.

    '}
    """ html = self.get_base_template("Action Required", content) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def handle_todos_post(self, form_data): action = form_data.get('action', [''])[0] todos = self.load_data('todos.json') if action == 'complete': idx = int(form_data.get('index', ['0'])[0]) pending = [t for t in todos if t.get('status') == 'pending'] if 0 <= idx < len(pending): target = pending[idx] target['status'] = 'done' target['completed'] = datetime.now().strftime('%Y-%m-%d %H:%M') self.save_data('todos.json', todos) self.log_activity("Todo Completed", target.get('title', '')) self.send_response(302) self.send_header('Location', '/todos') self.end_headers() def handle_accounts_post(self, form_data): if form_data.get('action', [''])[0] == 'add': accounts = self.load_data('accounts.json') new_account = { "service": form_data.get('service', [''])[0], "url": form_data.get('url', [''])[0], "username": form_data.get('username', [''])[0], "status": form_data.get('status', ['active'])[0], "notes": form_data.get('notes', [''])[0], "created": datetime.now().isoformat(), "last_accessed": "Never" } accounts.append(new_account) self.save_data('accounts.json', accounts) self.log_activity("Account Added", f"Added {new_account['service']}") # Redirect back to accounts page self.send_response(302) self.send_header('Location', '/accounts') self.end_headers() def handle_api_keys_post(self, form_data): if form_data.get('action', [''])[0] == 'add': api_keys = self.load_data('api-keys.json') new_key = { "service": form_data.get('service', [''])[0], "name": form_data.get('name', [''])[0], "key": form_data.get('key', [''])[0], "created": datetime.now().strftime('%Y-%m-%d'), "expires": form_data.get('expires', ['Never'])[0] or "Never", "usage": 0 } api_keys.append(new_key) self.save_data('api-keys.json', api_keys) self.log_activity("API Key Added", f"Added key for {new_key['service']}") # Redirect back to api-keys page self.send_response(302) self.send_header('Location', '/api-keys') self.end_headers() def handle_budget_post(self, form_data): if form_data.get('action', [''])[0] == 'add': budget = self.load_data('budget.json') new_entry = { "type": form_data.get('type', [''])[0], "service": form_data.get('service', ['General'])[0] or "General", "amount": float(form_data.get('amount', ['0'])[0]), "description": form_data.get('description', [''])[0], "timestamp": datetime.now().isoformat() } budget.append(new_entry) self.save_data('budget.json', budget) self.log_activity("Budget Entry Added", f"{new_entry['type']} of ${new_entry['amount']:.2f}") # Redirect back to budget page self.send_response(302) self.send_header('Location', '/budget') self.end_headers() def log_message(self, format, *args): """Override to reduce logging noise""" pass def initialize_data(): """Pre-populate with known accounts and services""" data_dir = "/home/wdjones/.openclaw/workspace/projects/control-panel/data" os.makedirs(data_dir, exist_ok=True) # Pre-populate accounts accounts_file = os.path.join(data_dir, "accounts.json") if not os.path.exists(accounts_file): initial_accounts = [ { "service": "ProtonMail", "url": "https://mail.proton.me", "username": "case-lgn@protonmail.com", "status": "active", "notes": "Primary email account", "created": datetime.now().isoformat(), "last_accessed": "Never" }, { "service": "Polymarket", "url": "https://polymarket.com", "username": "", "status": "inactive", "notes": "Not yet registered", "created": datetime.now().isoformat(), "last_accessed": "Never" }, { "service": "Feed Hunter Portal", "url": "http://localhost:8888", "username": "", "status": "active", "notes": "Local service", "created": datetime.now().isoformat(), "last_accessed": "Never" }, { "service": "Chrome Debug", "url": "http://localhost:9222", "username": "", "status": "active", "notes": "Browser debugging interface", "created": datetime.now().isoformat(), "last_accessed": "Never" }, { "service": "OpenClaw Gateway", "url": "http://localhost:18789", "username": "", "status": "active", "notes": "OpenClaw main service", "created": datetime.now().isoformat(), "last_accessed": "Never" } ] with open(accounts_file, 'w') as f: json.dump(initial_accounts, f, indent=2) # Initialize empty files if they don't exist for filename in ["api-keys.json", "budget.json", "activity.json"]: filepath = os.path.join(data_dir, filename) if not os.path.exists(filepath): with open(filepath, 'w') as f: json.dump([], f) def main(): initialize_data() server_address = ('0.0.0.0', 8000) httpd = ThreadedHTTPServer(server_address, ControlPanelHandler) print(f"🖤 Case Control Panel starting on http://0.0.0.0:8000") print("Press Ctrl+C to stop") try: httpd.serve_forever() except KeyboardInterrupt: print("\nShutting down...") httpd.shutdown() sys.exit(0) if __name__ == '__main__': main()