mirror of
https://github.com/F1bonacc1/process-compose.git
synced 2024-08-16 06:50:24 +03:00
Add support for logs selection and copy to clipboard
This commit is contained in:
parent
a835aa5f53
commit
636872f7d3
2
go.mod
2
go.mod
@ -20,6 +20,7 @@ replace github.com/InVisionApp/go-health/v2 => github.com/f1bonacc1/go-health/v2
|
||||
require (
|
||||
github.com/InVisionApp/go-logger v1.0.1 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/f1bonacc1/glippy v0.0.0-20221207220753-a53cdbf9bae7 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
@ -31,6 +32,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jezek/xgb v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -25,6 +25,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/f1bonacc1/glippy v0.0.0-20221207220753-a53cdbf9bae7 h1:4e3jb2TWZp24ME220mKFMLMPVb6/T6b5bdtXfQPnz68=
|
||||
github.com/f1bonacc1/glippy v0.0.0-20221207220753-a53cdbf9bae7/go.mod h1:4FvlEkhBa/BJMEuMGVlocGYDJAvO7FwhJhHH9MY6vaM=
|
||||
github.com/f1bonacc1/go-health/v2 v2.1.3 h1:UbiB5hSNpnqAAs7tsCZ8aA/ScdIpuGbDo2r7lhfIGkQ=
|
||||
github.com/f1bonacc1/go-health/v2 v2.1.3/go.mod h1:Iz2FZRfK3sJecRvGCIgyBsKOjILdKTdLGiGFaO+JDYc=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
@ -82,6 +84,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
|
@ -16,6 +16,7 @@ const (
|
||||
ActionLogScreen = ActionName("log_screen")
|
||||
ActionFollowLog = ActionName("log_follow")
|
||||
ActionWrapLog = ActionName("log_wrap")
|
||||
ActionLogSelection = ActionName("log_select")
|
||||
ActionProcessStart = ActionName("process_start")
|
||||
ActionProcessInfo = ActionName("process_info")
|
||||
ActionProcessStop = ActionName("process_stop")
|
||||
@ -28,6 +29,7 @@ var defaultShortcuts = map[ActionName]tcell.Key{
|
||||
ActionLogScreen: tcell.KeyF4,
|
||||
ActionFollowLog: tcell.KeyF5,
|
||||
ActionWrapLog: tcell.KeyF6,
|
||||
ActionLogSelection: tcell.KeyCtrlS,
|
||||
ActionProcessInfo: tcell.KeyF3,
|
||||
ActionProcessStart: tcell.KeyF7,
|
||||
ActionProcessStop: tcell.KeyF9,
|
||||
@ -162,6 +164,12 @@ func getDefaultActions() ShortCuts {
|
||||
false: "Wrap Off",
|
||||
},
|
||||
},
|
||||
ActionLogSelection: {
|
||||
ToggleDescription: map[bool]string{
|
||||
true: "Select On",
|
||||
false: "Select Off",
|
||||
},
|
||||
},
|
||||
ActionProcessInfo: {
|
||||
Description: "Info",
|
||||
},
|
||||
|
@ -1,64 +1,17 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/f1bonacc1/process-compose/src/app"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (pv *pcView) showProcInfo(info *app.ProcessConfig) {
|
||||
f := tview.NewForm()
|
||||
f.SetCancelFunc(func() {
|
||||
pv.pages.RemovePage(PageDialog)
|
||||
})
|
||||
f.SetItemPadding(1)
|
||||
f.SetBorder(true)
|
||||
f.SetFieldBackgroundColor(tcell.ColorLightSkyBlue)
|
||||
f.SetFieldTextColor(tcell.ColorBlack)
|
||||
f.SetButtonsAlign(tview.AlignCenter)
|
||||
f.SetTitle("Process " + info.Name + " Info")
|
||||
addStringIfNotEmpty("Command:", info.Command, f)
|
||||
addStringIfNotEmpty("Working Directory:", info.WorkingDir, f)
|
||||
addStringIfNotEmpty("Log Location:", info.LogLocation, f)
|
||||
addSliceIfNotEmpty("Environment:", info.Environment, f)
|
||||
addSliceIfNotEmpty("Depends On:", mapKeysToSlice(info.DependsOn), f)
|
||||
f.AddCheckbox("Is Disabled:", info.Disabled, nil)
|
||||
f.AddCheckbox("Is Daemon:", info.IsDaemon, nil)
|
||||
f.AddButton("Close", func() {
|
||||
pv.pages.RemovePage(PageDialog)
|
||||
})
|
||||
f.SetFocus(f.GetFormItemCount())
|
||||
pv.pages.AddPage(PageDialog, createPage(f, 0, 0), true, true)
|
||||
func (pv *pcView) showDialog(primitive tview.Primitive) {
|
||||
pv.pages.AddPage(PageDialog, createDialogPage(primitive, 0, 0), true, true)
|
||||
pv.appView.SetFocus(primitive)
|
||||
}
|
||||
|
||||
func createPage(p tview.Primitive, width, height int) tview.Primitive {
|
||||
func createDialogPage(p tview.Primitive, width, height int) tview.Primitive {
|
||||
return tview.NewGrid().
|
||||
SetColumns(0, width, 0).
|
||||
SetRows(0, height, 0).
|
||||
AddItem(p, 1, 1, 1, 1, 0, 0, true)
|
||||
}
|
||||
|
||||
func addStringIfNotEmpty(label, value string, f *tview.Form) {
|
||||
if len(strings.TrimSpace(value)) > 0 {
|
||||
f.AddInputField(label, value, 0, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func addSliceIfNotEmpty(label string, value []string, f *tview.Form) {
|
||||
if len(value) > 0 {
|
||||
f.AddDropDown(label, value, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// mapKeysToSlice extract keys of map as slice,
|
||||
func mapKeysToSlice[K comparable, V any](m map[K]V) []K {
|
||||
keys := make([]K, len(m))
|
||||
|
||||
i := 0
|
||||
for k := range m {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
@ -22,9 +22,12 @@ func NewLogView(maxLines int) *LogView {
|
||||
|
||||
l := &LogView{
|
||||
isWrapOn: true,
|
||||
TextView: *tview.NewTextView().SetDynamicColors(true).SetScrollable(true).SetMaxLines(maxLines),
|
||||
buffer: &strings.Builder{},
|
||||
useAnsi: false,
|
||||
TextView: *tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(true).
|
||||
SetMaxLines(maxLines),
|
||||
buffer: &strings.Builder{},
|
||||
useAnsi: false,
|
||||
}
|
||||
l.ansiWriter = tview.ANSIWriter(l)
|
||||
l.SetBorder(true)
|
||||
|
58
src/tui/proc-info-form.go
Normal file
58
src/tui/proc-info-form.go
Normal file
@ -0,0 +1,58 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/f1bonacc1/process-compose/src/app"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (pv *pcView) createProcInfoForm(info *app.ProcessConfig) *tview.Form {
|
||||
f := tview.NewForm()
|
||||
f.SetCancelFunc(func() {
|
||||
pv.pages.RemovePage(PageDialog)
|
||||
})
|
||||
f.SetItemPadding(1)
|
||||
f.SetBorder(true)
|
||||
f.SetFieldBackgroundColor(tcell.ColorLightSkyBlue)
|
||||
f.SetFieldTextColor(tcell.ColorBlack)
|
||||
f.SetButtonsAlign(tview.AlignCenter)
|
||||
f.SetTitle("Process " + info.Name + " Info")
|
||||
addStringIfNotEmpty("Command:", info.Command, f)
|
||||
addStringIfNotEmpty("Working Directory:", info.WorkingDir, f)
|
||||
addStringIfNotEmpty("Log Location:", info.LogLocation, f)
|
||||
addSliceIfNotEmpty("Environment:", info.Environment, f)
|
||||
addSliceIfNotEmpty("Depends On:", mapKeysToSlice(info.DependsOn), f)
|
||||
f.AddCheckbox("Is Disabled:", info.Disabled, nil)
|
||||
f.AddCheckbox("Is Daemon:", info.IsDaemon, nil)
|
||||
f.AddButton("Close", func() {
|
||||
pv.pages.RemovePage(PageDialog)
|
||||
})
|
||||
|
||||
f.SetFocus(f.GetFormItemCount())
|
||||
return f
|
||||
}
|
||||
|
||||
func addStringIfNotEmpty(label, value string, f *tview.Form) {
|
||||
if len(strings.TrimSpace(value)) > 0 {
|
||||
f.AddInputField(label, value, 0, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func addSliceIfNotEmpty(label string, value []string, f *tview.Form) {
|
||||
if len(value) > 0 {
|
||||
f.AddDropDown(label, value, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// mapKeysToSlice extract keys of map as slice,
|
||||
func mapKeysToSlice[K comparable, V any](m map[K]V) []K {
|
||||
keys := make([]K, len(m))
|
||||
|
||||
i := 0
|
||||
for k := range m {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
@ -2,6 +2,7 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/f1bonacc1/glippy"
|
||||
"github.com/f1bonacc1/process-compose/src/config"
|
||||
"github.com/f1bonacc1/process-compose/src/updater"
|
||||
"os"
|
||||
@ -43,10 +44,13 @@ type pcView struct {
|
||||
pages *tview.Pages
|
||||
procNames []string
|
||||
logFollow bool
|
||||
logSelect bool
|
||||
fullScrState FullScrState
|
||||
loggedProc string
|
||||
shortcuts ShortCuts
|
||||
procCountCell *tview.TableCell
|
||||
mainGrid *tview.Grid
|
||||
logsTextArea *tview.TextArea
|
||||
}
|
||||
|
||||
func newPcView(logLength int) *pcView {
|
||||
@ -62,13 +66,29 @@ func newPcView(logLength int) *pcView {
|
||||
loggedProc: "",
|
||||
shortcuts: getDefaultActions(),
|
||||
procCountCell: tview.NewTableCell(""),
|
||||
mainGrid: tview.NewGrid(),
|
||||
logsTextArea: tview.NewTextArea(),
|
||||
logSelect: false,
|
||||
}
|
||||
pv.loadShortcuts()
|
||||
pv.procTable = pv.createProcTable()
|
||||
pv.statTable = pv.createStatTable()
|
||||
pv.updateHelpTextView()
|
||||
pv.createGrid()
|
||||
pv.logsTextArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Key() {
|
||||
case tcell.KeyCR:
|
||||
text, start, _ := pv.logsTextArea.GetSelection()
|
||||
glippy.Set(text)
|
||||
pv.logsTextArea.Select(start, start)
|
||||
case tcell.KeyEsc:
|
||||
pv.toggleLogSelection()
|
||||
pv.updateHelpTextView()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
pv.pages = tview.NewPages().
|
||||
AddPage(PageMain, pv.createGrid(pv.fullScrState), true, true)
|
||||
AddPage(PageMain, pv.mainGrid, true, true)
|
||||
|
||||
pv.appView.SetRoot(pv.pages, true).EnableMouse(true).SetInputCapture(pv.onAppKey)
|
||||
if len(pv.procNames) > 0 {
|
||||
@ -97,20 +117,25 @@ func (pv *pcView) onAppKey(event *tcell.EventKey) *tcell.EventKey {
|
||||
} else {
|
||||
pv.fullScrState = LogFull
|
||||
}
|
||||
pv.appView.SetRoot(pv.createGrid(pv.fullScrState), true)
|
||||
pv.redrawGrid()
|
||||
pv.updateHelpTextView()
|
||||
case pv.shortcuts.ShortCutKeys[ActionFollowLog].key:
|
||||
pv.toggleLogFollow()
|
||||
case pv.shortcuts.ShortCutKeys[ActionWrapLog].key:
|
||||
pv.logsText.ToggleWrap()
|
||||
pv.updateHelpTextView()
|
||||
case pv.shortcuts.ShortCutKeys[ActionLogSelection].key:
|
||||
pv.stopFollowLog()
|
||||
pv.toggleLogSelection()
|
||||
pv.appView.SetFocus(pv.logsTextArea)
|
||||
pv.updateHelpTextView()
|
||||
case pv.shortcuts.ShortCutKeys[ActionProcessScreen].key:
|
||||
if pv.fullScrState == ProcFull {
|
||||
pv.fullScrState = LogProcHalf
|
||||
} else {
|
||||
pv.fullScrState = ProcFull
|
||||
}
|
||||
pv.appView.SetRoot(pv.createGrid(pv.fullScrState), true)
|
||||
pv.redrawGrid()
|
||||
pv.onProcRowSpanChange()
|
||||
pv.updateHelpTextView()
|
||||
case tcell.KeyCtrlC:
|
||||
@ -136,7 +161,7 @@ func (pv *pcView) terminateAppView() {
|
||||
pv.pages.RemovePage(PageDialog)
|
||||
})
|
||||
// Display and focus the dialog
|
||||
pv.pages.AddPage(PageDialog, createPage(m, 50, 50), true, true)
|
||||
pv.pages.AddPage(PageDialog, createDialogPage(m, 50, 50), true, true)
|
||||
}
|
||||
|
||||
func (pv *pcView) showError(errMessage string) {
|
||||
@ -148,7 +173,7 @@ func (pv *pcView) showError(errMessage string) {
|
||||
pv.pages.RemovePage(PageDialog)
|
||||
})
|
||||
|
||||
pv.pages.AddPage(PageDialog, createPage(m, 50, 50), true, true)
|
||||
pv.pages.AddPage(PageDialog, createDialogPage(m, 50, 50), true, true)
|
||||
}
|
||||
|
||||
func (pv *pcView) showInfo() {
|
||||
@ -158,7 +183,22 @@ func (pv *pcView) showInfo() {
|
||||
pv.showError(err.Error())
|
||||
return
|
||||
}
|
||||
pv.showProcInfo(info)
|
||||
form := pv.createProcInfoForm(info)
|
||||
pv.showDialog(form)
|
||||
}
|
||||
|
||||
func (pv *pcView) toggleLogSelection() {
|
||||
name := pv.getSelectedProcName()
|
||||
pv.logSelect = !pv.logSelect
|
||||
if pv.logSelect {
|
||||
pv.logsTextArea.SetText(pv.logsText.GetText(true), true).
|
||||
SetBorder(true).
|
||||
SetTitle(name + " [Select & Press Enter to Copy]")
|
||||
} else {
|
||||
pv.logsTextArea.SetText("", false)
|
||||
}
|
||||
|
||||
pv.redrawGrid()
|
||||
}
|
||||
|
||||
func (pv *pcView) handleShutDown() {
|
||||
@ -347,6 +387,7 @@ func (pv *pcView) updateHelpTextView() {
|
||||
pv.shortcuts.ShortCutKeys[ActionLogScreen].writeToggleButton(pv.helpText, logScrBool)
|
||||
pv.shortcuts.ShortCutKeys[ActionFollowLog].writeToggleButton(pv.helpText, !pv.logFollow)
|
||||
pv.shortcuts.ShortCutKeys[ActionWrapLog].writeToggleButton(pv.helpText, !pv.logsText.IsWrapOn())
|
||||
pv.shortcuts.ShortCutKeys[ActionLogSelection].writeToggleButton(pv.helpText, !pv.logSelect)
|
||||
fmt.Fprintf(pv.helpText, "%s ", "[lightskyblue::b]PROCESS:[-:-:-]")
|
||||
pv.shortcuts.ShortCutKeys[ActionProcessInfo].writeButton(pv.helpText)
|
||||
pv.shortcuts.ShortCutKeys[ActionProcessStart].writeButton(pv.helpText)
|
||||
@ -356,27 +397,38 @@ func (pv *pcView) updateHelpTextView() {
|
||||
pv.shortcuts.ShortCutKeys[ActionQuit].writeButton(pv.helpText)
|
||||
}
|
||||
|
||||
func (pv *pcView) createGrid(fullScrState FullScrState) *tview.Grid {
|
||||
|
||||
grid := tview.NewGrid().
|
||||
func (pv *pcView) createGrid() {
|
||||
pv.mainGrid.Clear().
|
||||
SetRows(3, 0, 0, 1).
|
||||
//SetColumns(30, 0, 30).
|
||||
SetBorders(true).
|
||||
AddItem(pv.statTable, 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(pv.helpText, 3, 0, 1, 1, 0, 0, false)
|
||||
|
||||
switch fullScrState {
|
||||
var log tview.Primitive
|
||||
if !pv.logSelect {
|
||||
log = pv.logsText
|
||||
} else {
|
||||
log = pv.logsTextArea
|
||||
}
|
||||
switch pv.fullScrState {
|
||||
case LogFull:
|
||||
grid.AddItem(pv.logsText, 1, 0, 2, 1, 0, 0, true)
|
||||
pv.mainGrid.AddItem(log, 1, 0, 2, 1, 0, 0, true)
|
||||
case ProcFull:
|
||||
grid.AddItem(pv.procTable, 1, 0, 2, 1, 0, 0, true)
|
||||
pv.mainGrid.AddItem(pv.procTable, 1, 0, 2, 1, 0, 0, true)
|
||||
case LogProcHalf:
|
||||
grid.AddItem(pv.procTable, 1, 0, 1, 1, 0, 0, true)
|
||||
grid.AddItem(pv.logsText, 2, 0, 1, 1, 0, 0, false)
|
||||
pv.mainGrid.AddItem(pv.procTable, 1, 0, 1, 1, 0, 0, true)
|
||||
pv.mainGrid.AddItem(log, 2, 0, 1, 1, 0, 0, false)
|
||||
}
|
||||
|
||||
grid.SetTitle("Process Compose")
|
||||
return grid
|
||||
pv.mainGrid.SetTitle("Process Compose")
|
||||
//pv.mainGrid = grid
|
||||
}
|
||||
|
||||
func (pv *pcView) redrawGrid() {
|
||||
go pv.appView.QueueUpdateDraw(func() {
|
||||
pv.createGrid()
|
||||
})
|
||||
}
|
||||
|
||||
func (pv *pcView) updateTable() {
|
||||
|
Loading…
Reference in New Issue
Block a user