feat: implement MonitorPanel dashboard component with status indicators and grid view

This commit is contained in:
2569718930@qq.com
2026-05-14 02:00:00 +08:00
parent 357e01be3c
commit d7d1744313
2 changed files with 516 additions and 177 deletions
@@ -43,13 +43,34 @@
color: var(--color-text-muted);
}
.root :global(.monitor-load-progress) {
margin-top: 3px;
font-size: 12px;
color: var(--color-text-muted);
font-variant-numeric: tabular-nums;
}
.root :global(.monitor-syncing-badge) {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: var(--color-accent-primary);
background: rgba(77, 163, 255, 0.08);
border: 1px solid rgba(77, 163, 255, 0.18);
border-radius: 999px;
padding: 2px 8px;
font-variant-numeric: tabular-nums;
}
.root :global(.monitor-loading-dot) {
display: inline-block;
width: 8px;
height: 8px;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--color-accent-primary);
animation: monitorPulse 1.2s ease-in-out infinite;
flex-shrink: 0;
}
.root :global(.monitor-notify-btn) {
@@ -240,7 +261,200 @@
font-size: 13px;
}
/* ── Runway sub-section ── */
/* ── Freshness legend ── */
.root :global(.monitor-legend) {
display: flex;
gap: 16px;
margin-bottom: 16px;
margin-top: -12px;
}
.root :global(.monitor-legend-item) {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: var(--color-text-muted);
}
/* ── Freshness dot ── */
.root :global(.monitor-freshness-dot) {
display: inline-block;
flex-shrink: 0;
width: 8px;
height: 8px;
border-radius: 50%;
cursor: help;
}
.root :global(.monitor-freshness-dot.fresh) {
background: var(--color-signal-success);
box-shadow: 0 0 5px rgba(34, 197, 94, 0.5);
}
.root :global(.monitor-freshness-dot.aging) {
background: var(--color-signal-warning);
box-shadow: 0 0 5px rgba(245, 158, 11, 0.4);
}
.root :global(.monitor-freshness-dot.stale) {
background: var(--color-signal-danger);
box-shadow: 0 0 5px rgba(239, 68, 68, 0.4);
}
.root :global(.monitor-freshness-dot.unknown) {
background: var(--color-text-muted);
opacity: 0.5;
}
/* ── Obs-age with freshness colour ── */
.root :global(.monitor-obs-age) {
font-size: 13px;
font-variant-numeric: tabular-nums;
color: var(--color-text-muted);
}
.root :global(.monitor-obs-age.fresh) { color: var(--color-signal-success); }
.root :global(.monitor-obs-age.aging) { color: var(--color-signal-warning); }
.root :global(.monitor-obs-age.stale) { color: var(--color-signal-danger); }
.root :global(.monitor-obs-age.unknown) { color: var(--color-text-muted); }
/* ── Refresh progress bar (top edge of card while fetching) ── */
.root :global(.monitor-refresh-bar) {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
border-radius: 12px 12px 0 0;
background: linear-gradient(
90deg,
transparent 0%,
var(--color-accent-primary) 50%,
transparent 100%
);
background-size: 200% 100%;
animation: monitorSweep 1.4s linear infinite;
}
/* ── Card refreshing state ── */
.root :global(.monitor-card.refreshing) {
position: relative;
overflow: hidden;
}
/* Slight dim on stale cards so fresh ones stand out */
.root :global(.monitor-card:not(.refreshing)) {
position: relative;
}
/* ══════════════════════════════════════════════════════════════
LIGHT THEME OVERRIDES
══════════════════════════════════════════════════════════════ */
.root :global(.scan-terminal.light .monitor-panel) {
background: #F7F9FC;
color: #0F172A;
}
.root :global(.scan-terminal.light .monitor-header) {
border-bottom-color: #E2E8F0;
}
.root :global(.scan-terminal.light .monitor-title) {
color: #0F172A;
}
.root :global(.scan-terminal.light .monitor-legend-item) {
color: #64748B;
}
.root :global(.scan-terminal.light .monitor-time),
.root :global(.scan-terminal.light .monitor-stat-label),
.root :global(.scan-terminal.light .monitor-obs-time),
.root :global(.scan-terminal.light .monitor-rw-label),
.root :global(.scan-terminal.light .monitor-high-time),
.root :global(.scan-terminal.light .monitor-load-progress) {
color: #475569;
}
.root :global(.scan-terminal.light .monitor-card) {
background: #FFFFFF;
border-color: #E2E8F0;
}
.root :global(.scan-terminal.light .monitor-card.new-high) {
border-color: rgba(124, 58, 237, 0.24);
}
.root :global(.scan-terminal.light .monitor-skeleton-card) {
background: #FFFFFF;
border-color: #E2E8F0;
}
.root :global(.scan-terminal.light .monitor-skeleton-line) {
background: #E2E8F0;
}
.root :global(.scan-terminal.light .monitor-city-name) { color: #0F172A; }
.root :global(.scan-terminal.light .monitor-airport-name),
.root :global(.scan-terminal.light .monitor-high-value),
.root :global(.scan-terminal.light .monitor-rw-temp) {
color: #334155;
}
.root :global(.scan-terminal.light .monitor-temp-value) { color: #0F172A; }
.root :global(.scan-terminal.light .monitor-temp-unit),
.root :global(.scan-terminal.light .monitor-temp-missing),
.root :global(.scan-terminal.light .monitor-stat-missing) {
color: #94A3B8;
}
.root :global(.scan-terminal.light .monitor-divider) { background: #E2E8F0; }
.root :global(.scan-terminal.light .monitor-notify-btn) {
border-color: rgba(148, 163, 184, 0.3);
color: #334155;
}
.root :global(.scan-terminal.light .monitor-notify-btn:hover) {
border-color: rgba(37, 99, 235, 0.38);
background: #EEF2F7;
}
.root :global(.scan-terminal.light .monitor-trend.rising) { color: #16a34a; }
.root :global(.scan-terminal.light .monitor-trend.falling) { color: #2563EB; }
.root :global(.scan-terminal.light .monitor-syncing-badge) {
color: #2563EB;
background: #EFF6FF;
border-color: rgba(37, 99, 235, 0.22);
}
/* obs-age colours stay semantic in light mode */
.root :global(.scan-terminal.light .monitor-obs-age.fresh) { color: #16a34a; }
.root :global(.scan-terminal.light .monitor-obs-age.aging) { color: #d97706; }
.root :global(.scan-terminal.light .monitor-obs-age.stale) { color: #dc2626; }
.root :global(.scan-terminal.light .monitor-obs-age.unknown) { color: #64748b; }
/* ── Animations ── */
@keyframes monitorPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
@keyframes monitorShimmer {
0%, 100% { opacity: 0.55; }
50% { opacity: 1; }
}
@keyframes monitorSweep {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.root :global(.monitor-divider) {
height: 1px;
background: var(--color-border-subtle);