feat: about

This commit is contained in:
Rob Watson 2025-02-28 06:43:36 +01:00
parent 4e3a72893b
commit f359dcaac5
4 changed files with 78 additions and 26 deletions

View File

@ -14,32 +14,34 @@ import (
"git.netflux.io/rob/octoplex/terminal" "git.netflux.io/rob/octoplex/terminal"
) )
const uiUpdateInterval = 2 * time.Second // RunParams holds the parameters for running the application.
type RunParams struct {
Config config.Config
DockerClient container.DockerClient
ClipboardAvailable bool
BuildInfo domain.BuildInfo
Logger *slog.Logger
}
// Run starts the application, and blocks until it exits. // Run starts the application, and blocks until it exits.
func Run( func Run(ctx context.Context, params RunParams) error {
ctx context.Context, state := newStateFromRunParams(params)
cfg config.Config, logger := params.Logger
dockerClient container.DockerClient,
clipboardAvailable bool,
logger *slog.Logger,
) error {
state := new(domain.AppState)
applyConfig(cfg, state)
ui, err := terminal.StartActor(ctx, terminal.StartActorParams{ ui, err := terminal.StartActor(ctx, terminal.StartActorParams{
ClipboardAvailable: clipboardAvailable, ClipboardAvailable: params.ClipboardAvailable,
BuildInfo: params.BuildInfo,
Logger: logger.With("component", "ui"), Logger: logger.With("component", "ui"),
}) })
if err != nil { if err != nil {
return fmt.Errorf("start tui: %w", err) return fmt.Errorf("start terminal user interface: %w", err)
} }
defer ui.Close() defer ui.Close()
updateUI := func() { ui.SetState(*state) } updateUI := func() { ui.SetState(*state) }
updateUI() updateUI()
containerClient, err := container.NewClient(ctx, dockerClient, logger.With("component", "container_client")) containerClient, err := container.NewClient(ctx, params.DockerClient, logger.With("component", "container_client"))
if err != nil { if err != nil {
return fmt.Errorf("new container client: %w", err) return fmt.Errorf("new container client: %w", err)
} }
@ -70,8 +72,9 @@ func Run(
}) })
defer mp.Close() defer mp.Close()
uiTicker := time.NewTicker(uiUpdateInterval) const uiUpdateInterval = 2 * time.Second
defer uiTicker.Stop() uiUpdateT := time.NewTicker(uiUpdateInterval)
defer uiUpdateT.Stop()
for { for {
select { select {
@ -89,7 +92,7 @@ func Run(
case terminal.CommandQuit: case terminal.CommandQuit:
return nil return nil
} }
case <-uiTicker.C: case <-uiUpdateT.C:
updateUI() updateUI()
case serverState := <-srv.C(): case serverState := <-srv.C():
applyServerState(serverState, state) applyServerState(serverState, state)
@ -120,13 +123,17 @@ func applyMultiplexerState(mpState multiplexer.State, appState *domain.AppState)
} }
} }
// applyConfig applies the configuration to the app state. // newStateFromRunParams creates a new app state from the run parameters.
func applyConfig(cfg config.Config, appState *domain.AppState) { func newStateFromRunParams(params RunParams) *domain.AppState {
appState.Destinations = make([]domain.Destination, 0, len(cfg.Destinations)) var state domain.AppState
for _, dest := range cfg.Destinations {
appState.Destinations = append(appState.Destinations, domain.Destination{ state.Destinations = make([]domain.Destination, 0, len(params.Config.Destinations))
for _, dest := range params.Config.Destinations {
state.Destinations = append(state.Destinations, domain.Destination{
Name: dest.Name, Name: dest.Name,
URL: dest.URL, URL: dest.URL,
}) })
} }
return &state
} }

View File

@ -6,6 +6,13 @@ import "time"
type AppState struct { type AppState struct {
Source Source Source Source
Destinations []Destination Destinations []Destination
BuildInfo BuildInfo
}
// BuildInfo holds information about the build.
type BuildInfo struct {
GoVersion string
Version string
} }
// Source represents the source, currently always the mediaserver. // Source represents the source, currently always the mediaserver.

20
main.go
View File

@ -7,6 +7,7 @@ import (
"log/slog" "log/slog"
"os" "os"
"os/exec" "os/exec"
"runtime/debug"
"syscall" "syscall"
"git.netflux.io/rob/octoplex/app" "git.netflux.io/rob/octoplex/app"
@ -73,12 +74,23 @@ func run(ctx context.Context) error {
return fmt.Errorf("new docker client: %w", err) return fmt.Errorf("new docker client: %w", err)
} }
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return fmt.Errorf("read build info: %w", err)
}
return app.Run( return app.Run(
ctx, ctx,
cfg, app.RunParams{
dockerClient, Config: cfg,
clipboardAvailable, DockerClient: dockerClient,
logger, ClipboardAvailable: clipboardAvailable,
BuildInfo: domain.BuildInfo{
GoVersion: buildInfo.GoVersion,
Version: buildInfo.Main.Version,
},
Logger: logger,
},
) )
} }

View File

@ -30,6 +30,7 @@ type Actor struct {
pages *tview.Pages pages *tview.Pages
ch chan action ch chan action
commandCh chan Command commandCh chan Command
buildInfo domain.BuildInfo
logger *slog.Logger logger *slog.Logger
sourceViews sourceViews sourceViews sourceViews
destView *tview.Table destView *tview.Table
@ -45,6 +46,7 @@ type StartActorParams struct {
ChanSize int ChanSize int
Logger *slog.Logger Logger *slog.Logger
ClipboardAvailable bool ClipboardAvailable bool
BuildInfo domain.BuildInfo
} }
// StartActor starts the terminal user interface actor. // StartActor starts the terminal user interface actor.
@ -110,7 +112,8 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) {
aboutView.SetDirection(tview.FlexRow) aboutView.SetDirection(tview.FlexRow)
aboutView.SetBorder(true) aboutView.SetBorder(true)
aboutView.SetTitle("Actions") aboutView.SetTitle("Actions")
aboutView.AddItem(tview.NewTextView().SetText("[C] Copy ingress URL"), 1, 0, false) aboutView.AddItem(tview.NewTextView().SetText("[C] Copy ingress RTMP URL"), 1, 0, false)
aboutView.AddItem(tview.NewTextView().SetText("[?] About"), 1, 0, false)
sidebar.AddItem(aboutView, 0, 1, false) sidebar.AddItem(aboutView, 0, 1, false)
@ -146,6 +149,7 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) {
actor := &Actor{ actor := &Actor{
ch: ch, ch: ch,
commandCh: commandCh, commandCh: commandCh,
buildInfo: params.BuildInfo,
logger: params.Logger, logger: params.Logger,
app: app, app: app,
pages: pages, pages: pages,
@ -167,6 +171,8 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) {
switch event.Rune() { switch event.Rune() {
case 'c', 'C': case 'c', 'C':
actor.copySourceURLToClipboard(params.ClipboardAvailable) actor.copySourceURLToClipboard(params.ClipboardAvailable)
case '?':
actor.showAbout()
} }
case tcell.KeyCtrlC: case tcell.KeyCtrlC:
actor.confirmQuit() actor.confirmQuit()
@ -437,6 +443,26 @@ func (a *Actor) confirmQuit() {
a.pages.AddPage("modal", modal, true, true) a.pages.AddPage("modal", modal, true, true)
} }
func (a *Actor) showAbout() {
modal := tview.NewModal()
modal.SetText(fmt.Sprintf(
"%s: live stream multiplexer\n\nv0.0.0 %s (%s)",
domain.AppName,
a.buildInfo.Version,
a.buildInfo.GoVersion,
)).
AddButtons([]string{"Ok"}).
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite).
SetDoneFunc(func(buttonIndex int, _ string) {
a.pages.RemovePage("modal")
a.app.SetFocus(a.destView)
})
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite))
a.pages.AddPage("modal", modal, true, true)
}
func rightPad(s string, n int) string { func rightPad(s string, n int) string {
if s == "" { if s == "" {
return s return s