diff --git a/core/bench/dashboard/frontend/assets/iggy-dark.png b/core/bench/dashboard/frontend/assets/iggy-dark.png deleted file mode 100644 index 63e67aa006..0000000000 Binary files a/core/bench/dashboard/frontend/assets/iggy-dark.png and /dev/null differ diff --git a/core/bench/dashboard/frontend/assets/iggy-dark.svg b/core/bench/dashboard/frontend/assets/iggy-dark.svg new file mode 100644 index 0000000000..d04b36a029 --- /dev/null +++ b/core/bench/dashboard/frontend/assets/iggy-dark.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/bench/dashboard/frontend/assets/iggy-light.png b/core/bench/dashboard/frontend/assets/iggy-light.png deleted file mode 100644 index adc45a899d..0000000000 Binary files a/core/bench/dashboard/frontend/assets/iggy-light.png and /dev/null differ diff --git a/core/bench/dashboard/frontend/assets/iggy-light.svg b/core/bench/dashboard/frontend/assets/iggy-light.svg new file mode 100644 index 0000000000..a03645fb3e --- /dev/null +++ b/core/bench/dashboard/frontend/assets/iggy-light.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/bench/dashboard/frontend/assets/style.css b/core/bench/dashboard/frontend/assets/style.css index 077bbd14f4..287a880dbe 100644 --- a/core/bench/dashboard/frontend/assets/style.css +++ b/core/bench/dashboard/frontend/assets/style.css @@ -142,93 +142,6 @@ body { to { opacity: 1; transform: translateY(0); } } -/* Mobile: sidebar becomes slide-in drawer, content spans full width. */ -@media (max-width: 768px) { - .app-shell.detail-layout { - grid-template-columns: 1fr; - grid-template-areas: - "bar" - "main"; - } - - .app-shell.detail-layout .sidebar { - position: fixed; - top: 56px; - left: 0; - width: min(88vw, 380px); - height: calc(100vh - 56px); - z-index: 40; - box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15); - transform: translateX(0); - transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1); - } - - .app-shell.detail-layout.sidebar-collapsed .sidebar { - transform: translateX(-100%); - pointer-events: none; - } - - .app-shell.detail-layout:not(.sidebar-collapsed)::after { - content: ''; - position: fixed; - top: 56px; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.35); - z-index: 35; - backdrop-filter: blur(2px); - } - - .app-bar { - padding: 0 10px; - } - - .app-bar-brand-text { display: none; } - .app-bar-tab { padding: 6px 10px; font-size: 12px; } - .app-bar-text-btn { padding: 6px 10px; font-size: 12px; } - - .hero-v2 { padding: 32px 16px 24px; } - .hero-v2-inner { gap: 20px; } - .hero-v2-title { font-size: clamp(40px, 14vw, 72px); } - .hero-v2-cards { grid-template-columns: repeat(2, 1fr); gap: 10px; } - .hero-v2-card { padding: 12px; } - .hero-v2-card-value { font-size: 22px; } - - .compare-banner { - flex-wrap: wrap; - gap: 8px; - padding: 8px 10px; - } - .compare-banner-names { - flex: 1 1 100%; - order: 2; - flex-wrap: wrap; - gap: 8px; - } - .compare-banner-unpin { order: 1; margin-left: auto; } - .compare-grid { grid-template-columns: 1fr; } - - .tail-chart-wrap { padding: 12px; } - .tail-chart-header { flex-wrap: wrap; gap: 8px; } - .tail-chart-subject { max-width: 180px; } - - .footer-inner { - grid-template-columns: 1fr; - text-align: center; - gap: 10px; - } - .footer-links { justify-content: center; } - .footer-meta { justify-content: center; } -} - -@media (max-width: 520px) { - .app-bar-tabs { gap: 0; } - .app-bar-tab { padding: 6px 8px; font-size: 11px; } - .app-bar-icon-btn { width: 30px; height: 30px; } - .hero-v2-cards { grid-template-columns: 1fr; } -} - .app-bar { grid-area: bar; } .sidebar { grid-area: side; } .main-content { grid-area: main; } @@ -640,8 +553,9 @@ body.dark .app-bar-toast { } .app-bar-brand img { - width: 28px; - height: 28px; + width: auto; + height: 40px; + max-width: 180px; object-fit: contain; } @@ -1180,6 +1094,28 @@ body.dark .footer-version { .footer-sep { opacity: 0.4; } +.footer-tagline { + font-size: 11px; + color: var(--color-text-secondary); + letter-spacing: 0.02em; +} + +.footer-heart { + color: #ef4444; + font-size: 12px; + margin: 0 2px; + display: inline-block; + animation: footer-heart-beat 2s ease-in-out infinite; +} + +@keyframes footer-heart-beat { + 0%, 100% { transform: scale(1); } + 14% { transform: scale(1.15); } + 28% { transform: scale(1); } + 42% { transform: scale(1.15); } + 70% { transform: scale(1); } +} + body.dark .footer { background-color: var(--color-dark-background); border-top-color: var(--color-dark-border); @@ -1223,26 +1159,6 @@ body.dark .footer { background: rgba(0, 0, 0, 0.8); } -.loading-spinner { - width: var(--button-size); - height: var(--button-size); - border: var(--spacing-xs) solid var(--color-background); - border-top: var(--spacing-xs) solid var(--color-text); - border-radius: 50%; - animation: spin var(--transition-speed-slow) linear infinite; - transform: scale(0.5); - transition: transform var(--transition-speed-slow); -} - -.loading-overlay.visible .loading-spinner { - transform: scale(1); -} - -.dark .loading-spinner { - border-color: var(--color-dark-background); - border-top-color: var(--color-dark-text); -} - @keyframes spin { 0% { transform: rotate(0deg); @@ -1944,18 +1860,14 @@ body.dark .skeleton-dot { } .dense-row { - display: grid; - grid-template-columns: 10px 1fr auto; - align-items: center; - gap: 10px; - padding: 10px 12px; + display: flex; + align-items: stretch; + gap: 4px; + padding: 2px 4px; background: transparent; border: 1px solid transparent; border-radius: 8px; color: var(--color-text); - cursor: pointer; - text-align: left; - font-family: inherit; transition: background-color 120ms, border-color 120ms; } @@ -1981,6 +1893,28 @@ body.dark .dense-row.active { box-shadow: inset 3px 0 0 #ff9103; } +.dense-row-select { + flex: 1 1 auto; + min-width: 0; + display: grid; + grid-template-columns: 10px 1fr; + align-items: center; + gap: 10px; + padding: 8px 8px; + background: transparent; + border: none; + border-radius: 6px; + color: inherit; + cursor: pointer; + text-align: left; + font-family: inherit; +} + +.dense-row-select:focus-visible { + outline: 2px solid #ff9103; + outline-offset: 1px; +} + .dense-row-dot { width: 8px; height: 8px; @@ -2295,9 +2229,17 @@ body.dark .hero-v2 { } .hero-v2-loading { + position: fixed; + inset: 0; + padding: 0; justify-content: center; align-items: center; - min-height: 520px; + z-index: 9998; + background: var(--color-background); +} + +body.dark .hero-v2-loading { + background: var(--color-dark-background); } .hero-v2-loading-inner { @@ -2314,8 +2256,9 @@ body.dark .hero-v2 { } .hero-v2-loading-mark { - width: 112px; - height: 112px; + width: auto; + height: 128px; + max-width: 440px; object-fit: contain; filter: drop-shadow(0 0 24px rgba(255, 145, 3, 0.35)); animation: hero-loading-pulse 1800ms ease-in-out infinite; @@ -2370,6 +2313,36 @@ body.dark .hero-v2 { border: 0; } +.iggy-loader { + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 14px; +} + +.iggy-loader-mark { + object-fit: contain; + width: auto; + filter: drop-shadow(0 0 16px rgba(255, 145, 3, 0.3)); + animation: hero-loading-pulse 1800ms ease-in-out infinite; +} + +.iggy-loader-md .iggy-loader-mark { height: 72px; max-width: 260px; } +.iggy-loader-lg .iggy-loader-mark { height: 128px; max-width: 440px; } + +.iggy-loader-label { + margin: 0; + color: var(--color-text-secondary); + font-size: 14px; + font-weight: 500; + letter-spacing: 0.02em; +} + +body.dark .iggy-loader-label { + color: var(--color-dark-text-secondary); +} + .hero-v2-headline { display: flex; flex-direction: column; @@ -3270,3 +3243,264 @@ body.dark .sweep-view { background: var(--color-dark-sidebar, #1a2234); border-color: var(--color-dark-border); } + +/* Mobile overrides kept at end of file so they win the cascade over + later desktop rules declared with the same specificity. */ +@media (max-width: 768px) { + .app-bar { + display: grid !important; + grid-template-columns: auto 1fr auto; + grid-template-areas: + "left right right" + "center center center"; + row-gap: 2px; + column-gap: 6px; + align-items: center; + padding: 4px 8px; + height: auto; + min-height: 52px; + } + .app-bar-left { + grid-area: left; + display: flex; + align-items: center; + gap: 4px; + min-width: 0; + } + .app-bar-right { + grid-area: right; + justify-self: end; + display: flex; + align-items: center; + gap: 2px; + } + .app-bar-center { + grid-area: center; + min-width: 0; + max-width: 100%; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + padding-bottom: 2px; + scrollbar-width: none; + } + .app-bar-center:empty { display: none; } + .app-bar-center::-webkit-scrollbar { display: none; } + .app-bar-tabs { + flex-wrap: nowrap; + justify-content: flex-start; + width: max-content; + } + .app-bar-brand img { width: 40px; height: 40px; } + .app-bar-brand-text { display: none; } + .app-bar-icon-btn { width: 36px; height: 36px; flex-shrink: 0; } + .app-bar-icon-btn svg { width: 16px; height: 16px; } + .app-bar-icon-wrap { flex-shrink: 0; } + .mobile-hide { display: none !important; } + + .app-shell.detail-layout, + .app-shell.detail-layout.sidebar-collapsed { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + grid-template-areas: + "bar" + "main"; + } + .app-shell.detail-layout .sidebar { + position: fixed; + top: 56px; + left: 0; + width: min(92vw, 380px); + height: calc(100vh - 56px); + z-index: 40; + box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15); + transform: translateX(0); + transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1); + } + .app-shell.detail-layout.sidebar-collapsed .sidebar { + transform: translateX(-100%); + pointer-events: none; + } + .app-shell.detail-layout:not(.sidebar-collapsed)::after { + content: ''; + position: fixed; + top: 56px; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.35); + z-index: 35; + backdrop-filter: blur(2px); + } + + .hero-v2 { padding: 32px 16px 24px; } + .hero-v2-inner { gap: 20px; } + .hero-v2-title { font-size: clamp(40px, 14vw, 72px); } + .hero-v2-cards { grid-template-columns: repeat(2, 1fr); gap: 10px; } + .hero-v2-card { padding: 12px; } + .hero-v2-card-value { font-size: 22px; } + + .compare-banner { + flex-wrap: wrap; + gap: 8px; + padding: 8px 10px; + } + .compare-banner-names { + flex: 1 1 100%; + order: 2; + flex-wrap: wrap; + gap: 8px; + } + .compare-banner-unpin { order: 1; margin-left: auto; } + .compare-grid { grid-template-columns: 1fr; } + + .tail-chart-wrap { padding: 12px; } + .tail-chart-header { flex-wrap: wrap; gap: 8px; } + .tail-chart-subject { max-width: 180px; } + + .footer-inner { + grid-template-columns: 1fr; + text-align: center; + gap: 10px; + } + .footer-links { justify-content: center; } + .footer-meta { justify-content: center; } + + html, body { + height: auto !important; + min-height: 100vh; + max-height: none !important; + overflow: auto !important; + } + #app { + display: block; + height: auto !important; + min-height: 100vh; + max-height: none !important; + overflow: visible !important; + } + .app-container { + min-height: auto; + } + .app-shell { + flex: initial; + height: auto !important; + min-height: calc(100vh - 40px); + max-height: none !important; + overflow: visible !important; + } + .app-shell.detail-layout, + .app-shell.detail-layout.sidebar-collapsed { + height: auto; + min-height: 100vh; + } + .main-content { + padding-top: 8px; + overflow: visible !important; + min-height: 0; + height: auto; + } + .content-wrapper { + padding: 0; + overflow: visible !important; + min-height: 0; + height: auto; + } + + .chart-title { + margin: 0 8px 8px; + padding: 6px 4px 8px; + gap: 2px; + } + .chart-title-primary { + font-size: 14px; + line-height: 1.3; + word-break: break-word; + } + .chart-title-identifier { + font-size: 11px; + gap: 6px; + margin-top: 4px; + padding: 0; + } + .chart-title-gitref { padding: 1px 7px; font-size: 11px; } + + .single-view { + padding: 0 4px; + min-height: 320px; + height: 55vh; + max-height: 480px; + } + .single-view > div { min-height: 0; } + + .benchmark-meta { margin: 0 8px; } + .benchmark-meta-toggle { padding: 8px 10px; } + .benchmark-meta-body { padding: 8px 10px 10px; } + .benchmark-meta-row { + grid-template-columns: 1fr; + gap: 4px; + align-items: start; + } + .benchmark-meta-label { + border-right: none; + padding-right: 0; + font-size: 9.5px; + } + .benchmark-meta-chip { + font-size: 10.5px; + padding: 2px 6px; + gap: 5px; + } + .benchmark-meta-chip-label { font-size: 9.5px; } + + .compare-wrapper { gap: 10px; } + .compare-pane { padding: 8px; } + .compare-pane-label { font-size: 10px; } + .compare-banner-badge { font-size: 10px; padding: 3px 6px; } + .compare-banner-name { max-width: 42vw; } + + .app-bar-toast { + right: auto; + left: 50%; + transform: translateX(-50%); + max-width: calc(100vw - 24px); + } + + .benchmark-info-tooltip, + .embed-modal { + min-width: 0; + width: min(340px, calc(100vw - 24px)); + position: fixed; + right: 8px; + top: 104px; + } +} + +@media (max-width: 520px) { + .app-bar-tabs { gap: 0; } + .app-bar-tab { padding: 6px 8px; font-size: 11px; } + .hero-v2-cards { grid-template-columns: 1fr; } + .chart-title-primary { font-size: 13px; } + .chart-title-identifier { font-size: 10.5px; } + .benchmark-meta-toggle-hint { display: none; } + .benchmark-meta-chip { font-size: 10px; } + .sweep-view { padding: 8px; margin: 8px; } + .sweep-view-title { font-size: 12px; flex-wrap: wrap; } + .sweep-view-hint { display: none; } + .sweep-chart { height: 200px; } + .compare-banner { + flex-direction: column; + align-items: stretch; + } + .compare-banner-names { justify-content: center; } + .compare-banner-unpin { align-self: center; } +} + +@media (max-width: 400px) { + .app-bar { padding: 4px 6px; column-gap: 2px; } + .app-bar-left { gap: 2px; } + .app-bar-right { gap: 0; } + .app-bar-icon-btn { width: 34px; height: 34px; } + .app-bar-brand img { width: 36px; height: 36px; } + .app-bar-brand { padding: 2px; } +} diff --git a/core/bench/dashboard/frontend/index.dev.html b/core/bench/dashboard/frontend/index.dev.html index ff968b22e4..4d73168572 100644 --- a/core/bench/dashboard/frontend/index.dev.html +++ b/core/bench/dashboard/frontend/index.dev.html @@ -40,7 +40,64 @@ -
+
+
+
+ Apache Iggy +
Benchmarks
+
+
+
+ diff --git a/core/bench/dashboard/frontend/index.html b/core/bench/dashboard/frontend/index.html index ff968b22e4..4d73168572 100644 --- a/core/bench/dashboard/frontend/index.html +++ b/core/bench/dashboard/frontend/index.html @@ -40,7 +40,64 @@ -
+
+
+
+ Apache Iggy +
Benchmarks
+
+
+
+ diff --git a/core/bench/dashboard/frontend/index.prod.html b/core/bench/dashboard/frontend/index.prod.html index de430f3300..f86b5ed0aa 100644 --- a/core/bench/dashboard/frontend/index.prod.html +++ b/core/bench/dashboard/frontend/index.prod.html @@ -55,6 +55,63 @@ -
+
+
+
+ Apache Iggy +
Benchmarks
+
+
+
+ diff --git a/core/bench/dashboard/frontend/src/components/chart/single_chart.rs b/core/bench/dashboard/frontend/src/components/chart/single_chart.rs index d4e3c491d7..e89151c14f 100644 --- a/core/bench/dashboard/frontend/src/components/chart/single_chart.rs +++ b/core/bench/dashboard/frontend/src/components/chart/single_chart.rs @@ -17,6 +17,7 @@ use crate::api::fetch_benchmark_report_full; use crate::components::chart::{PlotConfig, dispose_chart}; +use crate::components::loader::{IggyLoader, LoaderSize}; use crate::components::selectors::measurement_type_selector::MeasurementType; use crate::hooks::use_size; use bench_report::report::BenchmarkReport; @@ -26,7 +27,6 @@ use bench_report::{ use charming::theme::Theme; use charming::{Echarts, WasmRenderer}; use gloo::console::log; -use gloo::history::{BrowserHistory, History}; use uuid::Uuid; use yew::platform::spawn_local; use yew::prelude::*; @@ -57,17 +57,6 @@ pub fn single_chart(props: &SingleChartProps) -> Html { let benchmark_uuid = *benchmark_uuid; is_loading.set(true); - let current_location = web_sys::window() - .and_then(|w| w.location().pathname().ok()) - .unwrap_or_default(); - - let expected_path = format!("/benchmarks/{benchmark_uuid}"); - - if current_location != expected_path { - let history = BrowserHistory::new(); - history.push(expected_path); - } - spawn_local(async move { match fetch_benchmark_report_full(&benchmark_uuid).await { Ok(data) => { @@ -152,7 +141,7 @@ pub fn single_chart(props: &SingleChartProps) -> Html {
-
+
} diff --git a/core/bench/dashboard/frontend/src/components/footer.rs b/core/bench/dashboard/frontend/src/components/footer.rs index 58cfd6f0f8..573d00f29c 100644 --- a/core/bench/dashboard/frontend/src/components/footer.rs +++ b/core/bench/dashboard/frontend/src/components/footer.rs @@ -44,6 +44,12 @@ pub fn footer() -> Html { {"Apache Software Foundation"} + {"•"} + + {"Built with "} + {"❤"} + {" for the message streaming community"} + diff --git a/core/bench/dashboard/frontend/src/components/layout/hero.rs b/core/bench/dashboard/frontend/src/components/layout/hero.rs index 899ccfc851..dcc0b9bda7 100644 --- a/core/bench/dashboard/frontend/src/components/layout/hero.rs +++ b/core/bench/dashboard/frontend/src/components/layout/hero.rs @@ -21,7 +21,6 @@ use crate::format::format_ms; use crate::router::AppRoute; use crate::state::benchmark::{latest_sweep, pick_best_from_recent_batch}; use bench_dashboard_shared::BenchmarkReportLight; -use chrono::DateTime; use gloo::console::log; use gloo::timers::callback::Timeout; use std::cell::Cell; @@ -166,7 +165,6 @@ struct HeroStats { peak_msg_s: Option<(f64, String)>, max_scale: Option, total: usize, - latest_ts: Option, showcase: Option, } @@ -226,13 +224,6 @@ fn compute_stats<'a>(benchmarks: impl Iterator) pretty_name: pretty_name.clone(), }); } - if stats - .latest_ts - .as_ref() - .is_none_or(|current| &benchmark.timestamp > current) - { - stats.latest_ts = Some(benchmark.timestamp.clone()); - } } stats } @@ -339,7 +330,7 @@ fn render_stat_cards(stats: &HeroStats) -> Html { } { render_scale_card(1, stats.max_scale.as_ref()) } { render_showcase_card(2, stats.showcase.as_ref()) } - { render_summary_card(3, stats.total, stats.latest_ts.as_deref()) } + { render_volume_card(3, stats.showcase.as_ref()) } } } @@ -413,23 +404,58 @@ fn render_showcase_card(stagger: usize, showcase: Option<&BenchmarkReportLight>) } } -fn render_summary_card(stagger: usize, total: usize, latest_ts: Option<&str>) -> Html { - let sub = match latest_ts { - Some(ts) => format!("Latest: {}", format_date(ts)), - None => String::new(), +fn render_volume_card(stagger: usize, showcase: Option<&BenchmarkReportLight>) -> Html { + let Some(benchmark) = showcase else { + return html! {}; + }; + let total_bytes = benchmark.total_bytes(); + if total_bytes == 0 { + return html! {}; + } + let (value, unit) = format_volume(total_bytes); + let messages = benchmark.total_messages_sent() + benchmark.total_messages_received(); + let sub = if messages > 0 { + format!("{} messages moved", format_count(messages)) + } else { + String::new() }; html! {
- {total} - {"runs"} + {value} + {unit}
-
{"Benchmarks loaded"}
+
{"Volume pushed in run"}
{sub}
} } +fn format_volume(bytes: u64) -> (String, &'static str) { + let bytes = bytes as f64; + if bytes >= 1_000_000_000_000.0 { + (format_significant(bytes / 1_000_000_000_000.0), "TB") + } else if bytes >= 1_000_000_000.0 { + (format_significant(bytes / 1_000_000_000.0), "GB") + } else if bytes >= 1_000_000.0 { + (format_significant(bytes / 1_000_000.0), "MB") + } else { + (format_significant(bytes / 1_000.0), "kB") + } +} + +fn format_count(value: u64) -> String { + if value >= 1_000_000_000 { + format!("{:.2}B", value as f64 / 1_000_000_000.0) + } else if value >= 1_000_000 { + format!("{:.1}M", value as f64 / 1_000_000.0) + } else if value >= 1_000 { + format!("{:.1}k", value as f64 / 1_000.0) + } else { + value.to_string() + } +} + fn format_throughput_bytes(mb_per_s: f64) -> (String, &'static str) { if mb_per_s >= 1_000_000.0 { (format_significant(mb_per_s / 1_000_000.0), "TB/s") @@ -462,18 +488,11 @@ fn format_significant(v: f64) -> String { } } -fn format_date(timestamp_str: &str) -> String { - match DateTime::parse_from_rfc3339(timestamp_str) { - Ok(t) => t.format("%Y-%m-%d").to_string(), - Err(_) => "unknown".to_string(), - } -} - fn render_hero_loading(is_dark: bool, is_slow: bool) -> Html { let logo_src = if is_dark { - "/assets/iggy-light.png" + "/assets/iggy-light.svg" } else { - "/assets/iggy-dark.png" + "/assets/iggy-dark.svg" }; html! {
@@ -485,7 +504,6 @@ fn render_hero_loading(is_dark: bool, is_slow: bool) -> Html { alt="" aria-hidden="true" /> -
{"Apache Iggy"}
{"Benchmarks"}
if is_slow {

@@ -498,10 +516,6 @@ fn render_hero_loading(is_dark: bool, is_slow: bool) -> Html { } } -/// Every headline field comes from the SAME benchmark that powers the -/// tail chart and "View details" link. Built through this single helper -/// so the big number, subject name, CPU, and gitref can never drift to -/// different benchmarks. #[derive(Default, Debug, PartialEq)] pub struct ShowcaseDisplay { pub formatted_value: String, diff --git a/core/bench/dashboard/frontend/src/components/layout/main_content.rs b/core/bench/dashboard/frontend/src/components/layout/main_content.rs index 4125b053e1..6dc8c2fe29 100644 --- a/core/bench/dashboard/frontend/src/components/layout/main_content.rs +++ b/core/bench/dashboard/frontend/src/components/layout/main_content.rs @@ -19,6 +19,7 @@ use crate::components::chart::single_chart::SingleChart; use crate::components::chart::tail_chart::TailChart; use crate::components::layout::benchmark_meta::BenchmarkMeta; use crate::components::layout::sweep_view::SweepView; +use crate::components::loader::{IggyLoader, LoaderSize}; use crate::components::selectors::measurement_type_selector::MeasurementType; use crate::router::AppRoute; use crate::state::benchmark::use_benchmark; @@ -224,17 +225,15 @@ fn render_loading() -> Html { html! {

-
-

{"Loading benchmark..."}

-
+
} } -/// Map a benchmark gitref to the matching apache/iggy URL. -/// Tagged releases (`X.Y.Z`, `X.Y.Z-edge.N`) are prefixed `server-` to -/// match the repo's tag scheme. Plain commit hashes link directly. fn iggy_gitref_url(gitref: &str) -> String { if crate::version::parse_semver_recency(gitref).is_some() { format!("https://github.com/apache/iggy/tree/server-{gitref}") diff --git a/core/bench/dashboard/frontend/src/components/layout/top_app_bar.rs b/core/bench/dashboard/frontend/src/components/layout/top_app_bar.rs index 6b1f28e1e6..76c81513d2 100644 --- a/core/bench/dashboard/frontend/src/components/layout/top_app_bar.rs +++ b/core/bench/dashboard/frontend/src/components/layout/top_app_bar.rs @@ -172,9 +172,9 @@ pub fn top_app_bar(props: &TopAppBarProps) -> Html { "Switch to dark theme" }; let logo_src = if is_dark { - "/assets/iggy-light.png" + "/assets/iggy-light.svg" } else { - "/assets/iggy-dark.png" + "/assets/iggy-dark.svg" }; html! { @@ -241,14 +241,14 @@ pub fn top_app_bar(props: &TopAppBarProps) -> Html {
-
+
-
+
Html { /> }
-
+
&'static str { + match self { + Self::Medium => "iggy-loader-md", + Self::Large => "iggy-loader-lg", + } + } +} + +#[derive(Properties, PartialEq)] +pub struct IggyLoaderProps { + #[prop_or(LoaderSize::Medium)] + pub size: LoaderSize, + #[prop_or_default] + pub label: Option, +} + +#[function_component(IggyLoader)] +pub fn iggy_loader(props: &IggyLoaderProps) -> Html { + let (is_dark, _) = use_context::<(bool, Callback<()>)>().expect("Theme context not found"); + let logo_src = if is_dark { + "/assets/iggy-light.svg" + } else { + "/assets/iggy-dark.svg" + }; + let class = classes!("iggy-loader", props.size.css_class()); + + html! { +
+ + if let Some(label) = props.label.as_ref() { +

{label.clone()}

+ } + + { props.label.clone().unwrap_or_else(|| AttrValue::from("Loading")) } + +
+ } +} diff --git a/core/bench/dashboard/frontend/src/components/mod.rs b/core/bench/dashboard/frontend/src/components/mod.rs index 0781daaf9b..cf9eece088 100644 --- a/core/bench/dashboard/frontend/src/components/mod.rs +++ b/core/bench/dashboard/frontend/src/components/mod.rs @@ -20,6 +20,7 @@ pub mod chart; pub mod embed_modal; pub mod footer; pub mod layout; +pub mod loader; pub mod selectors; pub mod theme; pub mod tooltips; diff --git a/core/bench/dashboard/frontend/src/components/selectors/dense_benchmark_row.rs b/core/bench/dashboard/frontend/src/components/selectors/dense_benchmark_row.rs index bd96d97bbf..e94bca6158 100644 --- a/core/bench/dashboard/frontend/src/components/selectors/dense_benchmark_row.rs +++ b/core/bench/dashboard/frontend/src/components/selectors/dense_benchmark_row.rs @@ -50,20 +50,16 @@ pub fn dense_benchmark_row(props: &DenseBenchmarkRowProps) -> Html { .to_lowercase() .replace(' ', "-"); - let on_click = { + let on_select_click = { let on_select = props.on_select.clone(); let benchmark = benchmark.clone(); - Callback::from(move |_| on_select.emit(benchmark.clone())) + Callback::from(move |_: MouseEvent| on_select.emit(benchmark.clone())) }; let on_pin_click = { let on_toggle_pin = props.on_toggle_pin.clone(); let benchmark = benchmark.clone(); - Callback::from(move |event: MouseEvent| { - event.stop_propagation(); - event.prevent_default(); - on_toggle_pin.emit(benchmark.clone()); - }) + Callback::from(move |_: MouseEvent| on_toggle_pin.emit(benchmark.clone())) }; let pin_title = if is_pinned { @@ -71,46 +67,33 @@ pub fn dense_benchmark_row(props: &DenseBenchmarkRowProps) -> Html { } else { "Compare with this" }; + let select_aria = format!("Select benchmark {display_name}"); - let on_key_down = { - let on_click = on_click.clone(); - Callback::from(move |event: KeyboardEvent| { - if event.target() != event.current_target() { - return; - } - if event.key() == "Enter" || event.key() == " " { - event.prevent_default(); - on_click.emit(MouseEvent::new("click").unwrap()); - } - }) - }; - - let row_aria = format!("Select benchmark {display_name}"); html! { -
- -
-
{display_name}
-
- { render_metrics(benchmark) } - if props.show_timestamp { - {"·"} - { relative_time(&benchmark.timestamp) } - } +
+
+