WIP components (as built)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 15:51:57 -04:00
1 parent 1095ed2b9d
commit 7e4f275f0a
80 files changed
+2754 -132

No files matched your search

+21 -1
View File
@@ -1,3 +1,23 @@
package main
var devtoolsPages = []page{}
const devCategory = "Dashboards & Dev Tools"
var devtoolsPages = []page{
{ID: "442", Name: "StatCard", ZhName: "统计卡片", Category: devCategory, Demo: devStatCardDemo, Code: devStatCardCode, Keys: "Tab 聚焦趋势或重试;方向键读取数据;Enter 激活重试。"},
{ID: "443", Name: "AlertList", ZhName: "告警列表", Category: devCategory, Demo: devAlertListDemo, Code: devAlertListCode, Keys: "Tab 聚焦按钮;Enter 或 Space 激活。"},
{ID: "444", Name: "IncidentCard", ZhName: "事件卡片", Category: devCategory, Demo: devIncidentCardDemo, Code: devIncidentCardCode, Keys: "展示内容随调用方更新。"},
{ID: "445", Name: "UptimeBar", ZhName: "可用性条", Category: devCategory, Demo: devUptimeBarDemo, Code: devUptimeBarCode, Keys: "Tab 聚焦图;方向键逐日移动;Home、End 到边界;Escape 清除读取。"},
{ID: "446", Name: "ServiceStatus", ZhName: "服务状态", Category: devCategory, Demo: devServiceStatusDemo, Code: devServiceStatusCode, Keys: "滚动列表浏览服务。"},
{ID: "447", Name: "LogStream", ZhName: "日志流", Category: devCategory, Demo: devLogStreamDemo, Code: devLogStreamCode, Keys: "Tab 聚焦暂停和级别;方向键切换级别;Space 激活暂停。"},
{ID: "448", Name: "TraceWaterfall", ZhName: "调用链瀑布图", Category: devCategory, Demo: devTraceWaterfallDemo, Code: devTraceWaterfallCode, Keys: "Tab 聚焦跨度;上下方向键移动;Enter 读取详情。"},
{ID: "449", Name: "ResourceGauge", ZhName: "资源仪表", Category: devCategory, Demo: devResourceGaugeDemo, Code: devResourceGaugeCode, Keys: "Tab 聚焦仪表;方向键读取数值与阈值文字。"},
{ID: "450", Name: "SystemMonitor", ZhName: "系统监视器", Category: devCategory, Demo: devSystemMonitorDemo, Code: devSystemMonitorCode, Keys: "Tab 到图例及图;方向键读取采样点;Home、End 到边界。"},
{ID: "451", Name: "RefreshIntervalSelector", ZhName: "刷新间隔选择", Category: devCategory, Demo: devRefreshIntervalSelectorDemo, Code: devRefreshIntervalSelectorCode, Keys: "Tab 到选择器与按钮;方向键选择间隔;Enter 激活。"},
{ID: "452", Name: "DashboardFilterBar", ZhName: "仪表盘过滤栏", Category: devCategory, Demo: devDashboardFilterBarDemo, Code: devDashboardFilterBarCode, Keys: "Tab 遍历控件;方向键选择;Space 切换标签;Escape 关闭弹层。"},
{ID: "453", Name: "JsonViewer", ZhName: "JSON 查看器", Category: devCategory, Demo: devJsonViewerDemo, Code: devJsonViewerCode, Keys: "Tab 到搜索、复制和树;上下移动;左右展开折叠;Enter 切换节点。"},
{ID: "454", Name: "JsonEditor", ZhName: "JSON 编辑器", Category: devCategory, Demo: devJsonEditorDemo, Code: devJsonEditorCode, Keys: "Tab 到格式化和文本框;标准文本编辑与剪贴板快捷键。"},
{ID: "455", Name: "SchemaTree", ZhName: "数据库结构树", Category: devCategory, Demo: devSchemaTreeDemo, Code: devSchemaTreeCode, Keys: "Tab 进入树;上下选择;左右展开折叠。"},
{ID: "456", Name: "ApiRequestBuilder", ZhName: "API 请求构建器", Category: devCategory, Demo: devApiRequestBuilderDemo, Code: devApiRequestBuilderCode, Keys: "Tab 遍历控件;URL 框 Enter 发送;左右切换标签页。"},
{ID: "457", Name: "ResponseViewer", ZhName: "响应查看器", Category: devCategory, Demo: devResponseViewerDemo, Code: devResponseViewerCode, Keys: "Tab 到标签页;左右切换正文与响应头;JSON 树沿用查看器键盘操作。"},
{ID: "458", Name: "KeyValueInput", ZhName: "键值对输入", Category: devCategory, Demo: devKeyValueInputDemo, Code: devKeyValueInputCode, Keys: "Tab 到各字段及按钮;Space 切换启用;Enter 激活增删。"},
}
+175
View File
@@ -0,0 +1,175 @@
package main
const devStatCardCode = `value := "12,480"
opts := mujica.StatCardOptions{Label: "Requests", Change: 0.12, HasChange: true, Trend: []float64{3, 5, 4, 8}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.StatCard(c, value, opts).Changed() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devAlertListCode = `alerts := []mujica.AlertItem{{ID: "cpu", Title: "CPU usage high", Level: mujica.AlertWarning, Source: "worker", At: time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.AlertList(c, alerts, mujica.AlertListOptions{})
if r.Ack != "" {
fmt.Println("ack", r.Ack)
}
if r.Silence != "" {
fmt.Println("silence", r.Silence)
}
}
ui.Render(view, 720, 480, 1)`
const devIncidentCardCode = `incident := mujica.Incident{Title: "API latency", Status: mujica.IncidentMonitoring, Impact: mujica.ImpactMinor, Updates: []mujica.IncidentUpdate{{Status: mujica.IncidentIdentified, Text: "Slow query located"}, {Status: mujica.IncidentMonitoring, Text: "Recovery observed"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.IncidentCard(c, incident)
}
ui.Render(view, 720, 480, 1)`
const devUptimeBarCode = `days := []mujica.UptimeDay{{Date: mujica.Date{Year: 2026, Month: 10, Day: 1}, Uptime: 1}, {Date: mujica.Date{Year: 2026, Month: 10, Day: 2}, Uptime: 0.91, Note: "Recovery"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.UptimeBar(c, days, mujica.UptimeBarOptions{Frame: mujica.ChartFrame{Label: "API uptime"}})
if r.Changed() {
fmt.Println("day", r.Point)
}
if r.Retried() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devServiceStatusCode = `services := []mujica.ServiceItem{{Name: "API", Health: mujica.ServiceOperational}, {Name: "Search", Health: mujica.ServiceDegraded, Note: "Slow queries"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ServiceStatus(c, services, mujica.ServiceStatusOptions{})
}
ui.Render(view, 720, 480, 1)`
const devLogStreamCode = `lines := []mujica.LogLine{{Level: mujica.LogInfo, Text: "server ready"}, {Level: mujica.LogWarn, Text: "slow query"}}
var state mujica.LogStreamState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.LogStream(c, lines, &state, mujica.LogStreamOptions{}).Changed() {
fmt.Println("filter changed")
}
}
ui.Render(view, 720, 480, 1)`
const devTraceWaterfallCode = `spans := []mujica.TraceSpan{{ID: "root", Name: "GET /api", Service: "gateway", Duration: 200 * time.Millisecond}, {ID: "db", Parent: "root", Name: "Query", Service: "database", Start: 20 * time.Millisecond, Duration: 80 * time.Millisecond}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.TraceWaterfall(c, spans, mujica.TraceWaterfallOptions{}).Changed() {
fmt.Println("span selected")
}
}
ui.Render(view, 720, 480, 1)`
const devResourceGaugeCode = `readings := []mujica.ResourceReading{{Name: "CPU", Used: 45, Total: 100}, {Name: "Memory", Used: 6.4, Total: 8, Unit: "GB"}, {Name: "Disk", Used: 470, Total: 500, Unit: "GB"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ResourceGauge(c, readings, mujica.ResourceGaugeOptions{}).Changed() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devSystemMonitorCode = `times := []time.Time{time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC), time.Date(2026, 10, 5, 9, 0, 5, 0, time.UTC)}
metrics := []mujica.MonitorMetric{{Name: "CPU", Unit: "%", Samples: []float64{42, 51}}, {Name: "Memory", Unit: "GB", Samples: []float64{3.2, 3.4}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SystemMonitor(c, times, metrics, mujica.SystemMonitorOptions{}).Changed() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devRefreshIntervalSelectorCode = `interval := 30 * time.Second
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.RefreshIntervalSelector(c, &interval, mujica.RefreshIntervalSelectorOptions{})
if r.Refresh {
fmt.Println("refresh")
}
if r.Changed {
fmt.Println("interval", interval)
}
}
ui.Render(view, 720, 480, 1)`
const devDashboardFilterBarCode = `var filters mujica.DashboardFilters
opts := mujica.DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "db"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.DashboardFilterBar(c, &filters, opts).Changed() {
fmt.Println("filters changed")
}
}
ui.Render(view, 720, 480, 1)`
const devJsonViewerCode = `data := ` + "`" + `{"server":{"port":8080},"ready":true}` + "`" + `
var state mujica.JsonViewerState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.JsonViewer(c, data, &state, mujica.JsonViewerOptions{})
if r.Copied != "" {
fmt.Println("path copied")
}
}
ui.Render(view, 720, 480, 1)`
const devJsonEditorCode = `text := ` + "`" + `{"port":8080,"ready":true}` + "`" + `
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.JsonEditor(c, &text, mujica.JsonEditorOptions{})
if r.Changed {
fmt.Println("valid", r.Valid)
}
}
ui.Render(view, 720, 480, 1)`
const devSchemaTreeCode = `roots := []mujica.SchemaNode{{Name: "app", Children: []mujica.SchemaNode{{Name: "users", Kind: mujica.SchemaTable, Children: []mujica.SchemaNode{{Name: "id", Kind: mujica.SchemaColumn, Detail: "bigint"}, {Name: "users_pk", Kind: mujica.SchemaIndex, Detail: "(id)"}}}}}}
var state mujica.SchemaTreeState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SchemaTree(c, &state, roots, mujica.SchemaTreeOptions{}).Changed() {
path, _ := state.Selected()
fmt.Println("selected", path)
}
}
ui.Render(view, 720, 480, 1)`
const devApiRequestBuilderCode = `request := mujica.ApiRequest{Method: "POST", URL: "https://example.com/api", Headers: []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ApiRequestBuilder(c, &request, mujica.ApiRequestBuilderOptions{})
if r.Send {
fmt.Println("send", request.Method)
}
if r.Changed {
fmt.Println("request changed")
}
}
ui.Render(view, 720, 480, 1)`
const devResponseViewerCode = `response := &mujica.ApiResponse{Status: 200, Duration: 182 * time.Millisecond, Size: 32, Body: ` + "`" + `{"ready":true}` + "`" + `, Headers: []mujica.KeyValue{{Key: "Content-Type", Value: "application/json"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ResponseViewer(c, response, mujica.ResponseViewerOptions{}).Changed() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devKeyValueInputCode = `rows := []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.KeyValueInput(c, &rows, mujica.KeyValueInputOptions{}).Changed() {
fmt.Println("rows", len(rows))
}
}
ui.Render(view, 720, 480, 1)`
+133
View File
@@ -0,0 +1,133 @@
package main
import (
"fmt"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
func devJsonViewerDemo(c *ui.Context, log func(string)) {
dtSection(c, "JsonViewer", func() {
s := devControls(c, "453", log, false)
type state struct {
tree mujica.JsonViewerState
invalid bool
}
st := ui.Local(c.Root(), "453", func() state { return state{} })
if mujica.Checkbox(c, &st.invalid, "Invalid JSON", mujica.CheckboxOptions{}).Changed() {
log("JSON validity changed")
}
devPreview(c, s, func() {
data := `{"server":{"port":8080,"hosts":["east","west"]},"ready":true}`
if st.invalid {
data = `{"port":}`
}
if s.empty {
data = `{}`
}
if mujica.JsonViewer(c, data, &st.tree, mujica.JsonViewerOptions{}).Copied != "" {
log("path copied")
}
})
})
}
func devJsonEditorDemo(c *ui.Context, log func(string)) {
dtSection(c, "JsonEditor", func() {
s := devControls(c, "454", log, false)
text := ui.Local(c.Root(), "454", func() string { return `{"port":8080,"ready":true}` })
if mujica.Button(c, "Invalid sample", mujica.ButtonOptions{}).Clicked() {
*text = "{\n \"port\": [1,2,]\n}"
log("invalid sample loaded")
}
devPreview(c, s, func() {
r := mujica.JsonEditor(c, text, mujica.JsonEditorOptions{})
if r.Changed {
log(fmt.Sprintf("JSON edited; valid=%t", r.Valid))
}
})
})
}
func devSchemaTreeDemo(c *ui.Context, log func(string)) {
dtSection(c, "SchemaTree", func() {
s := devControls(c, "455", log, false)
type state struct {
tree mujica.SchemaTreeState
roots []mujica.SchemaNode
}
st := ui.Local(c.Root(), "455", func() state {
return state{roots: []mujica.SchemaNode{{Name: "app", Children: []mujica.SchemaNode{{Name: "users", Kind: mujica.SchemaTable, Children: []mujica.SchemaNode{{Name: "id", Kind: mujica.SchemaColumn, Detail: "bigint"}, {Name: "name", Kind: mujica.SchemaColumn, Detail: "text"}, {Name: "users_pk", Kind: mujica.SchemaIndex, Detail: "(id)"}}}, {Name: "active_users", Kind: mujica.SchemaView}}}}}
})
devPreview(c, s, func() {
roots := st.roots
if s.empty {
roots = nil
}
if mujica.SchemaTree(c, &st.tree, roots, mujica.SchemaTreeOptions{}).Changed() {
log("schema node selected")
}
})
})
}
func devApiRequestBuilderDemo(c *ui.Context, log func(string)) {
dtSection(c, "ApiRequestBuilder", func() {
s := devControls(c, "456", log, false)
request := ui.Local(c.Root(), "456", func() mujica.ApiRequest {
return mujica.ApiRequest{URL: "https://example.com/api", Headers: []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}}
})
sending := ui.Local(c.Root(), "456-sending", func() bool { return false })
if mujica.Checkbox(c, sending, "Sending", mujica.CheckboxOptions{}).Changed() {
log("send state changed")
}
devPreview(c, s, func() {
r := mujica.ApiRequestBuilder(c, request, mujica.ApiRequestBuilderOptions{Sending: *sending})
if r.Send {
log("send requested")
}
if r.Changed {
log("request edited")
}
})
})
}
func devResponseViewerDemo(c *ui.Context, log func(string)) {
dtSection(c, "ResponseViewer", func() {
s := devControls(c, "457", log, true)
text := ui.Local(c.Root(), "457", func() bool { return false })
if mujica.Checkbox(c, text, "Text body", mujica.CheckboxOptions{}).Changed() {
log("response format changed")
}
devPreview(c, s, func() {
resp := &mujica.ApiResponse{Status: 200, Duration: 182 * time.Millisecond, Size: 2048, Body: `{"ready":true,"requests":1248}`, Headers: []mujica.KeyValue{{Key: "Content-Type", Value: "application/json"}, {Key: "X-Trace", Value: "demo"}}}
if *text {
resp.Body = "Response received successfully."
}
if s.empty {
resp = nil
}
if mujica.ResponseViewer(c, resp, mujica.ResponseViewerOptions{Height: 260, Status: s.status, Error: "Connection refused"}).Changed() {
log("retry requested")
}
})
})
}
func devKeyValueInputDemo(c *ui.Context, log func(string)) {
dtSection(c, "KeyValueInput", func() {
s := devControls(c, "458", log, false)
rows := ui.Local(c.Root(), "458", func() []mujica.KeyValue {
return []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}, {Key: "debug", Value: "true"}}
})
devPreview(c, s, func() {
if mujica.KeyValueInput(c, rows, mujica.KeyValueInputOptions{Label: "Headers"}).Changed() {
log("pairs edited")
}
})
})
}
+173
View File
@@ -0,0 +1,173 @@
package main
import (
"fmt"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
type devDemoSettings struct {
disabled bool
status mujica.DataStatus
empty bool
}
func devControls(c *ui.Context, id string, log func(string), states bool) *devDemoSettings {
s := ui.Local(c.Root(), id+"-controls", func() devDemoSettings { return devDemoSettings{} })
dtControls(c, func() {
if mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
log("disabled changed")
}
switch id {
case "451", "454", "456", "458":
default:
if mujica.Checkbox(c, &s.empty, "Empty data", mujica.CheckboxOptions{}).Changed() {
log("data state changed")
}
}
if states {
options := []mujica.SelectOption[mujica.DataStatus]{{Value: mujica.DataReady, Label: "Ready"}, {Value: mujica.DataLoading, Label: "Loading"}, {Value: mujica.DataFailed, Label: "Failed"}}
if mujica.Select(c, &s.status, options, mujica.SelectOptions{Label: "Status"}).Changed() {
log("status changed")
}
}
})
return s
}
func devPreview(c *ui.Context, s *devDemoSettings, build func()) {
ui.Column(c).Disabled(s.disabled).Gap(8).Children(build)
}
func devStatCardDemo(c *ui.Context, log func(string)) {
dtSection(c, "StatCard", func() {
s := devControls(c, "442", log, true)
lower := ui.Local(c.Root(), "442-lower", func() bool { return false })
if mujica.Checkbox(c, lower, "Lower is better", mujica.CheckboxOptions{}).Changed() {
log("change direction updated")
}
devPreview(c, s, func() {
value := "12,480"
trend := []float64{3, 5, 4, 8, 7, 9}
if s.empty {
value, trend = "—", nil
}
if mujica.StatCard(c, value, mujica.StatCardOptions{Label: "Requests", Change: 0.12, HasChange: true, LowerIsBetter: *lower, Trend: trend, Status: s.status, Error: "Source unreachable"}).Changed() {
log("retry requested")
}
})
})
}
func devAlertListDemo(c *ui.Context, log func(string)) {
dtSection(c, "AlertList", func() {
s := devControls(c, "443", log, false)
alerts := ui.Local(c.Root(), "443", func() []mujica.AlertItem {
return []mujica.AlertItem{{ID: "cpu", Title: "CPU usage high", Level: mujica.AlertWarning, Source: "worker", At: time.Now()}, {ID: "api", Title: "API unavailable", Level: mujica.AlertCritical, Source: "gateway", At: time.Now()}}
})
devPreview(c, s, func() {
shown := *alerts
if s.empty {
shown = nil
}
r := mujica.AlertList(c, shown, mujica.AlertListOptions{})
for i := range *alerts {
if r.Ack == (*alerts)[i].ID {
(*alerts)[i].Acked = true
log("acknowledged " + r.Ack)
}
if r.Silence == (*alerts)[i].ID {
(*alerts)[i].Silence = c.Now().Add(time.Hour)
log("silenced " + r.Silence)
}
}
})
})
}
func devIncidentCardDemo(c *ui.Context, log func(string)) {
dtSection(c, "IncidentCard", func() {
s := devControls(c, "444", log, false)
status := ui.Local(c.Root(), "444", func() mujica.IncidentStatus { return mujica.IncidentMonitoring })
choices := []mujica.SelectOption[mujica.IncidentStatus]{{Value: mujica.IncidentInvestigating, Label: "Investigating"}, {Value: mujica.IncidentIdentified, Label: "Identified"}, {Value: mujica.IncidentMonitoring, Label: "Monitoring"}, {Value: mujica.IncidentResolved, Label: "Resolved"}}
if mujica.Select(c, status, choices, mujica.SelectOptions{Label: "Incident status"}).Changed() {
log("incident status changed")
}
devPreview(c, s, func() {
updates := []mujica.IncidentUpdate{{Status: mujica.IncidentIdentified, Text: "Slow query located"}, {Status: mujica.IncidentMonitoring, Text: "Recovery observed"}}
if s.empty {
updates = nil
}
mujica.IncidentCard(c, mujica.Incident{Title: "API latency", Status: *status, Impact: mujica.ImpactMinor, Updates: updates})
})
})
}
func devUptimeBarDemo(c *ui.Context, log func(string)) {
dtSection(c, "UptimeBar", func() {
s := devControls(c, "445", log, true)
days := ui.Local(c.Root(), "445", func() []mujica.UptimeDay {
out := make([]mujica.UptimeDay, 30)
start := mujica.Date{Year: 2026, Month: 10, Day: 1}
for i := range out {
out[i] = mujica.UptimeDay{Date: start.AddDays(i), Uptime: 1}
}
out[7].Uptime, out[7].Note = 0.91, "2 h outage"
out[12].Uptime = 0.98
return out
})
devPreview(c, s, func() {
data := *days
if s.empty {
data = nil
}
r := mujica.UptimeBar(c, data, mujica.UptimeBarOptions{Frame: mujica.ChartFrame{Label: "API uptime", Status: s.status, Error: "Source unreachable"}})
if r.Changed() {
log(fmt.Sprintf("day %d", r.Point))
}
if r.Retried() {
log("retry requested")
}
})
})
}
func devServiceStatusDemo(c *ui.Context, log func(string)) {
dtSection(c, "ServiceStatus", func() {
s := devControls(c, "446", log, false)
health := ui.Local(c.Root(), "446", func() mujica.ServiceHealth { return mujica.ServiceDegraded })
choices := []mujica.SelectOption[mujica.ServiceHealth]{{Value: mujica.ServiceOperational, Label: "Operational"}, {Value: mujica.ServiceDegraded, Label: "Degraded"}, {Value: mujica.ServiceOutage, Label: "Outage"}}
if mujica.Select(c, health, choices, mujica.SelectOptions{Label: "Search health"}).Changed() {
log("service health changed")
}
devPreview(c, s, func() {
data := []mujica.ServiceItem{{Name: "API", Health: mujica.ServiceOperational}, {Name: "Search", Health: *health, Note: "Search cluster"}}
if s.empty {
data = nil
}
mujica.ServiceStatus(c, data, mujica.ServiceStatusOptions{})
})
})
}
func devResourceGaugeDemo(c *ui.Context, log func(string)) {
dtSection(c, "ResourceGauge", func() {
s := devControls(c, "449", log, true)
used := ui.Local(c.Root(), "449", func() float64 { return 45 })
if mujica.NumberInput(c, used, mujica.NumberInputOptions{Min: 0, Max: 100, Step: 5, Label: "CPU usage"}).Changed() {
log("resource reading changed")
}
devPreview(c, s, func() {
data := []mujica.ResourceReading{{Name: "CPU", Used: *used, Total: 100}, {Name: "Memory", Used: 6.4, Total: 8, Unit: "GB"}, {Name: "Disk", Used: 470, Total: 500, Unit: "GB"}}
if s.empty {
data = nil
}
if mujica.ResourceGauge(c, data, mujica.ResourceGaugeOptions{Status: s.status, Error: "Source unreachable"}).Changed() {
log("retry requested")
}
})
})
}
+127
View File
@@ -0,0 +1,127 @@
package main
import (
"fmt"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
func devLogStreamDemo(c *ui.Context, log func(string)) {
dtSection(c, "LogStream", func() {
s := devControls(c, "447", log, false)
type state struct {
lines []mujica.LogLine
stream mujica.LogStreamState
}
st := ui.Local(c.Root(), "447", func() state {
return state{lines: []mujica.LogLine{{Level: mujica.LogInfo, Text: "server ready"}, {Level: mujica.LogWarn, Text: "slow query"}}}
})
if mujica.Button(c, "Append line", mujica.ButtonOptions{}).Clicked() {
st.lines = append(st.lines, mujica.LogLine{Time: c.Now(), Level: mujica.LogInfo, Text: fmt.Sprintf("request %d completed", len(st.lines))})
log("line appended")
}
devPreview(c, s, func() {
data := st.lines
if s.empty {
data = nil
}
if mujica.LogStream(c, data, &st.stream, mujica.LogStreamOptions{}).Changed() {
log("pause or level changed")
}
})
})
}
func devTraceWaterfallDemo(c *ui.Context, log func(string)) {
dtSection(c, "TraceWaterfall", func() {
s := devControls(c, "448", log, false)
spans := ui.Local(c.Root(), "448", func() []mujica.TraceSpan {
return []mujica.TraceSpan{{ID: "root", Name: "GET /api", Service: "gateway", Duration: 320 * time.Millisecond}, {ID: "db", Parent: "root", Name: "Query", Service: "database", Start: 40 * time.Millisecond, Duration: 90 * time.Millisecond}, {ID: "pay", Parent: "root", Name: "Charge", Service: "payments", Start: 230 * time.Millisecond, Duration: 80 * time.Millisecond, Error: true}}
})
devPreview(c, s, func() {
data := *spans
if s.empty {
data = nil
}
if mujica.TraceWaterfall(c, data, mujica.TraceWaterfallOptions{}).Changed() {
log("span selected")
}
})
})
}
func devSystemMonitorDemo(c *ui.Context, log func(string)) {
dtSection(c, "SystemMonitor", func() {
s := devControls(c, "450", log, true)
type state struct {
times []time.Time
metrics []mujica.MonitorMetric
}
st := ui.Local(c.Root(), "450", func() state {
out := state{metrics: []mujica.MonitorMetric{{Name: "CPU", Unit: "%"}, {Name: "Memory", Unit: "GB"}}}
for i := range 20 {
out.times = append(out.times, time.Date(2026, 10, 5, 9, 0, i*5, 0, time.UTC))
out.metrics[0].Samples = append(out.metrics[0].Samples, 40+float64(i%7)*3)
out.metrics[1].Samples = append(out.metrics[1].Samples, 3.2+float64(i)*0.02)
}
return out
})
if mujica.Button(c, "Append sample", mujica.ButtonOptions{}).Clicked() {
st.times = append(append([]time.Time(nil), st.times...), st.times[len(st.times)-1].Add(5*time.Second))
for i := range st.metrics {
last := st.metrics[i].Samples[len(st.metrics[i].Samples)-1]
st.metrics[i].Samples = append(append([]float64(nil), st.metrics[i].Samples...), last+0.1)
}
log("sample appended")
}
devPreview(c, s, func() {
times, metrics := st.times, st.metrics
if s.empty {
times, metrics = nil, nil
}
if mujica.SystemMonitor(c, times, metrics, mujica.SystemMonitorOptions{Status: s.status, Error: "Agent offline"}).Changed() {
log("retry requested")
}
})
})
}
func devRefreshIntervalSelectorDemo(c *ui.Context, log func(string)) {
dtSection(c, "RefreshIntervalSelector", func() {
s := devControls(c, "451", log, false)
interval := ui.Local(c.Root(), "451", func() time.Duration { return 0 })
refreshing := ui.Local(c.Root(), "451-refreshing", func() bool { return false })
if mujica.Checkbox(c, refreshing, "Refreshing", mujica.CheckboxOptions{}).Changed() {
log("refresh state changed")
}
devPreview(c, s, func() {
r := mujica.RefreshIntervalSelector(c, interval, mujica.RefreshIntervalSelectorOptions{Refreshing: *refreshing})
if r.Refresh {
log("refresh requested")
}
if r.Changed {
log("interval changed")
}
})
})
}
func devDashboardFilterBarDemo(c *ui.Context, log func(string)) {
dtSection(c, "DashboardFilterBar", func() {
s := devControls(c, "452", log, false)
filters := ui.Local(c.Root(), "452", func() mujica.DashboardFilters { return mujica.DashboardFilters{} })
devPreview(c, s, func() {
opts := mujica.DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "db", "web"}}
if s.empty {
opts.Envs, opts.Tags = nil, nil
*filters = mujica.DashboardFilters{}
}
if mujica.DashboardFilterBar(c, filters, opts).Changed() {
log("filters changed")
}
})
})
}
+87
View File
@@ -0,0 +1,87 @@
package main
import (
"testing"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
func devPageTester(p page, mode mujica.Mode, events *[]string) *ui.Tester {
return ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mode})
ui.Column(c).Padding(16).Children(func() { p.Demo(c, func(s string) { *events = append(*events, s) }) })
}, 1000, 800)
}
func TestDevtoolsPagesControlsAndThemes(t *testing.T) {
if len(devtoolsPages) != 17 {
t.Fatalf("pages=%d", len(devtoolsPages))
}
for _, p := range devtoolsPages {
t.Run(p.Name, func(t *testing.T) {
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
var events []string
tt := devPageTester(p, mode, &events)
if p.Category != "Dashboards & Dev Tools" || p.Code == "" || p.Keys == "" {
t.Fatal("page metadata")
}
if err := tt.Click("Disabled"); err != nil {
t.Fatal(err)
}
if len(events) != 1 || events[0] != "disabled changed" {
t.Fatalf("control events %v", events)
}
if err := tt.Click("Disabled"); err != nil {
t.Fatal(err)
}
if _, ok := tt.Find("Empty data"); ok {
if err := tt.Click("Empty data"); err != nil {
t.Fatal(err)
}
if events[len(events)-1] != "data state changed" {
t.Fatal("empty event")
}
if err := tt.Click("Empty data"); err != nil {
t.Fatal(err)
}
}
}
})
}
}
func TestDevtoolsDemoEvents(t *testing.T) {
cases := []struct{ id, label, event string }{
{"443", "Acknowledge", "acknowledged cpu"},
{"447", "Append line", "line appended"},
{"450", "Append sample", "sample appended"},
{"451", "Refresh", "refresh requested"},
{"453", "Copy path", "path copied"},
{"454", "Format", "JSON edited; valid=true"},
{"456", "Send", "send requested"},
{"458", "Add row", "pairs edited"},
}
for _, tc := range cases {
t.Run(tc.id, func(t *testing.T) {
for _, p := range devtoolsPages {
if p.ID != tc.id {
continue
}
var events []string
tt := devPageTester(p, mujica.Light, &events)
if err := tt.Click(tc.label); err != nil {
t.Fatal(err)
}
found := false
for _, event := range events {
found = found || event == tc.event
}
if !found {
t.Fatalf("events %v want %s", events, tc.event)
}
}
})
}
}