Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 60 additions & 6 deletions src/internal/app/actions_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/larkly/lazystack/internal/ui/routerdetail"
"github.com/larkly/lazystack/internal/ui/serverpicker"
"github.com/larkly/lazystack/internal/ui/subnetpicker"
"github.com/larkly/lazystack/internal/ui/volumepicker"
"github.com/larkly/lazystack/internal/ui/sgcreate"
"github.com/larkly/lazystack/internal/ui/sgrulecreate"
"github.com/larkly/lazystack/internal/ui/volumecreate"
Expand Down Expand Up @@ -71,8 +72,19 @@ func (m Model) openVolumeDeleteConfirm() (Model, tea.Cmd) {
}

func (m Model) openVolumeAttach() (Model, tea.Cmd) {
id := m.volumeDetail.SelectedVolumeID()
name := m.volumeDetail.SelectedVolumeName()
var id, name string
switch m.view {
case viewVolumeDetail:
id = m.volumeDetail.SelectedVolumeID()
name = m.volumeDetail.SelectedVolumeName()
case viewVolumeList:
if v := m.volumeList.SelectedVolume(); v != nil {
id, name = v.ID, v.Name
if name == "" {
name = id
}
}
}
if id == "" {
return m, nil
}
Expand All @@ -82,11 +94,22 @@ func (m Model) openVolumeAttach() (Model, tea.Cmd) {
}

func (m Model) openVolumeDetach() (Model, tea.Cmd) {
if m.view != viewVolumeDetail {
return m, nil
var id, name string
switch m.view {
case viewVolumeDetail:
id = m.volumeDetail.SelectedVolumeID()
name = m.volumeDetail.SelectedVolumeName()
case viewVolumeList:
if v := m.volumeList.SelectedVolume(); v != nil {
if v.AttachedServerID == "" {
return m, nil
}
id, name = v.ID, v.Name
if name == "" {
name = id
}
}
}
id := m.volumeDetail.SelectedVolumeID()
name := m.volumeDetail.SelectedVolumeName()
if id == "" {
return m, nil
}
Expand All @@ -98,6 +121,37 @@ func (m Model) openVolumeDetach() (Model, tea.Cmd) {
return m, nil
}

func (m Model) openServerVolumeAttach() (Model, tea.Cmd) {
if m.view != viewServerDetail {
return m, nil
}
serverID := m.serverDetail.ServerID()
serverName := m.serverDetail.ServerName()
if serverID == "" {
return m, nil
}
m.volumePicker = volumepicker.New(m.client.Compute, m.client.BlockStorage, serverID, serverName)
m.volumePicker.SetSize(m.width, m.height)
return m, m.volumePicker.Init()
}

func (m Model) openServerVolumeDetach() (Model, tea.Cmd) {
if m.view != viewServerDetail {
return m, nil
}
volID := m.serverDetail.SelectedVolumeID()
volName := m.serverDetail.SelectedVolumeName()
if volID == "" {
return m, nil
}
m.confirm = modal.NewConfirm("detach_volume", volID, volName)
m.confirm.Title = "Detach Volume"
m.confirm.Body = fmt.Sprintf("Are you sure you want to detach volume %q from server %q?", volName, m.serverDetail.ServerName())
m.confirm.SetSize(m.width, m.height)
m.activeModal = modalConfirm
return m, nil
}

// --- Floating IP actions ---

func (m Model) openFIPReleaseConfirm() (Model, tea.Cmd) {
Expand Down
33 changes: 32 additions & 1 deletion src/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/larkly/lazystack/internal/ui/sgcreate"
"github.com/larkly/lazystack/internal/ui/sgrulecreate"
"github.com/larkly/lazystack/internal/ui/serverpicker"
"github.com/larkly/lazystack/internal/ui/volumepicker"
"github.com/larkly/lazystack/internal/ui/serverdetail"
"github.com/larkly/lazystack/internal/ui/serverrename"
"github.com/larkly/lazystack/internal/ui/serverrebuild"
Expand Down Expand Up @@ -127,6 +128,7 @@ type Model struct {
consoleURL consoleurl.Model
fipPicker fippicker.Model
serverPicker serverpicker.Model
volumePicker volumepicker.Model
sgCreate sgcreate.Model
networkCreate networkcreate.Model
subnetCreate subnetcreate.Model
Expand Down Expand Up @@ -302,6 +304,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.consoleURL.SetSize(m.width, m.height)
m.fipPicker.SetSize(m.width, m.height)
m.serverPicker.SetSize(m.width, m.height)
m.volumePicker.SetSize(m.width, m.height)
m.sgCreate.SetSize(m.width, m.height)
m.sgRuleCreate.SetSize(m.width, m.height)
m.networkCreate.SetSize(m.width, m.height)
Expand Down Expand Up @@ -415,6 +418,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
}

// Volume picker modal intercepts all keys when active
if m.volumePicker.Active {
var cmd tea.Cmd
m.volumePicker, cmd = m.volumePicker.Update(msg)
return m, cmd
}

// Router create modal intercepts all keys when active
if m.routerCreate.Active {
var cmd tea.Cmd
Expand Down Expand Up @@ -549,6 +559,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.openActionLog()
}

// Volume attach/detach from server detail volumes pane
if m.view == viewServerDetail && m.serverDetail.FocusedOnVolumes() {
if key.Matches(msg, shared.Keys.Attach) {
return m.openServerVolumeAttach()
}
if key.Matches(msg, shared.Keys.Detach) {
return m.openServerVolumeDetach()
}
}

// Block all mutating actions on locked servers
if m.isSelectedServerLocked() && key.Matches(msg,
shared.Keys.Delete, shared.Keys.Reboot, shared.Keys.HardReboot,
Expand Down Expand Up @@ -615,7 +635,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}

// Volume list: Enter to open detail, ctrl+d to delete, ctrl+n to create
// Volume list: Enter to open detail, ctrl+d to delete, ctrl+n to create, ctrl+a attach, ctrl+t detach
if m.view == viewVolumeList {
if key.Matches(msg, shared.Keys.Enter) {
return m.openVolumeDetail()
Expand All @@ -626,6 +646,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if key.Matches(msg, shared.Keys.Create) {
return m.openVolumeCreate()
}
if key.Matches(msg, shared.Keys.Attach) {
return m.openVolumeAttach()
}
if key.Matches(msg, shared.Keys.Detach) {
return m.openVolumeDetach()
}
}

// Volume detail: ctrl+d delete, ctrl+a attach, ctrl+t detach
Expand Down Expand Up @@ -1162,6 +1188,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.serverPicker, cmd = m.serverPicker.Update(msg)
return m, tea.Batch(viewCmd, cmd)
}
if m.volumePicker.Active {
var cmd tea.Cmd
m.volumePicker, cmd = m.volumePicker.Update(msg)
return m, tea.Batch(viewCmd, cmd)
}
if m.routerCreate.Active {
var cmd tea.Cmd
m.routerCreate, cmd = m.routerCreate.Update(msg)
Expand Down
3 changes: 3 additions & 0 deletions src/internal/app/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ func (m Model) viewContent() string {
if m.serverPicker.Active {
return m.serverPicker.View()
}
if m.volumePicker.Active {
return m.volumePicker.View()
}
if m.routerCreate.Active {
return m.routerCreate.View()
}
Expand Down
30 changes: 29 additions & 1 deletion src/internal/ui/serverdetail/serverdetail.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,34 @@ func (m Model) ServerName() string {
return m.serverID
}

// SelectedVolumeID returns the volume ID under the cursor in the volumes pane.
func (m Model) SelectedVolumeID() string {
if m.server == nil || len(m.server.VolAttach) == 0 {
return ""
}
if m.volumeCursor >= 0 && m.volumeCursor < len(m.server.VolAttach) {
return m.server.VolAttach[m.volumeCursor].ID
}
return ""
}

// SelectedVolumeName returns the name of the volume under the cursor.
func (m Model) SelectedVolumeName() string {
id := m.SelectedVolumeID()
if id == "" {
return ""
}
if vol, ok := m.volumeInfo[id]; ok && vol.Name != "" {
return vol.Name
}
return id
}

// FocusedOnVolumes returns true when the volumes pane has focus.
func (m Model) FocusedOnVolumes() bool {
return m.focus == focusVolumes
}

// ServerStatus returns the current server status.
func (m Model) ServerStatus() string {
if m.server != nil {
Expand Down Expand Up @@ -1365,7 +1393,7 @@ func (m Model) Hints() string {
case focusInterfaces:
return "\u2191\u2193 scroll interfaces \u2022 " + base
case focusVolumes:
return "\u2191\u2193 select \u2022 enter detail \u2022 " + base
return "\u2191\u2193 select \u2022 enter detail \u2022 ^a attach \u2022 ^t detach \u2022 " + base
case focusConsole:
return "\u2191\u2193 scroll log \u2022 g top \u2022 G bottom \u2022 " + base
case focusActions:
Expand Down
2 changes: 1 addition & 1 deletion src/internal/ui/volumelist/volumelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,5 +665,5 @@ func (m *Model) applyHighlight() {

// Hints returns key hints.
func (m Model) Hints() string {
return "↑↓ navigate • enter detail • ^n create • ^d delete • R refresh • 1-5/←→ switch tab • ? help"
return "↑↓ navigate • enter detail • ^n create • ^d delete • ^a attach • ^t detach • R refresh • 1-5/←→ switch tab • ? help"
}
Loading
Loading