'more bubbletea, less look bad plz'
This commit is contained in:
58
app.go
58
app.go
@@ -132,9 +132,9 @@ func (a *App) chooseStartupWorkflow() (string, error) {
|
|||||||
"Use the client tools to add or maintain CIFS mounts in /etc/fstab.",
|
"Use the client tools to add or maintain CIFS mounts in /etc/fstab.",
|
||||||
},
|
},
|
||||||
[]menuOption{
|
[]menuOption{
|
||||||
{Key: "s", Value: "server", Label: "Set up or edit shares on this computer", Description: "Create, edit, and save Samba share definitions."},
|
{Key: "s", Value: "server", Label: "Server shares"},
|
||||||
{Key: "c", Value: "client", Label: "Connect this computer to a remote share", Description: "Manage CIFS client mounts and mount points."},
|
{Key: "c", Value: "client", Label: "Client mounts"},
|
||||||
{Key: "q", Value: "quit", Label: "Quit", Description: "Exit without changing anything."},
|
{Key: "q", Value: "quit", Label: "Quit"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -170,13 +170,13 @@ loaded:
|
|||||||
fmt.Sprintf("Working against %s", a.configPath),
|
fmt.Sprintf("Working against %s", a.configPath),
|
||||||
intro,
|
intro,
|
||||||
[]menuOption{
|
[]menuOption{
|
||||||
{Key: "a", Value: "add", Label: "Add share", Description: "Create a new share definition."},
|
{Key: "a", Value: "add", Label: "Add share"},
|
||||||
{Key: "e", Value: "edit", Label: "Edit share", Description: "Update an existing share."},
|
{Key: "e", Value: "edit", Label: "Edit share"},
|
||||||
{Key: "d", Value: "delete", Label: "Delete share", Description: "Remove a share definition."},
|
{Key: "d", Value: "delete", Label: "Delete share"},
|
||||||
{Key: "m", Value: "mount", Label: "Set up client mount", Description: "Jump to the remote-mount workflow."},
|
{Key: "m", Value: "mount", Label: "Client mounts"},
|
||||||
{Key: "u", Value: "users", Label: "Manage users", Description: "Check accounts, passwords, and cleanup."},
|
{Key: "u", Value: "users", Label: "Users"},
|
||||||
{Key: "w", Value: "write", Label: "Write config and exit", Description: "Save smb.conf and leave the app."},
|
{Key: "w", Value: "write", Label: "Write and exit"},
|
||||||
{Key: "q", Value: "quit", Label: "Quit without saving", Description: "Leave the editor immediately."},
|
{Key: "q", Value: "quit", Label: "Quit"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -239,10 +239,10 @@ func (a *App) setupClientMount() error {
|
|||||||
fmt.Sprintf("Editing %s", a.fstabPath),
|
fmt.Sprintf("Editing %s", a.fstabPath),
|
||||||
intro,
|
intro,
|
||||||
[]menuOption{
|
[]menuOption{
|
||||||
{Key: "a", Value: "add", Label: "Add client mount", Description: "Create a new CIFS mount entry."},
|
{Key: "a", Value: "add", Label: "Add mount"},
|
||||||
{Key: "e", Value: "edit", Label: "Edit client mount", Description: "Change an existing mount definition."},
|
{Key: "e", Value: "edit", Label: "Edit mount"},
|
||||||
{Key: "d", Value: "delete", Label: "Delete client mount", Description: "Remove a saved mount entry."},
|
{Key: "d", Value: "delete", Label: "Delete mount"},
|
||||||
{Key: "b", Value: "back", Label: "Back", Description: "Return to the previous menu."},
|
{Key: "b", Value: "back", Label: "Back"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -280,10 +280,10 @@ func (a *App) manageUsers(doc *Document) error {
|
|||||||
fmt.Sprintf("%d share accounts referenced in the current config.", len(shareUserReferences(doc))),
|
fmt.Sprintf("%d share accounts referenced in the current config.", len(shareUserReferences(doc))),
|
||||||
},
|
},
|
||||||
[]menuOption{
|
[]menuOption{
|
||||||
{Key: "c", Value: "check", Label: "Check share accounts", Description: "Create missing local users referenced by shares."},
|
{Key: "c", Value: "check", Label: "Check accounts"},
|
||||||
{Key: "p", Value: "password", Label: "Change a Samba password", Description: "Set or update a Samba credential."},
|
{Key: "p", Value: "password", Label: "Change password"},
|
||||||
{Key: "x", Value: "delete", Label: "Delete an unused account", Description: "Remove unused share-style accounts."},
|
{Key: "x", Value: "delete", Label: "Delete account"},
|
||||||
{Key: "b", Value: "back", Label: "Back", Description: "Return to the share editor."},
|
{Key: "b", Value: "back", Label: "Back"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -370,7 +370,7 @@ func (a *App) deleteUnusedAccount(doc *Document) error {
|
|||||||
Description: "shell: " + strings.TrimSpace(entry.Shell),
|
Description: "shell: " + strings.TrimSpace(entry.Shell),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
options = append(options, menuOption{Key: "b", Value: "", Label: "Back", Description: "Leave accounts unchanged."})
|
options = append(options, menuOption{Key: "b", Value: "", Label: "Back"})
|
||||||
|
|
||||||
choice, err := a.chooseMenu(
|
choice, err := a.chooseMenu(
|
||||||
"Delete An Unused Account",
|
"Delete An Unused Account",
|
||||||
@@ -435,7 +435,7 @@ func (a *App) changeSambaPassword(doc *Document) error {
|
|||||||
Description: "used by: " + strings.Join(ref.Shares, ", "),
|
Description: "used by: " + strings.Join(ref.Shares, ", "),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
options = append(options, menuOption{Key: "b", Value: "", Label: "Back", Description: "Return without changing a password."})
|
options = append(options, menuOption{Key: "b", Value: "", Label: "Back"})
|
||||||
|
|
||||||
choice, err := a.chooseMenu(
|
choice, err := a.chooseMenu(
|
||||||
"Choose An Account",
|
"Choose An Account",
|
||||||
@@ -667,7 +667,7 @@ func (a *App) collectCIFSMountConfig(defaults CIFSMountConfig) (CIFSMountConfig,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
share, err := a.promptDefault("Share name on that server", defaults.Share)
|
share, err := a.promptDefault("Share name", defaults.Share)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
@@ -679,23 +679,23 @@ func (a *App) collectCIFSMountConfig(defaults CIFSMountConfig) (CIFSMountConfig,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
username, err := a.promptDefault("Username for the remote share", defaults.Username)
|
username, err := a.promptDefault("Remote username", defaults.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
password, err := a.promptDefault("Password for the remote share", defaults.Password)
|
password, err := a.promptDefault("Remote password", defaults.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
domain, err := a.promptDefault("Domain or workgroup (optional)", defaults.Domain)
|
domain, err := a.promptDefault("Domain or workgroup", defaults.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
uid, err := a.promptDefault("Local owner username or uid (optional)", defaults.UID)
|
uid, err := a.promptDefault("Local owner username or uid", defaults.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
gid, err := a.promptDefault("Local group name or gid (optional)", defaults.GID)
|
gid, err := a.promptDefault("Local group name or gid", defaults.GID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CIFSMountConfig{}, err
|
return CIFSMountConfig{}, err
|
||||||
}
|
}
|
||||||
@@ -1143,8 +1143,8 @@ func (a *App) promptDefault(label, defaultValue string) (string, error) {
|
|||||||
|
|
||||||
func (a *App) confirm(label string, defaultYes bool) (bool, error) {
|
func (a *App) confirm(label string, defaultYes bool) (bool, error) {
|
||||||
options := []menuOption{
|
options := []menuOption{
|
||||||
{Key: "y", Value: "yes", Label: "Yes", Description: "Continue with this action."},
|
{Key: "y", Value: "yes", Label: "Yes"},
|
||||||
{Key: "n", Value: "no", Label: "No", Description: "Leave things as they are."},
|
{Key: "n", Value: "no", Label: "No"},
|
||||||
}
|
}
|
||||||
if !defaultYes {
|
if !defaultYes {
|
||||||
options[0], options[1] = options[1], options[0]
|
options[0], options[1] = options[1], options[0]
|
||||||
@@ -1681,7 +1681,7 @@ func (a *App) selectCIFSMount(entries []FstabMountEntry) (*FstabMountEntry, erro
|
|||||||
Description: entry.MountPoint,
|
Description: entry.MountPoint,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
options = append(options, menuOption{Key: "b", Value: "", Label: "Back", Description: "Return to the previous menu."})
|
options = append(options, menuOption{Key: "b", Value: "", Label: "Back"})
|
||||||
|
|
||||||
selection, err := a.chooseMenu("Choose A Client Mount", "Select the saved mount entry you want to change.", nil, options)
|
selection, err := a.chooseMenu("Choose A Client Mount", "Select the saved mount entry you want to change.", nil, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
BIN
samba-configer
BIN
samba-configer
Binary file not shown.
301
tui.go
301
tui.go
@@ -332,7 +332,7 @@ func (m menuModel) View() string {
|
|||||||
)
|
)
|
||||||
|
|
||||||
rowLines := []string{top}
|
rowLines := []string{top}
|
||||||
if option.Description != "" {
|
if option.Description != "" && width >= 72 {
|
||||||
rowLines = append(rowLines, hintStyle.Render(option.Description))
|
rowLines = append(rowLines, hintStyle.Render(option.Description))
|
||||||
}
|
}
|
||||||
rows = append(rows, rowStyle.Width(contentWidth).Render(strings.Join(rowLines, "\n")))
|
rows = append(rows, rowStyle.Width(contentWidth).Render(strings.Join(rowLines, "\n")))
|
||||||
@@ -445,7 +445,7 @@ func runMenuPrompt(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle
|
|||||||
options: options,
|
options: options,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out)).Run()
|
result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out), tea.WithAltScreen()).Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -460,7 +460,7 @@ func runMenuPrompt(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle
|
|||||||
func runTextPrompt(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle, label, defaultValue string) (string, error) {
|
func runTextPrompt(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle, label, defaultValue string) (string, error) {
|
||||||
model := newTextPromptModel(theme, title, subtitle, label, defaultValue)
|
model := newTextPromptModel(theme, title, subtitle, label, defaultValue)
|
||||||
|
|
||||||
result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out)).Run()
|
result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out), tea.WithAltScreen()).Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -471,3 +471,298 @@ func runTextPrompt(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle,
|
|||||||
}
|
}
|
||||||
return final.value, nil
|
return final.value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cifsFormTextField struct {
|
||||||
|
key string
|
||||||
|
label string
|
||||||
|
required bool
|
||||||
|
defaultValue string
|
||||||
|
input textinput.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
type cifsFormToggle struct {
|
||||||
|
label string
|
||||||
|
value bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type cifsMountFormModel struct {
|
||||||
|
theme tuiTheme
|
||||||
|
title string
|
||||||
|
subtitle string
|
||||||
|
help []string
|
||||||
|
fields []cifsFormTextField
|
||||||
|
toggles []cifsFormToggle
|
||||||
|
cursor int
|
||||||
|
submitted bool
|
||||||
|
cancel bool
|
||||||
|
width int
|
||||||
|
errMessage string
|
||||||
|
result CIFSMountConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCIFSMountFormModel(theme tuiTheme, title, subtitle string, defaults CIFSMountConfig) cifsMountFormModel {
|
||||||
|
makeInput := func(value string, password bool) textinput.Model {
|
||||||
|
input := textinput.New()
|
||||||
|
input.Prompt = ""
|
||||||
|
input.SetValue(value)
|
||||||
|
input.Focus()
|
||||||
|
input.CharLimit = 512
|
||||||
|
input.Width = 48
|
||||||
|
if password {
|
||||||
|
input.EchoMode = textinput.EchoPassword
|
||||||
|
input.EchoCharacter = '•'
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := []cifsFormTextField{
|
||||||
|
{key: "server", label: "Server name or IP", required: true, defaultValue: defaults.Server, input: makeInput(defaults.Server, false)},
|
||||||
|
{key: "share", label: "Share name on that server", required: true, defaultValue: defaults.Share, input: makeInput(defaults.Share, false)},
|
||||||
|
{key: "mount", label: "Local mount folder", required: true, defaultValue: defaults.MountPoint, input: makeInput(defaults.MountPoint, false)},
|
||||||
|
{key: "username", label: "Username for the remote share", required: true, defaultValue: defaults.Username, input: makeInput(defaults.Username, false)},
|
||||||
|
{key: "password", label: "Password for the remote share", required: true, defaultValue: defaults.Password, input: makeInput(defaults.Password, true)},
|
||||||
|
{key: "domain", label: "Domain or workgroup", defaultValue: defaults.Domain, input: makeInput(defaults.Domain, false)},
|
||||||
|
{key: "uid", label: "Local owner username or uid", defaultValue: defaults.UID, input: makeInput(defaults.UID, false)},
|
||||||
|
{key: "gid", label: "Local group name or gid", defaultValue: defaults.GID, input: makeInput(defaults.GID, false)},
|
||||||
|
{key: "filemode", label: "File permissions", defaultValue: defaultString(defaults.FileMode, "0664"), input: makeInput(defaultString(defaults.FileMode, "0664"), false)},
|
||||||
|
{key: "dirmode", label: "Folder permissions", defaultValue: defaultString(defaults.DirMode, "0775"), input: makeInput(defaultString(defaults.DirMode, "0775"), false)},
|
||||||
|
}
|
||||||
|
|
||||||
|
model := cifsMountFormModel{
|
||||||
|
theme: theme,
|
||||||
|
title: title,
|
||||||
|
subtitle: subtitle,
|
||||||
|
help: []string{
|
||||||
|
"Tab or arrows move between fields.",
|
||||||
|
"Enter toggles switches or submits when Save is selected.",
|
||||||
|
},
|
||||||
|
fields: fields,
|
||||||
|
toggles: []cifsFormToggle{
|
||||||
|
{label: "Mount automatically at startup", value: defaults.AutoMount || (defaults.Server == "" && defaults.Share == "" && defaults.MountPoint == "")},
|
||||||
|
{label: "Make this mount read-only", value: defaults.ReadOnly},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
model.focusCursor()
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *cifsMountFormModel) totalItems() int {
|
||||||
|
return len(m.fields) + len(m.toggles) + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *cifsMountFormModel) focusCursor() {
|
||||||
|
for i := range m.fields {
|
||||||
|
if i == m.cursor {
|
||||||
|
m.fields[i].input.Focus()
|
||||||
|
} else {
|
||||||
|
m.fields[i].input.Blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *cifsMountFormModel) moveCursor(delta int) {
|
||||||
|
total := m.totalItems()
|
||||||
|
if total == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.cursor = (m.cursor + delta + total) % total
|
||||||
|
m.focusCursor()
|
||||||
|
m.errMessage = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *cifsMountFormModel) currentConfig() CIFSMountConfig {
|
||||||
|
values := map[string]string{}
|
||||||
|
for _, field := range m.fields {
|
||||||
|
value := strings.TrimSpace(field.input.Value())
|
||||||
|
if value == "" {
|
||||||
|
value = strings.TrimSpace(field.defaultValue)
|
||||||
|
}
|
||||||
|
values[field.key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return CIFSMountConfig{
|
||||||
|
Server: values["server"],
|
||||||
|
Share: values["share"],
|
||||||
|
MountPoint: values["mount"],
|
||||||
|
Username: values["username"],
|
||||||
|
Password: values["password"],
|
||||||
|
Domain: values["domain"],
|
||||||
|
UID: values["uid"],
|
||||||
|
GID: values["gid"],
|
||||||
|
FileMode: values["filemode"],
|
||||||
|
DirMode: values["dirmode"],
|
||||||
|
AutoMount: m.toggles[0].value,
|
||||||
|
ReadOnly: m.toggles[1].value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *cifsMountFormModel) validate() string {
|
||||||
|
for _, field := range m.fields {
|
||||||
|
if !field.required {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := strings.TrimSpace(field.input.Value())
|
||||||
|
if value == "" {
|
||||||
|
value = strings.TrimSpace(field.defaultValue)
|
||||||
|
}
|
||||||
|
if value == "" {
|
||||||
|
return field.label + " is required."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m cifsMountFormModel) Init() tea.Cmd {
|
||||||
|
return textinput.Blink
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m cifsMountFormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width = msg.Width
|
||||||
|
available := innerWidth(clampFrameWidth(m.width)) - 8
|
||||||
|
if available < 16 {
|
||||||
|
available = 16
|
||||||
|
}
|
||||||
|
for i := range m.fields {
|
||||||
|
m.fields[i].input.Width = available
|
||||||
|
}
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "ctrl+c", "esc":
|
||||||
|
m.cancel = true
|
||||||
|
return m, tea.Quit
|
||||||
|
case "shift+tab", "up", "k":
|
||||||
|
m.moveCursor(-1)
|
||||||
|
return m, nil
|
||||||
|
case "tab", "down", "j":
|
||||||
|
m.moveCursor(1)
|
||||||
|
return m, nil
|
||||||
|
case "enter":
|
||||||
|
textCount := len(m.fields)
|
||||||
|
toggleStart := textCount
|
||||||
|
saveIndex := textCount + len(m.toggles)
|
||||||
|
cancelIndex := saveIndex + 1
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case m.cursor < textCount:
|
||||||
|
m.moveCursor(1)
|
||||||
|
return m, nil
|
||||||
|
case m.cursor >= toggleStart && m.cursor < saveIndex:
|
||||||
|
idx := m.cursor - toggleStart
|
||||||
|
m.toggles[idx].value = !m.toggles[idx].value
|
||||||
|
return m, nil
|
||||||
|
case m.cursor == saveIndex:
|
||||||
|
if errMessage := m.validate(); errMessage != "" {
|
||||||
|
m.errMessage = errMessage
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
m.result = m.currentConfig()
|
||||||
|
m.submitted = true
|
||||||
|
return m, tea.Quit
|
||||||
|
case m.cursor == cancelIndex:
|
||||||
|
m.cancel = true
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.cursor < len(m.fields) {
|
||||||
|
var cmd tea.Cmd
|
||||||
|
m.fields[m.cursor].input, cmd = m.fields[m.cursor].input.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m cifsMountFormModel) View() string {
|
||||||
|
width := clampFrameWidth(m.width)
|
||||||
|
if width == 0 {
|
||||||
|
width = clampFrameWidth(84)
|
||||||
|
}
|
||||||
|
contentWidth := innerWidth(width)
|
||||||
|
|
||||||
|
formRows := make([]string, 0, len(m.fields)+len(m.toggles)+3)
|
||||||
|
for i, field := range m.fields {
|
||||||
|
active := i == m.cursor
|
||||||
|
boxStyle := m.theme.panel
|
||||||
|
if active {
|
||||||
|
boxStyle = m.theme.panelAccent
|
||||||
|
}
|
||||||
|
|
||||||
|
label := field.label
|
||||||
|
if field.required {
|
||||||
|
label += " *"
|
||||||
|
}
|
||||||
|
fieldLines := []string{m.theme.inputLabel.Render(label)}
|
||||||
|
fieldLines = append(fieldLines, m.theme.inputBox.Width(max(12, contentWidth-6)).Render(field.input.View()))
|
||||||
|
formRows = append(formRows, boxStyle.Width(contentWidth).Render(strings.Join(fieldLines, "\n")))
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleStart := len(m.fields)
|
||||||
|
for i, toggle := range m.toggles {
|
||||||
|
active := toggleStart+i == m.cursor
|
||||||
|
rowStyle := m.theme.row
|
||||||
|
marker := "[ ]"
|
||||||
|
if toggle.value {
|
||||||
|
marker = "[x]"
|
||||||
|
}
|
||||||
|
if active {
|
||||||
|
rowStyle = m.theme.rowSelected
|
||||||
|
}
|
||||||
|
formRows = append(formRows, rowStyle.Width(contentWidth).Render(
|
||||||
|
m.theme.rowTitle.Render(marker+" "+toggle.label),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
saveIndex := len(m.fields) + len(m.toggles)
|
||||||
|
saveStyle := m.theme.row
|
||||||
|
cancelStyle := m.theme.row
|
||||||
|
if m.cursor == saveIndex {
|
||||||
|
saveStyle = m.theme.rowSelected
|
||||||
|
}
|
||||||
|
if m.cursor == saveIndex+1 {
|
||||||
|
cancelStyle = m.theme.rowSelected
|
||||||
|
}
|
||||||
|
formRows = append(formRows, saveStyle.Width(contentWidth).Render(m.theme.rowTitle.Render("Save mount")))
|
||||||
|
formRows = append(formRows, cancelStyle.Width(contentWidth).Render(m.theme.rowTitle.Render("Cancel")))
|
||||||
|
|
||||||
|
previewCfg := m.currentConfig()
|
||||||
|
previewLines := []string{
|
||||||
|
buildFstabLine(previewCfg),
|
||||||
|
"Passwords are written directly into /etc/fstab by this workflow.",
|
||||||
|
}
|
||||||
|
bodyParts := []string{
|
||||||
|
m.theme.renderSection(width, "Workflow", m.help),
|
||||||
|
m.theme.sectionTitle.Width(contentWidth).Render("Mount Details"),
|
||||||
|
strings.Join(formRows, "\n"),
|
||||||
|
m.theme.renderSection(width, "Preview", previewLines),
|
||||||
|
}
|
||||||
|
if m.errMessage != "" {
|
||||||
|
bodyParts = append(bodyParts, m.theme.renderMessage(width, "warn", m.errMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.theme.renderFrame(
|
||||||
|
m.width,
|
||||||
|
m.title,
|
||||||
|
m.subtitle,
|
||||||
|
strings.Join(bodyParts, "\n\n"),
|
||||||
|
"tab move • enter next/toggle/save • esc cancel",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCIFSMountForm(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle string, defaults CIFSMountConfig) (CIFSMountConfig, error) {
|
||||||
|
model := newCIFSMountFormModel(theme, title, subtitle, defaults)
|
||||||
|
|
||||||
|
result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out), tea.WithAltScreen()).Run()
|
||||||
|
if err != nil {
|
||||||
|
return CIFSMountConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
final := result.(cifsMountFormModel)
|
||||||
|
if final.cancel {
|
||||||
|
return CIFSMountConfig{}, ErrCancelled
|
||||||
|
}
|
||||||
|
return final.result, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user