#!/usr/bin/env python3 """ Stargate Server Status Dashboard A beautiful TUI dashboard for monitoring server status. Usage: status # One-time snapshot status -l # Live mode (updates every 2s) status -l -r 5 # Live mode with 5s refresh """ import subprocess import argparse import sys import signal from datetime import datetime from rich.console import Console from rich.table import Table from rich.panel import Panel from rich.layout import Layout from rich.live import Live from rich.text import Text from rich.align import Align from rich import box console = Console() # ASCII art header HEADER = """ ╔═╗╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗ ╚═╗ ║ ╠═╣╠╦╝║ ╦╠═╣ ║ ║╣ ╚═╝ ╩ ╩ ╩╩╚═╚═╝╩ ╩ ╩ ╚═╝ """ def ssh_command(cmd: str, timeout: int = 5) -> str: """Execute command on server via SSH.""" try: result = subprocess.run( ["ssh", "-o", "ConnectTimeout=5", "server", cmd], capture_output=True, text=True, timeout=timeout ) return result.stdout.strip() if result.returncode == 0 else "" except (subprocess.TimeoutExpired, Exception): return "" def fetch_data() -> dict: """Fetch all data from server.""" return { "uptime": ssh_command("uptime -p"), "load": ssh_command("cat /proc/loadavg | cut -d' ' -f1-3"), "memory": ssh_command("free -h | awk '/^Mem:/ {print $3\"/\"$2}'"), "onion_main": ssh_command("sudo cat /var/lib/tor/krisyotam/hostname"), "onion_notes": ssh_command("sudo cat /var/lib/tor/notes-krisyotam/hostname"), "nginx": ssh_command("systemctl is-active nginx"), "tor": ssh_command("systemctl is-active tor"), "disks": ssh_command("df -h / /home /mnt/storage 2>/dev/null | tail -n +2"), "services": ssh_command("docker service ls --format '{{.Name}}|{{.Replicas}}'"), "containers": ssh_command("docker ps --format '{{.Names}}|{{.Status}}' | head -12"), } def make_header() -> Panel: """Create header panel.""" header_text = Text(HEADER, style="bold magenta", justify="center") return Panel( Align.center(header_text), box=box.ROUNDED, border_style="magenta", padding=(0, 0), ) def make_onion_panel(data: dict) -> Panel: """Create Tor hidden services panel.""" table = Table(show_header=False, box=None, padding=(0, 2)) table.add_column("Site", style="bold white", width=22) table.add_column("Onion Address", style="green") onion_main = data.get("onion_main") or "[dim]unavailable[/dim]" onion_notes = data.get("onion_notes") or "[dim]unavailable[/dim]" table.add_row("krisyotam.com", onion_main) table.add_row("notes.krisyotam.com", onion_notes) return Panel( table, title="[bold cyan]TOR HIDDEN SERVICES[/bold cyan]", box=box.ROUNDED, border_style="cyan", ) def make_system_panel(data: dict) -> Panel: """Create system info panel.""" table = Table(show_header=False, box=None, padding=(0, 2)) table.add_column("Metric", style="bold white", width=15) table.add_column("Value", style="white") table.add_row("Uptime", data.get("uptime") or "unavailable") table.add_row("Load", data.get("load") or "unavailable") table.add_row("Memory", data.get("memory") or "unavailable") nginx_status = data.get("nginx", "") tor_status = data.get("tor", "") nginx_style = "[green]running[/green]" if nginx_status == "active" else "[red]stopped[/red]" tor_style = "[green]running[/green]" if tor_status == "active" else "[red]stopped[/red]" table.add_row("nginx", nginx_style) table.add_row("tor", tor_style) return Panel( table, title="[bold cyan]SYSTEM[/bold cyan]", box=box.ROUNDED, border_style="cyan", ) def make_disk_panel(data: dict) -> Panel: """Create disk usage panel.""" table = Table(box=box.SIMPLE, padding=(0, 1)) table.add_column("Mount", style="bold white", width=16) table.add_column("Size", justify="right", width=8) table.add_column("Used", justify="right", width=8) table.add_column("Avail", justify="right", width=8) table.add_column("Use%", justify="right", width=8) disks = data.get("disks", "") if disks: for line in disks.strip().split("\n"): if line: parts = line.split() if len(parts) >= 5: fs = parts[0][:16] size, used, avail = parts[1], parts[2], parts[3] pct = parts[4].rstrip('%') try: pct_int = int(pct) if pct_int >= 90: pct_style = f"[red]{pct}%[/red]" elif pct_int >= 70: pct_style = f"[yellow]{pct}%[/yellow]" else: pct_style = f"[green]{pct}%[/green]" except ValueError: pct_style = f"{pct}%" table.add_row(fs, size, used, avail, pct_style) return Panel( table, title="[bold cyan]DISK USAGE[/bold cyan]", box=box.ROUNDED, border_style="cyan", ) def make_services_panel(data: dict) -> Panel: """Create Docker Swarm services panel.""" table = Table(box=box.SIMPLE, padding=(0, 1)) table.add_column("Service", style="bold white", width=35) table.add_column("Replicas", justify="right", width=12) services = data.get("services", "") if services: for line in services.strip().split("\n"): if "|" in line: name, replicas = line.split("|", 1) if replicas.strip() == "1/1": rep_style = f"[green]{replicas}[/green]" else: rep_style = f"[red]{replicas}[/red]" table.add_row(name.strip(), rep_style) else: table.add_row("[dim]No services found[/dim]", "") return Panel( table, title="[bold cyan]DOCKER SWARM[/bold cyan]", box=box.ROUNDED, border_style="cyan", ) def make_containers_panel(data: dict) -> Panel: """Create Docker containers panel.""" table = Table(box=box.SIMPLE, padding=(0, 1)) table.add_column("Container", style="bold white", width=28) table.add_column("Status", width=30) containers = data.get("containers", "") if containers: for line in containers.strip().split("\n"): if "|" in line: name, status = line.split("|", 1) name = name.strip()[:28] status = status.strip()[:30] if "Up" in status: status_style = f"[green]{status}[/green]" else: status_style = f"[red]{status}[/red]" table.add_row(name, status_style) else: table.add_row("[dim]No containers found[/dim]", "") return Panel( table, title="[bold cyan]CONTAINERS[/bold cyan]", box=box.ROUNDED, border_style="cyan", ) def make_footer(live_mode: bool, refresh: int) -> Text: """Create footer text.""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if live_mode: return Text(f" Last updated: {timestamp} │ Refresh: {refresh}s │ Press Ctrl+C to exit ", style="dim") else: return Text(f" Last updated: {timestamp} │ Run with -l for live mode ", style="dim") def build_dashboard(data: dict, live_mode: bool = False, refresh: int = 2) -> Layout: """Build the complete dashboard layout.""" layout = Layout() layout.split_column( Layout(name="header", size=6), Layout(name="onion", size=6), Layout(name="middle", size=9), Layout(name="bottom"), Layout(name="footer", size=1), ) layout["middle"].split_row( Layout(name="system"), Layout(name="disks"), ) layout["bottom"].split_row( Layout(name="services"), Layout(name="containers"), ) layout["header"].update(make_header()) layout["onion"].update(make_onion_panel(data)) layout["system"].update(make_system_panel(data)) layout["disks"].update(make_disk_panel(data)) layout["services"].update(make_services_panel(data)) layout["containers"].update(make_containers_panel(data)) layout["footer"].update(Align.center(make_footer(live_mode, refresh))) return layout def main(): parser = argparse.ArgumentParser(description="Stargate Server Status Dashboard") parser.add_argument("-l", "--live", action="store_true", help="Enable live updating mode") parser.add_argument("-r", "--refresh", type=int, default=2, help="Refresh interval in seconds (default: 2)") args = parser.parse_args() def signal_handler(sig, frame): console.clear() sys.exit(0) signal.signal(signal.SIGINT, signal_handler) if args.live: with Live(console=console, refresh_per_second=1, screen=True) as live: while True: data = fetch_data() live.update(build_dashboard(data, live_mode=True, refresh=args.refresh)) import time time.sleep(args.refresh) else: console.clear() data = fetch_data() console.print(build_dashboard(data)) if __name__ == "__main__": main()