diff --git a/src/tui/actions.go b/src/tui/actions.go index 44352b3..ca4b392 100644 --- a/src/tui/actions.go +++ b/src/tui/actions.go @@ -28,6 +28,7 @@ const ( ActionLogFindNext = ActionName("find_next") ActionLogFindPrev = ActionName("find_prev") ActionLogFindExit = ActionName("find_exit") + ActionNsFilter = ActionName("ns_filter") ) var defaultShortcuts = map[ActionName]tcell.Key{ @@ -46,6 +47,7 @@ var defaultShortcuts = map[ActionName]tcell.Key{ ActionLogFindNext: tcell.KeyCtrlN, ActionLogFindPrev: tcell.KeyCtrlP, ActionLogFindExit: tcell.KeyEsc, + ActionNsFilter: tcell.KeyCtrlG, } type ShortCuts struct { @@ -216,6 +218,9 @@ func getDefaultActions() ShortCuts { ActionLogFindExit: { Description: "Exit Search", }, + ActionNsFilter: { + Description: "Select Namespace", + }, }, } for k, v := range sc.ShortCutKeys { diff --git a/src/tui/namespace-selector.go b/src/tui/namespace-selector.go new file mode 100644 index 0000000..64e3b65 --- /dev/null +++ b/src/tui/namespace-selector.go @@ -0,0 +1,96 @@ +package tui + +import ( + "github.com/rivo/tview" + "github.com/rs/zerolog/log" + "slices" +) + +func (pv *pcView) showNsFilter() { + filter := pv.createNsFilterPrimitive() + _, _, w, h := filter.GetRect() + pv.pages.AddPage(PageDialog, createDialogPage(filter, w, h), true, true) + pv.appView.SetFocus(filter) +} + +func (pv *pcView) createNsFilterPrimitive() tview.Primitive { + selectAllNsLbl := "Select all the namespaces" + list := tview.NewList(). + AddItem("All", selectAllNsLbl, '0', func() { + pv.setSelectedNs(AllNS) + pv.pages.RemovePage(PageDialog) + }) + nsList, err := pv.getSortedNsList() + if err != nil { + log.Err(err).Msg("failed to get sorted ns list") + } + maxLblLen := len(selectAllNsLbl) + const scList = "123456789abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + for i, ns := range nsList { + nsToAdd := ns + selectNsLbl := "Select " + ns + r := '0' + if i < len(scList) { + r = rune(scList[i]) + } + list.AddItem(ns, selectNsLbl, r, func() { + pv.setSelectedNs(nsToAdd) + pv.pages.RemovePage(PageDialog) + }) + if len(selectNsLbl) > maxLblLen { + maxLblLen = len(selectNsLbl) + } + } + list.AddItem("Cancel", "Select to close", 'x', func() { + pv.pages.RemovePage(PageDialog) + }) + list.SetBorder(true).SetTitle("Namespaces") + + flex := tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(list, (len(nsList)+3)*2, 1, true), maxLblLen+10, 1, true). + AddItem(nil, 0, 1, false) + return flex +} + +func (pv *pcView) getSortedNsList() ([]string, error) { + states, err := pv.project.GetProcessesState() + if err != nil { + log.Err(err).Msg("failed to get processes state") + return []string{}, err + } + nsList := make(map[string]struct{}) + for _, state := range states.States { + nsList[state.Namespace] = struct{}{} + } + var nsListSorted []string + for ns := range nsList { + nsListSorted = append(nsListSorted, ns) + } + slices.Sort(nsListSorted) + return nsListSorted, nil +} + +func (pv *pcView) isNsSelected(ns string) bool { + pv.selectedNsMtx.Lock() + defer pv.selectedNsMtx.Unlock() + if pv.selectedNs == AllNS || pv.selectedNs == ns { + return true + } + + return false +} + +func (pv *pcView) setSelectedNs(ns string) { + pv.selectedNsMtx.Lock() + defer pv.selectedNsMtx.Unlock() + pv.selectedNsChanged.Store(pv.selectedNs != ns) + pv.selectedNs = ns +} + +func (pv *pcView) getSelectedNs() string { + pv.selectedNsMtx.Lock() + defer pv.selectedNsMtx.Unlock() + return pv.selectedNs +} diff --git a/src/tui/proc-table.go b/src/tui/proc-table.go index 87d53a5..f32d501 100644 --- a/src/tui/proc-table.go +++ b/src/tui/proc-table.go @@ -27,29 +27,44 @@ func (pv *pcView) fillTableData() { log.Err(err).Msg("failed to sort states") return } - for r, state := range states.States { + row := 1 + for _, state := range states.States { + if !pv.isNsSelected(state.Namespace) { + pv.procTable.RemoveRow(row) + continue + } icon, color := getIconForState(state) - pv.procTable.SetCell(r+1, int(ProcessStateIcon), tview.NewTableCell(icon).SetAlign(tview.AlignCenter).SetExpansion(0).SetTextColor(color)) - pv.procTable.SetCell(r+1, int(ProcessStatePid), tview.NewTableCell(strconv.Itoa(state.Pid)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, int(ProcessStateName), tview.NewTableCell(state.Name).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, int(ProcessStateNamespace), tview.NewTableCell(state.Namespace).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, int(ProcessStateStatus), tview.NewTableCell(state.Status).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, int(ProcessStateAge), tview.NewTableCell(state.SystemTime).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, int(ProcessStateHealth), tview.NewTableCell(state.Health).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, int(ProcessStateRestarts), tview.NewTableCell(strconv.Itoa(state.Restarts)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, int(ProcessStateExit), tview.NewTableCell(strconv.Itoa(state.ExitCode)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateIcon), tview.NewTableCell(icon).SetAlign(tview.AlignCenter).SetExpansion(0).SetTextColor(color)) + pv.procTable.SetCell(row, int(ProcessStatePid), tview.NewTableCell(strconv.Itoa(state.Pid)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateName), tview.NewTableCell(state.Name).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateNamespace), tview.NewTableCell(state.Namespace).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateStatus), tview.NewTableCell(state.Status).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateAge), tview.NewTableCell(state.SystemTime).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateHealth), tview.NewTableCell(state.Health).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateRestarts), tview.NewTableCell(strconv.Itoa(state.Restarts)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(row, int(ProcessStateExit), tview.NewTableCell(strconv.Itoa(state.ExitCode)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) if state.IsRunning { runningProcCount += 1 } + row += 1 } + // remove unnecessary rows, don't forget the title row (-1) - if pv.procTable.GetRowCount()-1 > len(states.States) { + if pv.procTable.GetRowCount()-1 > row { for i := len(states.States); i < pv.procTable.GetRowCount()-1; i++ { pv.procTable.RemoveRow(i) } } + if pv.selectedNsChanged.Swap(false) { + pv.procTable.Select(1, 1) + } + if pv.procCountCell != nil { - pv.procCountCell.SetText(fmt.Sprintf("%d/%d", runningProcCount, len(pv.procNames))) + nsLbl := "" + if !pv.isNsSelected(AllNS) { + nsLbl = " (" + pv.getSelectedNs() + ")" + } + pv.procCountCell.SetText(fmt.Sprintf("%d/%d%s", runningProcCount, len(pv.procNames), nsLbl)) } } diff --git a/src/tui/view.go b/src/tui/view.go index 4276888..4cdba57 100644 --- a/src/tui/view.go +++ b/src/tui/view.go @@ -12,6 +12,7 @@ import ( "os/signal" "strconv" "sync" + "sync/atomic" "syscall" "time" @@ -30,6 +31,7 @@ const ( const ( PageMain = "main" PageDialog = "dialog" + AllNS = "PC_ALL_NS_FILTER" ) const shutDownAfterSec = 10 @@ -37,30 +39,33 @@ const shutDownAfterSec = 10 var pcv *pcView = nil type pcView struct { - procTable *tview.Table - statTable *tview.Table - appView *tview.Application - logsText *LogView - statusText *tview.TextView - helpText *tview.TextView - pages *tview.Pages - procNames []string - logFollow bool - logSelect bool - fullScrState FullScrState - loggedProc string - shortcuts ShortCuts - procCountCell *tview.TableCell - mainGrid *tview.Grid - logsTextArea *tview.TextArea - project app.IProject - sortMtx sync.Mutex - stateSorter StateSorter - procColumns map[ColumnID]string - refreshRate time.Duration - cancelFn context.CancelFunc - cancelLogFn context.CancelFunc - cancelSigFn context.CancelFunc + procTable *tview.Table + statTable *tview.Table + appView *tview.Application + logsText *LogView + statusText *tview.TextView + helpText *tview.TextView + pages *tview.Pages + procNames []string + logFollow bool + logSelect bool + fullScrState FullScrState + loggedProc string + shortcuts ShortCuts + procCountCell *tview.TableCell + mainGrid *tview.Grid + logsTextArea *tview.TextArea + project app.IProject + sortMtx sync.Mutex + stateSorter StateSorter + procColumns map[ColumnID]string + refreshRate time.Duration + cancelFn context.CancelFunc + cancelLogFn context.CancelFunc + cancelSigFn context.CancelFunc + selectedNsMtx sync.Mutex + selectedNs string + selectedNsChanged atomic.Bool } func newPcView(project app.IProject) *pcView { @@ -85,6 +90,7 @@ func newPcView(project app.IProject) *pcView { isAsc: true, }, procColumns: map[ColumnID]string{}, + selectedNs: AllNS, } pv.statTable = pv.createStatTable() go pv.loadProcNames() @@ -174,7 +180,8 @@ func (pv *pcView) onAppKey(event *tcell.EventKey) *tcell.EventKey { pv.logsText.SetTitle(pv.getLogTitle(pv.getSelectedProcName())) case pv.shortcuts.ShortCutKeys[ActionLogFindExit].key: pv.exitSearch() - + case pv.shortcuts.ShortCutKeys[ActionNsFilter].key: + pv.showNsFilter() default: return event } @@ -343,6 +350,7 @@ func (pv *pcView) updateHelpTextView() { pv.shortcuts.ShortCutKeys[ActionProcessScreen].writeToggleButton(pv.helpText, procScrBool) pv.shortcuts.ShortCutKeys[ActionProcessStop].writeButton(pv.helpText) pv.shortcuts.ShortCutKeys[ActionProcessRestart].writeButton(pv.helpText) + pv.shortcuts.ShortCutKeys[ActionNsFilter].writeButton(pv.helpText) pv.shortcuts.ShortCutKeys[ActionQuit].writeButton(pv.helpText) }