Skip to content

Commit

Permalink
Merge pull request #46 from ewilliams0305/service-view
Browse files Browse the repository at this point in the history
Service view
  • Loading branch information
ewilliams0305 committed Dec 21, 2023
2 parents a7b5d9f + b7be1cd commit d6e04aa
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 8 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/gorilla/css v1.0.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/yuin/goldmark v1.6.0 // indirect
github.com/yuin/goldmark-emoji v1.0.2 // indirect
golang.org/x/net v0.17.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand Down
6 changes: 0 additions & 6 deletions pkg/tui/actions_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ func (m ActionsModel) Init() tea.Cmd {

if m.action == loadAndCreate {

//t := time.Now()
//zone, offset := t.Zone()

///fmt.Print(offset)

pops := &vc.ProgramOptions{
AppFile: ProgramFile,
Name: ProgramName,
Expand All @@ -89,7 +84,6 @@ func (m ActionsModel) Init() tea.Cmd {
Name: RoomID,
ProgramInstanceId: RoomID,
AddressSetsLocation: true,
//TimeZone: zone,
}
cmd = CreateAndRunRoomAction(pops, rops)
return tea.Batch(cmd, actionsTickCmd())
Expand Down
12 changes: 10 additions & 2 deletions pkg/tui/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ const (
devices appState = 4
// AUTH VIEW, displays all auth and api tokens
auth appState = 5
// SYSTEM SERVICE VIEW, displays logs and service status
systemd appState = 6
// HELP VIEW
helpState appState = 6
helpState appState = 7
)

var app *MainModel
Expand All @@ -51,7 +53,7 @@ func InitialModel() MainModel {
w, h, _ := term.GetSize(int(os.Stdout.Fd()))
app = &MainModel{
device: vc.DeviceInfo{},
actions: []string{"Refresh", "Manage Programs", "Manage Rooms", "Device Information", "Devices", "Authorization", "Help"},
actions: []string{"Refresh", "Manage Programs", "Manage Rooms", "Device Information", "Devices", "Authorization", "System Service", "Help"},
help: NewHelpModel(),
width: w,
height: h,
Expand Down Expand Up @@ -132,6 +134,10 @@ func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "p", "ctrl+p":
m.state = programs
return InitialProgramsModel(m.width, m.height), DeviceInfoCommand

case "s", "ctrl+s":
m.state = programs
return InitialSystemModel(), nil
}

}
Expand Down Expand Up @@ -193,6 +199,8 @@ func arrowSelected(m *MainModel) (tea.Model, tea.Cmd) {

case int(auth):
case int(devices):
case int(systemd):
return InitialSystemModel(), nil
case int(helpState):
return NewHelpModel(), nil

Expand Down
190 changes: 190 additions & 0 deletions pkg/tui/service_exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package tui

import (
"os/exec"
"time"

"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

type VirtualControlServiceModel struct {
altscreenActive bool
err error
help SystemsHelpModel
list list.Model
progress progress.Model
}

type serviceOption struct {
title, desc string
}

type journalClosedMessage struct{ err error }

type serviceClosedMessage struct{ err error }

var docStyle = lipgloss.NewStyle().Margin(1, 2)

func (i serviceOption) Title() string { return i.title }
func (i serviceOption) Description() string { return i.desc }
func (i serviceOption) FilterValue() string { return i.title }

func InitialSystemModel() VirtualControlServiceModel {

items := []list.Item{
serviceOption{title: "Stop", desc: "stops the virtual control systemd service"},
serviceOption{title: "Start", desc: "starts the virtual control systemd service"},
serviceOption{title: "Restart", desc: "restarts the virtual control systemd service"},
serviceOption{title: "Logs", desc: "views the virtual control service journal"},
}

prog := progress.New(progress.WithDefaultGradient())
prog.Width = app.width

m := VirtualControlServiceModel{
help: NewSystensHelpModel(),
altscreenActive: true,
list: list.New(items, list.NewDefaultDelegate(), 100, 20),
progress: prog,
}

m.list.Title = "Virtual Control Service Actions"
return m
}

func (m VirtualControlServiceModel) Init() tea.Cmd {
return nil
}

func (m VirtualControlServiceModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {

case "s", "ctrl+s":
if m.list.FilterState() != list.Filtering {
return m, tea.Batch(stopService(), systemTickCmd())
}
case "n", "ctrl+n":
if m.list.FilterState() != list.Filtering {
return m, tea.Batch(startService(), systemTickCmd())
}
case "r", "ctrl+r":
if m.list.FilterState() != list.Filtering {
return m, tea.Batch(restartService(), systemTickCmd())
}
case "l", "ctrl+l":
if m.list.FilterState() != list.Filtering {
return m, tea.Batch(openJournal(), tea.EnterAltScreen)
}
case "esc", "ctrl+q", "q":
if m.list.FilterState() != list.Filtering {
return ReturnToHomeModel(systemd), DeviceInfoCommand
}

case "enter":

switch m.list.Cursor() {
case 0:
return m, tea.Batch(stopService(), systemTickCmd())
case 1:
return m, tea.Batch(startService(), systemTickCmd())
case 2:
return m, tea.Batch(restartService(), systemTickCmd())
case 3:
return m, tea.Batch(openJournal(), tea.EnterAltScreen)

}
}

case error:
m.err = msg
return m, nil

case journalClosedMessage:
if msg.err != nil {
m.err = msg.err
return m, nil
}
case serviceClosedMessage:
if msg.err != nil {
m.err = msg.err
return m, nil
}

case progress.FrameMsg:
progressModel, cmd := m.progress.Update(msg)
m.progress = progressModel.(progress.Model)
return m, cmd

case progressTick:
if m.progress.Percent() == 1.0 {
prog := progress.New(progress.WithDefaultGradient())
prog.Width = app.width
m.progress = prog
return m, tea.Batch(openJournal(), tea.EnterAltScreen)
}
return m, tea.Batch(systemTickCmd(), m.progress.IncrPercent(0.20))
}

var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}

func (m VirtualControlServiceModel) View() string {
s := "\n"
s += docStyle.Render(m.list.View())
s += "\n\n\n"

if m.err != nil {
s += RenderErrorBox("Failed interacting with the virtual control service", m.err)
}

if m.progress.Percent() != 0.0 {
s += "\n" + m.progress.View() + "\n"
} else {
s += "\n\n\n"
}

s += m.help.renderHelpInfo()
return s
}

func systemTickCmd() tea.Cmd {
return tea.Tick(time.Millisecond*300, func(t time.Time) tea.Msg {
return progressTick(t)
})
}

func openJournal() tea.Cmd {

c := exec.Command("journalctl", "-u", "virtualcontrol.service", "-f")
return tea.ExecProcess(c, func(err error) tea.Msg {
return journalClosedMessage{err}
})
}
func stopService() tea.Cmd {

c := exec.Command("systemctl", "stop", "virtualcontrol.service")
return tea.ExecProcess(c, func(err error) tea.Msg {
return serviceClosedMessage{err}
})
}
func startService() tea.Cmd {

c := exec.Command("systemctl", "start", "virtualcontrol.service")
return tea.ExecProcess(c, func(err error) tea.Msg {
return serviceClosedMessage{err}
})
}
func restartService() tea.Cmd {

c := exec.Command("systemctl", "restart", "virtualcontrol.service")
return tea.ExecProcess(c, func(err error) tea.Msg {
return serviceClosedMessage{err}
})
}
122 changes: 122 additions & 0 deletions pkg/tui/service_help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package tui

import (
"strings"

"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

// keyMap defines a set of keybindings. To work for help it must satisfy
// key.Map. It could also very easily be a map[string]key.Binding.
type serviceKeyMap struct {
Up key.Binding
Down key.Binding
Help key.Binding
Quit key.Binding
Start key.Binding
Stop key.Binding
Restart key.Binding
Logs key.Binding
}

// ShortHelp returns keybindings to be shown in the mini help view. It's part
// of the key.Map interface.
func (k serviceKeyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Quit, k.Up, k.Down, k.Start, k.Stop, k.Restart, k.Logs}
}

// FullHelp returns keybindings for the expanded help view. It's part of the
// key.Map interface.
func (k serviceKeyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.Help, k.Up, k.Down}, // first column
{k.Start, k.Stop, k.Logs}, // second column
}
}

var serviceKeys = serviceKeyMap{
Up: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("↑/k", "move up"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("↓/j", "move down"),
),
Help: key.NewBinding(
key.WithKeys("?", "h"),
key.WithHelp("?", "toggle help"),
),
Quit: key.NewBinding(
key.WithKeys("q", "esc", "ctrl+c"),
key.WithHelp("q", "quit"),
),
Stop: key.NewBinding(
key.WithKeys("ctrl+s"),
key.WithHelp("ctrl+s", "stop service"),
),
Start: key.NewBinding(
key.WithKeys("ctrl+n"),
key.WithHelp("ctrl+n", "start service"),
),
Restart: key.NewBinding(
key.WithKeys("ctrl+r"),
key.WithHelp("ctrl+r", "restart room"),
),
Logs: key.NewBinding(
key.WithKeys("ctrl+l"),
key.WithHelp("ctrl+l", "display logs"),
),
}

type SystemsHelpModel struct {
keys serviceKeyMap
help help.Model
inputStyle lipgloss.Style
}

func NewSystensHelpModel() SystemsHelpModel {
return SystemsHelpModel{
keys: serviceKeys,
help: help.New(),
inputStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#FF75B7")),
}
}

func (m SystemsHelpModel) Init() tea.Cmd {
return nil
}

func (m SystemsHelpModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.help.Width = msg.Width

case tea.KeyMsg:
switch {

case key.Matches(msg, m.keys.Help):
m.help.ShowAll = !m.help.ShowAll
case key.Matches(msg, m.keys.Quit):
return VirtualControlServiceModel{}, nil
}
}
return m, nil
}

func (m SystemsHelpModel) View() string {
var status string

helpView := m.help.View(m.keys)
height := 8 - strings.Count(status, "\n") - strings.Count(helpView, "\n")

return "\n" + status + strings.Repeat("\n", height) + helpView
}

func (m SystemsHelpModel) renderHelpInfo() string {
helpView := m.help.View(m.keys)
return "\n" + helpView
}

0 comments on commit d6e04aa

Please sign in to comment.