248 lines
6.7 KiB
Go
248 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/coreos/go-systemd/sdjournal"
|
|
"github.com/gdamore/tcell"
|
|
"github.com/rivo/tview"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
servicesCmd = &cobra.Command{
|
|
Use: "services",
|
|
Short: "monitor service states",
|
|
Long: `The services command starts an interactive service monitor`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
form, err := servicesForm()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = app.SetRoot(form, true).Run(); err != nil {
|
|
return err
|
|
}
|
|
return apperr
|
|
},
|
|
}
|
|
)
|
|
|
|
func servicesForm() (tview.Primitive, error) {
|
|
var pipe *LogPipe
|
|
activeOnly := false
|
|
filter := logLevelFilter(6)
|
|
|
|
pages := tview.NewPages()
|
|
confirmDialog := tview.NewModal()
|
|
list := NewServicesView()
|
|
list.SetMainTextColor(tcell.GetColor(theme.Colors["Service"]))
|
|
list.SetSecondaryTextColor(tcell.GetColor(theme.Colors["Description"]))
|
|
|
|
err := list.loadModel(false, activeOnly)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
infoTable := tview.NewTable().
|
|
SetBorders(false)
|
|
infoTable.SetCell(0, 0, tview.NewTableCell("State:"))
|
|
infoTable.SetCell(1, 0, tview.NewTableCell("Description:"))
|
|
infoTable.SetCell(3, 0, tview.NewTableCell("Load Successful:"))
|
|
infoTable.SetCell(4, 0, tview.NewTableCell("SubState:"))
|
|
|
|
serviceView := tview.NewFlex().
|
|
SetDirection(tview.FlexRow)
|
|
serviceView.
|
|
SetBorder(true).
|
|
SetTitle("Service")
|
|
|
|
logView := tview.NewTextView()
|
|
list.SetSelectedFunc(func(index int, primText, secText string, shortcut rune) {
|
|
app.SetFocus(logView)
|
|
})
|
|
list.SetChangedFunc(func(index int, primText, secText string, shortcut rune) {
|
|
pipe = selectService(pipe, list.Model[index], filter, logView, serviceView, infoTable, confirmDialog)
|
|
})
|
|
|
|
logView.
|
|
SetDynamicColors(true).
|
|
SetScrollable(true).
|
|
SetChangedFunc(func() {
|
|
app.Draw()
|
|
}).
|
|
SetDoneFunc(func(key tcell.Key) {
|
|
app.SetFocus(list)
|
|
})
|
|
logView.
|
|
SetBorder(true).
|
|
SetTitle("Log")
|
|
|
|
serviceView.
|
|
AddItem(infoTable, 6, 1, false).
|
|
AddItem(logView, 0, 1, false)
|
|
|
|
flex := tview.NewFlex().
|
|
AddItem(list, 40, 1, true).
|
|
AddItem(serviceView, 0, 1, false)
|
|
|
|
logLevelDropDown := tview.NewList()
|
|
logLevelDropDown.
|
|
SetBorder(true).
|
|
SetTitle("Log-level")
|
|
logLevelDialog := tview.NewFlex().
|
|
AddItem(tview.NewBox(), 0, 1, false).
|
|
AddItem(tview.NewFlex().
|
|
SetDirection(tview.FlexRow).
|
|
AddItem(tview.NewBox(), 0, 1, false).
|
|
AddItem(logLevelDropDown, 18, 1, true).
|
|
AddItem(tview.NewBox(), 0, 1, false), 30, 1, true).
|
|
AddItem(tview.NewBox(), 0, 1, false)
|
|
|
|
confirmDialog.
|
|
SetText("Do you want to start the service?").
|
|
AddButtons([]string{"Yes", "No"}).
|
|
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
|
pages.HidePage("confirm_dialog")
|
|
if buttonIndex == 0 {
|
|
err := toggleServiceState(list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
updateServiceUI(list.Model[list.GetCurrentItem()].Name, serviceView, infoTable, confirmDialog)
|
|
}
|
|
app.SetFocus(list)
|
|
})
|
|
|
|
pages.AddPage("flex", flex, true, true)
|
|
pages.AddPage("dropdown_loglevel", logLevelDialog, true, false)
|
|
pages.AddPage("confirm_dialog", confirmDialog, false, false)
|
|
|
|
menuPages := tview.NewPages()
|
|
searchInput := tview.NewInputField()
|
|
searchInput.
|
|
SetLabel("Search for: ").
|
|
SetFieldWidth(40).
|
|
SetAcceptanceFunc(nil).
|
|
SetDoneFunc(func(key tcell.Key) {
|
|
search = searchInput.GetText()
|
|
menuPages.HidePage("search")
|
|
pipe = selectService(pipe, list.Model[list.GetCurrentItem()], filter, logView, serviceView, infoTable, confirmDialog)
|
|
app.SetFocus(list)
|
|
})
|
|
|
|
menuPages.AddPage("menu", menu, true, true)
|
|
menuPages.AddPage("search", searchInput, true, false)
|
|
|
|
logLevelDropDown.AddItem("Emergency", "Only Emergencies", 0, nil).
|
|
AddItem("Alert", "Alerts or worse", 0, nil).
|
|
AddItem("Critical", "Critical or worse", 0, nil).
|
|
AddItem("Error", "Errors or worse", 0, nil).
|
|
AddItem("Warning", "Warnings or worse", 0, nil).
|
|
AddItem("Notice", "Notice or worse", 0, nil).
|
|
AddItem("Informational", "Informational or worse", 0, nil).
|
|
AddItem("Debug", "Debug or worse", 0, nil)
|
|
logLevelDropDown.SetSelectedFunc(func(index int, primText, secText string, shortcut rune) {
|
|
filter = logLevelFilter(index)
|
|
pages.HidePage("dropdown_loglevel")
|
|
pipe = selectService(pipe, list.Model[list.GetCurrentItem()], filter, logView, serviceView, infoTable, confirmDialog)
|
|
})
|
|
|
|
menu.AddItem("Active Services", tcell.KeyF1, func() {
|
|
activeOnly = !activeOnly
|
|
if activeOnly {
|
|
menu.Items[0].Text = "All Services"
|
|
} else {
|
|
menu.Items[0].Text = "Active Services"
|
|
}
|
|
list.loadModel(false, activeOnly)
|
|
app.SetFocus(list)
|
|
})
|
|
menu.AddItem("Log-level", tcell.KeyF2, func() {
|
|
pages.ShowPage("dropdown_loglevel")
|
|
})
|
|
menu.AddItem("Filter", tcell.KeyF3, func() {
|
|
menuPages.ShowPage("search")
|
|
app.SetFocus(searchInput)
|
|
})
|
|
menu.AddItem("Start Service", tcell.KeyF8, func() {
|
|
pages.ShowPage("confirm_dialog")
|
|
})
|
|
|
|
// Create the main layout.
|
|
layout := tview.NewFlex().
|
|
SetDirection(tview.FlexRow).
|
|
AddItem(pages, 0, 1, true).
|
|
AddItem(menuPages, 1, 1, false)
|
|
|
|
pipe = selectService(pipe, list.Model[0], filter, logView, serviceView, infoTable, confirmDialog)
|
|
|
|
return layout, nil
|
|
}
|
|
|
|
func updateServiceUI(name string, serviceView *tview.Flex, infoTable *tview.Table, confirmDialog *tview.Modal) {
|
|
u, err := service(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if u.ActiveState == "active" {
|
|
menu.Items[3].Text = "Stop Service"
|
|
confirmDialog.SetText(fmt.Sprintf("Do you want to stop service %s?", u.Name))
|
|
} else {
|
|
menu.Items[3].Text = "Start Service"
|
|
confirmDialog.SetText(fmt.Sprintf("Do you want to start service %s?", u.Name))
|
|
}
|
|
|
|
serviceView.SetTitle(u.Name)
|
|
infoTable.SetCell(0, 1, tview.NewTableCell(u.ActiveState))
|
|
infoTable.SetCell(1, 1, tview.NewTableCell(u.Description))
|
|
infoTable.SetCell(3, 1, tview.NewTableCell(u.LoadState))
|
|
infoTable.SetCell(4, 1, tview.NewTableCell(u.SubState))
|
|
}
|
|
|
|
func selectService(pipe *LogPipe, l ServiceItem, filter []sdjournal.Match, logView *tview.TextView, serviceView *tview.Flex, infoTable *tview.Table, confirmDialog *tview.Modal) *LogPipe {
|
|
updateServiceUI(l.Name, serviceView, infoTable, confirmDialog)
|
|
|
|
// cancel previous reader
|
|
if pipe != nil {
|
|
pipe.Cancel <- time.Now()
|
|
pipe.WaitGroup.Wait()
|
|
}
|
|
|
|
logView.Clear()
|
|
|
|
title := "Log"
|
|
if len(search) > 0 {
|
|
title += fmt.Sprintf(" (filtered by %s)", search)
|
|
}
|
|
logView.SetTitle(title)
|
|
logView.ScrollToEnd()
|
|
|
|
pipe = logPipe(l.Matches, filter)
|
|
go pipeReader(pipe, logView)
|
|
|
|
return pipe
|
|
}
|
|
|
|
func toggleServiceState(list *ServicesView) error {
|
|
s := list.Model[list.GetCurrentItem()]
|
|
u, err := service(s.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if u.ActiveState == "active" {
|
|
err = stopService(s.Name)
|
|
} else {
|
|
err = startService(s.Name)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func init() {
|
|
RootCmd.AddCommand(servicesCmd)
|
|
}
|