package app import ( "fmt" "os" "time" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) type Dashboard struct { table table.Model spinner spinner.Model help help.Model keys keyMap quitting bool loading bool lastUpdate time.Time } type keyMap struct { refresh key.Binding subscribers key.Binding services key.Binding billing key.Binding monitoring key.Binding config key.Binding deploy key.Binding help key.Binding quit key.Binding } func (k keyMap) ShortHelp() []key.Binding { return []key.Binding{k.help, k.quit} } func (k keyMap) FullHelp() [][]key.Binding { return [][]key.Binding{ {k.refresh, k.subscribers, k.services}, {k.billing, k.monitoring, k.config}, {k.deploy, k.help, k.quit}, } } var defaultKeyMap = keyMap{ refresh: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "refresh")), subscribers: key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "subscribers")), services: key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "services")), billing: key.NewBinding(key.WithKeys("b"), key.WithHelp("b", "billing")), monitoring: key.NewBinding(key.WithKeys("m"), key.WithHelp("m", "monitoring")), config: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "config")), deploy: key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "deploy")), help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "help")), quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")), } func NewDashboard() *Dashboard { s := spinner.New() s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) t := table.New() t.SetColumns([]table.Column{ {Title: "Service", Width: 20}, {Title: "Status", Width: 10}, {Title: "CPU", Width: 8}, {Title: "Memory", Width: 10}, {Title: "Uptime", Width: 12}, {Title: "Version", Width: 10}, }) t.SetRows([]table.Row{ {"API Server", "Running", "45%", "256MB", "2h15m", "v1.0.0"}, {"Charging Engine", "Running", "23%", "128MB", "2h15m", "v1.0.0"}, {"Packet Gateway", "Running", "67%", "512MB", "2h15m", "v1.0.0"}, {"Web Dashboard", "Running", "12%", "64MB", "2h15m", "v1.0.0"}, {"Prometheus", "Running", "8%", "32MB", "2h15m", "v2.45.0"}, {"Grafana", "Running", "5%", "24MB", "2h15m", "v9.5.2"}, }) t.SetStyles(table.DefaultStyles()) return &Dashboard{ table: t, spinner: s, help: help.New(), keys: defaultKeyMap, lastUpdate: time.Now(), } } func (d *Dashboard) Init() tea.Cmd { return tea.Batch(d.spinner.Tick) } func (d *Dashboard) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: switch { case key.Matches(msg, d.keys.quit): d.quitting = true return d, tea.Quit case key.Matches(msg, d.keys.help): d.help.ShowAll = !d.help.ShowAll return d, nil case key.Matches(msg, d.keys.refresh): d.loading = true return d, d.refreshData case key.Matches(msg, d.keys.subscribers): // Navigate to subscribers view return d, tea.Quit case key.Matches(msg, d.keys.services): // Navigate to services view return d, tea.Quit case key.Matches(msg, d.keys.billing): // Navigate to billing view return d, tea.Quit case key.Matches(msg, d.keys.monitoring): // Navigate to monitoring view return d, tea.Quit case key.Matches(msg, d.keys.config): // Navigate to config view return d, tea.Quit case key.Matches(msg, d.keys.deploy): // Navigate to deploy view return d, tea.Quit } // Timer removed - manual refresh only case spinner.TickMsg: var cmd tea.Cmd d.spinner, cmd = d.spinner.Update(msg) return d, cmd } d.table, cmd = d.table.Update(msg) return d, cmd } func (d *Dashboard) View() string { title := d.titleView() table := d.table.View() footer := d.footerView() helpView := "" if d.help.ShowAll { helpView = d.help.View(d.keys) } return fmt.Sprintf( "%s\n\n%s\n\n%s\n%s", title, table, footer, helpView, ) } func (d *Dashboard) titleView() string { title := "Telecom Platform Dashboard" subtitle := fmt.Sprintf("Last updated: %s", d.lastUpdate.Format("15:04:05")) if d.loading { subtitle += " " + d.spinner.View() } titleStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("205")). Background(lipgloss.Color("240")). Padding(0, 2). Bold(true) subtitleStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("241")). Padding(0, 2) return titleStyle.Render(title) + "\n" + subtitleStyle.Render(subtitle) } func (d *Dashboard) footerView() string { info := "Press '?' for help | 'r' to refresh | 'q' to quit" // Calculate totals totalServices := len(d.table.Rows()) runningServices := 0 for _, row := range d.table.Rows() { if len(row) > 1 && row[1] == "Running" { runningServices++ } } status := fmt.Sprintf("Services: %d/%d running", runningServices, totalServices) footerStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("241")). Padding(0, 2) return footerStyle.Render(fmt.Sprintf("%s | %s", info, status)) } func (d *Dashboard) refreshData() tea.Msg { // Simulate data refresh time.Sleep(500 * time.Millisecond) d.loading = false // Update with new data newRows := []table.Row{ {"API Server", "Running", fmt.Sprintf("%d%%", 40+int(time.Now().Unix()%40)), "256MB", "2h15m", "v1.0.0"}, {"Charging Engine", "Running", fmt.Sprintf("%d%%", 20+int(time.Now().Unix()%30)), "128MB", "2h15m", "v1.0.0"}, {"Packet Gateway", "Running", fmt.Sprintf("%d%%", 60+int(time.Now().Unix()%30)), "512MB", "2h15m", "v1.0.0"}, {"Web Dashboard", "Running", fmt.Sprintf("%d%%", 10+int(time.Now().Unix()%20)), "64MB", "2h15m", "v1.0.0"}, {"Prometheus", "Running", fmt.Sprintf("%d%%", 5+int(time.Now().Unix()%10)), "32MB", "2h15m", "v2.45.0"}, {"Grafana", "Running", fmt.Sprintf("%d%%", 3+int(time.Now().Unix()%7)), "24MB", "2h15m", "v9.5.2"}, } d.table.SetRows(newRows) return nil } func RunDashboard() { p := tea.NewProgram(NewDashboard()) if _, err := p.Run(); err != nil { fmt.Printf("Error running dashboard: %v\n", err) os.Exit(1) } }