mirror of
https://github.com/F1bonacc1/process-compose.git
synced 2024-09-11 19:37:08 +03:00
Namespaces and TUI sorting
This commit is contained in:
parent
ca1748240a
commit
ff3a25f2dd
17
README.md
17
README.md
@ -23,6 +23,7 @@ Process Compose is a simple and flexible scheduler and orchestrator to manage no
|
||||
- [Functions as both server and client](#-client-mode)
|
||||
- Configurable shortcuts (see [Wiki](https://github.com/F1bonacc1/process-compose/wiki/Shortcuts-Configuration))
|
||||
- [Merge Configuration Files](#-merge-2-or-more-configuration-files-with-override-values)
|
||||
- [Namespaces](#-Namespaces)
|
||||
|
||||
It is heavily inspired by [docker-compose](https://github.com/docker/compose), but without the need for containers. The configuration syntax tries to follow the docker-compose specifications, with a few minor additions and lots of subtractions.
|
||||
|
||||
@ -340,6 +341,12 @@ processes:
|
||||
|
||||
Even if disabled, it is still listed in the TUI and the REST client, can be started manually when needed.
|
||||
|
||||
##### Processes State Columns Sorting
|
||||
|
||||
Sorting is performed by pressing `shift` + the letter that appears in `()` next to the column title. Pressing the same combination again will reverse the sort order.
|
||||
|
||||
For example: To sort by process `AGE(A)` press `shift+A`
|
||||
|
||||
#### ✅ <u>Logger</u>
|
||||
|
||||
##### ✅ Per Process Log Collection
|
||||
@ -605,7 +612,17 @@ Using multiple `process-compose` files lets you to customize a `process-compose`
|
||||
|
||||
See the `process-compose` wiki for more information on [Multiple Compose Files](https://github.com/F1bonacc1/process-compose/wiki/Multiple-Compose-Files).
|
||||
|
||||
### ✅ Namespaces
|
||||
|
||||
Assigning namespaces to processes allows better grouping and sorting, especially in TUI:
|
||||
|
||||
```yaml
|
||||
processes:
|
||||
process1:
|
||||
command: "tail -f -n100 process-compose-${USER}.log"
|
||||
working_dir: "/tmp"
|
||||
namespace: debug # if not defined 'default' namespaces is automatically assigned to each process
|
||||
```
|
||||
|
||||
#### ✅ <u>Multi-platform</u>
|
||||
|
||||
|
@ -69,6 +69,7 @@ processes:
|
||||
|
||||
process4:
|
||||
command: "./test_loop.bash process4-override"
|
||||
namespace: test
|
||||
disable_ansi_colors: true
|
||||
# availability:
|
||||
# restart: on_failure
|
||||
|
@ -127,10 +127,12 @@ processes:
|
||||
depends_on:
|
||||
process0:
|
||||
condition: process_completed
|
||||
namespace: debug
|
||||
|
||||
__pc_log_client:
|
||||
command: "tail -f -n100 process-compose-${USER}-client.log"
|
||||
working_dir: "/tmp"
|
||||
namespace: debug
|
||||
|
||||
|
||||
bat_config:
|
||||
|
@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/f1bonacc1/process-compose/src/types"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@ -65,23 +64,13 @@ func (api *PcApi) GetProcessInfo(c *gin.Context) {
|
||||
// @Success 200 {object} object "Processes Status"
|
||||
// @Router /processes [get]
|
||||
func (api *PcApi) GetProcesses(c *gin.Context) {
|
||||
procs, err := api.project.GetLexicographicProcessNames()
|
||||
states, err := api.project.GetProcessesState()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
states := []*types.ProcessState{}
|
||||
for _, name := range procs {
|
||||
state, err := api.project.GetProcessState(name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
states = append(states, state)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": states})
|
||||
c.JSON(http.StatusOK, states)
|
||||
}
|
||||
|
||||
// @Schemes
|
||||
|
@ -309,6 +309,7 @@ func (p *Process) updateProcState() {
|
||||
if p.isRunning() {
|
||||
dur := time.Since(p.startTime)
|
||||
p.procState.SystemTime = durationToString(dur)
|
||||
p.procState.Age = dur
|
||||
p.procState.IsRunning = true
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ type IProject interface {
|
||||
GetLexicographicProcessNames() ([]string, error)
|
||||
GetProcessInfo(name string) (*types.ProcessConfig, error)
|
||||
GetProcessState(name string) (*types.ProcessState, error)
|
||||
GetProcessesState() (*types.ProcessesState, error)
|
||||
StopProcess(name string) error
|
||||
StartProcess(name string) error
|
||||
RestartProcess(name string) error
|
||||
|
@ -131,6 +131,7 @@ func (p *ProjectRunner) initProcessStates() {
|
||||
for key, proc := range p.project.Processes {
|
||||
p.processStates[key] = &types.ProcessState{
|
||||
Name: key,
|
||||
Namespace: proc.Namespace,
|
||||
Status: types.ProcessStatePending,
|
||||
SystemTime: "",
|
||||
Health: types.ProcessHealthUnknown,
|
||||
@ -159,6 +160,7 @@ func (p *ProjectRunner) GetProcessState(name string) (*types.ProcessState, error
|
||||
} else {
|
||||
procState.Pid = 0
|
||||
procState.SystemTime = ""
|
||||
procState.Age = time.Duration(0)
|
||||
procState.Health = types.ProcessHealthUnknown
|
||||
procState.IsRunning = false
|
||||
}
|
||||
@ -169,6 +171,20 @@ func (p *ProjectRunner) GetProcessState(name string) (*types.ProcessState, error
|
||||
return nil, fmt.Errorf("no such process: %s", name)
|
||||
}
|
||||
|
||||
func (p *ProjectRunner) GetProcessesState() (*types.ProcessesState, error) {
|
||||
states := &types.ProcessesState{
|
||||
States: make([]types.ProcessState, 0),
|
||||
}
|
||||
for name, _ := range p.processStates {
|
||||
state, err := p.GetProcessState(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
states.States = append(states.States, *state)
|
||||
}
|
||||
return states, nil
|
||||
}
|
||||
|
||||
func (p *ProjectRunner) addRunningProcess(process *Process) {
|
||||
p.mapMutex.Lock()
|
||||
p.runningProcesses[process.getName()] = process
|
||||
|
@ -81,6 +81,10 @@ func (p *PcClient) GetProcessState(name string) (*types.ProcessState, error) {
|
||||
return state, err
|
||||
}
|
||||
|
||||
func (p *PcClient) GetProcessesState() (*types.ProcessesState, error) {
|
||||
return GetProcessesState(p.address, p.port)
|
||||
}
|
||||
|
||||
func (p *PcClient) StopProcess(name string) error {
|
||||
return StopProcesses(p.address, p.port, name)
|
||||
}
|
||||
|
@ -6,44 +6,38 @@ import (
|
||||
"github.com/f1bonacc1/process-compose/src/types"
|
||||
"github.com/rs/zerolog/log"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func GetProcessesName(address string, port int) ([]string, error) {
|
||||
url := fmt.Sprintf("http://%s:%d/processes", address, port)
|
||||
resp, err := http.Get(url)
|
||||
states, err := GetProcessesState(address, port)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
//Create a variable of the same type as our model
|
||||
var sResp types.ProcessStates
|
||||
|
||||
//Decode the data
|
||||
if err := json.NewDecoder(resp.Body).Decode(&sResp); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
procs := make([]string, len(sResp.States))
|
||||
for i, proc := range sResp.States {
|
||||
procs := make([]string, len(states.States))
|
||||
for i, proc := range states.States {
|
||||
procs[i] = proc.Name
|
||||
}
|
||||
sort.Strings(procs)
|
||||
return procs, nil
|
||||
}
|
||||
|
||||
func GetProcessesState(address string, port int) ([]types.ProcessState, error) {
|
||||
func GetProcessesState(address string, port int) (*types.ProcessesState, error) {
|
||||
url := fmt.Sprintf("http://%s:%d/processes", address, port)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return []types.ProcessState{}, err
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
//Create a variable of the same type as our model
|
||||
var sResp types.ProcessStates
|
||||
var sResp types.ProcessesState
|
||||
|
||||
//Decode the data
|
||||
if err := json.NewDecoder(resp.Body).Decode(&sResp); err != nil {
|
||||
return []types.ProcessState{}, err
|
||||
log.Err(err).Msgf("failed to decode process states")
|
||||
return nil, err
|
||||
}
|
||||
return sResp.States, nil
|
||||
return &sResp, nil
|
||||
}
|
||||
|
||||
func GetProcessState(address string, port int, name string) (*types.ProcessState, error) {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/rs/zerolog/log"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@ -13,19 +14,26 @@ func (pv *pcView) fillTableData() {
|
||||
return
|
||||
}
|
||||
runningProcCount := 0
|
||||
for r, name := range pv.procNames {
|
||||
state, err := pv.project.GetProcessState(name)
|
||||
if err != nil || state == nil {
|
||||
return
|
||||
}
|
||||
|
||||
states, err := pv.project.GetProcessesState()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get processes state")
|
||||
return
|
||||
}
|
||||
sorter := pv.getTableSorter()
|
||||
err = sortProcessesState(sorter.sortByColumn, sorter.isAsc, states)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to sort states")
|
||||
return
|
||||
}
|
||||
for r, state := range states.States {
|
||||
pv.procTable.SetCell(r+1, 0, tview.NewTableCell(strconv.Itoa(state.Pid)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 1, tview.NewTableCell(state.Name).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 2, tview.NewTableCell(state.Status).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 3, tview.NewTableCell(state.SystemTime).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 4, tview.NewTableCell(state.Health).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 5, tview.NewTableCell(strconv.Itoa(state.Restarts)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 6, tview.NewTableCell(strconv.Itoa(state.ExitCode)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 2, tview.NewTableCell(state.Namespace).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 3, tview.NewTableCell(state.Status).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 4, tview.NewTableCell(state.SystemTime).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 5, tview.NewTableCell(state.Health).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 6, tview.NewTableCell(strconv.Itoa(state.Restarts)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
pv.procTable.SetCell(r+1, 7, tview.NewTableCell(strconv.Itoa(state.ExitCode)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue))
|
||||
if state.IsRunning {
|
||||
runningProcCount += 1
|
||||
}
|
||||
@ -54,7 +62,17 @@ func (pv *pcView) onTableSelectionChange(row, column int) {
|
||||
|
||||
func (pv *pcView) createProcTable() *tview.Table {
|
||||
table := tview.NewTable().SetBorders(false).SetSelectable(true, false)
|
||||
//pv.fillTableData()
|
||||
pv.procColumns = map[ColumnID]string{
|
||||
ProcessStatePid: "PID(P)",
|
||||
ProcessStateName: "NAME(N)",
|
||||
ProcessStateNamespace: "NAMESPACE(C)",
|
||||
ProcessStateStatus: "STATUS(S)",
|
||||
ProcessStateAge: "AGE(A)",
|
||||
ProcessStateHealth: "HEALTH(H)",
|
||||
ProcessStateRestarts: "RESTARTS(R)",
|
||||
ProcessStateExit: "EXIT CODE(E)",
|
||||
}
|
||||
|
||||
table.Select(1, 1).SetFixed(1, 0).SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Key() {
|
||||
case pv.shortcuts.ShortCutKeys[ActionProcessStop].key:
|
||||
@ -66,27 +84,42 @@ func (pv *pcView) createProcTable() *tview.Table {
|
||||
case pv.shortcuts.ShortCutKeys[ActionProcessRestart].key:
|
||||
name := pv.getSelectedProcName()
|
||||
pv.project.RestartProcess(name)
|
||||
case tcell.KeyRune:
|
||||
if event.Rune() == 'S' {
|
||||
pv.setTableSorter(ProcessStateStatus)
|
||||
} else if event.Rune() == 'N' {
|
||||
pv.setTableSorter(ProcessStateName)
|
||||
} else if event.Rune() == 'C' {
|
||||
pv.setTableSorter(ProcessStateNamespace)
|
||||
} else if event.Rune() == 'A' {
|
||||
pv.setTableSorter(ProcessStateAge)
|
||||
} else if event.Rune() == 'H' {
|
||||
pv.setTableSorter(ProcessStateHealth)
|
||||
} else if event.Rune() == 'R' {
|
||||
pv.setTableSorter(ProcessStateRestarts)
|
||||
} else if event.Rune() == 'E' {
|
||||
pv.setTableSorter(ProcessStateExit)
|
||||
} else if event.Rune() == 'P' {
|
||||
pv.setTableSorter(ProcessStatePid)
|
||||
}
|
||||
}
|
||||
return event
|
||||
})
|
||||
columns := []string{
|
||||
"PID", "NAME", "STATUS", "AGE", "READINESS", "RESTARTS", "EXIT CODE",
|
||||
}
|
||||
for i := 0; i < len(columns); i++ {
|
||||
expan := 1
|
||||
for i := 0; i < len(pv.procColumns); i++ {
|
||||
expansion := 10
|
||||
align := tview.AlignLeft
|
||||
switch columns[i] {
|
||||
switch ColumnID(i) {
|
||||
case
|
||||
"PID":
|
||||
expan = 0
|
||||
ProcessStatePid:
|
||||
expansion = 1
|
||||
case
|
||||
"RESTARTS",
|
||||
"EXIT CODE":
|
||||
ProcessStateRestarts,
|
||||
ProcessStateExit:
|
||||
align = tview.AlignRight
|
||||
}
|
||||
|
||||
table.SetCell(0, i, tview.NewTableCell(columns[i]).
|
||||
SetSelectable(false).SetExpansion(expan).SetAlign(align))
|
||||
table.SetCell(0, i, tview.NewTableCell(pv.procColumns[ColumnID(i)]).
|
||||
SetSelectable(false).SetExpansion(expansion).SetAlign(align))
|
||||
}
|
||||
table.SetSelectionChangedFunc(pv.onTableSelectionChange)
|
||||
return table
|
||||
@ -100,3 +133,30 @@ func (pv *pcView) updateTable() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (pv *pcView) setTableSorter(sortBy ColumnID) {
|
||||
pv.sortMtx.Lock()
|
||||
defer pv.sortMtx.Unlock()
|
||||
prevSortColumn := ProcessStateUndefined
|
||||
if pv.stateSorter.sortByColumn == sortBy {
|
||||
pv.stateSorter.isAsc = !pv.stateSorter.isAsc
|
||||
} else {
|
||||
prevSortColumn = pv.stateSorter.sortByColumn
|
||||
pv.stateSorter.sortByColumn = sortBy
|
||||
pv.stateSorter.isAsc = true
|
||||
}
|
||||
order := "[pink]↓[-:-:-]"
|
||||
if !pv.stateSorter.isAsc {
|
||||
order = "[pink]↑[-:-:-]"
|
||||
}
|
||||
pv.procTable.GetCell(0, int(sortBy)).SetText(pv.procColumns[sortBy] + order)
|
||||
if prevSortColumn != ProcessStateUndefined {
|
||||
pv.procTable.GetCell(0, int(prevSortColumn)).SetText(pv.procColumns[prevSortColumn])
|
||||
}
|
||||
}
|
||||
|
||||
func (pv *pcView) getTableSorter() StateSorter {
|
||||
pv.sortMtx.Lock()
|
||||
defer pv.sortMtx.Unlock()
|
||||
return pv.stateSorter
|
||||
}
|
||||
|
114
src/tui/procstate_sorter.go
Normal file
114
src/tui/procstate_sorter.go
Normal file
@ -0,0 +1,114 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/f1bonacc1/process-compose/src/types"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type ColumnID int
|
||||
|
||||
const (
|
||||
ProcessStateUndefined ColumnID = -1
|
||||
ProcessStatePid ColumnID = 0
|
||||
ProcessStateName ColumnID = 1
|
||||
ProcessStateNamespace ColumnID = 2
|
||||
ProcessStateStatus ColumnID = 3
|
||||
ProcessStateAge ColumnID = 4
|
||||
ProcessStateHealth ColumnID = 5
|
||||
ProcessStateRestarts ColumnID = 6
|
||||
ProcessStateExit ColumnID = 7
|
||||
)
|
||||
|
||||
type StateSorter struct {
|
||||
sortByColumn ColumnID
|
||||
isAsc bool
|
||||
}
|
||||
|
||||
type sortFn func(i, j int) bool
|
||||
|
||||
func sortProcessesState(sortBy ColumnID, asc bool, states *types.ProcessesState) error {
|
||||
|
||||
if states == nil {
|
||||
return fmt.Errorf("empty states")
|
||||
}
|
||||
sorter := getSorter(sortBy, states)
|
||||
if !asc {
|
||||
sorter = reverse(sorter)
|
||||
}
|
||||
sort.Slice(states.States, sorter)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSorter(sortBy ColumnID, states *types.ProcessesState) sortFn {
|
||||
switch sortBy {
|
||||
case ProcessStatePid:
|
||||
return func(i, j int) bool {
|
||||
if states.States[i].Pid == states.States[j].Pid {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
} else {
|
||||
return states.States[i].Pid < states.States[j].Pid
|
||||
}
|
||||
}
|
||||
case ProcessStateNamespace:
|
||||
return func(i, j int) bool {
|
||||
if states.States[i].Namespace == states.States[j].Namespace {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
} else {
|
||||
return states.States[i].Namespace < states.States[j].Namespace
|
||||
}
|
||||
}
|
||||
case ProcessStateStatus:
|
||||
return func(i, j int) bool {
|
||||
if states.States[i].Status == states.States[j].Status {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
} else {
|
||||
return states.States[i].Status < states.States[j].Status
|
||||
}
|
||||
}
|
||||
case ProcessStateAge:
|
||||
return func(i, j int) bool {
|
||||
if states.States[i].Age == states.States[j].Age {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
} else {
|
||||
return states.States[i].Age < states.States[j].Age
|
||||
}
|
||||
}
|
||||
case ProcessStateHealth:
|
||||
return func(i, j int) bool {
|
||||
if states.States[i].Health == states.States[j].Health {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
} else {
|
||||
return states.States[i].Health < states.States[j].Health
|
||||
}
|
||||
}
|
||||
case ProcessStateRestarts:
|
||||
return func(i, j int) bool {
|
||||
if states.States[i].Restarts == states.States[j].Restarts {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
} else {
|
||||
return states.States[i].Restarts < states.States[j].Restarts
|
||||
}
|
||||
}
|
||||
case ProcessStateExit:
|
||||
return func(i, j int) bool {
|
||||
if states.States[i].ExitCode == states.States[j].ExitCode {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
} else {
|
||||
return states.States[i].ExitCode < states.States[j].ExitCode
|
||||
}
|
||||
}
|
||||
case ProcessStateName:
|
||||
fallthrough
|
||||
default:
|
||||
return func(i, j int) bool {
|
||||
return states.States[i].Name < states.States[j].Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reverse(less func(i, j int) bool) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
return !less(i, j)
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/f1bonacc1/process-compose/src/client"
|
||||
"github.com/f1bonacc1/process-compose/src/config"
|
||||
"github.com/f1bonacc1/process-compose/src/updater"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/f1bonacc1/process-compose/src/app"
|
||||
@ -47,15 +48,17 @@ type pcView struct {
|
||||
mainGrid *tview.Grid
|
||||
logsTextArea *tview.TextArea
|
||||
project app.IProject
|
||||
sortMtx sync.Mutex
|
||||
stateSorter StateSorter
|
||||
procColumns map[ColumnID]string
|
||||
}
|
||||
|
||||
func newPcView(project app.IProject) *pcView {
|
||||
//_ = pv.shortcuts.loadFromFile("short-cuts-new.yaml")
|
||||
pv := &pcView{
|
||||
appView: tview.NewApplication(),
|
||||
logsText: NewLogView(project.GetLogLength()),
|
||||
statusText: tview.NewTextView().SetDynamicColors(true),
|
||||
|
||||
appView: tview.NewApplication(),
|
||||
logsText: NewLogView(project.GetLogLength()),
|
||||
statusText: tview.NewTextView().SetDynamicColors(true),
|
||||
logFollow: true,
|
||||
fullScrState: LogProcHalf,
|
||||
helpText: tview.NewTextView().SetDynamicColors(true),
|
||||
@ -66,6 +69,11 @@ func newPcView(project app.IProject) *pcView {
|
||||
logsTextArea: tview.NewTextArea(),
|
||||
logSelect: false,
|
||||
project: project,
|
||||
stateSorter: StateSorter{
|
||||
sortByColumn: ProcessStateName,
|
||||
isAsc: true,
|
||||
},
|
||||
procColumns: map[ColumnID]string{},
|
||||
}
|
||||
pv.statTable = pv.createStatTable()
|
||||
go pv.loadProcNames()
|
||||
@ -240,7 +248,7 @@ func (pv *pcView) getSelectedProcName() string {
|
||||
}
|
||||
row, _ := pv.procTable.GetSelection()
|
||||
if row > 0 && row <= len(pv.procNames) {
|
||||
return pv.procNames[row-1]
|
||||
return pv.procTable.GetCell(row, 1).Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
package types
|
||||
|
||||
import "github.com/f1bonacc1/process-compose/src/health"
|
||||
import (
|
||||
"github.com/f1bonacc1/process-compose/src/health"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultNamespace = "default"
|
||||
|
||||
type Processes map[string]ProcessConfig
|
||||
type Environment []string
|
||||
@ -18,6 +23,7 @@ type ProcessConfig struct {
|
||||
ShutDownParams ShutDownParams `yaml:"shutdown,omitempty"`
|
||||
DisableAnsiColors bool `yaml:"disable_ansi_colors,omitempty"`
|
||||
WorkingDir string `yaml:"working_dir"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
Extensions map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
@ -33,17 +39,19 @@ func (p ProcessConfig) GetDependencies() []string {
|
||||
}
|
||||
|
||||
type ProcessState struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
SystemTime string `json:"system_time"`
|
||||
Health string `json:"is_ready"`
|
||||
Restarts int `json:"restarts"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
Pid int `json:"pid"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Status string `json:"status"`
|
||||
SystemTime string `json:"system_time"`
|
||||
Age time.Duration `json:"age"`
|
||||
Health string `json:"is_ready"`
|
||||
Restarts int `json:"restarts"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
Pid int `json:"pid"`
|
||||
IsRunning bool
|
||||
}
|
||||
|
||||
type ProcessStates struct {
|
||||
type ProcessesState struct {
|
||||
States []ProcessState `json:"data"`
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,7 @@ package types
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/f1bonacc1/process-compose/src/command"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
@ -19,56 +16,6 @@ type Project struct {
|
||||
ShellConfig *command.ShellConfig `yaml:"shell,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Project) Validate() {
|
||||
p.validateLogLevel()
|
||||
p.setConfigDefaults()
|
||||
p.deprecationCheck()
|
||||
p.validateProcessConfig()
|
||||
}
|
||||
|
||||
func (p *Project) validateLogLevel() {
|
||||
if p.LogLevel != "" {
|
||||
lvl, err := zerolog.ParseLevel(p.LogLevel)
|
||||
if err != nil {
|
||||
log.Warn().Msgf("Unknown log level %s defaulting to %s",
|
||||
p.LogLevel, zerolog.GlobalLevel().String())
|
||||
} else {
|
||||
zerolog.SetGlobalLevel(lvl)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Project) setConfigDefaults() {
|
||||
if p.ShellConfig == nil {
|
||||
p.ShellConfig = command.DefaultShellConfig()
|
||||
}
|
||||
log.Info().Msgf("Global shell command: %s %s", p.ShellConfig.ShellCommand, p.ShellConfig.ShellArgument)
|
||||
command.ValidateShellConfig(*p.ShellConfig)
|
||||
}
|
||||
|
||||
func (p *Project) deprecationCheck() {
|
||||
for key, proc := range p.Processes {
|
||||
if proc.RestartPolicy.Restart == RestartPolicyOnFailureDeprecated {
|
||||
deprecationHandler("2022-10-30", key, RestartPolicyOnFailureDeprecated, RestartPolicyOnFailure, "restart policy")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Project) validateProcessConfig() {
|
||||
for key, proc := range p.Processes {
|
||||
if len(proc.Extensions) == 0 {
|
||||
continue
|
||||
}
|
||||
for extKey := range proc.Extensions {
|
||||
if strings.HasPrefix(extKey, "x-") {
|
||||
continue
|
||||
}
|
||||
log.Error().Msgf("Unknown key %s found in process %s", extKey, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessFunc func(process ProcessConfig) error
|
||||
|
||||
// WithProcesses run ProcesseFunc on each Process and dependencies in dependency order
|
||||
|
68
src/types/validators.go
Normal file
68
src/types/validators.go
Normal file
@ -0,0 +1,68 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/f1bonacc1/process-compose/src/command"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Project) Validate() {
|
||||
p.validateLogLevel()
|
||||
p.setConfigDefaults()
|
||||
p.deprecationCheck()
|
||||
p.validateProcessConfig()
|
||||
p.assignDefaultNamespace()
|
||||
}
|
||||
|
||||
func (p *Project) validateLogLevel() {
|
||||
if p.LogLevel != "" {
|
||||
lvl, err := zerolog.ParseLevel(p.LogLevel)
|
||||
if err != nil {
|
||||
log.Warn().Msgf("Unknown log level %s defaulting to %s",
|
||||
p.LogLevel, zerolog.GlobalLevel().String())
|
||||
} else {
|
||||
zerolog.SetGlobalLevel(lvl)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Project) setConfigDefaults() {
|
||||
if p.ShellConfig == nil {
|
||||
p.ShellConfig = command.DefaultShellConfig()
|
||||
}
|
||||
log.Info().Msgf("Global shell command: %s %s", p.ShellConfig.ShellCommand, p.ShellConfig.ShellArgument)
|
||||
command.ValidateShellConfig(*p.ShellConfig)
|
||||
}
|
||||
|
||||
func (p *Project) deprecationCheck() {
|
||||
for key, proc := range p.Processes {
|
||||
if proc.RestartPolicy.Restart == RestartPolicyOnFailureDeprecated {
|
||||
deprecationHandler("2022-10-30", key, RestartPolicyOnFailureDeprecated, RestartPolicyOnFailure, "restart policy")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Project) validateProcessConfig() {
|
||||
for key, proc := range p.Processes {
|
||||
if len(proc.Extensions) == 0 {
|
||||
continue
|
||||
}
|
||||
for extKey := range proc.Extensions {
|
||||
if strings.HasPrefix(extKey, "x-") {
|
||||
continue
|
||||
}
|
||||
log.Error().Msgf("Unknown key %s found in process %s", extKey, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Project) assignDefaultNamespace() {
|
||||
for name, proc := range p.Processes {
|
||||
if proc.Namespace == "" {
|
||||
proc.Namespace = DefaultNamespace
|
||||
p.Processes[name] = proc
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user