feat: safely control Shell Target

This commit is contained in:
proxyctl coordinator
2026-07-17 02:59:41 +08:00
parent 4e3f780eba
commit 12e857932b
15 changed files with 1552 additions and 16 deletions
+70 -2
View File
@@ -8,6 +8,7 @@ import (
"proxyctl/internal/app"
"proxyctl/internal/profile"
"proxyctl/internal/target"
)
type page int
@@ -32,6 +33,11 @@ type Model struct {
form formState
confirm bool
status string
applying bool
}
type targetOperationDone struct {
result app.Result
}
func NewModel(application app.Module) Model {
@@ -41,6 +47,16 @@ func NewModel(application app.Module) Model {
func (m Model) Init() tea.Cmd { return nil }
func (m Model) Update(message tea.Msg) (tea.Model, tea.Cmd) {
if completed, ok := message.(targetOperationDone); ok {
m.applying = false
m.snapshot = completed.result.Snapshot
if completed.result.Err != nil {
m.status = "\x1b[31mError: " + completed.result.Err.Error() + "\x1b[0m"
} else {
m.status = completed.result.Notice
}
return m, nil
}
key, ok := message.(tea.KeyMsg)
if !ok {
return m, nil
@@ -58,10 +74,29 @@ func (m Model) Update(message tea.Msg) (tea.Model, tea.Cmd) {
}
func (m Model) updateMain(key tea.KeyMsg) (tea.Model, tea.Cmd) {
targets := m.availableTargets()
switch key.String() {
case "p":
m.page = profilesPage
m.status = ""
case "up", "k":
if !m.applying && m.selected > 0 {
m.selected--
}
case "down", "j":
if !m.applying && m.selected+1 < len(targets) {
m.selected++
}
case " ":
if !m.applying && m.selected >= 0 && m.selected < len(targets) {
selected := targets[m.selected]
desired := selected.State != target.Enabled
m.applying = true
m.status = ""
return m, func() tea.Msg {
return targetOperationDone{result: m.application.Dispatch(app.Intent{Kind: app.SetTargetDesired, TargetID: selected.ID, Enabled: desired})}
}
}
case "q", "esc", "ctrl+c":
return m, tea.Quit
}
@@ -208,6 +243,16 @@ func (m Model) View() string {
}
}
func (m Model) availableTargets() []app.TargetSnapshot {
var result []app.TargetSnapshot
for _, item := range m.snapshot.Targets {
if item.Available {
result = append(result, item)
}
}
return result
}
func (m Model) mainView() string {
var b strings.Builder
b.WriteString("proxyctl — Target panel\n\n")
@@ -217,8 +262,31 @@ func (m Model) mainView() string {
} else {
fmt.Fprintf(&b, "Active Profile: %s\n", m.snapshot.ActiveProfile)
}
b.WriteString("\nNo Targets are available in this build.\n")
b.WriteString("\np: manage Proxy Profiles q/Esc: quit\n")
targets := m.availableTargets()
if len(targets) == 0 {
b.WriteString("\nNo Targets are available in this build.\n")
} else {
b.WriteString("\nTargets\n")
for i, item := range targets {
cursor := " "
if i == m.selected {
cursor = "> "
}
state := string(item.State)
if m.applying && i == m.selected {
state = "Applying…"
}
fmt.Fprintf(&b, "%s[%-9s] %s\n", cursor, state, item.Description.Name)
}
if m.selected >= 0 && m.selected < len(targets) {
selected := targets[m.selected]
fmt.Fprintf(&b, "\n%s\n%s\n", selected.Description.Summary, selected.Evidence)
}
}
b.WriteString("\nSpace: toggle ↑/k ↓/j: select p: manage Proxy Profiles q/Esc: quit\n")
if m.status != "" {
fmt.Fprintf(&b, "\n%s\n", m.status)
}
return b.String()
}