Spaces:
Sleeping
Sleeping
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -105,6 +105,19 @@ def level(text):
|
|
| 105 |
return "neutral"
|
| 106 |
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
def module_items(payload):
|
| 109 |
rows = []
|
| 110 |
sources = ["containers", "container_status", "container", "docker", "modules", "module_status", "services", "checks"]
|
|
@@ -112,17 +125,16 @@ def module_items(payload):
|
|
| 112 |
value = payload.get(key)
|
| 113 |
if isinstance(value, dict):
|
| 114 |
for name, info in value.items():
|
| 115 |
-
|
| 116 |
-
status = info.get("status") or info.get("state") or info.get("health") or info.get("alive") or info.get("ok") or "unknown"
|
| 117 |
-
else:
|
| 118 |
-
status = info
|
| 119 |
rows.append((str(name), str(status)))
|
| 120 |
elif isinstance(value, list):
|
| 121 |
for i, info in enumerate(value):
|
| 122 |
if isinstance(info, dict):
|
| 123 |
name = info.get("name") or info.get("module") or info.get("container") or f"module_{i+1}"
|
| 124 |
-
status =
|
| 125 |
rows.append((str(name), str(status)))
|
|
|
|
|
|
|
| 126 |
header = payload.get("latest_rep_header") or {}
|
| 127 |
if isinstance(header, dict) and header.get("file"):
|
| 128 |
rows.append(("Earthworm EEW header", "available"))
|
|
@@ -141,24 +153,48 @@ def render_status(source):
|
|
| 141 |
with open(FIXTURES / "normal_event.json", "r", encoding="utf-8") as f:
|
| 142 |
payload = json.load(f)
|
| 143 |
label = f"fallback: {e}"
|
| 144 |
-
|
|
|
|
|
|
|
| 145 |
for name, status in module_items(payload):
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
return f"""
|
| 149 |
<style>
|
| 150 |
-
.wrap{{display:grid;gap:
|
| 151 |
-
.hero
|
| 152 |
-
.
|
| 153 |
-
.
|
| 154 |
-
.
|
| 155 |
-
.
|
| 156 |
-
.
|
| 157 |
-
.
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
</style>
|
| 161 |
-
<div class='wrap'>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
"""
|
| 163 |
|
| 164 |
|
|
@@ -275,7 +311,7 @@ def trace_stats(label, y):
|
|
| 275 |
def plot_waveform(source):
|
| 276 |
try:
|
| 277 |
series, label = load_waveform(source)
|
| 278 |
-
msg = f"✅ 已載入 {label},
|
| 279 |
except Exception as e:
|
| 280 |
series, label = demo_series(), "demo://synthetic fallback"
|
| 281 |
msg = f"⚠️ 遠端波形讀取失敗,改顯示示範資料:{e}"
|
|
@@ -347,7 +383,7 @@ status_opts = status_choices()
|
|
| 347 |
wave_opts = waveform_choices()
|
| 348 |
|
| 349 |
with gr.Blocks(title="EEW Dashboard") as demo:
|
| 350 |
-
gr.Markdown("# EEW Dashboard\n系統狀態
|
| 351 |
with gr.Tab("系統狀態"):
|
| 352 |
with gr.Row():
|
| 353 |
s = gr.Dropdown(choices=status_opts, value=status_opts[0], label="Status file")
|
|
|
|
| 105 |
return "neutral"
|
| 106 |
|
| 107 |
|
| 108 |
+
def is_alive_status(text):
|
| 109 |
+
return str(text).strip().lower() == "alive"
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def normalized_status_from_info(info):
|
| 113 |
+
if isinstance(info, dict):
|
| 114 |
+
if "alive" in info or "is_alive" in info:
|
| 115 |
+
value = info.get("alive", info.get("is_alive"))
|
| 116 |
+
return "alive" if str(value).strip().lower() in {"true", "1", "yes", "alive"} else "not alive"
|
| 117 |
+
return info.get("status") or info.get("state") or info.get("health") or info.get("ok") or "unknown"
|
| 118 |
+
return info
|
| 119 |
+
|
| 120 |
+
|
| 121 |
def module_items(payload):
|
| 122 |
rows = []
|
| 123 |
sources = ["containers", "container_status", "container", "docker", "modules", "module_status", "services", "checks"]
|
|
|
|
| 125 |
value = payload.get(key)
|
| 126 |
if isinstance(value, dict):
|
| 127 |
for name, info in value.items():
|
| 128 |
+
status = normalized_status_from_info(info)
|
|
|
|
|
|
|
|
|
|
| 129 |
rows.append((str(name), str(status)))
|
| 130 |
elif isinstance(value, list):
|
| 131 |
for i, info in enumerate(value):
|
| 132 |
if isinstance(info, dict):
|
| 133 |
name = info.get("name") or info.get("module") or info.get("container") or f"module_{i+1}"
|
| 134 |
+
status = normalized_status_from_info(info)
|
| 135 |
rows.append((str(name), str(status)))
|
| 136 |
+
else:
|
| 137 |
+
rows.append((f"module_{i+1}", str(info)))
|
| 138 |
header = payload.get("latest_rep_header") or {}
|
| 139 |
if isinstance(header, dict) and header.get("file"):
|
| 140 |
rows.append(("Earthworm EEW header", "available"))
|
|
|
|
| 153 |
with open(FIXTURES / "normal_event.json", "r", encoding="utf-8") as f:
|
| 154 |
payload = json.load(f)
|
| 155 |
label = f"fallback: {e}"
|
| 156 |
+
|
| 157 |
+
alive_cards = []
|
| 158 |
+
info_cards = []
|
| 159 |
for name, status in module_items(payload):
|
| 160 |
+
if is_alive_status(status):
|
| 161 |
+
alive_cards.append(
|
| 162 |
+
f"<div class='alive-card'><span class='lamp'></span><div><b>{esc(name)}</b><small>{esc(status)}</small></div></div>"
|
| 163 |
+
)
|
| 164 |
+
else:
|
| 165 |
+
cls = level(status)
|
| 166 |
+
info_cards.append(
|
| 167 |
+
f"<div class='info-card {cls}'><div><b>{esc(name)}</b><small>{esc(status)}</small></div><span class='tag'>{esc(status)}</span></div>"
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
alive_html = "".join(alive_cards) if alive_cards else "<div class='empty'>目前沒有 alive 型式狀態。</div>"
|
| 171 |
+
info_html = "".join(info_cards) if info_cards else "<div class='empty'>沒有其他狀態資訊。</div>"
|
| 172 |
+
|
| 173 |
return f"""
|
| 174 |
<style>
|
| 175 |
+
.status-wrap{{display:grid;gap:16px}}
|
| 176 |
+
.status-hero{{border-radius:20px;padding:18px;color:#fff;background:linear-gradient(135deg,#0f172a,#2563eb);box-shadow:0 10px 24px rgba(15,23,42,.16)}}
|
| 177 |
+
.status-hero h2{{margin:0 0 6px;font-size:24px}}.src{{font-size:13px;opacity:.85;word-break:break-all}}
|
| 178 |
+
.section{{background:#fff;border:1px solid #e5e7eb;border-radius:18px;padding:14px;box-shadow:0 8px 22px rgba(15,23,42,.06)}}
|
| 179 |
+
.section-title{{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;color:#0f172a;font-weight:900;font-size:17px}}
|
| 180 |
+
.section-note{{font-size:12px;color:#64748b;font-weight:600}}
|
| 181 |
+
.alive-grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px}}
|
| 182 |
+
.info-grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}}
|
| 183 |
+
.alive-card{{display:flex;gap:12px;align-items:center;padding:15px;border-radius:16px;background:linear-gradient(180deg,#f0fdf4,#ffffff);border:1px solid rgba(34,197,94,.28)}}
|
| 184 |
+
.lamp{{width:24px;height:24px;border-radius:99px;background:#22c55e;box-shadow:0 0 0 7px rgba(34,197,94,.16),0 0 20px rgba(34,197,94,.70);flex:0 0 auto}}
|
| 185 |
+
.info-card{{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:14px;border-radius:16px;background:#f8fafc;border:1px solid #e2e8f0}}
|
| 186 |
+
.info-card.ok{{background:#f8fafc;border-color:#dbeafe}}.info-card.warn{{background:#fffbeb;border-color:#fde68a}}.info-card.bad{{background:#fef2f2;border-color:#fecaca}}
|
| 187 |
+
b{{display:block;color:#0f172a}}small{{display:block;color:#64748b;margin-top:4px;word-break:break-all}}
|
| 188 |
+
.tag{{font-size:12px;font-weight:800;border-radius:999px;padding:6px 10px;background:#e2e8f0;color:#334155;white-space:nowrap;max-width:140px;overflow:hidden;text-overflow:ellipsis}}
|
| 189 |
+
.info-card.ok .tag{{background:#dbeafe;color:#1d4ed8}}.info-card.warn .tag{{background:#fef3c7;color:#b45309}}.info-card.bad .tag{{background:#fee2e2;color:#b91c1c}}
|
| 190 |
+
.empty{{padding:14px;border-radius:14px;background:#f8fafc;color:#64748b;border:1px dashed #cbd5e1}}
|
| 191 |
+
@media(max-width:640px){{.alive-grid,.info-grid{{grid-template-columns:1fr}}.status-hero h2{{font-size:21px}}}}
|
| 192 |
</style>
|
| 193 |
+
<div class='status-wrap'>
|
| 194 |
+
<div class='status-hero'><h2>系統狀態</h2><div class='src'>來源:{esc(STATUS_DATASET_ID)} / {esc(label)}</div></div>
|
| 195 |
+
<div class='section'><div class='section-title'>Alive 模組綠燈 <span class='section-note'>僅 alive 使用燈號</span></div><div class='alive-grid'>{alive_html}</div></div>
|
| 196 |
+
<div class='section'><div class='section-title'>其他狀態資訊 <span class='section-note'>非 alive 不使用燈號</span></div><div class='info-grid'>{info_html}</div></div>
|
| 197 |
+
</div>
|
| 198 |
"""
|
| 199 |
|
| 200 |
|
|
|
|
| 311 |
def plot_waveform(source):
|
| 312 |
try:
|
| 313 |
series, label = load_waveform(source)
|
| 314 |
+
msg = f"✅ 已載入 {label},波形分開繪製並附統計資訊。"
|
| 315 |
except Exception as e:
|
| 316 |
series, label = demo_series(), "demo://synthetic fallback"
|
| 317 |
msg = f"⚠️ 遠端波形讀取失敗,改顯示示範資料:{e}"
|
|
|
|
| 383 |
wave_opts = waveform_choices()
|
| 384 |
|
| 385 |
with gr.Blocks(title="EEW Dashboard") as demo:
|
| 386 |
+
gr.Markdown("# EEW Dashboard\n系統狀態分為 Alive 綠燈與其他狀態資訊;非 alive 不使用燈號。靜態波形使用 Plotly,10 條波線分開畫、one column,並附統計資訊。")
|
| 387 |
with gr.Tab("系統狀態"):
|
| 388 |
with gr.Row():
|
| 389 |
s = gr.Dropdown(choices=status_opts, value=status_opts[0], label="Status file")
|