Skip to content

Commit d356504

Browse files
committed
update: gui
1 parent a209a80 commit d356504

File tree

3 files changed

+294
-8
lines changed

3 files changed

+294
-8
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,13 @@ curl -fsSL https://raw.githubusercontent.com/MuNeNICK/setup-k8s/main/setup-k8s.s
9090

9191
This starts a local HTTP server (default: `http://127.0.0.1:8080`) that mirrors the CLI
9292
options. Once you submit the form, the script continues in the terminal with the selected
93-
configuration. Pass the bind address (optionally with a port) right after `--gui`, e.g.
94-
`--gui 0.0.0.0` or `--gui 0.0.0.0:9000`. You can also feed values via the
95-
`GUI_BIND_ADDRESS` / `GUI_PORT` environment variables. Ensure `python3` is installed for the
96-
web UI to run.
93+
configuration and the browser tab automatically pivots to a `/progress` page that streams
94+
the same log output. That means you can watch the installation either from the terminal or
95+
right from your browser without switching contexts.
96+
97+
Pass the bind address (optionally with a port) right after `--gui`, e.g. `--gui 0.0.0.0` or
98+
`--gui 0.0.0.0:9000`. You can also feed values via the `GUI_BIND_ADDRESS` / `GUI_PORT`
99+
environment variables. Ensure `python3` is installed for the web UI to run.
97100

98101
Example exposing the installer on all interfaces and a different port:
99102

common/gui.sh

Lines changed: 284 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,218 @@
11
#!/bin/bash
22

3+
GUI_PROGRESS_LOG_FILE=""
4+
GUI_PROGRESS_SERVER_PID=""
5+
GUI_PROGRESS_URL=""
6+
GUI_PROGRESS_LOGGING_ACTIVE="false"
7+
8+
gui_append_exit_trap() {
9+
local new_cmd="$1"
10+
local existing_trap
11+
existing_trap=$(trap -p EXIT | sed -n "s/^trap -- '\(.*\)' EXIT$/\1/p")
12+
if [ -n "$existing_trap" ]; then
13+
trap "$existing_trap"$'\n'"$new_cmd" EXIT
14+
else
15+
trap "$new_cmd" EXIT
16+
fi
17+
}
18+
19+
gui_cleanup_progress_server() {
20+
if [ -n "$GUI_PROGRESS_SERVER_PID" ]; then
21+
if kill -0 "$GUI_PROGRESS_SERVER_PID" 2>/dev/null; then
22+
kill "$GUI_PROGRESS_SERVER_PID" 2>/dev/null || true
23+
wait "$GUI_PROGRESS_SERVER_PID" 2>/dev/null || true
24+
fi
25+
GUI_PROGRESS_SERVER_PID=""
26+
fi
27+
28+
if [ -n "$GUI_PROGRESS_LOG_FILE" ] && [ -f "$GUI_PROGRESS_LOG_FILE" ]; then
29+
rm -f "$GUI_PROGRESS_LOG_FILE" 2>/dev/null || true
30+
fi
31+
}
32+
33+
gui_launch_progress_server() {
34+
local bind_addr="$1"
35+
local gui_port="$2"
36+
local log_file="$3"
37+
38+
if [ -z "$bind_addr" ] || [ -z "$gui_port" ] || [ -z "$log_file" ]; then
39+
return 1
40+
fi
41+
42+
if ! command -v python3 >/dev/null 2>&1; then
43+
echo "Warning: Live progress UI requires python3." >&2
44+
return 1
45+
fi
46+
47+
python3 - "$bind_addr" "$gui_port" "$log_file" <<'PYTHON' &
48+
import http.server
49+
import socketserver
50+
import sys
51+
from pathlib import Path
52+
53+
BIND_ADDR = sys.argv[1]
54+
PORT = int(sys.argv[2])
55+
LOG_PATH = Path(sys.argv[3])
56+
57+
PROGRESS_PAGE = """<!DOCTYPE html>
58+
<html lang=\"en\">
59+
<head>
60+
<meta charset=\"utf-8\">
61+
<title>setup-k8s progress</title>
62+
<style>
63+
body { font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; background: #0f172a; color: #e2e8f0; margin: 0; }
64+
.wrap { max-width: 900px; margin: 0 auto; padding: 32px; }
65+
.panel { background: rgba(15,23,42,0.75); backdrop-filter: blur(4px); border-radius: 16px; padding: 32px; box-shadow: 0 25px 50px rgba(15,23,42,0.35); }
66+
h1 { margin-top: 0; font-weight: 600; }
67+
.status { margin-bottom: 16px; font-size: 15px; color: #cbd5f5; }
68+
.log-shell { background: #020617; border-radius: 12px; border: 1px solid rgba(148,163,184,0.35); padding: 16px; height: 60vh; overflow: auto; }
69+
.log-shell pre { margin: 0; font-family: SFMono-Regular,Consolas,monospace; font-size: 13px; line-height: 1.5; color: #f1f5f9; }
70+
.hint { margin-top: 18px; font-size: 13px; color: #94a3b8; }
71+
</style>
72+
</head>
73+
<body>
74+
<div class=\"wrap\">
75+
<div class=\"panel\">
76+
<h1>setup-k8s Installation Progress</h1>
77+
<p class=\"status\" id=\"status\">Waiting for installer output...</p>
78+
<div class=\"log-shell\"><pre id=\"log\"></pre></div>
79+
<p class=\"hint\">Keep this tab open while the installer runs. Logs also appear in the terminal.</p>
80+
</div>
81+
</div>
82+
<script>
83+
const logEl = document.getElementById('log');
84+
const statusEl = document.getElementById('status');
85+
let autoScroll = true;
86+
let consecutiveFailures = 0;
87+
88+
function updateAutoScroll() {
89+
autoScroll = (logEl.scrollTop + logEl.clientHeight) >= (logEl.scrollHeight - 4);
90+
}
91+
92+
logEl.addEventListener('scroll', updateAutoScroll);
93+
94+
async function fetchLog() {
95+
try {
96+
const resp = await fetch('progress-log?ts=' + Date.now());
97+
const text = await resp.text();
98+
logEl.textContent = text;
99+
if (text.trim().length === 0) {
100+
statusEl.textContent = 'Waiting for installer output...';
101+
} else {
102+
statusEl.textContent = 'Live log stream from setup-k8s';
103+
}
104+
if (autoScroll) {
105+
logEl.scrollTop = logEl.scrollHeight;
106+
}
107+
consecutiveFailures = 0;
108+
} catch (err) {
109+
consecutiveFailures += 1;
110+
if (consecutiveFailures > 3) {
111+
statusEl.textContent = 'Installer finished or connection lost. Check the terminal for final status.';
112+
} else {
113+
statusEl.textContent = 'Reconnecting to installer...';
114+
}
115+
} finally {
116+
setTimeout(fetchLog, 1500);
117+
}
118+
}
119+
120+
fetchLog();
121+
</script>
122+
</body>
123+
</html>"""
124+
125+
126+
class ProgressHandler(http.server.BaseHTTPRequestHandler):
127+
def send_body(self, body, *, status=200, content_type="text/html; charset=utf-8", head_only=False):
128+
data = body.encode("utf-8")
129+
self.send_response(status)
130+
self.send_header("Content-Type", content_type)
131+
self.send_header("Cache-Control", "no-store")
132+
self.send_header("Content-Length", str(len(data) if not head_only else 0))
133+
self.end_headers()
134+
if not head_only:
135+
self.wfile.write(data)
136+
137+
def do_HEAD(self):
138+
if self.path.startswith("/progress-log") or self.path == "/healthz":
139+
self.send_body("", content_type="text/plain; charset=utf-8", head_only=True)
140+
elif self.path == "/favicon.ico":
141+
self.send_response(204)
142+
self.end_headers()
143+
else:
144+
self.send_body(PROGRESS_PAGE, head_only=True)
145+
146+
def do_GET(self):
147+
if self.path.startswith("/progress-log"):
148+
try:
149+
body = LOG_PATH.read_text(encoding="utf-8", errors="ignore")
150+
except OSError:
151+
body = ""
152+
self.send_body(body, content_type="text/plain; charset=utf-8")
153+
elif self.path == "/healthz":
154+
self.send_body("ok", content_type="text/plain; charset=utf-8")
155+
elif self.path == "/favicon.ico":
156+
self.send_response(204)
157+
self.end_headers()
158+
else:
159+
self.send_body(PROGRESS_PAGE)
160+
161+
def log_message(self, fmt, *args):
162+
sys.stderr.write("%s - - [%s] %s\n" % (self.address_string(), self.log_date_time_string(), fmt % args))
163+
164+
165+
class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
166+
daemon_threads = True
167+
allow_reuse_address = True
168+
169+
170+
def main():
171+
try:
172+
httpd = ThreadingServer((BIND_ADDR, PORT), ProgressHandler)
173+
except OSError as exc:
174+
sys.stderr.write(f"Failed to start progress viewer: {exc}\n")
175+
sys.exit(2)
176+
177+
try:
178+
sys.stderr.write(f"Progress viewer listening on http://{BIND_ADDR}:{PORT}/progress\n")
179+
httpd.serve_forever()
180+
except KeyboardInterrupt:
181+
pass
182+
finally:
183+
httpd.server_close()
184+
185+
186+
if __name__ == "__main__":
187+
main()
188+
PYTHON
189+
GUI_PROGRESS_SERVER_PID=$!
190+
sleep 0.2
191+
if ! kill -0 "$GUI_PROGRESS_SERVER_PID" 2>/dev/null; then
192+
echo "Warning: Failed to launch live progress server." >&2
193+
GUI_PROGRESS_SERVER_PID=""
194+
return 1
195+
fi
196+
197+
gui_append_exit_trap "gui_cleanup_progress_server"
198+
return 0
199+
}
200+
201+
gui_enable_progress_logging() {
202+
if [ -z "$GUI_PROGRESS_LOG_FILE" ] || [ ! -f "$GUI_PROGRESS_LOG_FILE" ]; then
203+
return
204+
fi
205+
if [ "$GUI_PROGRESS_LOGGING_ACTIVE" = "true" ]; then
206+
return
207+
fi
208+
GUI_PROGRESS_LOGGING_ACTIVE="true"
209+
exec > >(tee -a "$GUI_PROGRESS_LOG_FILE") 2>&1
210+
if [ -n "$GUI_PROGRESS_URL" ]; then
211+
echo "Live GUI progress: $GUI_PROGRESS_URL"
212+
echo "The browser view refreshes automatically; keep this terminal open until completion."
213+
fi
214+
}
215+
3216
# Launch a lightweight web UI that collects installation options and
4217
# maps them to the regular CLI variables.
5218
run_gui_installer() {
@@ -141,18 +354,59 @@ document.addEventListener('DOMContentLoaded', updateWorkerFields);
141354
<button type=\"submit\">Start installation</button>
142355
</div>
143356
</form>
144-
<p class=\"hint\">This local server stops automatically after submission.</p>
357+
<p class=\"hint\">After submitting, this tab switches to a live log view so you can track progress here or watch the terminal output.</p>
145358
</div>
146359
</body>
147360
</html>
148361
"""
149362
150363
SUCCESS_PAGE = """<!DOCTYPE html>
151364
<html lang=\"en\">
152-
<head><meta charset=\"utf-8\"><title>setup-k8s</title>
153-
<style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#f4f5f7;color:#1f2933;display:flex;align-items:center;justify-content:center;height:100vh;} .card{background:#fff;padding:32px;border-radius:12px;box-shadow:0 10px 20px rgba(15,23,42,0.08);text-align:center;} a{color:#2563eb;text-decoration:none;}</style>
365+
<head>
366+
<meta charset=\"utf-8\">
367+
<title>setup-k8s</title>
368+
<style>
369+
body { font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; background: #f5f6fb; color: #0f172a; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
370+
.card { background: #fff; padding: 32px; border-radius: 14px; box-shadow: 0 30px 60px rgba(15,23,42,0.15); width: 440px; max-width: 90%; text-align: center; }
371+
.card h2 { margin-top: 0; }
372+
.card p { margin-bottom: 12px; }
373+
.muted { font-size: 13px; color: #64748b; }
374+
.card a { color: #2563eb; text-decoration: none; font-weight: 600; }
375+
</style>
154376
</head>
155-
<body><div class=\"card\"><h2>Configuration received</h2><p>You can close this tab and return to the terminal.</p></div></body></html>"""
377+
<body>
378+
<div class=\"card\">
379+
<h2>Installation starting</h2>
380+
<p>Keep this tab open to see live progress (you can also watch the terminal output).</p>
381+
<p><a id=\"progress-link\" href=\"#\">Opening progress view…</a></p>
382+
<p class=\"muted\">The page refreshes automatically once the log server is available.</p>
383+
</div>
384+
<script>
385+
(function() {
386+
var target = window.location.origin + '/progress';
387+
var link = document.getElementById('progress-link');
388+
link.href = target;
389+
link.textContent = target;
390+
391+
function tryOpen() {
392+
fetch(target, { method: 'HEAD' })
393+
.then(function(resp) {
394+
if (resp.ok) {
395+
window.location.href = target;
396+
return;
397+
}
398+
setTimeout(tryOpen, 2000);
399+
})
400+
.catch(function() {
401+
setTimeout(tryOpen, 2000);
402+
});
403+
}
404+
405+
setTimeout(tryOpen, 1500);
406+
})();
407+
</script>
408+
</body>
409+
</html>"""
156410
157411
FIELDS = [
158412
"NODE_TYPE",
@@ -274,6 +528,32 @@ PYTHON
274528
exit 1
275529
fi
276530

531+
if GUI_PROGRESS_LOG_FILE=$(mktemp -t setup-k8s-progress-XXXXXX.log 2>/dev/null); then
532+
: > "$GUI_PROGRESS_LOG_FILE"
533+
local display_host="$bind_addr"
534+
local display_hint=""
535+
if [ -z "$display_host" ] || [ "$display_host" = "0.0.0.0" ]; then
536+
display_host="127.0.0.1"
537+
display_hint=" (listening on all interfaces; replace the host as needed)"
538+
elif [ "$display_host" = "::" ]; then
539+
display_host="[::1]"
540+
display_hint=" (listening on all interfaces; replace the host as needed)"
541+
elif [[ "$display_host" == *:* && "$display_host" != [* ]]; then
542+
display_host="[$display_host]"
543+
fi
544+
545+
if gui_launch_progress_server "$bind_addr" "$gui_port" "$GUI_PROGRESS_LOG_FILE"; then
546+
GUI_PROGRESS_URL="http://${display_host}:${gui_port}/progress"
547+
echo "Live progress UI will be available at ${GUI_PROGRESS_URL}${display_hint}" >&2
548+
else
549+
rm -f "$GUI_PROGRESS_LOG_FILE" 2>/dev/null || true
550+
GUI_PROGRESS_LOG_FILE=""
551+
GUI_PROGRESS_URL=""
552+
fi
553+
else
554+
echo "Warning: Unable to create progress log file for GUI view." >&2
555+
fi
556+
277557
local gui_pod_network_cidr=""
278558
local gui_service_cidr=""
279559
local gui_api_addr=""

setup-k8s.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ main() {
208208
echo "Note: CLI options are ignored when --gui mode is active." >&2
209209
fi
210210
run_gui_installer
211+
if type -t gui_enable_progress_logging >/dev/null 2>&1; then
212+
gui_enable_progress_logging
213+
fi
211214
else
212215
# Parse command line arguments
213216
parse_setup_args "${cli_args[@]}"

0 commit comments

Comments
 (0)