fix(observability): polish topbar alert indicator
All checks were successful
CD Pipeline / deploy (push) Successful in 1m33s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m33s
This commit is contained in:
@@ -140,6 +140,22 @@ REQUIRED_BASE_SNIPPETS = [
|
||||
"momo-observability-mode",
|
||||
"/observability/overview",
|
||||
"/observability/api/health_indicator",
|
||||
"momo-obs-link",
|
||||
"momo-obs-badge",
|
||||
"classList.add('is-alert')",
|
||||
]
|
||||
|
||||
FORBIDDEN_BASE_PATTERNS = [
|
||||
Rule(
|
||||
"inline_obs_badge_style",
|
||||
re.compile(r"id=\"momo-obs-badge\"[^>]*style=", re.IGNORECASE | re.DOTALL),
|
||||
"Topbar 觀測台 badge 不得使用 inline style;請使用 .momo-obs-badge。",
|
||||
),
|
||||
Rule(
|
||||
"hardcoded_obs_alert_color",
|
||||
re.compile(r"(#dc3545|link\.style\.color|badge\.style\.display)", re.IGNORECASE),
|
||||
"Topbar 觀測台告警狀態不得用 JS/inline 硬改色;請切換 is-alert / hidden。",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -192,6 +208,23 @@ def scan_required_snippets(relative_path: Path, snippets: list[str], label: str)
|
||||
return findings
|
||||
|
||||
|
||||
def scan_base_topbar() -> list[str]:
|
||||
path = ROOT / BASE_PATH
|
||||
if not path.exists():
|
||||
return [f"{BASE_PATH}: missing required base/topbar file"]
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
findings = scan_required_snippets(BASE_PATH, REQUIRED_BASE_SNIPPETS, "base/topbar")
|
||||
for rule in FORBIDDEN_BASE_PATTERNS:
|
||||
for match in rule.pattern.finditer(text):
|
||||
line = line_number(text, match.start())
|
||||
snippet = match.group(0).replace("\n", " ")[:90]
|
||||
findings.append(
|
||||
f"{BASE_PATH}:{line}: [{rule.code}] {rule.message} ({snippet})"
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def scan_shell() -> list[str]:
|
||||
path = ROOT / SHELL_PATH
|
||||
if not path.exists():
|
||||
@@ -273,7 +306,7 @@ def main() -> int:
|
||||
findings.extend(scan_file(Path(template_path)))
|
||||
findings.extend(scan_css())
|
||||
findings.extend(scan_shell())
|
||||
findings.extend(scan_required_snippets(BASE_PATH, REQUIRED_BASE_SNIPPETS, "base/topbar"))
|
||||
findings.extend(scan_base_topbar())
|
||||
findings.extend(scan_nav_contract())
|
||||
|
||||
if findings:
|
||||
@@ -286,7 +319,7 @@ def main() -> int:
|
||||
print(f"- templates checked: {len(TEMPLATE_PATHS)}")
|
||||
print(f"- css guardrails checked: {len(REQUIRED_CSS_SNIPPETS)}")
|
||||
print(f"- sidebar/nav guardrails checked: {len(REQUIRED_SHELL_SNIPPETS)}")
|
||||
print(f"- base/topbar guardrails checked: {len(REQUIRED_BASE_SNIPPETS)}")
|
||||
print(f"- base/topbar guardrails checked: {len(REQUIRED_BASE_SNIPPETS) + len(FORBIDDEN_BASE_PATTERNS)}")
|
||||
print(f"- nav contract checked: {len(OBSERVABILITY_NAV_ITEMS)} pages")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -123,6 +123,36 @@
|
||||
opacity: 0.68;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.momo-obs-link {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
.momo-obs-link.is-alert {
|
||||
color: #c96442;
|
||||
box-shadow: 0 0 0 3px rgba(201, 100, 66, 0.12);
|
||||
}
|
||||
.momo-obs-badge {
|
||||
position: absolute;
|
||||
top: -0.22rem;
|
||||
right: -0.22rem;
|
||||
min-width: 1.05rem;
|
||||
height: 1.05rem;
|
||||
padding: 0 0.28rem;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgba(255, 248, 238, 0.86);
|
||||
background: linear-gradient(135deg, #d76a45, #9d4a32);
|
||||
color: #fff8ee;
|
||||
box-shadow: 0 8px 18px rgba(132, 58, 34, 0.26);
|
||||
font-size: 0.62rem;
|
||||
line-height: 1;
|
||||
font-weight: 900;
|
||||
}
|
||||
.momo-obs-badge[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% set _scheduler = scheduler_stats|default({}) %}
|
||||
{% set _momo_runs = _scheduler.get('momo_task', []) if _scheduler is mapping else [] %}
|
||||
|
||||
@@ -59,14 +59,9 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a class="momo-icon-button" href="/observability/overview" title="AI 觀測台" id="momo-obs-link"
|
||||
style="text-decoration: none; position: relative;">
|
||||
<a class="momo-icon-button momo-obs-link" href="/observability/overview" title="AI 觀測台" id="momo-obs-link">
|
||||
<i class="fas fa-satellite-dish"></i>
|
||||
<span id="momo-obs-badge"
|
||||
style="display: none; position: absolute; top: -2px; right: -2px;
|
||||
background: #dc3545; color: white; border-radius: 50%;
|
||||
font-size: 0.6em; padding: 2px 5px; min-width: 16px;
|
||||
text-align: center; line-height: 1;"></span>
|
||||
<span id="momo-obs-badge" class="momo-obs-badge" hidden></span>
|
||||
</a>
|
||||
<button class="momo-icon-button" type="button" title="說明">
|
||||
<i class="fas fa-circle-question"></i>
|
||||
@@ -360,11 +355,11 @@
|
||||
link.title = d.tooltip || 'AI 觀測台';
|
||||
if (d.alert_count > 0) {
|
||||
badge.textContent = d.alert_count;
|
||||
badge.style.display = 'inline-block';
|
||||
link.style.color = '#dc3545';
|
||||
badge.hidden = false;
|
||||
link.classList.add('is-alert');
|
||||
} else {
|
||||
badge.style.display = 'none';
|
||||
link.style.color = '';
|
||||
badge.hidden = true;
|
||||
link.classList.remove('is-alert');
|
||||
}
|
||||
} catch (e) { /* silent */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user