package main import ( "context" "crypto/tls" "fmt" "os" "strings" "time" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" pbhuman "github.com/flowy-live/admin-cli/genproto/helios/human" pbkeypad "github.com/flowy-live/admin-cli/genproto/helios/keypad" "github.com/mergestat/timediff" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" ) var ( titleStyle = lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#7D56F4")). Padding(0, 1). MarginBottom(1) selectedItemStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#FFFFFF")). Background(lipgloss.Color("#7D56F4")). Bold(true). Padding(0, 1) normalItemStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#FAFAFA")). Padding(0, 1) dimItemStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#666666")). Padding(0, 1) cursorStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#FF75B5")). Bold(true) helpStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#626262")). Padding(1, 0, 0, 1) statusStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#7D56F4")). Bold(true) borderStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#3C3C3C")) timestampStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#888888")). Italic(true) selectedTimestampStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#CCCCCC")). Italic(true) ) type HumanData struct { Email string DisplayName string JoinedAt time.Time KeypadConfiguration string } type model struct { humans []*HumanData cursor int selectedHumanEmail string viewport viewport.Model ready bool showKeypad bool } func initialModel(humans []*HumanData) model { return model{ humans: humans, cursor: 0, showKeypad: false, } } func (m model) Init() tea.Cmd { // Just return `nil`, which means "no I/O right now, please." return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var ( cmd tea.Cmd cmds []tea.Cmd ) switch msg := msg.(type) { case tea.WindowSizeMsg: headerHeight := 3 // title + status + separator footerHeight := 2 // help text verticalMarginHeight := headerHeight + footerHeight if !m.ready { m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) m.viewport.YPosition = headerHeight m.viewport.SetContent(m.renderList()) m.ready = true } else { m.viewport.Width = msg.Width m.viewport.Height = msg.Height - verticalMarginHeight } case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "up", "k": if m.cursor > 0 { m.cursor-- m.viewport.SetContent(m.renderList()) } case "down", "j": if m.cursor < len(m.humans)-1 { m.cursor++ m.viewport.SetContent(m.renderList()) } case "t": m.showKeypad = !m.showKeypad m.viewport.SetContent(m.renderList()) } } m.viewport, cmd = m.viewport.Update(msg) cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } func (m model) renderList() string { var b strings.Builder for i, human := range m.humans { email := human.Email // Get relative time ago timeAgo := timediff.TimeDiff(human.JoinedAt) if m.cursor == i { // Current selection - highlighted cursor := cursorStyle.Render("▸") item := selectedItemStyle.Render(email) timestamp := selectedTimestampStyle.Render(fmt.Sprintf("joined %s", timeAgo)) b.WriteString(fmt.Sprintf("%s %s %s\n", cursor, item, timestamp)) if m.showKeypad { b.WriteString(fmt.Sprintf("%s\n", selectedItemStyle.Render(human.KeypadConfiguration))) } } else { // Normal item cursor := dimItemStyle.Render(" ") item := normalItemStyle.Render(email) timestamp := timestampStyle.Render(fmt.Sprintf("joined %s", timeAgo)) b.WriteString(fmt.Sprintf("%s %s %s\n", cursor, item, timestamp)) } } return b.String() } func (m model) View() string { if !m.ready { return "\n Initializing..." } // Header with main data sevenDaysAgo := time.Now().AddDate(0, 0, -7) pastSevenDaysSignupsCount := 0 for _, human := range m.humans { if human.JoinedAt.After(sevenDaysAgo) { pastSevenDaysSignupsCount++ } } header := titleStyle.Render(fmt.Sprintf("👥 Humans | %d total | %d in the past 7 days", len(m.humans), pastSevenDaysSignupsCount)) // Status bar showing current position statusBar := statusStyle.Render(fmt.Sprintf(" %d/%d", m.cursor+1, len(m.humans))) // Help text help := helpStyle.Render("↑/k up • ↓/j down • t toggle keypads • q quit") // Separator separator := borderStyle.Render(strings.Repeat("─", m.viewport.Width)) return fmt.Sprintf("%s%s\n%s\n%s\n%s", header, statusBar, separator, m.viewport.View(), help) } func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { md := metadata.Pairs("authorization", "talksik") ctx = metadata.NewOutgoingContext(ctx, md) return invoker(ctx, method, req, reply, cc, opts...) } func main() { logrus.Infof("hello world") heliosAddr := "helios.flowy.live:443" creds := credentials.NewTLS(&tls.Config{ InsecureSkipVerify: true, }) heliosService, err := grpc.NewClient(heliosAddr, grpc.WithTransportCredentials(creds), grpc.WithUnaryInterceptor(unaryInterceptor)) if err != nil { logrus.Fatalf("connection to aero server invalid: %v", err) } defer heliosService.Close() humanService := pbhuman.NewHumanServiceClient(heliosService) keypadService := pbkeypad.NewKeypadServiceClient(heliosService) humansResponse, err := humanService.ListHumans(context.Background(), &pbhuman.ListHumansRequest{}) if err != nil { logrus.Fatalf("unable to fetch humans: %v", err) os.Exit(1) } keypadsResponse, err := keypadService.ListKeypads(context.Background(), &pbkeypad.ListKeypadsRequest{}) if err != nil { logrus.Fatalf("unable to fetch keypads: %v", err) os.Exit(1) } keypadsResponse.GetHumanKeypads()[0].GetHumanId() keypadsResponse.GetHumanKeypads()[0].GetYamlConfig() logrus.Info("connected to helios") logrus.Infof("fetched %d humans", len(humansResponse.GetHumans())) humansData := []*HumanData{} for _, human := range humansResponse.GetHumans() { keypadConfiguration := "" for _, keypad := range keypadsResponse.GetHumanKeypads() { if keypad.GetHumanId() == human.GetId() { keypadConfiguration = keypad.GetYamlConfig() } } humansData = append(humansData, &HumanData{ Email: human.GetEmail(), DisplayName: human.GetDisplayName(), JoinedAt: human.GetJoinedAt().AsTime(), KeypadConfiguration: keypadConfiguration, }) } p := tea.NewProgram(initialModel(humansData)) if _, err := p.Run(); err != nil { fmt.Printf("Alas, there's been an error: %v", err) os.Exit(1) } }