File size: 5,442 Bytes
09e8c1e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""Gradio Blocks application for the NACC dashboard."""

from __future__ import annotations

import json
from typing import Any

import gradio as gr
import requests

from .config import UIConfig


class OrchestratorHttpClient:
    def __init__(self, base_url: str | Any) -> None:  # type: ignore[override]
        self.base_url = str(base_url).rstrip("/")

    def list_nodes(self) -> list[dict[str, Any]]:
        response = requests.get(f"{self.base_url}/nodes", timeout=15)
        response.raise_for_status()
        return response.json()

    def list_files(self, node_id: str, path: str, recursive: bool = False) -> dict[str, Any]:
        response = requests.post(
            f"{self.base_url}/nodes/{node_id}/files",
            json={"path": path, "recursive": recursive},
            timeout=30,
        )
        response.raise_for_status()
        return response.json()

    def execute_command(
        self,
        description: str,
        command: str,
        *,
        preferred_tags: list[str] | None,
        parallelism: int,
    ) -> dict[str, Any]:
        payload = {
            "description": description,
            "command": command.strip() if isinstance(command, str) else command,
            "preferred_tags": preferred_tags,
            "parallelism": parallelism,
        }
        response = requests.post(f"{self.base_url}/commands/execute", json=payload, timeout=60)
        response.raise_for_status()
        return response.json()


def build_interface(config: UIConfig) -> gr.Blocks:
    client = OrchestratorHttpClient(config.orchestrator_url)

    def refresh_nodes() -> tuple[list[list[Any]], Any, Any]:
        nodes = client.list_nodes()
        rows = [
            [
                entry.get("node_id"),
                entry.get("display_name") or entry.get("node_id"),
                "✅" if entry.get("healthy") else "⚠️",
                (entry.get("metrics") or {}).get("cpu_percent"),
                (entry.get("metrics") or {}).get("memory_percent"),
                entry.get("last_seen"),
                ",".join(entry.get("tags") or []),
            ]
            for entry in nodes
        ]
        node_ids = [entry.get("node_id") for entry in nodes]
        default_selection = node_ids[0] if node_ids else None
        dropdown_update = gr.Dropdown.update(choices=node_ids, value=default_selection)
        file_dropdown_update = gr.Dropdown.update(choices=node_ids, value=default_selection)
        return rows, dropdown_update, file_dropdown_update

    def browse(node_id: str, path: str, recursive: bool) -> tuple[list[list[Any]], str]:
        if not node_id:
            raise gr.Error("Select a node first")
        payload = client.list_files(node_id, path, recursive)
        rows = [
            [entry["relative_path"], entry["is_dir"], entry["size"], entry["modified"], entry.get("hash")]
            for entry in payload["files"]
        ]
        return rows, json.dumps(payload, indent=2)

    def run_command(description: str, command: str, tags: str, parallelism: int) -> str:
        tag_list = [tag.strip() for tag in tags.split(",") if tag.strip()] or None
        result = client.execute_command(description, command, preferred_tags=tag_list, parallelism=parallelism)
        return json.dumps(result, indent=2)

    with gr.Blocks(title="NACC Dashboard") as demo:
        gr.Markdown("# NACC – Network Agentic Connection Call")
        with gr.Tab("Nodes"):
            nodes_table = gr.Dataframe(headers=["Node ID", "Display", "Healthy", "CPU%", "Mem%", "Last Seen", "Tags"], datatype=["str", "str", "str", "number", "number", "number", "str"], interactive=False)
            refresh_btn = gr.Button("Refresh Nodes")
            node_dropdown = gr.Dropdown(label="Node", choices=[], interactive=True)
        with gr.Tab("Files"):
            with gr.Row():
                file_node = gr.Dropdown(label="Node", interactive=True)
                file_path = gr.Textbox(label="Path", value=".")
                file_recursive = gr.Checkbox(label="Recursive", value=False)
            file_table = gr.Dataframe(headers=["Path", "Dir?", "Size", "Modified", "Hash"], datatype=["str", "bool", "number", "number", "str"], interactive=False)
            file_json = gr.Code(language="json", label="Raw Response")
            browse_btn = gr.Button("List Files")
            browse_btn.click(fn=browse, inputs=[file_node, file_path, file_recursive], outputs=[file_table, file_json])
            node_dropdown.change(
                fn=lambda value: gr.Dropdown.update(value=value),
                inputs=node_dropdown,
                outputs=file_node,
            )
        with gr.Tab("Command Center"):
            cmd_description = gr.Textbox(label="Description", value="Ad-hoc command")
            cmd_input = gr.Textbox(label="Command", placeholder="echo 'hello from node'")
            cmd_tags = gr.Textbox(label="Preferred tags (comma separated)")
            cmd_parallel = gr.Slider(label="Parallelism", minimum=1, maximum=4, value=1, step=1)
            cmd_output = gr.Code(language="json", label="Execution Result")
            run_btn = gr.Button("Run Command")
            run_btn.click(fn=run_command, inputs=[cmd_description, cmd_input, cmd_tags, cmd_parallel], outputs=cmd_output)

        refresh_btn.click(fn=refresh_nodes, outputs=[nodes_table, node_dropdown, file_node])

    return demo


__all__ = ["build_interface"]