prole/knoe/ui/screens/validate.py
chrisfu 4ee2b259c9 Checkpoint: rename installer to knoe + harden db build context
- Add build-context helper to copy Docker context safely (ignore runtime data, keep symlinks)

- Update UI and core actions to use ~/.prole/build and shared copy helper

- Add/adjust tests and scripts; introduce knoe ops helpers and update manifests

Co-authored-by: Junie <junie@jetbrains.com>
2026-03-22 01:45:21 -07:00

189 lines
6.7 KiB
Python

"""Validation screen with auto-refresh status monitoring."""
import subprocess
import threading
import time
import webbrowser
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from knoe.screen import TerminalConsole
from knoe import screen as ui
class ValidateScreenMixin:
"""Validation screen with auto-refresh status monitoring."""
def create_validate_screen(self):
"""Create the Validate screen"""
frame = self._page_container()
self.screens["validate"] = frame
# Title
title = ttk.Label(frame, text="Validate Deployment", style="Title.TLabel")
title.pack(pady=(0, 20))
# Prometheus link
prometheus_frame = tk.Frame(frame, bg="white")
prometheus_frame.pack(pady=(0, 20))
prometheus_label = tk.Label(
prometheus_frame,
text="Prometheus: ",
bg="white",
fg="#6e6e73",
font=("Helvetica", 11),
)
prometheus_label.pack(side="left")
prometheus_link = tk.Label(
prometheus_frame,
text="http://localhost:9090",
bg="white",
fg="#4a9eff",
font=("Helvetica", 11, "underline"),
cursor="hand2",
)
prometheus_link.pack(side="left")
prometheus_link.bind(
"<Button-1>", lambda e: webbrowser.open("http://localhost:9090")
)
# Status display
status_frame = tk.Frame(frame, bg="white")
status_frame.pack(fill="both", expand=True, pady=10)
status_label = ttk.Label(
status_frame, text="Cluster Status", style="Heading.TLabel"
)
status_label.pack(anchor="w", pady=(0, 10))
# Status text area - using TerminalConsole for consistency
# Use a background frame to ensure NO borders are visible around the console
console_bg = tk.Frame(status_frame, bg="white", highlightthickness=0, bd=0)
console_bg.pack(fill="both", expand=True, padx=1, pady=1)
self._validation_console = ui.TerminalConsole(
console_bg, highlightthickness=0, bd=0
)
self._validation_console.pack(fill="both", expand=True)
self.status_text = self._validation_console.text
# Auto-refresh checkbox
refresh_frame = tk.Frame(frame, bg="white")
refresh_frame.pack(pady=10)
self.auto_refresh_var = tk.BooleanVar(value=True)
refresh_check = tk.Checkbutton(
refresh_frame,
text="Auto-refresh every 10 seconds",
variable=self.auto_refresh_var,
bg="white",
fg="#1d1d1f",
selectcolor="#f0f0f0",
activebackground="white",
activeforeground="#1d1d1f",
font=("Helvetica", 10),
command=self.toggle_auto_refresh,
)
refresh_check.pack(side="left", padx=10)
# Manual refresh button
refresh_btn = tk.Button(
refresh_frame,
text="Refresh Now",
command=self.refresh_status,
bg="#4a9eff",
fg="white",
activebackground="#3a8eef",
highlightbackground="white",
font=("Helvetica", 10),
padx=15,
pady=5,
cursor="hand2",
relief="flat",
)
refresh_btn.pack(side="left", padx=10)
# Start auto-refresh
self.refresh_status()
self.toggle_auto_refresh()
def toggle_auto_refresh(self):
"""Toggle auto-refresh"""
if self.auto_refresh_var.get():
if not self.validation_running:
self.validation_running = True
self.validation_thread = threading.Thread(
target=self.auto_refresh_loop, daemon=True
)
self.validation_thread.start()
else:
self.validation_running = False
def auto_refresh_loop(self):
"""Auto-refresh loop"""
while self.validation_running:
time.sleep(10)
if self.validation_running:
self.root.after(0, self.refresh_status)
def refresh_status(self):
"""Refresh the cluster status"""
def update():
try:
full_status = ""
# Get k3d cluster list
try:
cluster_result = subprocess.run(
["k3d", "cluster", "list"],
capture_output=True,
text=True,
timeout=5,
)
full_status += (
f"=== k3d Cluster Status ===\n{cluster_result.stdout}\n\n"
)
except Exception as e:
full_status += f"=== k3d Cluster Status ===\nError: {str(e)}\n\n"
# Get kubectl cnpg status
try:
namespace = (self.db_namespace.get() or "default").strip()
cmd = self._kubectl_base_cmd() + [
"cnpg",
"status",
"prole-db",
"-n",
namespace,
]
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=10
)
if result.returncode == 0:
status_output = result.stdout
else:
# Try default namespace if prole fails, or just show error
status_output = f"Error: {result.stderr}\n\nNote: Make sure kubectl cnpg plugin is installed and cluster exists in '{namespace}' namespace."
full_status += f"=== CloudNativePG Status (namespace: {namespace}) ===\n{status_output}\n"
except FileNotFoundError:
full_status += "=== CloudNativePG Status ===\nError: kubectl not found. Please install dependencies first.\n"
except subprocess.TimeoutExpired:
full_status += (
"=== CloudNativePG Status ===\nError: Command timed out\n"
)
except Exception as e:
full_status += f"=== CloudNativePG Status ===\nError: {str(e)}\n"
full_status += f"\nLast updated: {time.strftime('%Y-%m-%d %H:%M:%S')}"
self.status_text.delete("1.0", tk.END)
self.status_text.insert("1.0", full_status)
except Exception as e:
self.status_text.delete("1.0", tk.END)
self.status_text.insert("1.0", f"Error: {str(e)}")
threading.Thread(target=update, daemon=True).start()