Bugfixes nov2025 pt2 (#5)

* UI bugfixes.
* Fix open port check during new fwd setup wizard
This commit is contained in:
2025-11-25 00:09:32 +00:00
committed by GitHub
parent 39fe4286b4
commit 49acba5679
8 changed files with 66 additions and 17 deletions
+1
View File
@@ -25,6 +25,7 @@ require (
github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+2
View File
@@ -23,6 +23,8 @@ github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+1
View File
@@ -357,6 +357,7 @@ func (m *Manager) startWorker(fwd config.Forward) error {
m.healthChecker.Register(fwd.ID(), fwd.LocalPort, func(forwardID string, status healthcheck.Status, errorMsg string) { m.healthChecker.Register(fwd.ID(), fwd.LocalPort, func(forwardID string, status healthcheck.Status, errorMsg string) {
if m.statusUI != nil { if m.statusUI != nil {
m.statusUI.UpdateStatus(forwardID, string(status)) m.statusUI.UpdateStatus(forwardID, string(status))
// Send error separately if there is one // Send error separately if there is one
if (status == healthcheck.StatusUnhealthy || status == healthcheck.StatusStale) && errorMsg != "" { if (status == healthcheck.StatusUnhealthy || status == healthcheck.StatusStale) && errorMsg != "" {
if ui, ok := m.statusUI.(interface{ SetError(id, msg string) }); ok { if ui, ok := m.statusUI.(interface{ SetError(id, msg string) }); ok {
+11
View File
@@ -201,6 +201,17 @@ func (c *Checker) GetStatus(forwardID string) (Status, bool) {
return StatusUnhealthy, false return StatusUnhealthy, false
} }
// GetLastCheckTime returns the last health check time for a forward
func (c *Checker) GetLastCheckTime(forwardID string) (time.Time, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
if health, exists := c.ports[forwardID]; exists {
return health.LastCheck, true
}
return time.Time{}, false
}
// GetAllErrors returns all forwards with errors and their error messages // GetAllErrors returns all forwards with errors and their error messages
func (c *Checker) GetAllErrors() map[string]string { func (c *Checker) GetAllErrors() map[string]string {
c.mu.RLock() c.mu.RLock()
+2 -6
View File
@@ -292,12 +292,8 @@ func CheckPortAvailability(port int) (bool, string, error) {
addr := fmt.Sprintf(":%d", port) addr := fmt.Sprintf(":%d", port)
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
// Port is in use // Port is in use - return error details
// Try to get process info (best-effort) return false, err.Error(), nil
processInfo := "unknown process"
// Note: Getting process info requires platform-specific code
// For now, just return a generic message
return false, processInfo, nil
} }
// Port is available, close the listener // Port is available, close the listener
+18 -1
View File
@@ -144,8 +144,25 @@ func validateSelectorCmd(discovery *k8s.Discovery, contextName, namespace, selec
} }
// checkPortCmd checks if a local port is available // checkPortCmd checks if a local port is available
func checkPortCmd(port int) tea.Cmd { func checkPortCmd(port int, configPath string) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
// First check if port is already in the configuration
cfg, err := config.LoadConfig(configPath)
if err == nil {
// Check all forwards in config for this port
allForwards := cfg.GetAllForwards()
for _, fwd := range allForwards {
if fwd.LocalPort == port {
return PortCheckedMsg{
port: port,
available: false,
message: fmt.Sprintf("✗ Port %d already assigned to %s", port, fwd.ID()),
}
}
}
}
// Then check if port is available at OS level
available, processInfo, err := k8s.CheckPortAvailability(port) available, processInfo, err := k8s.CheckPortAvailability(port)
msg := "" msg := ""
+29 -8
View File
@@ -374,6 +374,11 @@ func (m model) handleAddWizardKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
func (m model) handleAddWizardEnter() (tea.Model, tea.Cmd) { func (m model) handleAddWizardEnter() (tea.Model, tea.Cmd) {
wizard := m.ui.addWizard wizard := m.ui.addWizard
// Don't process Enter if we're currently loading
if wizard.loading {
return m, nil
}
switch wizard.step { switch wizard.step {
case StepSelectContext: case StepSelectContext:
filteredContexts := wizard.getFilteredContexts() filteredContexts := wizard.getFilteredContexts()
@@ -452,12 +457,14 @@ func (m model) handleAddWizardEnter() (tea.Model, tea.Cmd) {
filteredServices := wizard.getFilteredServices() filteredServices := wizard.getFilteredServices()
if wizard.cursor >= 0 && wizard.cursor < len(filteredServices) { if wizard.cursor >= 0 && wizard.cursor < len(filteredServices) {
wizard.resourceValue = filteredServices[wizard.cursor].Name wizard.resourceValue = filteredServices[wizard.cursor].Name
// Get ports from selected service (must do this BEFORE clearing search filter)
wizard.detectedPorts = filteredServices[wizard.cursor].Ports
wizard.step = StepEnterRemotePort wizard.step = StepEnterRemotePort
wizard.clearTextInput() wizard.clearTextInput()
wizard.clearSearchFilter() wizard.clearSearchFilter()
// Get ports from selected service
wizard.detectedPorts = filteredServices[wizard.cursor].Ports
if len(wizard.detectedPorts) > 0 { if len(wizard.detectedPorts) > 0 {
wizard.inputMode = InputModeList wizard.inputMode = InputModeList
wizard.cursor = 0 wizard.cursor = 0
@@ -500,14 +507,11 @@ func (m model) handleAddWizardEnter() (tea.Model, tea.Cmd) {
if err != nil || port < 1 || port > 65535 { if err != nil || port < 1 || port > 65535 {
wizard.error = fmt.Errorf("invalid port number") wizard.error = fmt.Errorf("invalid port number")
} else { } else {
// Check port availability before proceeding
wizard.localPort = port wizard.localPort = port
wizard.step = StepConfirmation
wizard.clearTextInput()
wizard.cursor = 0
wizard.inputMode = InputModeList
wizard.error = nil
wizard.loading = true wizard.loading = true
return m, checkPortCmd(port) wizard.error = nil
return m, checkPortCmd(port, m.ui.configPath)
} }
case StepConfirmation: case StepConfirmation:
@@ -520,6 +524,12 @@ func (m model) handleAddWizardEnter() (tea.Model, tea.Cmd) {
// Handle button selection // Handle button selection
if wizard.cursor == 0 { if wizard.cursor == 0 {
// Check if port is available before saving
if !wizard.portAvailable {
wizard.error = fmt.Errorf("port %d is not available. Please choose a different port", wizard.localPort)
return m, nil
}
// Confirmed - save the forward // Confirmed - save the forward
wizard.alias = wizard.textInput wizard.alias = wizard.textInput
@@ -771,6 +781,17 @@ func (m model) handlePortChecked(msg PortCheckedMsg) (tea.Model, tea.Cmd) {
m.ui.addWizard.loading = false m.ui.addWizard.loading = false
m.ui.addWizard.portAvailable = msg.available m.ui.addWizard.portAvailable = msg.available
m.ui.addWizard.portCheckMsg = msg.message m.ui.addWizard.portCheckMsg = msg.message
// Only proceed to confirmation if port is available
if msg.available {
m.ui.addWizard.step = StepConfirmation
m.ui.addWizard.clearTextInput()
m.ui.addWizard.cursor = 0
m.ui.addWizard.inputMode = InputModeList
} else {
// Port is not available - show error and stay on local port step
m.ui.addWizard.error = fmt.Errorf("port %d is in use, please choose another port", msg.port)
}
} }
return m, nil return m, nil
+2 -2
View File
@@ -373,7 +373,7 @@ func (m model) renderEnterRemotePort() string {
prefix = "▸ " prefix = "▸ "
b.WriteString(selectedStyle.Render(prefix + manualOption)) b.WriteString(selectedStyle.Render(prefix + manualOption))
} else { } else {
b.WriteString(mutedStyle.Render(prefix + manualOption)) b.WriteString(prefix + mutedStyle.Render(manualOption))
} }
b.WriteString("\n") b.WriteString("\n")
} }
@@ -443,7 +443,7 @@ func (m model) renderEnterLocalPort() string {
} else { } else {
b.WriteString(errorStyle.Render(wizard.portCheckMsg)) b.WriteString(errorStyle.Render(wizard.portCheckMsg))
} }
} else if wizard.textInput != "" && wizard.localPort > 0 { } else if wizard.textInput != "" {
b.WriteString(mutedStyle.Render("Press Enter to check availability")) b.WriteString(mutedStyle.Render("Press Enter to check availability"))
} }