charm bracelet init, brb
This commit is contained in:
407
app.go
407
app.go
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@@ -49,8 +50,10 @@ type App struct {
|
||||
users UserManager
|
||||
runner CommandRunner
|
||||
lookPath LookPathFunc
|
||||
reader *bufio.Reader
|
||||
input io.Reader
|
||||
output io.Writer
|
||||
writer *bufio.Writer
|
||||
theme tuiTheme
|
||||
}
|
||||
|
||||
type PasswdEntry struct {
|
||||
@@ -95,8 +98,10 @@ func NewApp(configPath string, users UserManager, runner CommandRunner, lookPath
|
||||
users: users,
|
||||
runner: runner,
|
||||
lookPath: lookPath,
|
||||
reader: bufio.NewReader(os.Stdin),
|
||||
input: os.Stdin,
|
||||
output: os.Stdout,
|
||||
writer: bufio.NewWriter(os.Stdout),
|
||||
theme: newTUITheme(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,30 +124,19 @@ func (a *App) Run() error {
|
||||
}
|
||||
|
||||
func (a *App) chooseStartupWorkflow() (string, error) {
|
||||
a.println("")
|
||||
a.println("Samba setup assistant")
|
||||
a.println("=====================")
|
||||
a.println("[s] set up or edit shares on this computer")
|
||||
a.println("[c] connect this computer to a share on another server")
|
||||
a.println("[q] quit")
|
||||
a.flush()
|
||||
|
||||
choice, err := a.prompt("What would you like to do")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch strings.ToLower(choice) {
|
||||
case "s", "server":
|
||||
return "server", nil
|
||||
case "c", "client":
|
||||
return "client", nil
|
||||
case "q", "quit":
|
||||
return "quit", nil
|
||||
default:
|
||||
a.println("Unknown choice.")
|
||||
return a.chooseStartupWorkflow()
|
||||
}
|
||||
return a.chooseMenu(
|
||||
"Choose A Workflow",
|
||||
"Server shares and client mounts in one place.",
|
||||
[]string{
|
||||
"Use the server tools to edit smb.conf and manage share accounts.",
|
||||
"Use the client tools to add or maintain CIFS mounts in /etc/fstab.",
|
||||
},
|
||||
[]menuOption{
|
||||
{Key: "s", Value: "server", Label: "Set up or edit shares on this computer", Description: "Create, edit, and save Samba share definitions."},
|
||||
{Key: "c", Value: "client", Label: "Connect this computer to a remote share", Description: "Manage CIFS client mounts and mount points."},
|
||||
{Key: "q", Value: "quit", Label: "Quit", Description: "Exit without changing anything."},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (a *App) runServerWorkflow() error {
|
||||
@@ -161,60 +155,62 @@ func (a *App) runServerWorkflow() error {
|
||||
loaded:
|
||||
for {
|
||||
shareSections := doc.ShareSections()
|
||||
a.println("")
|
||||
a.println("Samba share editor")
|
||||
a.println("==================")
|
||||
a.println("Current shares:")
|
||||
intro := []string{"Current shares:"}
|
||||
if len(shareSections) == 0 {
|
||||
a.println(" (none)")
|
||||
intro = append(intro, " none yet")
|
||||
} else {
|
||||
for i, section := range shareSections {
|
||||
for _, section := range shareSections {
|
||||
cfg := ShareFromSection(section)
|
||||
a.printf(" %d. %s -> %s\n", i+1, cfg.Name, cfg.Path)
|
||||
intro = append(intro, fmt.Sprintf(" %s -> %s", cfg.Name, cfg.Path))
|
||||
}
|
||||
}
|
||||
a.println("")
|
||||
a.println("[a] add share")
|
||||
a.println("[e] edit share")
|
||||
a.println("[d] delete share")
|
||||
a.println("[m] set up client mount")
|
||||
a.println("[u] manage users")
|
||||
a.println("[w] write config and exit")
|
||||
a.println("[q] quit without saving")
|
||||
a.flush()
|
||||
|
||||
choice, err := a.prompt("Select an action")
|
||||
choice, err := a.chooseMenu(
|
||||
"Server Share Editor",
|
||||
fmt.Sprintf("Working against %s", a.configPath),
|
||||
intro,
|
||||
[]menuOption{
|
||||
{Key: "a", Value: "add", Label: "Add share", Description: "Create a new share definition."},
|
||||
{Key: "e", Value: "edit", Label: "Edit share", Description: "Update an existing share."},
|
||||
{Key: "d", Value: "delete", Label: "Delete share", Description: "Remove a share definition."},
|
||||
{Key: "m", Value: "mount", Label: "Set up client mount", Description: "Jump to the remote-mount workflow."},
|
||||
{Key: "u", Value: "users", Label: "Manage users", Description: "Check accounts, passwords, and cleanup."},
|
||||
{Key: "w", Value: "write", Label: "Write config and exit", Description: "Save smb.conf and leave the app."},
|
||||
{Key: "q", Value: "quit", Label: "Quit without saving", Description: "Leave the editor immediately."},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrCancelled) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch strings.ToLower(choice) {
|
||||
case "a", "add":
|
||||
switch choice {
|
||||
case "add":
|
||||
if err := a.addShare(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
case "e", "edit":
|
||||
case "edit":
|
||||
if err := a.editShare(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
case "d", "delete":
|
||||
case "delete":
|
||||
if err := a.deleteShare(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
case "m", "mount":
|
||||
case "mount":
|
||||
if err := a.setupClientMount(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "u", "users":
|
||||
case "users":
|
||||
if err := a.manageUsers(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
case "w", "write":
|
||||
case "write":
|
||||
return a.writeConfig(doc)
|
||||
case "q", "quit":
|
||||
case "quit":
|
||||
return nil
|
||||
default:
|
||||
a.println("Unknown choice.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,84 +222,92 @@ func (a *App) setupClientMount() error {
|
||||
return err
|
||||
}
|
||||
|
||||
a.println("")
|
||||
a.println("Client mount setup")
|
||||
a.println("==================")
|
||||
a.println("This computer can connect to Samba or CIFS shares listed in /etc/fstab.")
|
||||
a.println("Current client mounts:")
|
||||
intro := []string{
|
||||
"Remote shares are persisted in /etc/fstab so they can mount cleanly on boot.",
|
||||
"Current client mounts:",
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
a.println(" (none)")
|
||||
intro = append(intro, " none yet")
|
||||
} else {
|
||||
for i, entry := range entries {
|
||||
a.printf(" %d. %s -> %s\n", i+1, entry.DisplayName, entry.MountPoint)
|
||||
for _, entry := range entries {
|
||||
intro = append(intro, fmt.Sprintf(" %s -> %s", entry.DisplayName, entry.MountPoint))
|
||||
}
|
||||
}
|
||||
a.println("")
|
||||
a.println("[a] add client mount")
|
||||
a.println("[e] edit client mount")
|
||||
a.println("[d] delete client mount")
|
||||
a.println("[b] back")
|
||||
a.flush()
|
||||
|
||||
choice, err := a.prompt("Select an action")
|
||||
choice, err := a.chooseMenu(
|
||||
"Client Mount Setup",
|
||||
fmt.Sprintf("Editing %s", a.fstabPath),
|
||||
intro,
|
||||
[]menuOption{
|
||||
{Key: "a", Value: "add", Label: "Add client mount", Description: "Create a new CIFS mount entry."},
|
||||
{Key: "e", Value: "edit", Label: "Edit client mount", Description: "Change an existing mount definition."},
|
||||
{Key: "d", Value: "delete", Label: "Delete client mount", Description: "Remove a saved mount entry."},
|
||||
{Key: "b", Value: "back", Label: "Back", Description: "Return to the previous menu."},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrCancelled) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch strings.ToLower(choice) {
|
||||
case "a", "add":
|
||||
switch choice {
|
||||
case "add":
|
||||
if err := a.addClientMount(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "e", "edit":
|
||||
case "edit":
|
||||
if err := a.editClientMount(entries); err != nil {
|
||||
return err
|
||||
}
|
||||
case "d", "delete":
|
||||
case "delete":
|
||||
if err := a.deleteClientMount(entries); err != nil {
|
||||
return err
|
||||
}
|
||||
case "b", "back":
|
||||
case "back":
|
||||
return nil
|
||||
default:
|
||||
a.println("Unknown choice.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) manageUsers(doc *Document) error {
|
||||
for {
|
||||
a.println("")
|
||||
a.println("User management")
|
||||
a.println("===============")
|
||||
a.println("[c] check share accounts")
|
||||
a.println("[p] change a Samba password")
|
||||
a.println("[x] delete an unused account")
|
||||
a.println("[b] back")
|
||||
a.flush()
|
||||
|
||||
choice, err := a.prompt("Select an action")
|
||||
choice, err := a.chooseMenu(
|
||||
"User Management",
|
||||
"Keep local Linux accounts and Samba credentials aligned with your shares.",
|
||||
[]string{
|
||||
fmt.Sprintf("%d share accounts referenced in the current config.", len(shareUserReferences(doc))),
|
||||
},
|
||||
[]menuOption{
|
||||
{Key: "c", Value: "check", Label: "Check share accounts", Description: "Create missing local users referenced by shares."},
|
||||
{Key: "p", Value: "password", Label: "Change a Samba password", Description: "Set or update a Samba credential."},
|
||||
{Key: "x", Value: "delete", Label: "Delete an unused account", Description: "Remove unused share-style accounts."},
|
||||
{Key: "b", Value: "back", Label: "Back", Description: "Return to the share editor."},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrCancelled) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch strings.ToLower(choice) {
|
||||
case "c", "check":
|
||||
switch choice {
|
||||
case "check":
|
||||
if err := a.checkShareAccounts(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
case "p", "password":
|
||||
case "password":
|
||||
if err := a.changeSambaPassword(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
case "x", "delete":
|
||||
case "delete":
|
||||
if err := a.deleteUnusedAccount(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
case "b", "back":
|
||||
case "back":
|
||||
return nil
|
||||
default:
|
||||
a.println("Unknown choice.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,12 +315,11 @@ func (a *App) manageUsers(doc *Document) error {
|
||||
func (a *App) checkShareAccounts(doc *Document) error {
|
||||
references := shareUserReferences(doc)
|
||||
if len(references) == 0 {
|
||||
a.println("No accounts are listed in any share.")
|
||||
a.showMessage("info", "No accounts are listed in any share.")
|
||||
return nil
|
||||
}
|
||||
|
||||
a.println("")
|
||||
a.println("Accounts used by the current shares:")
|
||||
a.showPanel("Accounts Used By Current Shares", "", nil)
|
||||
for _, ref := range references {
|
||||
status := "missing"
|
||||
if a.users.UserExists(ref.User) {
|
||||
@@ -333,13 +336,11 @@ func (a *App) checkShareAccounts(doc *Document) error {
|
||||
}
|
||||
|
||||
if len(missing) == 0 {
|
||||
a.println("")
|
||||
a.println("All listed share accounts already exist.")
|
||||
a.showMessage("success", "All listed share accounts already exist.")
|
||||
return nil
|
||||
}
|
||||
|
||||
a.println("")
|
||||
a.println("Some accounts are missing and those users may not be able to sign in.")
|
||||
a.showMessage("warn", "Some accounts are missing and those users may not be able to sign in.")
|
||||
if err := a.ensureUsers(missing); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -354,50 +355,55 @@ func (a *App) deleteUnusedAccount(doc *Document) error {
|
||||
|
||||
candidates := unusedAccountCandidates(entries, shareUsers(doc))
|
||||
if len(candidates) == 0 {
|
||||
a.println("I couldn't find any obvious unused share accounts to delete.")
|
||||
a.println("For safety, system accounts are excluded from this list.")
|
||||
a.showPanel("Unused Account Cleanup", "No obvious removable share accounts were found.", []string{
|
||||
"For safety, system accounts are excluded from this list.",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
a.println("")
|
||||
a.println("Accounts that look unused by the current shares:")
|
||||
options := make([]menuOption, 0, len(candidates)+1)
|
||||
for i, entry := range candidates {
|
||||
a.printf(" %d. %s (shell: %s)\n", i+1, entry.Name, entry.Shell)
|
||||
options = append(options, menuOption{
|
||||
Key: fmt.Sprintf("%d", i+1),
|
||||
Value: entry.Name,
|
||||
Label: entry.Name,
|
||||
Description: "shell: " + strings.TrimSpace(entry.Shell),
|
||||
})
|
||||
}
|
||||
a.println("Only accounts that are not listed in any share are shown here.")
|
||||
a.flush()
|
||||
options = append(options, menuOption{Key: "b", Value: "", Label: "Back", Description: "Leave accounts unchanged."})
|
||||
|
||||
raw, err := a.prompt("Account number to delete")
|
||||
choice, err := a.chooseMenu(
|
||||
"Delete An Unused Account",
|
||||
"Only accounts that are not listed in any share are shown here.",
|
||||
nil,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(raw)
|
||||
if err != nil || index < 1 || index > len(candidates) {
|
||||
a.println("Invalid selection.")
|
||||
if choice == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := candidates[index-1]
|
||||
a.println("")
|
||||
a.printf("This will remove the local account %q.\n", entry.Name)
|
||||
a.println("This is only safe if nobody needs this account for Samba or anything else.")
|
||||
a.flush()
|
||||
a.showPanel("Delete Account", "", []string{
|
||||
fmt.Sprintf("This will remove the local account %q.", choice),
|
||||
"This is only safe if nobody needs this account for Samba or anything else.",
|
||||
})
|
||||
|
||||
confirm, err := a.confirm("Delete this account now", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !confirm {
|
||||
a.println("Delete cancelled.")
|
||||
a.showMessage("info", "Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := a.deleteUser(entry.Name); err != nil {
|
||||
if err := a.deleteUser(choice); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.printf("Deleted account %s\n", entry.Name)
|
||||
a.showMessage("success", "Deleted account "+choice)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -412,38 +418,50 @@ func (a *App) changeSambaPassword(doc *Document) error {
|
||||
|
||||
references := shareUserReferences(doc)
|
||||
if len(references) == 0 {
|
||||
a.println("No accounts are listed in any share.")
|
||||
a.showMessage("info", "No accounts are listed in any share.")
|
||||
return nil
|
||||
}
|
||||
|
||||
a.println("")
|
||||
a.println("Accounts used by the current shares:")
|
||||
options := make([]menuOption, 0, len(references)+1)
|
||||
for i, ref := range references {
|
||||
status := "missing"
|
||||
if a.users.UserExists(ref.User) {
|
||||
status = "present"
|
||||
}
|
||||
a.printf(" %d. %s [%s] used by: %s\n", i+1, ref.User, status, strings.Join(ref.Shares, ", "))
|
||||
options = append(options, menuOption{
|
||||
Key: fmt.Sprintf("%d", i+1),
|
||||
Value: ref.User,
|
||||
Label: ref.User + " [" + status + "]",
|
||||
Description: "used by: " + strings.Join(ref.Shares, ", "),
|
||||
})
|
||||
}
|
||||
a.flush()
|
||||
options = append(options, menuOption{Key: "b", Value: "", Label: "Back", Description: "Return without changing a password."})
|
||||
|
||||
raw, err := a.prompt("Account number to update")
|
||||
choice, err := a.chooseMenu(
|
||||
"Choose An Account",
|
||||
"Select the local user whose Samba password you want to change.",
|
||||
nil,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(raw)
|
||||
if err != nil || index < 1 || index > len(references) {
|
||||
a.println("Invalid selection.")
|
||||
if choice == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := references[index-1]
|
||||
var ref ShareUserReference
|
||||
for _, candidate := range references {
|
||||
if candidate.User == choice {
|
||||
ref = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if !a.users.UserExists(ref.User) {
|
||||
a.println("")
|
||||
a.printf("The local account %q does not exist yet.\n", ref.User)
|
||||
a.println("I can create it first so a Samba password can be set.")
|
||||
a.flush()
|
||||
a.showPanel("Local User Missing", "", []string{
|
||||
fmt.Sprintf("The local account %q does not exist yet.", ref.User),
|
||||
"I can create it first so a Samba password can be set.",
|
||||
})
|
||||
|
||||
create, promptErr := a.confirm("Create this account now", true)
|
||||
if promptErr != nil {
|
||||
@@ -458,10 +476,10 @@ func (a *App) changeSambaPassword(doc *Document) error {
|
||||
}
|
||||
}
|
||||
|
||||
a.println("")
|
||||
a.printf("I’ll open the password setup for %q now.\n", ref.User)
|
||||
a.println("You’ll be asked to type the new Samba password.")
|
||||
a.flush()
|
||||
a.showPanel("Password Update", "", []string{
|
||||
fmt.Sprintf("I will open the password setup for %q now.", ref.User),
|
||||
"You will be asked to type the new Samba password.",
|
||||
})
|
||||
return a.setSambaPassword(ref.User)
|
||||
}
|
||||
|
||||
@@ -836,22 +854,30 @@ func (a *App) ensureShareDirectories(doc *Document) error {
|
||||
func (a *App) selectShare(doc *Document) (*Section, error) {
|
||||
shares := doc.ShareSections()
|
||||
if len(shares) == 0 {
|
||||
a.println("There are no shares to select.")
|
||||
a.showMessage("info", "There are no shares to select.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
raw, err := a.prompt("Share number")
|
||||
options := make([]menuOption, 0, len(shares)+1)
|
||||
for i, share := range shares {
|
||||
cfg := ShareFromSection(share)
|
||||
options = append(options, menuOption{
|
||||
Key: fmt.Sprintf("%d", i+1),
|
||||
Value: share.Name,
|
||||
Label: cfg.Name,
|
||||
Description: cfg.Path,
|
||||
})
|
||||
}
|
||||
options = append(options, menuOption{Key: "b", Value: "", Label: "Back", Description: "Return to the previous menu."})
|
||||
|
||||
raw, err := a.chooseMenu("Choose A Share", "Select the share you want to work with.", nil, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(raw)
|
||||
if err != nil || index < 1 || index > len(shares) {
|
||||
a.println("Invalid selection.")
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return shares[index-1], nil
|
||||
return doc.Section(raw), nil
|
||||
}
|
||||
|
||||
func (a *App) collectShareConfig(existing ShareConfig) (ShareConfig, error) {
|
||||
@@ -1060,54 +1086,35 @@ func (a *App) deleteUser(name string) error {
|
||||
}
|
||||
|
||||
func (a *App) prompt(label string) (string, error) {
|
||||
a.printf("%s: ", label)
|
||||
a.flush()
|
||||
text, err := a.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(text), nil
|
||||
return runTextPrompt(a.input, a.output, a.theme, "Enter A Value", "Field-by-field configuration", label, "")
|
||||
}
|
||||
|
||||
func (a *App) promptDefault(label, defaultValue string) (string, error) {
|
||||
if defaultValue == "" {
|
||||
return a.prompt(label)
|
||||
}
|
||||
|
||||
a.printf("%s [%s]: ", label, defaultValue)
|
||||
a.flush()
|
||||
text, err := a.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
return text, nil
|
||||
return runTextPrompt(a.input, a.output, a.theme, "Enter A Value", "Field-by-field configuration", label, defaultValue)
|
||||
}
|
||||
|
||||
func (a *App) confirm(label string, defaultYes bool) (bool, error) {
|
||||
suffix := "y/N"
|
||||
if defaultYes {
|
||||
suffix = "Y/n"
|
||||
options := []menuOption{
|
||||
{Key: "y", Value: "yes", Label: "Yes", Description: "Continue with this action."},
|
||||
{Key: "n", Value: "no", Label: "No", Description: "Leave things as they are."},
|
||||
}
|
||||
if !defaultYes {
|
||||
options[0], options[1] = options[1], options[0]
|
||||
}
|
||||
|
||||
answer, err := a.prompt(fmt.Sprintf("%s [%s]", label, suffix))
|
||||
answer, err := a.chooseMenu("Confirm Action", label, nil, options)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if answer == "" {
|
||||
return defaultYes, nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(answer) {
|
||||
case "y", "yes":
|
||||
switch answer {
|
||||
case "yes":
|
||||
return true, nil
|
||||
case "n", "no":
|
||||
case "no":
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.New("expected yes or no")
|
||||
return defaultYes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1123,6 +1130,24 @@ func (a *App) flush() {
|
||||
_ = a.writer.Flush()
|
||||
}
|
||||
|
||||
func (a *App) chooseMenu(title, subtitle string, intro []string, options []menuOption) (string, error) {
|
||||
a.flush()
|
||||
choice, err := runMenuPrompt(a.input, a.output, a.theme, title, subtitle, intro, options)
|
||||
a.flush()
|
||||
return choice, err
|
||||
}
|
||||
|
||||
func (a *App) showPanel(title, subtitle string, lines []string) {
|
||||
body := a.theme.renderSection(84, "", lines)
|
||||
fmt.Fprintln(a.writer, a.theme.renderFrame(84, title, subtitle, body, ""))
|
||||
a.flush()
|
||||
}
|
||||
|
||||
func (a *App) showMessage(kind, message string) {
|
||||
fmt.Fprintln(a.writer, a.theme.renderFrame(84, "Status", "", a.theme.renderMessage(84, kind, message), ""))
|
||||
a.flush()
|
||||
}
|
||||
|
||||
func defaultString(value, fallback string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return fallback
|
||||
@@ -1597,22 +1622,34 @@ func (a *App) deleteClientMount(entries []FstabMountEntry) error {
|
||||
|
||||
func (a *App) selectCIFSMount(entries []FstabMountEntry) (*FstabMountEntry, error) {
|
||||
if len(entries) == 0 {
|
||||
a.println("There are no client mounts to select.")
|
||||
a.showMessage("info", "There are no client mounts to select.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
selection, err := a.prompt("Select mount number")
|
||||
options := make([]menuOption, 0, len(entries)+1)
|
||||
for i, entry := range entries {
|
||||
options = append(options, menuOption{
|
||||
Key: fmt.Sprintf("%d", i+1),
|
||||
Value: strconv.Itoa(i),
|
||||
Label: entry.DisplayName,
|
||||
Description: entry.MountPoint,
|
||||
})
|
||||
}
|
||||
options = append(options, menuOption{Key: "b", Value: "", Label: "Back", Description: "Return to the previous menu."})
|
||||
|
||||
selection, err := a.chooseMenu("Choose A Client Mount", "Select the saved mount entry you want to change.", nil, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(selection)
|
||||
if err != nil || index < 1 || index > len(entries) {
|
||||
a.println("Invalid selection.")
|
||||
if selection == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entry := entries[index-1]
|
||||
index, err := strconv.Atoi(selection)
|
||||
if err != nil || index < 0 || index >= len(entries) {
|
||||
return nil, nil
|
||||
}
|
||||
entry := entries[index]
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user