mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2025-09-07 13:20:40 +02:00
• Add SourceFile field to SSHHost struct to track config file origins • Implement FindHostInAllConfigs() to locate hosts across all config files • Fix "host not found" errors when editing/deleting hosts from included files • Add GetAllConfigFiles() and GetAllConfigFilesFromBase() for config discovery • Create UpdateSSHHostV2() and DeleteSSHHostV2() for cross-file operations • Display config file source in edit and info forms for better visibility • Add intelligent file selector for host addition when multiple configs exist • Support -c parameter context with proper file resolution • Exclude .backup files from Include directive processing • Maintain backward compatibility with existing SSH config workflows Resolves limitation where hosts from included config files could be viewed but not edited, deleted, or properly managed through the interface.
163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sshm/internal/config"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
type fileSelectorModel struct {
|
|
files []string // Chemins absolus des fichiers
|
|
displayNames []string // Noms d'affichage conviviaux
|
|
selected int
|
|
styles Styles
|
|
width int
|
|
height int
|
|
title string
|
|
}
|
|
|
|
type fileSelectorMsg struct {
|
|
selectedFile string
|
|
cancelled bool
|
|
}
|
|
|
|
// NewFileSelector creates a new file selector for choosing config files
|
|
func NewFileSelector(title string, styles Styles, width, height int) (*fileSelectorModel, error) {
|
|
files, err := config.GetAllConfigFiles()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newFileSelectorFromFiles(title, styles, width, height, files)
|
|
}
|
|
|
|
// NewFileSelectorFromBase creates a new file selector starting from a specific base config file
|
|
func NewFileSelectorFromBase(title string, styles Styles, width, height int, baseConfigFile string) (*fileSelectorModel, error) {
|
|
var files []string
|
|
var err error
|
|
|
|
if baseConfigFile != "" {
|
|
files, err = config.GetAllConfigFilesFromBase(baseConfigFile)
|
|
} else {
|
|
files, err = config.GetAllConfigFiles()
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newFileSelectorFromFiles(title, styles, width, height, files)
|
|
}
|
|
|
|
// newFileSelectorFromFiles creates a file selector from a list of files
|
|
func newFileSelectorFromFiles(title string, styles Styles, width, height int, files []string) (*fileSelectorModel, error) {
|
|
|
|
// Convert absolute paths to more user-friendly names
|
|
var displayNames []string
|
|
homeDir, _ := config.GetSSHDirectory()
|
|
|
|
for _, file := range files {
|
|
// Check if it's the main config file
|
|
mainConfig, _ := config.GetDefaultSSHConfigPath()
|
|
if file == mainConfig {
|
|
displayNames = append(displayNames, "Main SSH Config (~/.ssh/config)")
|
|
} else {
|
|
// Try to make path relative to home/.ssh/
|
|
if strings.HasPrefix(file, homeDir) {
|
|
relPath, err := filepath.Rel(homeDir, file)
|
|
if err == nil {
|
|
displayNames = append(displayNames, fmt.Sprintf("~/.ssh/%s", relPath))
|
|
} else {
|
|
displayNames = append(displayNames, file)
|
|
}
|
|
} else {
|
|
displayNames = append(displayNames, file)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &fileSelectorModel{
|
|
files: files,
|
|
displayNames: displayNames,
|
|
selected: 0,
|
|
styles: styles,
|
|
width: width,
|
|
height: height,
|
|
title: title,
|
|
}, nil
|
|
}
|
|
|
|
func (m *fileSelectorModel) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (m *fileSelectorModel) Update(msg tea.Msg) (*fileSelectorModel, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
m.width = msg.Width
|
|
m.height = msg.Height
|
|
m.styles = NewStyles(m.width)
|
|
return m, nil
|
|
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "ctrl+c", "esc":
|
|
return m, func() tea.Msg {
|
|
return fileSelectorMsg{cancelled: true}
|
|
}
|
|
|
|
case "enter":
|
|
selectedFile := ""
|
|
if m.selected < len(m.files) {
|
|
selectedFile = m.files[m.selected]
|
|
}
|
|
return m, func() tea.Msg {
|
|
return fileSelectorMsg{selectedFile: selectedFile}
|
|
}
|
|
|
|
case "up", "k":
|
|
if m.selected > 0 {
|
|
m.selected--
|
|
}
|
|
|
|
case "down", "j":
|
|
if m.selected < len(m.files)-1 {
|
|
m.selected++
|
|
}
|
|
}
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m *fileSelectorModel) View() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(m.styles.FormTitle.Render(m.title))
|
|
b.WriteString("\n\n")
|
|
|
|
if len(m.files) == 0 {
|
|
b.WriteString(m.styles.Error.Render("No SSH config files found."))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(m.styles.FormHelp.Render("Esc: cancel"))
|
|
return b.String()
|
|
}
|
|
|
|
for i, displayName := range m.displayNames {
|
|
if i == m.selected {
|
|
b.WriteString(m.styles.Selected.Render(fmt.Sprintf("▶ %s", displayName)))
|
|
} else {
|
|
b.WriteString(fmt.Sprintf(" %s", displayName))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
b.WriteString(m.styles.FormHelp.Render("↑/↓: navigate • Enter: select • Esc: cancel"))
|
|
|
|
return b.String()
|
|
}
|