package setup import ( "fmt" "os" "strings" "charm.land/huh/v2" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/netbox" ) // configureInstances edits a copy; nothing is written until the whole wizard succeeds. func configureInstances(cfg *config.Config) ([]config.NetBoxConfig, error) { sources := append([]config.NetBoxConfig(nil), cfg.NetBoxes...) if len(sources) == 0 && cfg.NetBox.URL != "" { source := cfg.NetBox source.Name = "default" sources = append(sources, source) } for { if len(sources) == 0 { source, err := editInstance(config.NetBoxConfig{Name: "default"}, sources, "") if err != nil { return nil, err } sources = append(sources, source) } action := "done" var options []huh.Option[string] for _, source := range sources { options = append(options, huh.NewOption("Edit "+source.Name+" ("+source.URL+")", "edit:"+source.Name)) options = append(options, huh.NewOption("Remove "+source.Name, "remove:"+source.Name)) } options = append(options, huh.NewOption("Add another NetBox", "add"), huh.NewOption("Continue with these instances", "done")) err := huh.NewForm(huh.NewGroup(huh.NewSelect[string]().Title("NetBox instances").Description("All configured instances are searched together.").Options(options...).Value(&action))).Run() if err != nil { return nil, err } if action == "done" { return sources, nil } if action == "add" { source, err := editInstance(config.NetBoxConfig{}, sources, "") if err != nil { return nil, err } sources = append(sources, source) continue } verb, name, _ := strings.Cut(action, ":") for i, source := range sources { if source.Name != name { continue } if verb == "edit" { edited, err := editInstance(source, sources, name) if err != nil { return nil, err } sources[i] = edited } else { remove := false if err := huh.NewConfirm().Title("Remove " + name + "?").Value(&remove).Run(); err != nil { return nil, err } if remove { sources = append(sources[:i], sources[i+1:]...) } } break } } } func editInstance(source config.NetBoxConfig, sources []config.NetBoxConfig, original string) (config.NetBoxConfig, error) { err := huh.NewForm(huh.NewGroup( huh.NewInput().Title("Instance name").Description("Used in selectors such as office/web01.").Value(&source.Name).Validate(func(name string) error { name = strings.ToLower(strings.TrimSpace(name)) candidate := config.Config{NetBoxes: []config.NetBoxConfig{{Name: name, URL: "https://example.invalid"}}} if err := candidate.ValidateNetBoxes(); err != nil { return err } for _, existing := range sources { if strings.EqualFold(existing.Name, name) && !strings.EqualFold(existing.Name, original) { return fmt.Errorf("instance name %q is already used", name) } } return nil }), huh.NewInput().Title("NetBox URL").Placeholder("https://netbox.example.com").Value(&source.URL).Validate(func(value string) error { return (&config.Config{NetBoxes: []config.NetBoxConfig{{Name: "instance", URL: strings.TrimSpace(value)}}}).ValidateNetBoxes() }), huh.NewInput().Title("NetBox API token").EchoMode(huh.EchoModePassword).Value(&source.Token).Validate(func(value string) error { if strings.TrimSpace(value) == "" { return fmt.Errorf("required") } return nil }), ).Title("NetBox connection")).Run() if err != nil { return source, err } source.Name = strings.ToLower(strings.TrimSpace(source.Name)) source.URL = strings.TrimRight(strings.TrimSpace(source.URL), "/") source.Token = strings.TrimSpace(source.Token) source.TokenVersion = netbox.TokenVersion(source.Token) if source.TokenVersion == 1 { fmt.Fprintf(os.Stderr, "Note: NetBox %s uses a legacy v1 token. A v2 token starts with nbt_.\n", source.Name) } return source, nil }