shuishen
5 days ago f67d43a43f6c3e58922e3c2f457dd9caafebfc67
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
"""Serve the local GeoAI Workbench console from the repository root."""
 
from __future__ import annotations
 
import argparse
import os
from pathlib import PurePosixPath
from http import HTTPStatus
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import unquote, urlsplit
 
 
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 6173
ALLOWED_PATH_PREFIXES = (
    "apps/workbench-console",
    "shared/outputs",
    "shared/data/raw/01-object-detection",
)
 
 
class WorkbenchConsoleHandler(SimpleHTTPRequestHandler):
    """Read-only static handler rooted at the workbench repository."""
 
    def do_GET(self) -> None:  # noqa: N802 - inherited standard-library method name
        if urlsplit(self.path).path == "/":
            self.send_response(HTTPStatus.FOUND)
            self.send_header("Location", "/apps/workbench-console/")
            self.end_headers()
            return
        super().do_GET()
 
    def translate_path(self, path: str) -> str:
        """Expose only the static UI and artifacts required by the local console."""
        decoded_path = unquote(urlsplit(path).path).lstrip("/")
        requested = PurePosixPath(decoded_path)
        is_allowed = any(
            decoded_path == prefix or decoded_path.startswith(f"{prefix}/")
            for prefix in ALLOWED_PATH_PREFIXES
        )
        if ".." in requested.parts or not is_allowed:
            return os.fspath(Path(self.directory) / ".console-forbidden")
        if decoded_path == "apps/workbench-console" or decoded_path.startswith("apps/workbench-console/"):
            console_relative = requested.parts[2:]
            return os.fspath(Path(self.directory) / "apps" / "workbench-console" / "dist" / Path(*console_relative))
        return os.fspath(Path(self.directory).joinpath(*requested.parts))
 
    def end_headers(self) -> None:
        self.send_header("Cache-Control", "no-store")
        self.send_header("X-Content-Type-Options", "nosniff")
        super().end_headers()
 
 
def parse_args() -> argparse.Namespace:
    root = Path(__file__).resolve().parents[1]
    parser = argparse.ArgumentParser(description="Serve the local GeoAI Workbench console.")
    parser.add_argument("--host", default=DEFAULT_HOST, help="Bind address. Defaults to loopback only.")
    parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="TCP port in the 6000-6999 range.")
    parser.add_argument("--root", type=Path, default=root, help="Workbench repository root to serve.")
    return parser.parse_args()
 
 
def main() -> int:
    args = parse_args()
    if not 6000 <= args.port <= 6999:
        raise SystemExit("Port must be in the 6000-6999 range.")
    if args.host not in {"127.0.0.1", "localhost", "::1"}:
        raise SystemExit("This console is local-only. Use 127.0.0.1, localhost, or ::1.")
    root = args.root.resolve()
    app_dir = root / "apps" / "workbench-console"
    if not (root / "shared").is_dir() or not (app_dir / "dist" / "index.html").is_file():
        raise SystemExit(f"Not a GeoAI Workbench root: {root}")
 
    handler = lambda *handler_args, **handler_kwargs: WorkbenchConsoleHandler(  # noqa: E731
        *handler_args, directory=os.fspath(root), **handler_kwargs
    )
    server = ThreadingHTTPServer((args.host, args.port), handler)
    print(f"GeoAI Workbench console: http://{args.host}:{args.port}")
    print(f"Serving built console and read-only artifacts from: {root}")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nConsole stopped.")
    finally:
        server.server_close()
    return 0
 
 
if __name__ == "__main__":
    raise SystemExit(main())