feat: replace clipboard copy with config display for SSH sessions
Created ConfigDisplay component that shows full client configuration in a scrollable modal window, replacing non-functional clipboard copy. Benefits: - Works over SSH sessions (no clipboard API needed) - Shows complete configuration, not just public key - Scrollable for long configs with keyboard navigation - Users can select and copy text directly in terminal Changes: - Created internal/tui/components/config-display.go - Updated detail.go to replace copyPublicKey with loadConfig - Removed clipboard-related fields and message type - Updated help text: 'c' now shows config - Key bindings for scrolling: ↑↓, pgup/pgdn, g/G, Esc/q to close Fixes: wg-admin-qtb
This commit is contained in:
149
internal/tui/components/config-display.go
Normal file
149
internal/tui/components/config-display.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// ConfigDisplayModel represents a configuration display modal
|
||||
type ConfigDisplayModel struct {
|
||||
config string
|
||||
viewport viewport.Model
|
||||
Visible bool
|
||||
Width int
|
||||
Height int
|
||||
scrollPos int
|
||||
}
|
||||
|
||||
// Styles
|
||||
var (
|
||||
configModalStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("62")).
|
||||
Padding(1, 2).
|
||||
Background(lipgloss.Color("235"))
|
||||
configTitleStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("62")).
|
||||
Bold(true).
|
||||
MarginBottom(1)
|
||||
configHelpStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241")).
|
||||
MarginTop(1)
|
||||
configContentStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("255")).
|
||||
Width(76)
|
||||
)
|
||||
|
||||
// NewConfigDisplay creates a new configuration display modal
|
||||
func NewConfigDisplay(config string, width, height int) *ConfigDisplayModel {
|
||||
vp := viewport.New(76, 24)
|
||||
vp.SetContent(config)
|
||||
|
||||
return &ConfigDisplayModel{
|
||||
config: config,
|
||||
viewport: vp,
|
||||
Visible: true,
|
||||
Width: width,
|
||||
Height: height,
|
||||
scrollPos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the configuration display modal
|
||||
func (m *ConfigDisplayModel) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update handles messages for the configuration display modal
|
||||
func (m *ConfigDisplayModel) Update(msg tea.Msg) (*ConfigDisplayModel, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "esc", "q":
|
||||
m.Visible = false
|
||||
return m, nil
|
||||
case "up", "k":
|
||||
m.viewport.LineUp(1)
|
||||
case "down", "j":
|
||||
m.viewport.LineDown(1)
|
||||
case "pgup", "b":
|
||||
m.viewport.HalfViewUp()
|
||||
case "pgdown", "f", " ":
|
||||
m.viewport.HalfViewDown()
|
||||
case "g", "home":
|
||||
m.viewport.GotoTop()
|
||||
case "G", "end":
|
||||
m.viewport.GotoBottom()
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
// Update modal dimensions on resize
|
||||
m.Width = msg.Width
|
||||
m.Height = msg.Height
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// View renders the configuration display modal
|
||||
func (m *ConfigDisplayModel) View() string {
|
||||
if !m.Visible {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Build modal content
|
||||
title := configTitleStyle.Render("📋 Client Configuration")
|
||||
content := m.viewport.View()
|
||||
help := configHelpStyle.Render("↑/j: down • ↓/k: up • pgup/pgdn: page • g/G: top/bottom • Esc: close")
|
||||
|
||||
fullContent := lipgloss.JoinVertical(
|
||||
lipgloss.Left,
|
||||
title,
|
||||
"",
|
||||
content,
|
||||
help,
|
||||
)
|
||||
|
||||
// Apply modal style
|
||||
modal := configModalStyle.Render(fullContent)
|
||||
|
||||
// Center modal on screen
|
||||
modalWidth := lipgloss.Width(modal)
|
||||
modalHeight := lipgloss.Height(modal)
|
||||
|
||||
x := (m.Width - modalWidth) / 2
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
y := (m.Height - modalHeight) / 2
|
||||
if y < 0 {
|
||||
y = 0
|
||||
}
|
||||
|
||||
return lipgloss.Place(m.Width, m.Height,
|
||||
lipgloss.Left, lipgloss.Top,
|
||||
modal,
|
||||
lipgloss.WithWhitespaceChars(" "),
|
||||
lipgloss.WithWhitespaceForeground(lipgloss.Color("235")),
|
||||
)
|
||||
}
|
||||
|
||||
// IsVisible returns true if the modal is visible
|
||||
func (m *ConfigDisplayModel) IsVisible() bool {
|
||||
return m.Visible
|
||||
}
|
||||
|
||||
// Hide hides the modal
|
||||
func (m *ConfigDisplayModel) Hide() {
|
||||
m.Visible = false
|
||||
}
|
||||
|
||||
// Show shows the modal
|
||||
func (m *ConfigDisplayModel) Show(config string) {
|
||||
m.config = config
|
||||
m.viewport.SetContent(config)
|
||||
m.viewport.GotoTop()
|
||||
m.Visible = true
|
||||
}
|
||||
Reference in New Issue
Block a user