feat: sidebar

This commit is contained in:
Rob Watson 2025-02-22 10:48:38 +01:00
parent 53da4a34cf
commit 0141c54c64

View File

@ -11,6 +11,15 @@ import (
"github.com/rivo/tview" "github.com/rivo/tview"
) )
type sourceViews struct {
url *tview.TextView
status *tview.TextView
health *tview.TextView
cpu *tview.TextView
mem *tview.TextView
rx *tview.TextView
}
// Actor is responsible for managing the terminal user interface. // Actor is responsible for managing the terminal user interface.
type Actor struct { type Actor struct {
app *tview.Application app *tview.Application
@ -18,7 +27,7 @@ type Actor struct {
ch chan action ch chan action
commandCh chan Command commandCh chan Command
logger *slog.Logger logger *slog.Logger
sourceView *tview.Table sourceViews sourceViews
destView *tview.Table destView *tview.Table
} }
@ -41,12 +50,59 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) {
app := tview.NewApplication() app := tview.NewApplication()
sourceView := tview.NewTable() sidebar := tview.NewFlex()
sourceView.SetTitle("source") sidebar.SetDirection(tview.FlexRow)
sourceView := tview.NewFlex()
sourceView.SetDirection(tview.FlexColumn)
sourceView.SetBorder(true) sourceView.SetBorder(true)
sourceView.SetTitle("Ingress RTMP server")
sidebar.AddItem(sourceView, 8, 0, false)
leftCol := tview.NewFlex()
leftCol.SetDirection(tview.FlexRow)
rightCol := tview.NewFlex()
rightCol.SetDirection(tview.FlexRow)
sourceView.AddItem(leftCol, 8, 0, false)
sourceView.AddItem(rightCol, 0, 1, false)
urlHeaderTextView := tview.NewTextView().SetDynamicColors(true).SetText("[grey]" + headerURL)
leftCol.AddItem(urlHeaderTextView, 1, 0, false)
urlTextView := tview.NewTextView().SetDynamicColors(true).SetText("[white]" + dash)
rightCol.AddItem(urlTextView, 1, 0, false)
statusHeaderTextView := tview.NewTextView().SetDynamicColors(true).SetText("[grey]" + headerStatus)
leftCol.AddItem(statusHeaderTextView, 1, 0, false)
statusTextView := tview.NewTextView().SetDynamicColors(true).SetText("[white]" + dash)
rightCol.AddItem(statusTextView, 1, 0, false)
healthHeaderTextView := tview.NewTextView().SetDynamicColors(true).SetText("[grey]" + headerHealth)
leftCol.AddItem(healthHeaderTextView, 1, 0, false)
healthTextView := tview.NewTextView().SetDynamicColors(true).SetText("[white]" + dash)
rightCol.AddItem(healthTextView, 1, 0, false)
cpuHeaderTextView := tview.NewTextView().SetDynamicColors(true).SetText("[grey]" + headerCPU)
leftCol.AddItem(cpuHeaderTextView, 1, 0, false)
cpuTextView := tview.NewTextView().SetDynamicColors(true).SetText("[white]" + dash)
rightCol.AddItem(cpuTextView, 1, 0, false)
memHeaderTextView := tview.NewTextView().SetDynamicColors(true).SetText("[grey]" + headerMem)
leftCol.AddItem(memHeaderTextView, 1, 0, false)
memTextView := tview.NewTextView().SetDynamicColors(true).SetText("[white]" + dash)
rightCol.AddItem(memTextView, 1, 0, false)
rxHeaderTextView := tview.NewTextView().SetDynamicColors(true).SetText("[grey]" + headerRx)
leftCol.AddItem(rxHeaderTextView, 1, 0, false)
rxTextView := tview.NewTextView().SetDynamicColors(true).SetText("[white]" + dash)
rightCol.AddItem(rxTextView, 1, 0, false)
aboutView := tview.NewBox()
aboutView.SetBorder(true)
aboutView.SetTitle("Actions")
sidebar.AddItem(aboutView, 0, 1, false)
destView := tview.NewTable() destView := tview.NewTable()
destView.SetTitle("destinations") destView.SetTitle("Egress streams")
destView.SetBorder(true) destView.SetBorder(true)
destView.SetSelectable(true, false) destView.SetSelectable(true, false)
destView.SetWrapSelection(true, false) destView.SetWrapSelection(true, false)
@ -64,8 +120,8 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) {
flex := tview.NewFlex(). flex := tview.NewFlex().
SetDirection(tview.FlexColumn). SetDirection(tview.FlexColumn).
AddItem(sourceView, 0, 1, false). AddItem(sidebar, 40, 0, false).
AddItem(destView, 0, 1, false) AddItem(destView, 0, 6, false)
pages := tview.NewPages() pages := tview.NewPages()
pages.AddPage("main", flex, true, true) pages.AddPage("main", flex, true, true)
@ -80,7 +136,14 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) {
logger: params.Logger, logger: params.Logger,
app: app, app: app,
pages: pages, pages: pages,
sourceView: sourceView, sourceViews: sourceViews{
url: urlTextView,
status: statusTextView,
health: healthTextView,
cpu: cpuTextView,
mem: memTextView,
rx: rxTextView,
},
destView: destView, destView: destView,
} }
@ -192,31 +255,34 @@ func (a *Actor) redrawFromState(state domain.AppState) {
SetSelectable(false) SetSelectable(false)
} }
a.sourceView.Clear() a.sourceViews.url.SetText(state.Source.RTMPURL)
a.sourceView.SetCell(0, 0, headerCell("[grey]"+headerName, 3))
a.sourceView.SetCell(0, 1, headerCell("[grey]"+headerURL, 3))
a.sourceView.SetCell(0, 2, headerCell("[grey]"+headerStatus, 2))
a.sourceView.SetCell(0, 3, headerCell("[grey]"+headerContainer, 2))
a.sourceView.SetCell(0, 4, headerCell("[grey]"+headerHealth, 2))
a.sourceView.SetCell(0, 5, headerCell("[grey]"+headerCPU, 1))
a.sourceView.SetCell(0, 6, headerCell("[grey]"+headerMem, 1))
a.sourceView.SetCell(0, 7, headerCell("[grey]"+headerRx, 1))
a.sourceView.SetCell(1, 0, tview.NewTableCell("Local server"))
a.sourceView.SetCell(1, 1, tview.NewTableCell(state.Source.RTMPURL))
if state.Source.Live { if state.Source.Live {
a.sourceView.SetCell(1, 2, tview.NewTableCell("[black:green]receiving")) a.sourceViews.status.SetText("[black:green]receiving")
} else if state.Source.Container.State == "running" && state.Source.Container.HealthState == "healthy" { } else if state.Source.Container.State == "running" && state.Source.Container.HealthState == "healthy" {
a.sourceView.SetCell(1, 2, tview.NewTableCell("[black:yellow]ready")) a.sourceViews.status.SetText("[black:yellow]ready")
} else { } else {
a.sourceView.SetCell(1, 2, tview.NewTableCell("[white:red]not ready")) a.sourceViews.status.SetText("[white:red]not ready")
} }
a.sourceView.SetCell(1, 3, tview.NewTableCell("[white]"+state.Source.Container.State))
a.sourceView.SetCell(1, 4, tview.NewTableCell("[white]"+cmp.Or(state.Source.Container.HealthState, "starting"))) a.sourceViews.health.SetText("[white]" + cmp.Or(state.Source.Container.HealthState, dash))
a.sourceView.SetCell(1, 5, tview.NewTableCell("[white]"+fmt.Sprintf("%.1f", state.Source.Container.CPUPercent)))
a.sourceView.SetCell(1, 6, tview.NewTableCell("[white]"+fmt.Sprintf("%.1f", float64(state.Source.Container.MemoryUsageBytes)/1024/1024))) cpuPercent := dash
a.sourceView.SetCell(1, 7, tview.NewTableCell("[white]"+fmt.Sprintf("%d", state.Source.Container.RxRate))) if state.Source.Container.State == "running" {
cpuPercent = fmt.Sprintf("%.1f", state.Source.Container.CPUPercent)
}
a.sourceViews.cpu.SetText("[white]" + cpuPercent)
memUsage := dash
if state.Source.Container.State == "running" {
memUsage = fmt.Sprintf("%.1f", float64(state.Source.Container.MemoryUsageBytes)/1024/1024)
}
a.sourceViews.mem.SetText("[white]" + memUsage)
rxRate := dash
if state.Source.Container.State == "running" {
rxRate = fmt.Sprintf("%d", state.Source.Container.RxRate)
}
a.sourceViews.rx.SetText("[white]" + rxRate)
a.destView.Clear() a.destView.Clear()
a.destView.SetCell(0, 0, headerCell("[grey]"+headerName, 3)) a.destView.SetCell(0, 0, headerCell("[grey]"+headerName, 3))
@ -230,7 +296,7 @@ func (a *Actor) redrawFromState(state domain.AppState) {
for i, dest := range state.Destinations { for i, dest := range state.Destinations {
a.destView.SetCell(i+1, 0, tview.NewTableCell(dest.Name)) a.destView.SetCell(i+1, 0, tview.NewTableCell(dest.Name))
a.destView.SetCell(i+1, 1, tview.NewTableCell(truncate(dest.URL, 50)).SetReference(dest.URL)) a.destView.SetCell(i+1, 1, tview.NewTableCell(dest.URL).SetReference(dest.URL).SetMaxWidth(20))
switch dest.Status { switch dest.Status {
case domain.DestinationStatusLive: case domain.DestinationStatusLive:
a.destView.SetCell( a.destView.SetCell(
@ -293,10 +359,3 @@ func (a *Actor) redrawFromState(state domain.AppState) {
func (a *Actor) Close() { func (a *Actor) Close() {
a.app.Stop() a.app.Stop()
} }
func truncate(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max] + "…"
}