@@ -2,11 +2,14 @@ package instances
22
33import (
44 "fmt"
5+ "time"
56
67 tea "github.com/charmbracelet/bubbletea"
78 "github.com/hostodo/hostodo-cli/pkg/api"
89 "github.com/hostodo/hostodo-cli/pkg/auth"
910 "github.com/hostodo/hostodo-cli/pkg/config"
11+ "github.com/hostodo/hostodo-cli/pkg/deploy"
12+ "github.com/hostodo/hostodo-cli/pkg/resolver"
1013 "github.com/hostodo/hostodo-cli/pkg/ui"
1114 "github.com/spf13/cobra"
1215)
@@ -84,21 +87,18 @@ func runList(cmd *cobra.Command, args []string) {
8487
8588 // Display based on output format
8689 if instanceListJSON {
87- // JSON output
8890 output , err := ui .FormatInstancesJSON (instancesResp .Results )
8991 if err != nil {
9092 exitWithError ("Failed to format JSON: %v" , err )
9193 }
9294 fmt .Println (output )
9395 } else if instanceListSimple {
94- // Simple table output
95- fmt .Println () // Add spacing
96+ fmt .Println ()
9697 output := ui .FormatInstancesSimpleTable (instancesResp .Results )
9798 fmt .Println (output )
9899 fmt .Printf ("\n Total: %d instances\n " , instancesResp .Count )
99100 } else if instanceListDetails {
100- // Detailed output
101- fmt .Println () // Add spacing
101+ fmt .Println ()
102102 output := ui .FormatInstancesDetailedTable (instancesResp .Results )
103103 fmt .Println (output )
104104 fmt .Printf ("\n Total: %d instances\n " , instancesResp .Count )
@@ -109,9 +109,115 @@ func runList(cmd *cobra.Command, args []string) {
109109 if err != nil {
110110 exitWithError ("Failed to run interactive table: %v" , err )
111111 }
112- // Check if user requested SSH from detail view
113- if tm , ok := finalModel .(ui.TableModel ); ok && tm .SSHHostname != "" {
112+ tm , ok := finalModel .(ui.TableModel )
113+ if ! ok {
114+ return
115+ }
116+ // Handle actions requested from within the TUI
117+ if tm .SSHHostname != "" {
114118 RunSSH (SSHCmd , []string {tm .SSHHostname })
119+ return
120+ }
121+ if tm .APIAction != "" && tm .ActionInstance != nil {
122+ runTUIAction (client , tm )
123+ }
124+ }
125+ }
126+
127+ // runTUIAction executes an instance action that was triggered from the TUI detail view.
128+ func runTUIAction (client * api.Client , tm ui.TableModel ) {
129+ instance := tm .ActionInstance
130+ fmt .Println ()
131+
132+ switch tm .APIAction {
133+ case "start" :
134+ fmt .Printf ("Starting instance %s (%s)...\n " , instance .Hostname , instance .MainIP )
135+ if err := client .StartInstance (instance .InstanceID ); err != nil {
136+ exitWithError ("Failed to start instance: %v" , err )
137+ }
138+ fmt .Println ("✓ Start command sent" )
139+ fmt .Print ("\n Waiting for instance to start" )
140+ pollPower (client , instance .InstanceID , "running" , 30 )
141+
142+ case "stop" :
143+ fmt .Printf ("Stopping instance %s (%s)...\n " , instance .Hostname , instance .MainIP )
144+ if err := client .StopInstance (instance .InstanceID , false ); err != nil {
145+ exitWithError ("Failed to stop instance: %v" , err )
146+ }
147+ fmt .Println ("✓ Stop command sent" )
148+ fmt .Print ("\n Waiting for instance to stop" )
149+ pollPower (client , instance .InstanceID , "stopped" , 30 )
150+
151+ case "force-stop" :
152+ fmt .Printf ("Force stopping instance %s (%s)...\n " , instance .Hostname , instance .MainIP )
153+ if err := client .StopInstance (instance .InstanceID , true ); err != nil {
154+ exitWithError ("Failed to force stop instance: %v" , err )
155+ }
156+ fmt .Println ("✓ Force stop command sent" )
157+
158+ case "restart" :
159+ fmt .Printf ("Restarting instance %s (%s)...\n " , instance .Hostname , instance .MainIP )
160+ if err := client .RebootInstance (instance .InstanceID , false ); err != nil {
161+ exitWithError ("Failed to restart instance: %v" , err )
162+ }
163+ fmt .Println ("✓ Restart command sent" )
164+ fmt .Print ("\n Waiting for instance to restart" )
165+ pollRestart (client , instance .InstanceID , 60 )
166+
167+ case "force-restart" :
168+ fmt .Printf ("Force restarting instance %s (%s)...\n " , instance .Hostname , instance .MainIP )
169+ if err := client .RebootInstance (instance .InstanceID , true ); err != nil {
170+ exitWithError ("Failed to force restart instance: %v" , err )
171+ }
172+ fmt .Println ("✓ Force restart command sent" )
173+
174+ case "rename" :
175+ newHostname := tm .RenameTarget
176+ if err := deploy .Validate (newHostname ); err != nil {
177+ exitWithError ("Invalid hostname %q: %v" , newHostname , err )
178+ }
179+ fmt .Printf ("Renaming instance %s to %s...\n " , instance .Hostname , newHostname )
180+ if err := client .RenameInstance (instance .InstanceID , newHostname ); err != nil {
181+ exitWithError ("Failed to rename instance: %v" , err )
182+ }
183+ resolver .InvalidateCache ()
184+ fmt .Printf ("✓ Instance renamed to %s\n " , newHostname )
185+ }
186+ }
187+
188+ // pollPower polls until the instance reaches targetStatus or timeout.
189+ func pollPower (client * api.Client , instanceID , targetStatus string , seconds int ) {
190+ for i := 0 ; i < seconds ; i ++ {
191+ fmt .Print ("." )
192+ time .Sleep (1 * time .Second )
193+ status , err := client .GetInstancePowerStatus (instanceID )
194+ if err == nil && status == targetStatus {
195+ fmt .Println ()
196+ fmt .Printf ("✓ Instance is now %s\n " , targetStatus )
197+ return
198+ }
199+ }
200+ fmt .Println ()
201+ fmt .Println ("⚠ Operation in progress (this may take a few moments)" )
202+ }
203+
204+ // pollRestart polls for running state after a reboot, allowing for a stop transition.
205+ func pollRestart (client * api.Client , instanceID string , seconds int ) {
206+ sawStopped := false
207+ for i := 0 ; i < seconds ; i ++ {
208+ fmt .Print ("." )
209+ time .Sleep (1 * time .Second )
210+ status , err := client .GetInstancePowerStatus (instanceID )
211+ if err == nil {
212+ if status == "stopped" {
213+ sawStopped = true
214+ } else if status == "running" && (sawStopped || i >= 5 ) {
215+ fmt .Println ()
216+ fmt .Println ("✓ Instance has restarted and is now running" )
217+ return
218+ }
115219 }
116220 }
221+ fmt .Println ()
222+ fmt .Println ("⚠ Instance is restarting (this may take a few moments)" )
117223}
0 commit comments