diff --git a/app/app.go b/app/app.go index a47be60..462b7cf 100644 --- a/app/app.go +++ b/app/app.go @@ -14,32 +14,34 @@ import ( "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. -func Run( - ctx context.Context, - cfg config.Config, - dockerClient container.DockerClient, - clipboardAvailable bool, - logger *slog.Logger, -) error { - state := new(domain.AppState) - applyConfig(cfg, state) +func Run(ctx context.Context, params RunParams) error { + state := newStateFromRunParams(params) + logger := params.Logger ui, err := terminal.StartActor(ctx, terminal.StartActorParams{ - ClipboardAvailable: clipboardAvailable, + ClipboardAvailable: params.ClipboardAvailable, + BuildInfo: params.BuildInfo, Logger: logger.With("component", "ui"), }) if err != nil { - return fmt.Errorf("start tui: %w", err) + return fmt.Errorf("start terminal user interface: %w", err) } defer ui.Close() updateUI := func() { ui.SetState(*state) } 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 { return fmt.Errorf("new container client: %w", err) } @@ -70,8 +72,9 @@ func Run( }) defer mp.Close() - uiTicker := time.NewTicker(uiUpdateInterval) - defer uiTicker.Stop() + const uiUpdateInterval = 2 * time.Second + uiUpdateT := time.NewTicker(uiUpdateInterval) + defer uiUpdateT.Stop() for { select { @@ -89,7 +92,7 @@ func Run( case terminal.CommandQuit: return nil } - case <-uiTicker.C: + case <-uiUpdateT.C: updateUI() case serverState := <-srv.C(): applyServerState(serverState, state) @@ -120,13 +123,17 @@ func applyMultiplexerState(mpState multiplexer.State, appState *domain.AppState) } } -// applyConfig applies the configuration to the app state. -func applyConfig(cfg config.Config, appState *domain.AppState) { - appState.Destinations = make([]domain.Destination, 0, len(cfg.Destinations)) - for _, dest := range cfg.Destinations { - appState.Destinations = append(appState.Destinations, domain.Destination{ +// newStateFromRunParams creates a new app state from the run parameters. +func newStateFromRunParams(params RunParams) *domain.AppState { + var state domain.AppState + + 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, URL: dest.URL, }) } + + return &state } diff --git a/domain/types.go b/domain/types.go index 3bd5f54..60d233a 100644 --- a/domain/types.go +++ b/domain/types.go @@ -6,6 +6,13 @@ import "time" type AppState struct { Source Source 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. diff --git a/main.go b/main.go index 1583d77..6e5b513 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "log/slog" "os" "os/exec" + "runtime/debug" "syscall" "git.netflux.io/rob/octoplex/app" @@ -73,12 +74,23 @@ func run(ctx context.Context) error { 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( ctx, - cfg, - dockerClient, - clipboardAvailable, - logger, + app.RunParams{ + Config: cfg, + DockerClient: dockerClient, + ClipboardAvailable: clipboardAvailable, + BuildInfo: domain.BuildInfo{ + GoVersion: buildInfo.GoVersion, + Version: buildInfo.Main.Version, + }, + Logger: logger, + }, ) } diff --git a/terminal/actor.go b/terminal/actor.go index 4a1ff31..37f60c4 100644 --- a/terminal/actor.go +++ b/terminal/actor.go @@ -30,6 +30,7 @@ type Actor struct { pages *tview.Pages ch chan action commandCh chan Command + buildInfo domain.BuildInfo logger *slog.Logger sourceViews sourceViews destView *tview.Table @@ -45,6 +46,7 @@ type StartActorParams struct { ChanSize int Logger *slog.Logger ClipboardAvailable bool + BuildInfo domain.BuildInfo } // 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.SetBorder(true) 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) @@ -146,6 +149,7 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) { actor := &Actor{ ch: ch, commandCh: commandCh, + buildInfo: params.BuildInfo, logger: params.Logger, app: app, pages: pages, @@ -167,6 +171,8 @@ func StartActor(ctx context.Context, params StartActorParams) (*Actor, error) { switch event.Rune() { case 'c', 'C': actor.copySourceURLToClipboard(params.ClipboardAvailable) + case '?': + actor.showAbout() } case tcell.KeyCtrlC: actor.confirmQuit() @@ -437,6 +443,26 @@ func (a *Actor) confirmQuit() { 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 { if s == "" { return s