Files
miasma-installer/tui/steps/disk_sel.go
tumillanino ee79d720ec
Some checks failed
Build / build (push) Failing after 4m56s
updated styling and installation steps
2025-10-31 22:59:56 +11:00

210 lines
4.6 KiB
Go

package steps
import (
"bufio"
"fmt"
"io"
"miasma-installer/tui/styles"
"os"
"os/exec"
"strconv"
"strings"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
)
type Disk struct {
name string
description string
size string
}
func (i Disk) FilterValue() string { return i.name }
func (i Disk) Title() string { return i.name }
func (i Disk) Description() string { return fmt.Sprintf("%s - %s", i.size, i.description) }
type ItemDelegate struct{}
func (i ItemDelegate) Height() int { return 1 }
func (i ItemDelegate) Spacing() int { return 0 }
func (i ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
disk, ok := item.(Disk)
if !ok {
return
}
s := fmt.Sprintf("%d. %s - %s", index+1, disk.Title(), disk.Description())
if index == m.Index() {
s = styles.ActiveItemStyle.Render("> " + s)
} else {
s = styles.InactiveItemStyle.Render(" " + s)
}
fmt.Fprint(w, s)
}
type DiskSelModel struct {
list list.Model
Finished bool
SelectedDisk string
loading bool
}
type disksLoadedMsg struct {
disks []list.Item
}
func NewDiskSelModel(width, height int) DiskSelModel {
l := list.New([]list.Item{}, ItemDelegate{}, width, height-5)
l.Title = "Select Installation Disk"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
l.Styles.Title = styles.TitleStyle
return DiskSelModel{
list: l,
loading: true,
}
}
func (m DiskSelModel) Init() tea.Cmd {
return loadDisks
}
func loadDisks() tea.Msg {
disks, err := scanDisks()
if err != nil {
return disksLoadedMsg{disks: []list.Item{
Disk{name: "error", description: err.Error(), size: ""},
}}
}
return disksLoadedMsg{disks: disks}
}
func scanDisks() ([]list.Item, error) {
cmd := exec.Command("lsblk", "-ndo", "NAME,SIZE,TYPE,MODEL")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to list disks: %w", err)
}
var disks []list.Item
scanner := bufio.NewScanner(strings.NewReader(string(output)))
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
name := fields[0]
size := fields[1]
diskType := fields[2]
model := ""
if len(fields) > 3 {
model = strings.Join(fields[3:], " ")
}
if diskType != "disk" {
continue
}
if strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "sr") {
continue
}
disks = append(disks, Disk{
name: "/dev/" + name,
size: size,
description: model,
})
}
if len(disks) == 0 {
return nil, fmt.Errorf("no suitable disks found")
}
return disks, nil
}
func (m DiskSelModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.Finished {
return m, nil
}
switch msg := msg.(type) {
case disksLoadedMsg:
m.loading = false
m.list.SetItems(msg.disks)
return m, nil
case tea.KeyMsg:
if !m.loading && msg.String() == "enter" {
if item, ok := m.list.SelectedItem().(Disk); ok {
m.SelectedDisk = item.name
m.Finished = true
return m, nil
}
}
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m DiskSelModel) View() string {
var content strings.Builder
if m.loading {
content.WriteString(styles.TitleStyle.Render("💿 Scanning Disks..."))
content.WriteString("\n\n")
content.WriteString(styles.SpinnerStyle.Render("● Detecting available storage devices..."))
return styles.AppStyle.Render(content.String())
}
content.WriteString(styles.TitleStyle.Render("💿 Select Installation Disk"))
content.WriteString("\n\n")
warning := "⚠️ All data on the selected disk will be erased!"
content.WriteString(styles.WarningBoxStyle.Render(warning))
content.WriteString("\n\n")
content.WriteString(m.list.View())
content.WriteString("\n\n")
content.WriteString(styles.RenderHelp("↑/↓", "Navigate", "Enter", "Select"))
return styles.AppStyle.Render(content.String())
}
func formatBytes(bytes uint64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := uint64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
func getDiskSize(device string) (string, error) {
data, err := os.ReadFile(fmt.Sprintf("/sys/block/%s/size", strings.TrimPrefix(device, "/dev/")))
if err != nil {
return "", err
}
sectors, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
if err != nil {
return "", err
}
bytes := sectors * 512
return formatBytes(bytes), nil
}