fix: loading screen via Go handler instead of file:// HTML

- bee-web.service: remove After=bee-audit so Go starts immediately
- Go serves loading page from / when audit JSON not yet present;
  JS polls /api/ready (503 until file exists, 200 when ready)
  then redirects to dashboard
- bee-openbox-session: wait for /healthz (Go binds fast <2s),
  open http://localhost/ directly — no file:// cross-origin issues
- Remove loading.html static file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 10:31:46 +03:00
parent dfb94f9ca6
commit ada15ac777
4 changed files with 67 additions and 84 deletions

View File

@@ -163,6 +163,7 @@ func NewHandler(opts HandlerOptions) http.Handler {
// ── Infrastructure ──────────────────────────────────────────────────────
mux.HandleFunc("GET /healthz", h.handleHealthz)
mux.HandleFunc("GET /api/ready", h.handleReady)
// ── Existing read-only endpoints (preserved for compatibility) ──────────
mux.HandleFunc("GET /audit.json", h.handleAuditJSON)
@@ -654,9 +655,66 @@ func sparseLabels(labels []string, n int) []string {
// ── Page handler ─────────────────────────────────────────────────────────────
func (h *handler) handleReady(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
if _, err := os.Stat(h.opts.AuditPath); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
_, _ = w.Write([]byte("starting"))
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ready"))
}
const loadingPageHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>EASY-BEE</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;background:#0f1117;display:flex;align-items:center;justify-content:center;font-family:'Courier New',monospace;color:#e2e8f0}
.logo{font-size:13px;line-height:1.4;color:#f6c90e;margin-bottom:48px;white-space:pre}
.spinner{width:48px;height:48px;border:4px solid #2d3748;border-top-color:#f6c90e;border-radius:50%;animation:spin .8s linear infinite;margin:0 auto 24px}
@keyframes spin{to{transform:rotate(360deg)}}
.status{font-size:14px;color:#a0aec0;letter-spacing:.05em}
</style>
</head>
<body>
<div style="text-align:center">
<div class="logo"> ███████╗ █████╗ ███████╗██╗ ██╗ ██████╗ ███████╗███████╗
██╔════╝██╔══██╗██╔════╝╚██╗ ██╔╝ ██╔══██╗██╔════╝██╔════╝
█████╗ ███████║███████╗ ╚████╔╝ █████╗██████╔╝█████╗ █████╗
██╔══╝ ██╔══██║╚════██║ ╚██╔╝ ╚════╝██╔══██╗██╔══╝ ██╔══╝
███████╗██║ ██║███████║ ██║ ██████╔╝███████╗███████╗
╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝</div>
<div class="spinner"></div>
<div class="status" id="s">Starting up...</div>
</div>
<script>
function probe(){
fetch('/api/ready',{cache:'no-store'})
.then(function(r){
if(r.ok){window.location.replace('/');}
else{setTimeout(probe,1000);}
})
.catch(function(){setTimeout(probe,1000);});
}
probe();
</script>
</body>
</html>`
func (h *handler) handlePage(w http.ResponseWriter, r *http.Request) {
page := strings.TrimPrefix(r.URL.Path, "/")
if page == "" {
// Serve loading page until audit snapshot exists
if _, err := os.Stat(h.opts.AuditPath); err != nil {
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte(loadingPageHTML))
return
}
page = "dashboard"
}
// Redirect old routes to new names

View File

@@ -1,6 +1,6 @@
[Unit]
Description=Bee: hardware audit web viewer
After=bee-network.service bee-audit.service
After=bee-network.service
Wants=bee-audit.service
[Service]

View File

@@ -8,6 +8,13 @@ xset -dpms
xset s noblank
tint2 &
# Wait for bee-web to bind (Go starts fast, usually <2s)
i=0
while [ $i -lt 30 ]; do
if curl -sf http://localhost/healthz >/dev/null 2>&1; then break; fi
sleep 1
i=$((i+1))
done
chromium \
--disable-infobars \
--disable-translate \
@@ -15,6 +22,6 @@ chromium \
--disable-session-crashed-bubble \
--disable-features=TranslateUI \
--start-fullscreen \
file:///usr/local/share/bee/loading.html &
http://localhost/ &
exec openbox

View File

@@ -1,82 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>EASY-BEE</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
height: 100%;
background: #0f1117;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Courier New', monospace;
color: #e2e8f0;
}
.container {
text-align: center;
}
.logo {
font-size: 13px;
line-height: 1.4;
color: #f6c90e;
margin-bottom: 48px;
white-space: pre;
}
.spinner {
width: 48px;
height: 48px;
border: 4px solid #2d3748;
border-top-color: #f6c90e;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 0 auto 24px;
}
@keyframes spin { to { transform: rotate(360deg); } }
.status {
font-size: 14px;
color: #a0aec0;
letter-spacing: 0.05em;
}
.dots::after {
content: '';
animation: dots 1.5s steps(4, end) infinite;
}
@keyframes dots {
0% { content: ''; }
25% { content: '.'; }
50% { content: '..'; }
75% { content: '...'; }
}
</style>
</head>
<body>
<div class="container">
<div class="logo"> ███████╗ █████╗ ███████╗██╗ ██╗ ██████╗ ███████╗███████╗
██╔════╝██╔══██╗██╔════╝╚██╗ ██╔╝ ██╔══██╗██╔════╝██╔════╝
█████╗ ███████║███████╗ ╚████╔╝ █████╗██████╔╝█████╗ █████╗
██╔══╝ ██╔══██║╚════██║ ╚██╔╝ ╚════╝██╔══██╗██╔══╝ ██╔══╝
███████╗██║ ██║███████║ ██║ ██████╔╝███████╗███████╗
╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝</div>
<div class="spinner"></div>
<div class="status">Starting<span class="dots"></span></div>
</div>
<script>
function probe() {
fetch('http://localhost/healthz', { cache: 'no-store' })
.then(function(r) {
if (r.ok) {
window.location.replace('http://localhost/');
} else {
setTimeout(probe, 500);
}
})
.catch(function() {
setTimeout(probe, 500);
});
}
probe();
</script>
</body>
</html>