项目文件夹

文件
wehub-resource-sync 36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:06 +08:00

386 行
8.8 KiB
Go

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
package tui
import (
"fmt"
"os"
"time"
"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/pranshuparmar/witr/pkg/model"
)
var (
baseStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(colorBorderDim)
titleStyle = lipgloss.NewStyle().
Bold(true).
Foreground(colorBrandFg).
Background(colorBrandBg).
Padding(0, 1)
tableHeaderStyle = lipgloss.NewStyle().
Foreground(colorAccent).
Bold(true).
Border(lipgloss.NormalBorder(), false, false, true, false).
BorderForeground(colorBorderDim).
Padding(0, 1)
promptStyle = lipgloss.NewStyle().
Foreground(colorAccent).
Bold(true)
footerStyle = lipgloss.NewStyle().
Foreground(colorMuted).
Border(lipgloss.NormalBorder(), true, false, false, false).
BorderForeground(colorBorderDim).
Padding(0, 1).
Width(100)
activeTabStyle = lipgloss.NewStyle().
Foreground(colorOnAccent).
Background(colorGreenBg).
Padding(0, 1).
Bold(true)
inactiveTabStyle = lipgloss.NewStyle().
Foreground(colorOnAccent).
Background(colorIdleTabBg).
Padding(0, 1)
errorStyle = lipgloss.NewStyle().
Foreground(colorError).
Bold(true)
actionMenuStyle = lipgloss.NewStyle().
Foreground(colorAmber).
Bold(true)
confirmStyle = lipgloss.NewStyle().
Foreground(colorConfirm).
Bold(true)
pidStyle = lipgloss.NewStyle().
Background(colorGreenBg).
Foreground(colorOnAccent).
Padding(0, 1).
Bold(true)
spacerStyle = lipgloss.NewStyle().Height(1)
paddedStyle = lipgloss.NewStyle().PaddingLeft(1)
statusBarStyle = lipgloss.NewStyle().MarginBottom(1).PaddingLeft(1)
paneDividerStyle = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, false, true).
PaddingLeft(2)
detailDividerStyle = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, false, true).
PaddingLeft(1)
envPanelStyle = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, false, true).
PaddingLeft(1)
// Cached table styles with customized selection colors
cachedTableStyles = func() table.Styles {
s := table.DefaultStyles()
s.Selected = s.Selected.
Foreground(colorSelectFg).
Background(colorSelectBg).
Bold(false)
return s
}()
)
type tab int
const (
tabProcesses tab = iota
tabPorts
tabContainers
tabLocks
)
type modelState int
const (
stateList modelState = iota
stateDetail
)
type focusState int
const (
focusDetail focusState = iota
focusEnv
focusMain
focusSide
)
type actionKind int
const (
actionNone actionKind = iota
actionKill // SIGKILL
actionTerm // SIGTERM
actionPause // SIGSTOP
actionResume // SIGCONT
actionRenice // setpriority
)
type MainModel struct {
state modelState
table table.Model
input textinput.Model
viewport viewport.Model
treeViewport viewport.Model
envViewport viewport.Model
processes []model.Process
filtered []model.Process
selectedDetail *model.Result
detailFocus focusState
listFocus focusState
activeTab tab
portTable table.Model
portDetailTable table.Model
portInput textinput.Model
ports []model.OpenPort
containerTable table.Model
containerInput textinput.Model
containers []*model.ContainerMatch
filteredContainers []*model.ContainerMatch
selectedContainer *model.ContainerMatch
lockTable table.Model
lockInput textinput.Model
locks []*model.LockedFile
filteredLocks []*model.LockedFile
statusMsg string // transient status/error message shown in status line
width int
height int
quitting bool
selectionID int
sortCol string
sortDesc bool
sortPortCol string
sortPortDesc bool
sortContainerCol string
sortContainerDesc bool
sortLockCol string
sortLockDesc bool
showAllPorts bool
showAllFiles bool
showCmdCol bool
version string
// Mouse double-click tracking
lastClickTime time.Time
lastClickX int
lastClickY int
// Adaptive auto-refresh: refreshEvery is the current cadence (it adapts to
// how long refreshes take); lastRefresh gates the next one; refreshStartedAt
// marks an in-flight refresh so its duration can be measured and two don't
// overlap.
refreshEvery time.Duration
lastRefresh time.Time
refreshStartedAt time.Time
slowStreak int
fastStreak int
// Ancestry navigation in the side panel
treePIDs []int
treeCursor int
treeResult *model.Result
treeAncestry []model.Process
treeTargetPID int
// Process action state
actionMenuOpen bool
pendingAction actionKind
reniceInput textinput.Model
}
func InitialModel(version string) MainModel {
columns := []table.Column{
{Title: "PID", Width: 8},
{Title: "User", Width: 12},
{Title: "Name", Width: 20},
{Title: "CPU%", Width: 6},
{Title: "Mem", Width: 16},
{Title: "Started", Width: 19},
{Title: "Command", Width: 50},
}
t := table.New(
table.WithColumns(columns),
table.WithFocused(true),
table.WithHeight(20),
)
s := cachedTableStyles
s.Header = tableHeaderStyle.BorderForeground(colorBorderDim)
t.SetStyles(s)
portColumns := []table.Column{
{Title: centerHeader("Port", 6), Width: 6},
{Title: "Protocol", Width: 10},
{Title: "Address", Width: 30},
{Title: "State", Width: 20},
}
pt := table.New(
table.WithColumns(portColumns),
table.WithFocused(true),
table.WithHeight(20),
)
pt.SetStyles(s)
pdCols := []table.Column{
{Title: centerHeader("PID", 8), Width: 8},
{Title: "User", Width: 12},
{Title: "Name", Width: 15},
{Title: "Command", Width: 20},
}
pdt := table.New(
table.WithColumns(pdCols),
table.WithFocused(false),
table.WithHeight(20),
)
pdt.SetStyles(s)
containerColumns := []table.Column{
{Title: "ID", Width: 14},
{Title: "Name", Width: 22},
{Title: "Runtime", Width: 10},
{Title: "Image", Width: 28},
{Title: "Status", Width: 22},
{Title: "Ports", Width: 24},
{Title: "Command", Width: 28},
}
ct := table.New(
table.WithColumns(containerColumns),
table.WithFocused(true),
table.WithHeight(20),
)
ct.SetStyles(s)
lockColumns := []table.Column{
{Title: centerHeader("PID", 8), Width: 8},
{Title: "Process", Width: 18},
{Title: "Type", Width: 8},
{Title: "Mode", Width: 8},
{Title: "Path", Width: 50},
}
lt := table.New(
table.WithColumns(lockColumns),
table.WithFocused(true),
table.WithHeight(20),
)
lt.SetStyles(s)
li := textinput.New()
li.Placeholder = "Search PID, Process, Type, Mode, Path..."
li.CharLimit = 156
li.Width = 50
li.Prompt = "> "
li.PromptStyle = promptStyle
li.Blur()
ci := textinput.New()
ci.Placeholder = "Search ID, Name, Runtime, Image, Status, Ports, Command..."
ci.CharLimit = 156
ci.Width = 50
ci.Prompt = "> "
ci.PromptStyle = promptStyle
ci.Blur()
ti := textinput.New()
ti.Placeholder = "Search PID, Name, User, Command..."
ti.CharLimit = 156
ti.Width = 50
ti.Prompt = "> "
ti.PromptStyle = promptStyle
ti.Blur()
pi := textinput.New()
pi.Placeholder = "Search Port, Protocol, Address, State..."
pi.CharLimit = 156
pi.Width = 50
pi.Prompt = "> "
pi.PromptStyle = promptStyle
pi.Blur()
vp := viewport.New(0, 0)
vp.YPosition = 0
tvp := viewport.New(0, 0)
tvp.YPosition = 0
evp := viewport.New(0, 0)
evp.YPosition = 0
ri := textinput.New()
ri.Placeholder = "20…19"
ri.CharLimit = 4
ri.Width = 8
ri.Blur()
return MainModel{
state: stateList,
table: t,
portTable: pt,
portDetailTable: pdt,
containerTable: ct,
containerInput: ci,
lockTable: lt,
lockInput: li,
input: ti,
portInput: pi,
viewport: vp,
treeViewport: tvp,
envViewport: evp,
reniceInput: ri,
detailFocus: focusDetail,
listFocus: focusMain,
activeTab: tabProcesses,
sortCol: "mem",
sortDesc: true,
sortPortCol: "port",
sortPortDesc: false,
sortContainerCol: "name",
sortContainerDesc: false,
sortLockCol: "pid",
sortLockDesc: false,
version: version,
refreshEvery: refreshInterval,
}
}
func Start(version string) error {
if os.Getenv("COLORTERM") == "" {
os.Setenv("COLORTERM", "truecolor") //nolint:errcheck
}
p := tea.NewProgram(InitialModel(version), tea.WithAltScreen())
if _, err := p.Run(); err != nil {
return fmt.Errorf("error running tui: %w", err)
}
return nil
}
func (m MainModel) Init() tea.Cmd {
return tea.Batch(
textinput.Blink,
m.refreshProcesses(),
waitTick(),
tea.EnableMouseCellMotion,
)
}