Cleanup pane, Pi session tools, review from the UI, project registry, needs panel acts
This commit is contained in:
157
tui/repair.go
157
tui/repair.go
@@ -7,10 +7,12 @@ package main
|
||||
// pass, and a screen that says otherwise is how an afternoon disappears.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -27,12 +29,22 @@ type checkResult struct {
|
||||
}
|
||||
|
||||
type repairMsg struct {
|
||||
sessions []sessionRow
|
||||
checks []checkResult
|
||||
workspaces []layoutRow
|
||||
runs int
|
||||
err error
|
||||
}
|
||||
|
||||
// sessionRow is one TUIOS session as the cleanup tab sees it.
|
||||
type sessionRow struct {
|
||||
name string
|
||||
panes int
|
||||
lastUsed time.Time
|
||||
attached bool
|
||||
protected bool // this project's own session, or nothing to clean
|
||||
}
|
||||
|
||||
type layoutRow struct {
|
||||
number int
|
||||
name string
|
||||
@@ -54,9 +66,15 @@ type repairModel struct {
|
||||
|
||||
showHelp bool
|
||||
width, height int
|
||||
|
||||
sessions []sessionRow
|
||||
sessAt int
|
||||
ticked map[string]bool
|
||||
confirmKill bool
|
||||
killNote string
|
||||
}
|
||||
|
||||
var repairTabs = []string{"health", "layout", "rebuild log"}
|
||||
var repairTabs = []string{"health", "layout", "rebuild log", "cleanup"}
|
||||
|
||||
func newRepairModel(c *paperclip.Client, folder string) repairModel {
|
||||
return repairModel{client: c, folder: folder}
|
||||
@@ -120,7 +138,8 @@ func (m repairModel) load() tea.Cmd {
|
||||
}
|
||||
|
||||
ws, layoutErr := layoutRows(session)
|
||||
res := repairMsg{checks: checks, workspaces: ws}
|
||||
sess := listSessions(session)
|
||||
res := repairMsg{checks: checks, workspaces: ws, sessions: sess}
|
||||
if layoutErr != nil {
|
||||
res.err = layoutErr
|
||||
}
|
||||
@@ -197,6 +216,30 @@ func (m repairModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Batch(m.load(), tickEvery(8*time.Second), tea.WindowSize())
|
||||
|
||||
case tea.KeyMsg:
|
||||
if m.confirmKill {
|
||||
switch msg.String() {
|
||||
case "y":
|
||||
m.confirmKill = false
|
||||
killed := 0
|
||||
for _, row := range m.sessions {
|
||||
if !m.ticked[row.name] || row.protected || row.attached {
|
||||
continue
|
||||
}
|
||||
out, err := exec.Command("tuios", "kill-session", row.name).CombinedOutput()
|
||||
if err != nil {
|
||||
m.err = fmt.Errorf("could not kill %q: %v %s", row.name, err, strings.TrimSpace(string(out)))
|
||||
continue
|
||||
}
|
||||
killed++
|
||||
}
|
||||
m.ticked = map[string]bool{}
|
||||
m.killNote = fmt.Sprintf("%d session(s) closed", killed)
|
||||
return m, m.load()
|
||||
case "esc", "n", "q":
|
||||
m.confirmKill = false
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
if m.showHelp {
|
||||
m.showHelp = false
|
||||
return m, nil
|
||||
@@ -226,14 +269,33 @@ func (m repairModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.err = nil
|
||||
m.tab = 1
|
||||
return m, m.load()
|
||||
case "[", "h":
|
||||
case "(", "[", "h":
|
||||
m.tab = (m.tab + len(repairTabs) - 1) % len(repairTabs)
|
||||
m.scroll = 0
|
||||
return m, nil
|
||||
case "]", "l", "tab":
|
||||
case ")", "]", "l", "tab":
|
||||
m.tab = (m.tab + 1) % len(repairTabs)
|
||||
m.scroll = 0
|
||||
return m, nil
|
||||
case " ":
|
||||
if m.tab == 3 && len(m.sessions) > 0 {
|
||||
row := m.sessions[max(0, min(m.sessAt, len(m.sessions)-1))]
|
||||
if row.protected || row.attached {
|
||||
m.killNote = row.name + " is attached or is this project's session; it is not touched"
|
||||
return m, nil
|
||||
}
|
||||
if m.ticked == nil {
|
||||
m.ticked = map[string]bool{}
|
||||
}
|
||||
m.ticked[row.name] = !m.ticked[row.name]
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
case "x":
|
||||
if m.tab == 3 && len(m.ticked) > 0 {
|
||||
m.confirmKill = true
|
||||
}
|
||||
return m, nil
|
||||
case "up", "k":
|
||||
m.scroll--
|
||||
return m, nil
|
||||
@@ -245,6 +307,9 @@ func (m repairModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
case repairMsg:
|
||||
if msg.sessions != nil {
|
||||
m.sessions = msg.sessions
|
||||
}
|
||||
m.checks = msg.checks
|
||||
m.workspaces = msg.workspaces
|
||||
m.err = msg.err
|
||||
@@ -287,6 +352,8 @@ func (m repairModel) View() string {
|
||||
body = m.layoutLines()
|
||||
case 2:
|
||||
body = m.logLines()
|
||||
case 3:
|
||||
body = m.cleanupLines(m.width - 3)
|
||||
}
|
||||
|
||||
p := panel{title: "checks", width: m.width, height: m.height - 2, tabs: repairTabs, active: m.tab, focused: true}
|
||||
@@ -302,7 +369,7 @@ func (m repairModel) View() string {
|
||||
to := min(len(body), from+visible)
|
||||
|
||||
footer := hintLine([]string{
|
||||
keyHint("[ ]", "tab"),
|
||||
keyHint("( )", "tab"),
|
||||
keyHint("R", "rebuild"),
|
||||
keyHint("↑↓", "scroll"),
|
||||
keyHint("r", "recheck"),
|
||||
@@ -378,3 +445,83 @@ func (m repairModel) layoutLines() []string {
|
||||
out = append(out, dimStyle().Render(" so every panel is found by name, never by number."))
|
||||
return out
|
||||
}
|
||||
|
||||
// listSessions reads every TUIOS session on this machine, newest activity first.
|
||||
// It never reads or touches the Pi conversation files: those are separate and
|
||||
// are the person's history.
|
||||
func listSessions(current string) []sessionRow {
|
||||
out, err := exec.Command("tuios", "ls", "--all-hosts", "--json").Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var doc struct {
|
||||
Hosts []struct {
|
||||
Host string `json:"host"`
|
||||
Sessions []struct {
|
||||
Name string `json:"name"`
|
||||
Windows int `json:"window_count"`
|
||||
LastActive int64 `json:"last_active"`
|
||||
Attached bool `json:"attached"`
|
||||
} `json:"sessions"`
|
||||
} `json:"hosts"`
|
||||
}
|
||||
if json.Unmarshal(out, &doc) != nil {
|
||||
return nil
|
||||
}
|
||||
var rows []sessionRow
|
||||
for _, host := range doc.Hosts {
|
||||
for _, s := range host.Sessions {
|
||||
// A session on another machine is listed but never touched from here.
|
||||
row := sessionRow{
|
||||
name: s.Name,
|
||||
panes: s.Windows,
|
||||
lastUsed: time.Unix(s.LastActive, 0),
|
||||
attached: s.Attached || host.Host != "local",
|
||||
}
|
||||
if s.Name == current {
|
||||
row.protected = true
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
sort.Slice(rows, func(i, j int) bool { return rows[i].lastUsed.After(rows[j].lastUsed) })
|
||||
return rows
|
||||
}
|
||||
|
||||
// cleanupLines lists TUIOS sessions, marks what is protected, and lets a person
|
||||
// tick the ones to close. It never touches ~/.pi/paperclips: closing a TUIOS
|
||||
// session ends panes, and the Pi conversations are files that stay.
|
||||
func (m repairModel) cleanupLines(width int) []string {
|
||||
out := []string{"", dimStyle().Render(fmt.Sprintf(" %-34s %6s %-12s %s", "session", "panes", "last used", "state"))}
|
||||
for i, r := range m.sessions {
|
||||
state := ""
|
||||
switch {
|
||||
case r.protected:
|
||||
state = "this project's session"
|
||||
case r.attached:
|
||||
state = "attached, or on another machine"
|
||||
case m.ticked[r.name]:
|
||||
state = "ticked for closing"
|
||||
}
|
||||
line := fmt.Sprintf(" %-34s %6d %-12s %s", truncateVisible(r.name, 34), r.panes,
|
||||
r.lastUsed.Format("2 Jan 15:04"), state)
|
||||
mark := " "
|
||||
if m.ticked[r.name] {
|
||||
mark = "▸ "
|
||||
}
|
||||
if i == m.sessAt {
|
||||
out = append(out, selectedStyle2().Render(padTo(mark+line, width)))
|
||||
continue
|
||||
}
|
||||
out = append(out, mark+line)
|
||||
}
|
||||
out = append(out, "")
|
||||
if m.killNote != "" {
|
||||
out = append(out, " "+lipgloss.NewStyle().Foreground(lipgloss.Color(active.Warning)).Render(m.killNote))
|
||||
out = append(out, "")
|
||||
}
|
||||
out = append(out, dimStyle().Render(" space ticks a session, x closes the ticked ones after asking."))
|
||||
out = append(out, dimStyle().Render(" closing a TUIOS session ends its panes. It does not delete anything in"))
|
||||
out = append(out, dimStyle().Render(" ~/.pi/paperclips, where every Pi conversation is kept."))
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user