@@ -44,27 +44,29 @@ const (
4444)
4545
4646type 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
7072func 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
908910func (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
10661074func (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
20102032func (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+
22762309func (sf * ServerForm ) SetVersionInfo (version , commit string ) * ServerForm {
22772310 sf .version = version
22782311 sf .commit = commit
0 commit comments