Skip to content

Commit a785732

Browse files
feat: Phase 4d — remaining views ported from mockup
Query workspace (schema tree + highlighted-pre editor + results), Graph Explorer (Cypher/Builder/GraphRAG mode switch), Designer ER canvas, Vector Space scatter, Sync dashboard, Console REPL, Timeseries dashboard, Spatial map, FTS inspector. Heavy widgets (editor/graph/charts/map) are static placeholders per CLAUDE.md. De-arcadedb'd console CLI/leader labels and the FTS/GraphRAG sample titles.
1 parent 0505ba0 commit a785732

12 files changed

Lines changed: 879 additions & 27 deletions

File tree

nodedb-studio/src/views/console.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,40 @@
1-
//! Console view. Phase 3 placeholder; Phase 4 ports the mockup.
1+
//! Console: a REPL-style command log + input. CLI label de-arcadedb'd to
2+
//! "nodedb-cli dev"; leader node to node-1.
3+
24
use dioxus::prelude::*;
3-
use crate::views::ViewPlaceholder;
45

56
#[component]
67
pub fn Console() -> Element {
7-
rsx! { ViewPlaceholder { title: "Console", note: "Console placeholder — Phase 4." } }
8+
rsx! {
9+
div { class: "view active",
10+
div { style: "height: 100%; display: grid; grid-template-rows: auto 1fr auto; background: var(--bg-primary);",
11+
div { style: "padding: 10px 16px; background: var(--bg-secondary); border-bottom: 0.5px solid var(--border-mid); display: flex; align-items: center; gap: 10px; font-family: var(--font-mono); font-size: 11px;",
12+
span { class: "pill ok", span { class: "dot" } "repl ready" }
13+
span { style: "color: var(--text-tertiary);", "nodedb-cli dev · analytics" }
14+
div { style: "margin-left:auto; display:flex; gap:6px;",
15+
button { class: "btn small", "Clear" }
16+
button { class: "btn small", "Export" }
17+
}
18+
}
19+
div { style: "overflow:auto; padding: 14px 16px; font-family: var(--font-mono); font-size: 12px; line-height: 1.6;",
20+
div { span { style: "color:var(--text-tertiary)", "$" } " " span { style: "color:var(--text-info)", "SHOW CLUSTER STATUS;" } }
21+
div { style: "color:var(--text-secondary); padding-left: 14px;", "cluster: staging-cluster · nodes: 7 · leader: node-1 · healthy: 6/7" }
22+
div { style: "margin-top:10px;", span { style: "color:var(--text-tertiary)", "$" } " " span { style: "color:var(--text-info)", "SELECT count(*) FROM events WHERE ts > NOW() - '1h'::interval;" } }
23+
div { style: "color:var(--text-secondary); padding-left: 14px;", "count: 14,820 · 142ms · plan: index_scan(events_ts_idx)" }
24+
div { style: "margin-top:10px;", span { style: "color:var(--text-tertiary)", "$" } " " span { style: "color:var(--text-info)", "EXPLAIN MATCH (u:User)-[:POSTED]->(p:Post) WHERE p.topic = 'db' RETURN u LIMIT 10;" } }
25+
div { style: "color:var(--text-secondary); padding-left: 14px; white-space: pre;", " expand(out, POSTED, label=Post)\n filter(p.topic = 'db')\n limit(10)\n estimated cost: 1,408 · est. rows: 10" }
26+
div { style: "margin-top:10px;", span { style: "color:var(--text-tertiary)", "$" } " " span { style: "color:var(--text-success)", "PEER STATUS edge-sg-1;" } }
27+
div { style: "color:var(--text-secondary); padding-left: 14px;", "edge-sg-1 · follower · lag: 12ms · last_ack: 2s ago · healthy" }
28+
div { style: "margin-top:10px;", span { style: "color:var(--text-tertiary)", "$" } " " span { style: "color:var(--text-danger)", "SHOW SLOW QUERIES SINCE '1h';" } }
29+
div { style: "color:var(--text-secondary); padding-left: 14px;", "1 result · vector_reindex took 22m · OOM at step 4" }
30+
div { style: "margin-top:10px;", span { style: "color:var(--text-tertiary)", "$" } " " span { class: "tail-pulse", style: "display:inline-block;" } }
31+
}
32+
div { style: "display:flex; align-items:center; padding: 8px 16px; border-top: 0.5px solid var(--border-soft); font-family: var(--font-mono); font-size: 12px;",
33+
span { style: "color:var(--text-tertiary); margin-right:8px;", "$" }
34+
input { style: "flex:1; background: transparent; border: none; outline: none; font: inherit;", placeholder: "enter a SQL, Cypher, or admin command…" }
35+
span { style: "color:var(--text-tertiary); font-size:10px;", "⏎ to run · ↑↓ history" }
36+
}
37+
}
38+
}
39+
}
840
}
Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,75 @@
1-
//! Schema designer view. Phase 3 placeholder; Phase 4 ports the mockup.
1+
//! Schema designer: a static ER-style canvas of collections and relationships.
2+
//! Placeholder SVG (no real diagramming library).
3+
24
use dioxus::prelude::*;
3-
use crate::views::ViewPlaceholder;
45

56
#[component]
67
pub fn Designer() -> Element {
7-
rsx! { ViewPlaceholder { title: "Designer", note: "Schema designer placeholder — Phase 4." } }
8+
rsx! {
9+
div { class: "view active",
10+
div { style: "height: 100%; display: grid; grid-template-rows: auto 1fr;",
11+
div { style: "padding: 10px 16px; background: var(--bg-secondary); border-bottom: 0.5px solid var(--border-mid); display: flex; align-items: center; gap: 10px;",
12+
button { class: "btn small", "+ Collection" }
13+
button { class: "btn small", "+ Relationship" }
14+
div { style: "margin-left: auto; display: flex; gap: 6px;",
15+
button { class: "btn small ghost", "Auto-layout" }
16+
button { class: "btn small ghost", "Export DDL" }
17+
button { class: "btn small primary", "Apply changes (3 pending)" }
18+
}
19+
}
20+
div { class: "designer-canvas",
21+
svg { view_box: "0 0 1000 600", style: "width:100%; height:100%;",
22+
path { d: "M180,180 L380,180", stroke: "var(--text-tertiary)", stroke_width: "1", fill: "none" }
23+
path { d: "M280,260 L280,360", stroke: "var(--text-tertiary)", stroke_width: "1", fill: "none" }
24+
path { d: "M480,260 L660,360", stroke: "var(--text-tertiary)", stroke_width: "1", fill: "none" }
25+
path { d: "M180,460 L380,460", stroke: "var(--text-tertiary)", stroke_width: "1", fill: "none" }
26+
27+
g { transform: "translate(60, 100)",
28+
rect { class: "designer-table", width: "220", height: "160", rx: "6" }
29+
text { x: "14", y: "22", font_family: "var(--font-sans)", font_size: "12", font_weight: "600", fill: "var(--text-primary)", "users" }
30+
text { x: "200", y: "22", font_family: "var(--font-mono)", font_size: "9", fill: "var(--text-tertiary)", text_anchor: "end", "DOCUMENT" }
31+
line { x1: "0", y1: "34", x2: "220", y2: "34", stroke: "var(--border-mid)", stroke_width: "0.5" }
32+
text { x: "14", y: "54", font_family: "var(--font-mono)", font_size: "11", fill: "var(--text-primary)", "_id" tspan { fill: "var(--text-tertiary)", " string" } }
33+
text { x: "14", y: "72", font_family: "var(--font-mono)", font_size: "11", fill: "var(--text-primary)", "name" tspan { fill: "var(--text-tertiary)", " string" } }
34+
text { x: "14", y: "90", font_family: "var(--font-mono)", font_size: "11", fill: "var(--text-primary)", "email" tspan { fill: "var(--text-tertiary)", " string · unique" } }
35+
text { x: "14", y: "108", font_family: "var(--font-mono)", font_size: "11", fill: "var(--text-primary)", "country" tspan { fill: "var(--text-tertiary)", " char(2)" } }
36+
text { x: "14", y: "126", font_family: "var(--font-mono)", font_size: "11", fill: "var(--text-primary)", "created_at" tspan { fill: "var(--text-tertiary)", " timestamp" } }
37+
}
38+
g { transform: "translate(380, 100)",
39+
rect { class: "designer-table", width: "220", height: "160", rx: "6" }
40+
text { x: "14", y: "22", font_family: "var(--font-sans)", font_size: "12", font_weight: "600", fill: "var(--text-primary)", "orders" }
41+
text { x: "200", y: "22", font_family: "var(--font-mono)", font_size: "9", fill: "var(--text-tertiary)", text_anchor: "end", "STRICT" }
42+
line { x1: "0", y1: "34", x2: "220", y2: "34", stroke: "var(--border-mid)", stroke_width: "0.5" }
43+
text { x: "14", y: "54", font_family: "var(--font-mono)", font_size: "11", "id" tspan { fill: "var(--text-tertiary)", " int8 · pk" } }
44+
text { x: "14", y: "72", font_family: "var(--font-mono)", font_size: "11", "user_id" tspan { fill: "var(--text-tertiary)", " string · fk" } }
45+
text { x: "14", y: "90", font_family: "var(--font-mono)", font_size: "11", "total" tspan { fill: "var(--text-tertiary)", " decimal(10,2)" } }
46+
text { x: "14", y: "108", font_family: "var(--font-mono)", font_size: "11", "status" tspan { fill: "var(--text-tertiary)", " enum" } }
47+
text { x: "14", y: "126", font_family: "var(--font-mono)", font_size: "11", "placed_at" tspan { fill: "var(--text-tertiary)", " timestamptz" } }
48+
}
49+
g { transform: "translate(180, 360)",
50+
rect { class: "designer-table", width: "200", height: "140", rx: "6" }
51+
text { x: "14", y: "22", font_family: "var(--font-sans)", font_size: "12", font_weight: "600", "events" }
52+
text { x: "180", y: "22", font_family: "var(--font-mono)", font_size: "9", fill: "var(--text-tertiary)", text_anchor: "end", "DOCUMENT" }
53+
line { x1: "0", y1: "34", x2: "200", y2: "34", stroke: "var(--border-mid)", stroke_width: "0.5" }
54+
text { x: "14", y: "54", font_family: "var(--font-mono)", font_size: "11", "_id" tspan { fill: "var(--text-tertiary)", " string" } }
55+
text { x: "14", y: "72", font_family: "var(--font-mono)", font_size: "11", "user_id" tspan { fill: "var(--text-tertiary)", " string" } }
56+
text { x: "14", y: "90", font_family: "var(--font-mono)", font_size: "11", "type" tspan { fill: "var(--text-tertiary)", " string" } }
57+
text { x: "14", y: "108", font_family: "var(--font-mono)", font_size: "11", "ts" tspan { fill: "var(--text-tertiary)", " timestamp" } }
58+
}
59+
g { transform: "translate(660, 360)",
60+
rect { class: "designer-table", width: "200", height: "140", rx: "6" }
61+
text { x: "14", y: "22", font_family: "var(--font-sans)", font_size: "12", font_weight: "600", "products" }
62+
text { x: "180", y: "22", font_family: "var(--font-mono)", font_size: "9", fill: "var(--text-tertiary)", text_anchor: "end", "STRICT" }
63+
line { x1: "0", y1: "34", x2: "200", y2: "34", stroke: "var(--border-mid)", stroke_width: "0.5" }
64+
text { x: "14", y: "54", font_family: "var(--font-mono)", font_size: "11", "sku" tspan { fill: "var(--text-tertiary)", " string · pk" } }
65+
text { x: "14", y: "72", font_family: "var(--font-mono)", font_size: "11", "name" tspan { fill: "var(--text-tertiary)", " string" } }
66+
text { x: "14", y: "90", font_family: "var(--font-mono)", font_size: "11", "price" tspan { fill: "var(--text-tertiary)", " decimal(10,2)" } }
67+
}
68+
text { x: "280", y: "172", font_family: "var(--font-mono)", font_size: "9", fill: "var(--text-tertiary)", text_anchor: "middle", "PLACED · 1:N" }
69+
text { x: "180", y: "452", font_family: "var(--font-mono)", font_size: "9", fill: "var(--text-tertiary)", text_anchor: "middle", "EMITTED" }
70+
}
71+
}
72+
}
73+
}
74+
}
875
}
Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,130 @@
1-
//! FTS inspector view. Phase 3 placeholder; Phase 4 ports the mockup.
1+
//! FTS inspector: analyzer chain visualization + query tester + index stats.
2+
23
use dioxus::prelude::*;
3-
use crate::views::ViewPlaceholder;
44

55
#[component]
66
pub fn FtsInspector() -> Element {
7-
rsx! { ViewPlaceholder { title: "FTS Inspector", note: "Full-text search inspector placeholder — Phase 4." } }
7+
rsx! {
8+
div { class: "view active",
9+
div { class: "view-padded",
10+
div { class: "view-header",
11+
div {
12+
h1 { "FTS inspector" }
13+
p { "Watch text flow through the analyzer chain. Test queries against the live index." }
14+
}
15+
div { class: "view-header-actions",
16+
button { class: "btn", "Reindex" }
17+
button { class: "btn primary", "Save analyzer" }
18+
}
19+
}
20+
21+
div { class: "card", style: "padding:18px; margin-bottom:14px;",
22+
div { class: "eyebrow", style: "margin-bottom:10px;", "Analyzer chain · articles_idx" }
23+
div { style: "display:flex; align-items:center; gap:8px; overflow-x:auto;",
24+
ChainBox { eyebrow: "Input", lines: vec!["\"The Distributed Graph Database\"".to_string()] }
25+
Arrow {}
26+
ChainBox { eyebrow: "1 · Tokenizer", lines: vec!["standard".to_string(), "[The, Distributed, Graph, Database]".to_string()] }
27+
Arrow {}
28+
ChainBox { eyebrow: "2 · Lowercase", lines: vec!["[the, distributed, graph, database]".to_string()] }
29+
Arrow {}
30+
ChainBox { eyebrow: "3 · Stop words", lines: vec!["[distributed, graph, database]".to_string()] }
31+
Arrow {}
32+
ChainBox { eyebrow: "4 · Stemmer", lines: vec!["en_porter2".to_string(), "[distribut, graph, databas]".to_string()] }
33+
Arrow {}
34+
div { style: "background:var(--bg-primary); border:1px solid var(--text-success); border-radius:6px; padding:10px 14px; min-width:140px;",
35+
div { class: "eyebrow", style: "color:var(--text-success)", "Output tokens" }
36+
div { style: "font-family:var(--font-mono); font-size:11px; margin-top:4px;", "distribut graph databas" }
37+
}
38+
}
39+
div { style: "margin-top:12px; display:flex; gap:6px;",
40+
button { class: "btn small", "+ Filter" }
41+
button { class: "btn small", "Reorder" }
42+
button { class: "btn small ghost", "Reset to default" }
43+
}
44+
}
45+
46+
div { style: "display:grid; grid-template-columns: 1fr 1fr; gap: 14px;",
47+
div { class: "card", style: "padding:18px;",
48+
div { class: "eyebrow", style: "margin-bottom:10px;", "Query tester" }
49+
input { value: "distributed graph databases", style: "width:100%; padding:8px 12px; background:var(--bg-secondary); border:0.5px solid var(--border-mid); border-radius:5px; font-family:var(--font-mono); font-size:12px;" }
50+
div { style: "margin-top:14px; display:grid; grid-template-columns: auto 1fr; gap:6px 12px; font-family:var(--font-mono); font-size:11px;",
51+
span { style: "color:var(--text-tertiary)", "analyzed" } span { "[distribut, graph, databas]" }
52+
span { style: "color:var(--text-tertiary)", "hits" } span { "241" }
53+
span { style: "color:var(--text-tertiary)", "took" } span { "14ms" }
54+
span { style: "color:var(--text-tertiary)", "max score" } span { "0.94" }
55+
}
56+
div { style: "margin-top:14px;",
57+
div { class: "eyebrow", style: "margin-bottom:6px;", "Top results" }
58+
div { style: "font-family:var(--font-mono); font-size:11px; line-height:1.8;",
59+
div { span { style: "color:var(--text-warning)", "0.94" } " · Designing a multi-engine database" }
60+
// De-arcadedb'd (mockup said "Why ArcadeDB chose multi-model").
61+
div { span { style: "color:var(--text-warning)", "0.88" } " · Why NodeDB chose multi-model" }
62+
div { span { style: "color:var(--text-warning)", "0.71" } " · NodeDB roadmap to 1.0" }
63+
}
64+
}
65+
}
66+
div { class: "card", style: "padding:18px;",
67+
div { class: "eyebrow", style: "margin-bottom:10px;", "Index stats" }
68+
div { style: "display:grid; grid-template-columns:1fr 1fr; gap: 10px 16px; font-family:var(--font-mono); font-size:11px;",
69+
Stat { label: "documents", value: "241,005" }
70+
Stat { label: "unique terms", value: "88,402" }
71+
Stat { label: "avg doc length", value: "412 tokens" }
72+
Stat { label: "index size", value: "182 MB" }
73+
Stat { label: "last update", value: "2s ago" }
74+
Stat { label: "segments", value: "14" }
75+
}
76+
div { style: "margin-top:18px;",
77+
div { class: "eyebrow", style: "margin-bottom:6px;", "Most frequent terms" }
78+
div { style: "font-family:var(--font-mono); font-size:11px; line-height:1.8;",
79+
TermBar { term: "databas", width: "120px", count: "18,402" }
80+
TermBar { term: "graph", width: "104px", count: "15,108" }
81+
TermBar { term: "queri", width: "88px", count: "12,890" }
82+
TermBar { term: "distribut", width: "64px", count: "8,201" }
83+
TermBar { term: "vector", width: "52px", count: "6,841" }
84+
}
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
}
92+
93+
#[component]
94+
fn Arrow() -> Element {
95+
rsx! { span { style: "color:var(--text-tertiary); font-family:var(--font-mono);", "→" } }
96+
}
97+
98+
#[component]
99+
fn ChainBox(eyebrow: String, lines: Vec<String>) -> Element {
100+
rsx! {
101+
div { style: "background:var(--bg-secondary); border:0.5px solid var(--border-mid); border-radius:6px; padding:10px 14px; min-width:140px;",
102+
div { class: "eyebrow", "{eyebrow}" }
103+
for line in lines {
104+
div { style: "font-family:var(--font-mono); font-size:10px; color:var(--text-tertiary); margin-top:4px;", "{line}" }
105+
}
106+
}
107+
}
108+
}
109+
110+
#[component]
111+
fn Stat(label: String, value: String) -> Element {
112+
rsx! {
113+
div {
114+
div { style: "color:var(--text-tertiary)", "{label}" }
115+
div { style: "font-size:14px; margin-top:2px;", "{value}" }
116+
}
117+
}
118+
}
119+
120+
#[component]
121+
fn TermBar(term: String, width: String, count: String) -> Element {
122+
rsx! {
123+
div {
124+
span { style: "display:inline-block; width:60px;", "{term}" }
125+
" "
126+
span { style: "display:inline-block; width:{width}; height:6px; background:#185fa5; opacity:0.7; vertical-align:middle;" }
127+
" {count}"
128+
}
129+
}
8130
}

0 commit comments

Comments
 (0)