feat: control systemd Docker daemon target

This commit is contained in:
proxyctl coordinator
2026-07-17 03:24:52 +08:00
parent 3003450dce
commit be3bbbbe04
11 changed files with 919 additions and 25 deletions
+42 -11
View File
@@ -26,14 +26,15 @@ type formState struct {
}
type Model struct {
application app.Module
snapshot app.Snapshot
page page
selected int
form formState
confirm bool
status string
applying bool
application app.Module
snapshot app.Snapshot
page page
selected int
form formState
confirm bool
targetConfirm *app.Intent
status string
applying bool
}
type targetOperationDone struct {
@@ -50,9 +51,18 @@ 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 {
if completed.result.Confirmation != "" {
intent := app.Intent{Kind: app.SetTargetDesired}
if m.targetConfirm != nil {
intent = *m.targetConfirm
}
m.targetConfirm = &intent
m.status = completed.result.Confirmation
} else if completed.result.Err != nil {
m.targetConfirm = nil
m.status = "\x1b[31mError: " + completed.result.Err.Error() + "\x1b[0m"
} else {
m.targetConfirm = nil
m.status = completed.result.Notice
}
return m, nil
@@ -74,6 +84,21 @@ func (m Model) Update(message tea.Msg) (tea.Model, tea.Cmd) {
}
func (m Model) updateMain(key tea.KeyMsg) (tea.Model, tea.Cmd) {
if m.targetConfirm != nil && !m.applying {
switch key.String() {
case "y":
intent := *m.targetConfirm
intent.Confirm = true
m.targetConfirm = nil
m.applying = true
m.status = ""
return m, func() tea.Msg { return targetOperationDone{result: m.application.Dispatch(intent)} }
case "n", "esc", "q":
m.targetConfirm = nil
m.status = ""
}
return m, nil
}
targets := m.availableTargets()
switch key.String() {
case "p":
@@ -93,8 +118,10 @@ func (m Model) updateMain(key tea.KeyMsg) (tea.Model, tea.Cmd) {
desired := selected.State != target.Enabled
m.applying = true
m.status = ""
intent := app.Intent{Kind: app.SetTargetDesired, TargetID: selected.ID, Enabled: desired}
m.targetConfirm = &intent
return m, func() tea.Msg {
return targetOperationDone{result: m.application.Dispatch(app.Intent{Kind: app.SetTargetDesired, TargetID: selected.ID, Enabled: desired})}
return targetOperationDone{result: m.application.Dispatch(intent)}
}
}
case "q", "esc", "ctrl+c":
@@ -288,7 +315,11 @@ func (m Model) mainView() string {
fmt.Fprintf(&b, "\nUnavailable %s: %s\n", item.Description.Name, item.AvailabilityEvidence)
}
}
b.WriteString("\nSpace: toggle ↑/k ↓/j: select p: manage Proxy Profiles q/Esc: quit\n")
if m.targetConfirm != nil && !m.applying {
b.WriteString("\ny: confirm n/Esc: cancel\n")
} else {
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)
}
+36
View File
@@ -49,6 +49,42 @@ func TestTargetPanelShowsOnlyAvailableTargetsAndAppliesAsynchronously(t *testing
}
}
func TestTargetRestartConfirmationCancelsWithoutConfirmedMutation(t *testing.T) {
initial := app.Snapshot{ActiveProfile: "office", Targets: []app.TargetSnapshot{{ID: "docker-daemon", Description: target.Description{Name: "Docker daemon"}, Available: true, State: target.Disabled}}}
module := &targetModule{snapshot: initial, result: app.Result{Snapshot: initial, Confirmation: "Docker will restart immediately and may disrupt running work."}}
model := NewModel(module)
updated, command := model.Update(tea.KeyMsg{Type: tea.KeySpace})
model = updated.(Model)
updated, _ = model.Update(command())
model = updated.(Model)
if !strings.Contains(model.View(), "restart immediately") || len(module.intents) != 1 || module.intents[0].Confirm {
t.Fatalf("warning = intents %#v\n%s", module.intents, model.View())
}
updated, command = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'n'}})
model = updated.(Model)
if command != nil || len(module.intents) != 1 || strings.Contains(model.View(), "restart immediately") {
t.Fatalf("cancel mutated = intents %#v\n%s", module.intents, model.View())
}
updated, command = model.Update(tea.KeyMsg{Type: tea.KeySpace})
model = updated.(Model)
updated, _ = model.Update(command())
model = updated.(Model)
module.result = app.Result{
Snapshot: app.Snapshot{ActiveProfile: "office", Targets: []app.TargetSnapshot{{ID: "docker-daemon", Description: target.Description{Name: "Docker daemon"}, Available: true, State: target.Enabled}}},
Notice: "Docker daemon Target is now Enabled",
}
updated, command = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'y'}})
model = updated.(Model)
if command == nil {
t.Fatal("confirmation did not start operation")
}
_ = command()
if len(module.intents) != 3 || !module.intents[2].Confirm {
t.Fatalf("confirmed intents = %#v", module.intents)
}
}
func TestUnavailableShellIsOutsideMainListWithDetectionExplanation(t *testing.T) {
module := &targetModule{snapshot: app.Snapshot{Targets: []app.TargetSnapshot{
{ID: "shell", Description: target.Description{Name: "Shell"}, Available: false, AvailabilityEvidence: "Shell Target requires Bash or Zsh; neither was detected"},