Files
admin-cli/go-cli/cmd/main.go
T
2025-10-27 15:18:32 -07:00

263 lines
6.9 KiB
Go

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)
}
}