Skip to content

Commit f3df3c4

Browse files
committed
refactor: update server form to support initial data for Add mode
- Add initialData field to ServerForm struct for pre-filling forms - Add SetInitialData method to set pre-fill data separately from original - Update getDefaultValues to differentiate between Edit mode (original) and Add mode (initialData) - Fix issue where pasted servers were incorrectly treated as updates - Ensure proper mode handling when creating new servers from paste - Set default port to 22 when port is 0 or unspecified
1 parent 3af0c0c commit f3df3c4

File tree

1 file changed

+137
-104
lines changed

1 file changed

+137
-104
lines changed

internal/adapters/ui/server_form.go

Lines changed: 137 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,29 @@ const (
4444
)
4545

4646
type ServerForm struct {
47-
*tview.Flex // The root container (includes header, form panel and hint bar)
48-
header *AppHeader // The app header
49-
formPanel *tview.Flex // The actual form panel
50-
pages *tview.Pages
51-
tabBar *tview.TextView
52-
forms map[string]*tview.Form
53-
currentTab string
54-
tabs []string
55-
tabAbbrev map[string]string // Abbreviated tab names for narrow views
56-
mode ServerFormMode
57-
original *domain.Server
58-
onSave func(domain.Server, *domain.Server)
59-
onCancel func()
60-
app *tview.Application // Reference to app for showing modals
61-
version string // Version for header
62-
commit string // Commit for header
63-
validation *ValidationState // Validation state for all fields
64-
helpPanel *tview.TextView // Help panel for field descriptions
65-
helpMode HelpDisplayMode // Current help display mode
66-
currentField string // Currently focused field
67-
mainContainer *tview.Flex // Container for form and help panel
47+
*tview.Flex // The root container (includes header, form panel and hint bar)
48+
header *AppHeader // The app header
49+
formPanel *tview.Flex // The actual form panel
50+
pages *tview.Pages
51+
tabBar *tview.TextView
52+
forms map[string]*tview.Form
53+
currentTab string
54+
tabs []string
55+
tabAbbrev map[string]string // Abbreviated tab names for narrow views
56+
mode ServerFormMode
57+
original *domain.Server
58+
initialData *domain.Server // Initial data for pre-filling form (used in Add mode)
59+
existingAliases []string // List of existing aliases for validation
60+
onSave func(domain.Server, *domain.Server)
61+
onCancel func()
62+
app *tview.Application // Reference to app for showing modals
63+
version string // Version for header
64+
commit string // Commit for header
65+
validation *ValidationState // Validation state for all fields
66+
helpPanel *tview.TextView // Help panel for field descriptions
67+
helpMode HelpDisplayMode // Current help display mode
68+
currentField string // Currently focused field
69+
mainContainer *tview.Flex // Container for form and help panel
6870
}
6971

7072
func NewServerForm(mode ServerFormMode, original *domain.Server) *ServerForm {
@@ -906,7 +908,13 @@ func (sf *ServerForm) createAlgorithmAutocomplete(suggestions []string) func(str
906908

907909
// validateField validates a single field and updates the validation state
908910
func (sf *ServerForm) validateField(fieldName, value string) string {
909-
fieldValidators := GetFieldValidators()
911+
// Get validators with context for alias checking
912+
originalAlias := ""
913+
if sf.original != nil {
914+
originalAlias = sf.original.Alias
915+
}
916+
fieldValidators := GetFieldValidatorsWithContext(originalAlias, sf.existingAliases)
917+
910918
validator, exists := fieldValidators[fieldName]
911919
if !exists {
912920
// No validator for this field, it's valid
@@ -1064,78 +1072,92 @@ func (sf *ServerForm) validateAllFields() bool {
10641072

10651073
// getDefaultValues returns default form values based on mode
10661074
func (sf *ServerForm) getDefaultValues() ServerFormData {
1075+
// Determine which server data to use for defaults
1076+
var server *domain.Server
10671077
if sf.mode == ServerFormEdit && sf.original != nil {
1078+
server = sf.original
1079+
} else if sf.mode == ServerFormAdd && sf.initialData != nil {
1080+
server = sf.initialData
1081+
}
1082+
1083+
// Use server values if available
1084+
if server != nil {
10681085
return ServerFormData{
1069-
Alias: sf.original.Alias,
1070-
Host: sf.original.Host,
1071-
User: sf.original.User,
1072-
Port: fmt.Sprint(sf.original.Port),
1073-
Key: strings.Join(sf.original.IdentityFiles, ", "),
1074-
Tags: strings.Join(sf.original.Tags, ", "),
1075-
ProxyJump: sf.original.ProxyJump,
1076-
ProxyCommand: sf.original.ProxyCommand,
1077-
RemoteCommand: sf.original.RemoteCommand,
1078-
RequestTTY: sf.original.RequestTTY,
1079-
SessionType: sf.original.SessionType,
1080-
ConnectTimeout: sf.original.ConnectTimeout,
1081-
ConnectionAttempts: sf.original.ConnectionAttempts,
1082-
BindAddress: sf.original.BindAddress,
1083-
BindInterface: sf.original.BindInterface,
1084-
AddressFamily: sf.original.AddressFamily,
1085-
ExitOnForwardFailure: sf.original.ExitOnForwardFailure,
1086-
IPQoS: sf.original.IPQoS,
1086+
Alias: server.Alias,
1087+
Host: server.Host,
1088+
User: server.User,
1089+
Port: func() string {
1090+
if server.Port == 0 {
1091+
return "22" // Default SSH port
1092+
}
1093+
return fmt.Sprint(server.Port)
1094+
}(),
1095+
Key: strings.Join(server.IdentityFiles, ", "),
1096+
Tags: strings.Join(server.Tags, ", "),
1097+
ProxyJump: server.ProxyJump,
1098+
ProxyCommand: server.ProxyCommand,
1099+
RemoteCommand: server.RemoteCommand,
1100+
RequestTTY: server.RequestTTY,
1101+
SessionType: server.SessionType,
1102+
ConnectTimeout: server.ConnectTimeout,
1103+
ConnectionAttempts: server.ConnectionAttempts,
1104+
BindAddress: server.BindAddress,
1105+
BindInterface: server.BindInterface,
1106+
AddressFamily: server.AddressFamily,
1107+
ExitOnForwardFailure: server.ExitOnForwardFailure,
1108+
IPQoS: server.IPQoS,
10871109
// Hostname canonicalization
1088-
CanonicalizeHostname: sf.original.CanonicalizeHostname,
1089-
CanonicalDomains: sf.original.CanonicalDomains,
1090-
CanonicalizeFallbackLocal: sf.original.CanonicalizeFallbackLocal,
1091-
CanonicalizeMaxDots: sf.original.CanonicalizeMaxDots,
1092-
CanonicalizePermittedCNAMEs: sf.original.CanonicalizePermittedCNAMEs,
1093-
GatewayPorts: sf.original.GatewayPorts,
1094-
LocalForward: strings.Join(sf.original.LocalForward, ", "),
1095-
RemoteForward: strings.Join(sf.original.RemoteForward, ", "),
1096-
DynamicForward: strings.Join(sf.original.DynamicForward, ", "),
1097-
ClearAllForwardings: sf.original.ClearAllForwardings,
1110+
CanonicalizeHostname: server.CanonicalizeHostname,
1111+
CanonicalDomains: server.CanonicalDomains,
1112+
CanonicalizeFallbackLocal: server.CanonicalizeFallbackLocal,
1113+
CanonicalizeMaxDots: server.CanonicalizeMaxDots,
1114+
CanonicalizePermittedCNAMEs: server.CanonicalizePermittedCNAMEs,
1115+
GatewayPorts: server.GatewayPorts,
1116+
LocalForward: strings.Join(server.LocalForward, ", "),
1117+
RemoteForward: strings.Join(server.RemoteForward, ", "),
1118+
DynamicForward: strings.Join(server.DynamicForward, ", "),
1119+
ClearAllForwardings: server.ClearAllForwardings,
10981120
// Public key
1099-
PubkeyAuthentication: sf.original.PubkeyAuthentication,
1100-
IdentitiesOnly: sf.original.IdentitiesOnly,
1121+
PubkeyAuthentication: server.PubkeyAuthentication,
1122+
IdentitiesOnly: server.IdentitiesOnly,
11011123
// SSH Agent
1102-
AddKeysToAgent: sf.original.AddKeysToAgent,
1103-
IdentityAgent: sf.original.IdentityAgent,
1124+
AddKeysToAgent: server.AddKeysToAgent,
1125+
IdentityAgent: server.IdentityAgent,
11041126
// Password & Interactive
1105-
PasswordAuthentication: sf.original.PasswordAuthentication,
1106-
KbdInteractiveAuthentication: sf.original.KbdInteractiveAuthentication,
1107-
NumberOfPasswordPrompts: sf.original.NumberOfPasswordPrompts,
1127+
PasswordAuthentication: server.PasswordAuthentication,
1128+
KbdInteractiveAuthentication: server.KbdInteractiveAuthentication,
1129+
NumberOfPasswordPrompts: server.NumberOfPasswordPrompts,
11081130
// Advanced
1109-
PreferredAuthentications: sf.original.PreferredAuthentications,
1110-
ForwardAgent: sf.original.ForwardAgent,
1111-
ForwardX11: sf.original.ForwardX11,
1112-
ForwardX11Trusted: sf.original.ForwardX11Trusted,
1113-
ControlMaster: sf.original.ControlMaster,
1114-
ControlPath: sf.original.ControlPath,
1115-
ControlPersist: sf.original.ControlPersist,
1116-
ServerAliveInterval: sf.original.ServerAliveInterval,
1117-
ServerAliveCountMax: sf.original.ServerAliveCountMax,
1118-
Compression: sf.original.Compression,
1119-
TCPKeepAlive: sf.original.TCPKeepAlive,
1120-
BatchMode: sf.original.BatchMode,
1121-
StrictHostKeyChecking: sf.original.StrictHostKeyChecking,
1122-
UserKnownHostsFile: sf.original.UserKnownHostsFile,
1123-
HostKeyAlgorithms: sf.original.HostKeyAlgorithms,
1124-
PubkeyAcceptedAlgorithms: sf.original.PubkeyAcceptedAlgorithms,
1125-
HostbasedAcceptedAlgorithms: sf.original.HostbasedAcceptedAlgorithms,
1126-
MACs: sf.original.MACs,
1127-
Ciphers: sf.original.Ciphers,
1128-
KexAlgorithms: sf.original.KexAlgorithms,
1129-
VerifyHostKeyDNS: sf.original.VerifyHostKeyDNS,
1130-
UpdateHostKeys: sf.original.UpdateHostKeys,
1131-
HashKnownHosts: sf.original.HashKnownHosts,
1132-
VisualHostKey: sf.original.VisualHostKey,
1133-
LocalCommand: sf.original.LocalCommand,
1134-
PermitLocalCommand: sf.original.PermitLocalCommand,
1135-
EscapeChar: sf.original.EscapeChar,
1136-
SendEnv: strings.Join(sf.original.SendEnv, ", "),
1137-
SetEnv: strings.Join(sf.original.SetEnv, ", "),
1138-
LogLevel: sf.original.LogLevel,
1131+
PreferredAuthentications: server.PreferredAuthentications,
1132+
ForwardAgent: server.ForwardAgent,
1133+
ForwardX11: server.ForwardX11,
1134+
ForwardX11Trusted: server.ForwardX11Trusted,
1135+
ControlMaster: server.ControlMaster,
1136+
ControlPath: server.ControlPath,
1137+
ControlPersist: server.ControlPersist,
1138+
ServerAliveInterval: server.ServerAliveInterval,
1139+
ServerAliveCountMax: server.ServerAliveCountMax,
1140+
Compression: server.Compression,
1141+
TCPKeepAlive: server.TCPKeepAlive,
1142+
BatchMode: server.BatchMode,
1143+
StrictHostKeyChecking: server.StrictHostKeyChecking,
1144+
UserKnownHostsFile: server.UserKnownHostsFile,
1145+
HostKeyAlgorithms: server.HostKeyAlgorithms,
1146+
PubkeyAcceptedAlgorithms: server.PubkeyAcceptedAlgorithms,
1147+
HostbasedAcceptedAlgorithms: server.HostbasedAcceptedAlgorithms,
1148+
MACs: server.MACs,
1149+
Ciphers: server.Ciphers,
1150+
KexAlgorithms: server.KexAlgorithms,
1151+
VerifyHostKeyDNS: server.VerifyHostKeyDNS,
1152+
UpdateHostKeys: server.UpdateHostKeys,
1153+
HashKnownHosts: server.HashKnownHosts,
1154+
VisualHostKey: server.VisualHostKey,
1155+
LocalCommand: server.LocalCommand,
1156+
PermitLocalCommand: server.PermitLocalCommand,
1157+
EscapeChar: server.EscapeChar,
1158+
SendEnv: strings.Join(server.SendEnv, ", "),
1159+
SetEnv: strings.Join(server.SetEnv, ", "),
1160+
LogLevel: server.LogLevel,
11391161
}
11401162
}
11411163
// For new servers, use empty values instead of SSH defaults
@@ -2008,28 +2030,28 @@ func (sf *ServerForm) handleCancel() {
20082030

20092031
// hasUnsavedChanges checks if current form data differs from original
20102032
func (sf *ServerForm) hasUnsavedChanges() bool {
2011-
// If creating new server, any non-empty required fields mean changes
2012-
if sf.mode == ServerFormAdd {
2013-
data := sf.getFormData()
2014-
return data.Alias != "" || data.Host != "" || data.User != ""
2015-
}
2033+
// If we have original data to compare against (Edit mode or Add with paste)
2034+
if sf.original != nil {
2035+
currentData := sf.getFormData()
2036+
currentServer := sf.dataToServer(currentData)
20162037

2017-
// If editing, compare with original
2018-
if sf.original == nil {
2019-
return false
2020-
}
2038+
// Use DeepEqual for simple comparison first
2039+
if reflect.DeepEqual(currentServer, *sf.original) {
2040+
return false
2041+
}
20212042

2022-
currentData := sf.getFormData()
2023-
currentServer := sf.dataToServer(currentData)
2043+
// If DeepEqual says they're different, use our custom comparison
2044+
// that handles nil vs empty slice and other normalization
2045+
return sf.serversDiffer(currentServer, *sf.original)
2046+
}
20242047

2025-
// Use DeepEqual for simple comparison first
2026-
if reflect.DeepEqual(currentServer, *sf.original) {
2027-
return false
2048+
// For new servers without original (regular Add), check if any data has been entered
2049+
if sf.mode == ServerFormAdd {
2050+
data := sf.getFormData()
2051+
return data.Alias != "" || data.Host != "" || data.User != ""
20282052
}
20292053

2030-
// If DeepEqual says they're different, use our custom comparison
2031-
// that handles nil vs empty slice and other normalization
2032-
return sf.serversDiffer(currentServer, *sf.original)
2054+
return false
20332055
}
20342056

20352057
// serversDiffer compares two servers for differences using reflection
@@ -2273,6 +2295,17 @@ func (sf *ServerForm) SetApp(app *tview.Application) *ServerForm {
22732295
return sf
22742296
}
22752297

2298+
func (sf *ServerForm) SetExistingAliases(aliases []string) *ServerForm {
2299+
sf.existingAliases = aliases
2300+
return sf
2301+
}
2302+
2303+
// SetInitialData sets initial data for pre-filling the form in Add mode
2304+
func (sf *ServerForm) SetInitialData(server *domain.Server) *ServerForm {
2305+
sf.initialData = server
2306+
return sf
2307+
}
2308+
22762309
func (sf *ServerForm) SetVersionInfo(version, commit string) *ServerForm {
22772310
sf.version = version
22782311
sf.commit = commit

0 commit comments

Comments
 (0)