From 1abe0df25ae41a845d40cdf55f37f30491cc53a6 Mon Sep 17 00:00:00 2001 From: DevITWay Date: Mon, 26 Jan 2026 16:39:48 +0000 Subject: [PATCH] Fix formatting --- SESSION_NOTES.md | 152 ++++++++++++++++++++++++++++ TODO.md | 63 ++++++++++++ nora-registry/src/activity_log.rs | 7 +- nora-registry/src/registry/maven.rs | 20 +++- nora-registry/src/ui/api.rs | 7 +- nora-registry/src/ui/components.rs | 40 +++++++- nora-registry/src/ui/templates.rs | 99 ++++++++++-------- 7 files changed, 329 insertions(+), 59 deletions(-) create mode 100644 SESSION_NOTES.md diff --git a/SESSION_NOTES.md b/SESSION_NOTES.md new file mode 100644 index 0000000..ba49f04 --- /dev/null +++ b/SESSION_NOTES.md @@ -0,0 +1,152 @@ +# NORA Development Session Notes + +--- + +## 2026-01-26 - Dashboard Expansion + +### Iteration 1: Planning & Exploration +- Received detailed implementation plan for dashboard expansion +- Explored codebase structure using Task agent +- Identified key files to modify: + - `main.rs` - AppState + - `ui/api.rs`, `ui/mod.rs`, `ui/components.rs`, `ui/templates.rs` + - `registry/docker.rs`, `npm.rs`, `maven.rs`, `cargo_registry.rs` + +### Iteration 2: Infrastructure (Phase 1) +- Created `src/dashboard_metrics.rs`: + - `DashboardMetrics` struct with AtomicU64 counters + - Per-registry tracking (docker, npm, maven, cargo, pypi) + - `record_download()`, `record_upload()`, `record_cache_hit/miss()` + - `cache_hit_rate()` calculation + +- Created `src/activity_log.rs`: + - `ActionType` enum: Pull, Push, CacheHit, ProxyFetch + - `ActivityEntry` struct with timestamp, action, artifact, registry, source + - `ActivityLog` with RwLock (bounded to 50 entries) + +### Iteration 3: AppState Update (Phase 2) +- Updated `main.rs`: + - Added `mod activity_log` and `mod dashboard_metrics` + - Extended `AppState` with `metrics: DashboardMetrics` and `activity: ActivityLog` + - Initialized in `run_server()` + +### Iteration 4: API Endpoint (Phase 3) +- Updated `ui/api.rs`: + - Added structs: `DashboardResponse`, `GlobalStats`, `RegistryCardStats`, `MountPoint` + - Implemented `api_dashboard()` - aggregates all metrics, storage stats, activity + +- Updated `ui/mod.rs`: + - Added route `/api/ui/dashboard` + - Modified `dashboard()` handler to use new response + +### Iteration 5: Dark Theme UI (Phase 4) +- Updated `ui/components.rs` with ~400 new lines: + - `layout_dark()` - dark theme wrapper (#0f172a background) + - `sidebar_dark()`, `header_dark()` - dark theme navigation + - `render_global_stats()` - 5-column stats grid + - `render_registry_card()` - extended card with metrics + - `render_mount_points_table()` - registry paths and proxies + - `render_activity_row()`, `render_activity_log()` - activity display + - `render_polling_script()` - 5-second auto-refresh JS + +### Iteration 6: Dashboard Template (Phase 5) +- Updated `ui/templates.rs`: + - Refactored `render_dashboard()` to accept `DashboardResponse` + - Added uptime display, global stats, registry cards grid + - Added mount points table and activity log + - Added `format_relative_time()` helper + +### Iteration 7: Registry Instrumentation (Phase 6) +- `registry/docker.rs`: + - `download_blob()` - record download + cache hit + activity + - `get_manifest()` - record download + cache hit + activity + - `upload_blob()` - record upload + activity + - `put_manifest()` - record upload + activity + +- `registry/npm.rs`: + - Cache hit tracking for local storage + - Cache miss + proxy fetch tracking + +- `registry/maven.rs`: + - `download()` - cache hit/miss + activity + - `upload()` - record upload + activity + +- `registry/cargo_registry.rs`: + - `download()` - record download + activity + +### Iteration 8: Build & Test +- `cargo build` - compiled successfully with minor warnings +- Fixed warnings: + - Removed unused `RegistryStats` import + - Added `#[allow(dead_code)]` to `stat_card()` +- `cargo test` - all 75 tests passed + +### Iteration 9: Server Testing +- Started server: `cargo run --release --bin nora` +- Tested endpoints: + ``` + GET /health - OK + GET /api/ui/dashboard - returns full metrics JSON + GET /ui/ - dark theme dashboard HTML + GET /v2/test/manifests/v1 - triggered Docker metrics + GET /npm/lodash/-/lodash-4.17.21.tgz - triggered npm proxy metrics + ``` +- Verified metrics tracking: + - Downloads: 3 (2 Docker + 1 npm) + - Cache hit rate: 66.67% + - Activity log populated with Pull, ProxyFetch events + +### Iteration 10: Git Commit & Push +- Staged 11 files (2 new, 9 modified) +- Commit: `93f9655 Add dashboard metrics, activity log, and dark theme` +- Pushed to `origin/main` + +### Iteration 11: Documentation +- Updated `TODO.md` with v0.2.1 section +- Created this `SESSION_NOTES.md` + +--- + +### Key Decisions Made +1. **In-memory metrics** - AtomicU64 for thread-safety, reset on restart +2. **Bounded activity log** - 50 entries max, oldest evicted +3. **Polling over WebSocket** - simpler, 5-second interval sufficient +4. **Dark theme only for dashboard** - registry list pages keep light theme + +### Files Changed Summary +``` +New: + nora-registry/src/activity_log.rs + nora-registry/src/dashboard_metrics.rs + +Modified: + nora-registry/src/main.rs (+8 lines) + nora-registry/src/registry/cargo_registry.rs (+13 lines) + nora-registry/src/registry/docker.rs (+47 lines) + nora-registry/src/registry/maven.rs (+36 lines) + nora-registry/src/registry/npm.rs (+29 lines) + nora-registry/src/ui/api.rs (+154 lines) + nora-registry/src/ui/components.rs (+394 lines) + nora-registry/src/ui/mod.rs (+5 lines) + nora-registry/src/ui/templates.rs (+180/-79 lines) + +Total: ~1004 insertions, 79 deletions +``` + +### Useful Commands +```bash +# Start server +cargo run --release --bin nora + +# Test dashboard +curl http://127.0.0.1:4000/api/ui/dashboard + +# View UI +open http://127.0.0.1:4000/ui/ + +# Trigger metrics +curl http://127.0.0.1:4000/v2/test/manifests/v1 +curl http://127.0.0.1:4000/npm/lodash/-/lodash-4.17.21.tgz -o /dev/null +``` + +--- diff --git a/TODO.md b/TODO.md index 153dd9f..01be16d 100644 --- a/TODO.md +++ b/TODO.md @@ -11,6 +11,69 @@ --- +## v0.2.1 - Dashboard Expansion (2026-01-26) - DONE + +### Commit: 93f9655 + +### New Files +- `nora-registry/src/dashboard_metrics.rs` - AtomicU64 counters for metrics +- `nora-registry/src/activity_log.rs` - Bounded activity log (50 entries) + +### Modified Files +- `nora-registry/src/main.rs` - Added modules, updated AppState +- `nora-registry/src/ui/api.rs` - Added DashboardResponse, api_dashboard() +- `nora-registry/src/ui/mod.rs` - Added /api/ui/dashboard route +- `nora-registry/src/ui/components.rs` - Dark theme components +- `nora-registry/src/ui/templates.rs` - New render_dashboard() +- `nora-registry/src/registry/docker.rs` - Instrumented handlers +- `nora-registry/src/registry/npm.rs` - Instrumented with cache tracking +- `nora-registry/src/registry/maven.rs` - Instrumented download/upload +- `nora-registry/src/registry/cargo_registry.rs` - Instrumented download + +### Features Implemented +- [x] Global stats panel (downloads, uploads, artifacts, cache hit %, storage) +- [x] Per-registry metrics (Docker, Maven, npm, Cargo, PyPI) +- [x] Mount points table with proxy upstreams +- [x] Activity log (last 20 events) +- [x] Dark theme (#0f172a background, #1e293b cards) +- [x] Auto-refresh polling (5 seconds) +- [x] Cache hit/miss tracking + +### API Endpoints +- `GET /api/ui/dashboard` - Full dashboard data as JSON + +### Dark Theme Colors +``` +Background: #0f172a (slate-950) +Cards: #1e293b (slate-800) +Borders: slate-700 +Text primary: slate-200 +Text secondary: slate-400 +Accent: blue-400 +``` + +### Testing Commands +```bash +# Test dashboard API +curl http://127.0.0.1:4000/api/ui/dashboard + +# Test Docker pull (triggers metrics) +curl http://127.0.0.1:4000/v2/test/manifests/v1 + +# Test npm proxy (triggers cache miss) +curl http://127.0.0.1:4000/npm/lodash/-/lodash-4.17.21.tgz -o /dev/null +``` + +### Future Improvements (Dashboard) +- [ ] Add PyPI download instrumentation +- [ ] Persist metrics to disk (currently reset on restart) +- [ ] Add WebSocket for real-time updates (instead of polling) +- [ ] Add graphs/charts for metrics over time +- [ ] Add user/client tracking in activity log +- [ ] Dark/light theme toggle + +--- + ## v0.3.0 - OIDC / Workload Identity Federation ### Killer Feature: OIDC for CI/CD diff --git a/nora-registry/src/activity_log.rs b/nora-registry/src/activity_log.rs index e288054..936239f 100644 --- a/nora-registry/src/activity_log.rs +++ b/nora-registry/src/activity_log.rs @@ -71,12 +71,7 @@ impl ActivityLog { /// Get the most recent N entries (newest first) pub fn recent(&self, count: usize) -> Vec { let entries = self.entries.read(); - entries - .iter() - .rev() - .take(count) - .cloned() - .collect() + entries.iter().rev().take(count).cloned().collect() } /// Get all entries (newest first) diff --git a/nora-registry/src/registry/maven.rs b/nora-registry/src/registry/maven.rs index 02d2487..7dc3903 100644 --- a/nora-registry/src/registry/maven.rs +++ b/nora-registry/src/registry/maven.rs @@ -21,7 +21,15 @@ async fn download(State(state): State>, Path(path): Path) let key = format!("maven/{}", path); // Extract artifact name for logging (last 2-3 path components) - let artifact_name = path.split('/').rev().take(3).collect::>().into_iter().rev().collect::>().join("/"); + let artifact_name = path + .split('/') + .rev() + .take(3) + .collect::>() + .into_iter() + .rev() + .collect::>() + .join("/"); // Try local storage first if let Ok(data) = state.storage.get(&key).await { @@ -76,7 +84,15 @@ async fn upload( let key = format!("maven/{}", path); // Extract artifact name for logging - let artifact_name = path.split('/').rev().take(3).collect::>().into_iter().rev().collect::>().join("/"); + let artifact_name = path + .split('/') + .rev() + .take(3) + .collect::>() + .into_iter() + .rev() + .collect::>() + .join("/"); match state.storage.put(&key, &body).await { Ok(()) => { diff --git a/nora-registry/src/ui/api.rs b/nora-registry/src/ui/api.rs index 04db16a..187f300 100644 --- a/nora-registry/src/ui/api.rs +++ b/nora-registry/src/ui/api.rs @@ -139,8 +139,11 @@ pub async fn api_dashboard(State(state): State>) -> Json) -> String { } /// Dark theme layout wrapper for dashboard -pub fn layout_dark(title: &str, content: &str, active_page: Option<&str>, extra_scripts: &str) -> String { +pub fn layout_dark( + title: &str, + content: &str, + active_page: Option<&str>, + extra_scripts: &str, +) -> String { format!( r##" @@ -238,7 +243,13 @@ fn header_dark() -> String { } /// Render global stats row (5-column grid) -pub fn render_global_stats(downloads: u64, uploads: u64, artifacts: u64, cache_hit_percent: f64, storage_bytes: u64) -> String { +pub fn render_global_stats( + downloads: u64, + uploads: u64, + artifacts: u64, + cache_hit_percent: f64, + storage_bytes: u64, +) -> String { format!( r##"
@@ -273,7 +284,15 @@ pub fn render_global_stats(downloads: u64, uploads: u64, artifacts: u64, cache_h } /// Render registry card with extended metrics -pub fn render_registry_card(name: &str, icon_path: &str, artifact_count: usize, downloads: u64, uploads: u64, size_bytes: u64, href: &str) -> String { +pub fn render_registry_card( + name: &str, + icon_path: &str, + artifact_count: usize, + downloads: u64, + uploads: u64, + size_bytes: u64, + href: &str, +) -> String { format!( r##" @@ -359,7 +378,13 @@ pub fn render_mount_points_table(mount_points: &[(String, String, Option } /// Render a single activity log row -pub fn render_activity_row(timestamp: &str, action: &str, artifact: &str, registry: &str, source: &str) -> String { +pub fn render_activity_row( + timestamp: &str, + action: &str, + artifact: &str, + registry: &str, + source: &str, +) -> String { let action_color = match action { "PULL" => "text-blue-400", "PUSH" => "text-green-400", @@ -378,7 +403,12 @@ pub fn render_activity_row(timestamp: &str, action: &str, artifact: &str, regist {} "##, - timestamp, action_color, action, html_escape(artifact), registry, source + timestamp, + action_color, + action, + html_escape(artifact), + registry, + source ) } diff --git a/nora-registry/src/ui/templates.rs b/nora-registry/src/ui/templates.rs index b3ec1e0..a1a9635 100644 --- a/nora-registry/src/ui/templates.rs +++ b/nora-registry/src/ui/templates.rs @@ -13,37 +13,49 @@ pub fn render_dashboard(data: &DashboardResponse) -> String { ); // Render registry cards - let registry_cards: String = data.registry_stats.iter().map(|r| { - let icon = match r.name.as_str() { - "docker" => icons::DOCKER, - "maven" => icons::MAVEN, - "npm" => icons::NPM, - "cargo" => icons::CARGO, - "pypi" => icons::PYPI, - _ => icons::DOCKER, - }; - let display_name = match r.name.as_str() { - "docker" => "Docker", - "maven" => "Maven", - "npm" => "npm", - "cargo" => "Cargo", - "pypi" => "PyPI", - _ => &r.name, - }; - render_registry_card( - display_name, - icon, - r.artifact_count, - r.downloads, - r.uploads, - r.size_bytes, - &format!("/ui/{}", r.name), - ) - }).collect(); + let registry_cards: String = data + .registry_stats + .iter() + .map(|r| { + let icon = match r.name.as_str() { + "docker" => icons::DOCKER, + "maven" => icons::MAVEN, + "npm" => icons::NPM, + "cargo" => icons::CARGO, + "pypi" => icons::PYPI, + _ => icons::DOCKER, + }; + let display_name = match r.name.as_str() { + "docker" => "Docker", + "maven" => "Maven", + "npm" => "npm", + "cargo" => "Cargo", + "pypi" => "PyPI", + _ => &r.name, + }; + render_registry_card( + display_name, + icon, + r.artifact_count, + r.downloads, + r.uploads, + r.size_bytes, + &format!("/ui/{}", r.name), + ) + }) + .collect(); // Render mount points - let mount_data: Vec<(String, String, Option)> = data.mount_points.iter() - .map(|m| (m.registry.clone(), m.mount_path.clone(), m.proxy_upstream.clone())) + let mount_data: Vec<(String, String, Option)> = data + .mount_points + .iter() + .map(|m| { + ( + m.registry.clone(), + m.mount_path.clone(), + m.proxy_upstream.clone(), + ) + }) .collect(); let mount_points = render_mount_points_table(&mount_data); @@ -51,16 +63,19 @@ pub fn render_dashboard(data: &DashboardResponse) -> String { let activity_rows: String = if data.activity.is_empty() { r##"No recent activity"##.to_string() } else { - data.activity.iter().map(|entry| { - let time_ago = format_relative_time(&entry.timestamp); - render_activity_row( - &time_ago, - &entry.action.to_string(), - &entry.artifact, - &entry.registry, - &entry.source, - ) - }).collect() + data.activity + .iter() + .map(|entry| { + let time_ago = format_relative_time(&entry.timestamp); + render_activity_row( + &time_ago, + &entry.action.to_string(), + &entry.artifact, + &entry.registry, + &entry.source, + ) + }) + .collect() }; let activity_log = render_activity_log(&activity_rows); @@ -95,11 +110,7 @@ pub fn render_dashboard(data: &DashboardResponse) -> String { {}
"##, - uptime_str, - global_stats, - registry_cards, - mount_points, - activity_log, + uptime_str, global_stats, registry_cards, mount_points, activity_log, ); let polling_script = render_polling_script();