Compare commits
2 Commits
0476f1e227
...
bf71a7a659
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf71a7a659 | ||
|
|
f154c7ff69 |
File diff suppressed because one or more lines are too long
@@ -3,6 +3,7 @@ package components
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/calmcacil/wg-admin/internal/tui/theme"
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
@@ -18,37 +19,12 @@ type DeleteConfirmModel struct {
|
|||||||
showErrorMessage bool
|
showErrorMessage bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Styles
|
// Local styles for modal
|
||||||
var (
|
var (
|
||||||
deleteModalStyle = lipgloss.NewStyle().
|
modalBaseStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("196")).Background(lipgloss.Color("235"))
|
||||||
Border(lipgloss.RoundedBorder()).
|
modalTitleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("226")).Bold(true)
|
||||||
BorderForeground(lipgloss.Color("196")).
|
modalMessageStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("255"))
|
||||||
Padding(1, 3).
|
modalHelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
|
||||||
Background(lipgloss.Color("235"))
|
|
||||||
deleteTitleStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("226")).
|
|
||||||
Bold(true)
|
|
||||||
deleteMessageStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("255")).
|
|
||||||
Width(60)
|
|
||||||
deleteWarningStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("196")).
|
|
||||||
Bold(true).
|
|
||||||
MarginTop(1)
|
|
||||||
deleteInputStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("255")).
|
|
||||||
Width(60)
|
|
||||||
deleteHelpStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("241")).
|
|
||||||
MarginTop(1)
|
|
||||||
deleteErrorStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("196")).
|
|
||||||
Bold(true).
|
|
||||||
MarginTop(1)
|
|
||||||
deleteSuccessStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("46")).
|
|
||||||
Bold(true).
|
|
||||||
MarginTop(1)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDeleteConfirm creates a new deletion confirmation modal
|
// NewDeleteConfirm creates a new deletion confirmation modal
|
||||||
@@ -108,12 +84,12 @@ func (m *DeleteConfirmModel) View() string {
|
|||||||
matches := m.input.Value() == m.clientName
|
matches := m.input.Value() == m.clientName
|
||||||
|
|
||||||
// Build warning section
|
// Build warning section
|
||||||
warningText := deleteWarningStyle.Render(
|
warningText := theme.StyleError.Bold(true).MarginTop(1).Render(
|
||||||
fmt.Sprintf("⚠️ This will permanently delete client '%s'", m.clientName),
|
fmt.Sprintf("⚠️ This will permanently delete client '%s'", m.clientName),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build message section
|
// Build message section
|
||||||
messageText := deleteMessageStyle.Render(
|
messageText := modalMessageStyle.Width(60).Render(
|
||||||
fmt.Sprintf("This action cannot be undone. Please type the client name to confirm deletion."),
|
fmt.Sprintf("This action cannot be undone. Please type the client name to confirm deletion."),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,29 +97,29 @@ func (m *DeleteConfirmModel) View() string {
|
|||||||
inputSection := lipgloss.JoinVertical(
|
inputSection := lipgloss.JoinVertical(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
"",
|
"",
|
||||||
deleteInputStyle.Render("Client name:"),
|
theme.StyleValue.Width(60).Render("Client name:"),
|
||||||
m.input.View(),
|
m.input.View(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build status section
|
// Build status section
|
||||||
var statusText string
|
var statusText string
|
||||||
if matches {
|
if matches {
|
||||||
statusText = deleteSuccessStyle.Render("✓ Client name matches. Press Enter to confirm deletion.")
|
statusText = theme.StyleSuccess.Bold(true).MarginTop(1).Render("✓ Client name matches. Press Enter to confirm deletion.")
|
||||||
} else if m.showErrorMessage {
|
} else if m.showErrorMessage {
|
||||||
statusText = deleteErrorStyle.Render("✗ Client name does not match. Please try again.")
|
statusText = theme.StyleError.Bold(true).MarginTop(1).Render("✗ Client name does not match. Please try again.")
|
||||||
} else if m.input.Value() != "" {
|
} else if m.input.Value() != "" {
|
||||||
statusText = deleteHelpStyle.Render("Client name does not match yet...")
|
statusText = modalHelpStyle.Render("Client name does not match yet...")
|
||||||
} else {
|
} else {
|
||||||
statusText = deleteHelpStyle.Render("Type the client name to enable confirmation.")
|
statusText = modalHelpStyle.Render("Type the client name to enable confirmation.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build help section
|
// Build help section
|
||||||
helpText := deleteHelpStyle.Render("Esc to cancel • Enter to confirm (when name matches)")
|
helpText := modalHelpStyle.Render("Esc to cancel • Enter to confirm (when name matches)")
|
||||||
|
|
||||||
// Build modal content
|
// Build modal content
|
||||||
content := lipgloss.JoinVertical(
|
content := lipgloss.JoinVertical(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
deleteTitleStyle.Render("🗑️ Delete Client"),
|
modalTitleStyle.Render("🗑️ Delete Client"),
|
||||||
"",
|
"",
|
||||||
warningText,
|
warningText,
|
||||||
"",
|
"",
|
||||||
@@ -155,7 +131,7 @@ func (m *DeleteConfirmModel) View() string {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Apply modal style
|
// Apply modal style
|
||||||
modal := deleteModalStyle.Render(content)
|
modal := modalBaseStyle.Padding(1, 3).Render(content)
|
||||||
|
|
||||||
// Center modal on screen
|
// Center modal on screen
|
||||||
modalWidth := lipgloss.Width(modal)
|
modalWidth := lipgloss.Width(modal)
|
||||||
@@ -174,7 +150,7 @@ func (m *DeleteConfirmModel) View() string {
|
|||||||
lipgloss.Left, lipgloss.Top,
|
lipgloss.Left, lipgloss.Top,
|
||||||
modal,
|
modal,
|
||||||
lipgloss.WithWhitespaceChars(" "),
|
lipgloss.WithWhitespaceChars(" "),
|
||||||
lipgloss.WithWhitespaceForeground(lipgloss.Color("235")),
|
lipgloss.WithWhitespaceForeground(theme.StyleBackground),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package components
|
package components
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/calmcacil/wg-admin/internal/tui/theme"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -30,22 +31,24 @@ type SearchModel struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
|
|
||||||
|
// Styles (using theme package)
|
||||||
var (
|
var (
|
||||||
searchBarStyle = lipgloss.NewStyle().
|
searchBarStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("255")).
|
Foreground(lipgloss.Color("255")).
|
||||||
Background(lipgloss.Color("235")).
|
Background(theme.StyleBackground).
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Border(lipgloss.RoundedBorder()).
|
Border(lipgloss.RoundedBorder()).
|
||||||
BorderForeground(lipgloss.Color("240"))
|
BorderForeground(theme.StyleBorder)
|
||||||
searchPromptStyle = lipgloss.NewStyle().
|
searchPromptStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("226")).
|
Foreground(lipgloss.Color("226")).
|
||||||
Bold(true)
|
Bold(true)
|
||||||
searchFilterStyle = lipgloss.NewStyle().
|
searchFilterStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("147"))
|
Foreground(lipgloss.Color("147"))
|
||||||
searchCountStyle = lipgloss.NewStyle().
|
searchCountStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("241"))
|
Foreground(lipgloss.Color("241"))
|
||||||
searchHelpStyle = lipgloss.NewStyle().
|
searchHelpStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("243"))
|
Foreground(lipgloss.Color("243"))
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSearch creates a new search component
|
// NewSearch creates a new search component
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package screens
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/calmcacil/wg-admin/internal/tui/components"
|
"github.com/calmcacil/wg-admin/internal/tui/components"
|
||||||
|
"github.com/calmcacil/wg-admin/internal/tui/theme"
|
||||||
"github.com/charmbracelet/bubbletea"
|
"github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
)
|
)
|
||||||
@@ -41,55 +42,41 @@ func (s *HelpScreen) View() string {
|
|||||||
// Breadcrumb: Help
|
// Breadcrumb: Help
|
||||||
breadcrumb := components.RenderBreadcrumb([]components.BreadcrumbItem{{Label: "Help", ID: "help"}})
|
breadcrumb := components.RenderBreadcrumb([]components.BreadcrumbItem{{Label: "Help", ID: "help"}})
|
||||||
|
|
||||||
// Styles
|
// Styles using theme
|
||||||
borderStyle := lipgloss.NewStyle().
|
borderStyle := lipgloss.NewStyle().
|
||||||
BorderStyle(lipgloss.RoundedBorder()).
|
BorderStyle(lipgloss.RoundedBorder()).
|
||||||
BorderForeground(lipgloss.Color("62")).
|
BorderForeground(theme.StyleBorder).
|
||||||
Padding(1, 2)
|
Padding(1, 2)
|
||||||
|
|
||||||
headerStyle := lipgloss.NewStyle().
|
keyStyle := theme.StyleHelpKey.Width(12)
|
||||||
Foreground(lipgloss.Color("62")).
|
|
||||||
Bold(true).
|
|
||||||
MarginBottom(1)
|
|
||||||
|
|
||||||
categoryStyle := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("226")).
|
|
||||||
Bold(true).
|
|
||||||
MarginTop(1).
|
|
||||||
MarginBottom(0)
|
|
||||||
|
|
||||||
keyStyle := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("63")).
|
|
||||||
Bold(true).
|
|
||||||
Width(12)
|
|
||||||
|
|
||||||
descStyle := lipgloss.NewStyle().
|
descStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("250"))
|
Foreground(lipgloss.Color("250"))
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
header := headerStyle.Render("Keyboard Shortcuts")
|
header := theme.StyleTitle.MarginBottom(1).Render("Keyboard Shortcuts")
|
||||||
|
|
||||||
// Shortcut groups
|
// Shortcut groups
|
||||||
navigationGroup := categoryStyle.Render("Navigation") + "\n" +
|
navigationGroup := theme.StyleWarning.Bold(true).MarginTop(1).MarginBottom(0).Render("Navigation") + "\n" +
|
||||||
keyStyle.Render("j / ↓") + descStyle.Render("Move down") + "\n" +
|
keyStyle.Render("j / ↓") + descStyle.Render("Move down") + "\n" +
|
||||||
keyStyle.Render("k / ↑") + descStyle.Render("Move up") + "\n" +
|
keyStyle.Render("k / ↑") + descStyle.Render("Move up") + "\n" +
|
||||||
keyStyle.Render("← →") + descStyle.Render("Move left/right") + "\n" +
|
keyStyle.Render("← →") + descStyle.Render("Move left/right") + "\n" +
|
||||||
keyStyle.Render("Enter") + descStyle.Render("Select") + "\n" +
|
keyStyle.Render("Enter") + descStyle.Render("Select") + "\n" +
|
||||||
keyStyle.Render("Esc") + descStyle.Render("Go back")
|
keyStyle.Render("Esc") + descStyle.Render("Go back")
|
||||||
|
|
||||||
actionsGroup := categoryStyle.Render("Actions") + "\n" +
|
actionsGroup := theme.StyleWarning.Bold(true).MarginTop(1).MarginBottom(0).Render("Actions") + "\n" +
|
||||||
keyStyle.Render("a") + descStyle.Render("Add client") + "\n" +
|
keyStyle.Render("a") + descStyle.Render("Add client") + "\n" +
|
||||||
keyStyle.Render("d") + descStyle.Render("Delete client") + "\n" +
|
keyStyle.Render("d") + descStyle.Render("Delete client") + "\n" +
|
||||||
keyStyle.Render("Q") + descStyle.Render("Show QR code") + "\n" +
|
keyStyle.Render("Q") + descStyle.Render("Show QR code") + "\n" +
|
||||||
keyStyle.Render("r") + descStyle.Render("Refresh list") + "\n" +
|
keyStyle.Render("r") + descStyle.Render("Refresh list") + "\n" +
|
||||||
keyStyle.Render("l") + descStyle.Render("List view")
|
keyStyle.Render("l") + descStyle.Render("List view")
|
||||||
|
|
||||||
otherGroup := categoryStyle.Render("Other") + "\n" +
|
otherGroup := theme.StyleWarning.Bold(true).MarginTop(1).MarginBottom(0).Render("Other") + "\n" +
|
||||||
keyStyle.Render("?") + descStyle.Render("Show this help") + "\n" +
|
keyStyle.Render("?") + descStyle.Render("Show this help") + "\n" +
|
||||||
keyStyle.Render("/") + descStyle.Render("Search") + "\n" +
|
keyStyle.Render("/") + descStyle.Render("Search") + "\n" +
|
||||||
keyStyle.Render("q") + descStyle.Render("Quit")
|
keyStyle.Render("q") + descStyle.Render("Quit")
|
||||||
|
|
||||||
copyGroup := categoryStyle.Render("Text Selection & Copy") + "\n" +
|
copyGroup := theme.StyleWarning.Bold(true).MarginTop(1).MarginBottom(0).Render("Text Selection & Copy") + "\n" +
|
||||||
keyStyle.Render("SHIFT+drag") + descStyle.Render("Select text") + "\n" +
|
keyStyle.Render("SHIFT+drag") + descStyle.Render("Select text") + "\n" +
|
||||||
keyStyle.Render("Ctrl+Shift+C") + descStyle.Render("Copy (Linux)") + "\n" +
|
keyStyle.Render("Ctrl+Shift+C") + descStyle.Render("Copy (Linux)") + "\n" +
|
||||||
keyStyle.Render("Cmd+C") + descStyle.Render("Copy (macOS)")
|
keyStyle.Render("Cmd+C") + descStyle.Render("Copy (macOS)")
|
||||||
@@ -110,10 +97,7 @@ func (s *HelpScreen) View() string {
|
|||||||
content := lipgloss.JoinHorizontal(lipgloss.Top, leftColumn, " ", rightColumn)
|
content := lipgloss.JoinHorizontal(lipgloss.Top, leftColumn, " ", rightColumn)
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
footerStyle := lipgloss.NewStyle().
|
footer := theme.StyleMuted.MarginTop(1).Render("Press q or Esc to return")
|
||||||
Foreground(lipgloss.Color("241")).
|
|
||||||
MarginTop(1)
|
|
||||||
footer := footerStyle.Render("Press q or Esc to return")
|
|
||||||
|
|
||||||
// Combine all
|
// Combine all
|
||||||
return breadcrumb + "\n\n" + borderStyle.Render(
|
return breadcrumb + "\n\n" + borderStyle.Render(
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ type ListScreen struct {
|
|||||||
|
|
||||||
// ClientWithStatus wraps a client with its connection status
|
// ClientWithStatus wraps a client with its connection status
|
||||||
type ClientWithStatus struct {
|
type ClientWithStatus struct {
|
||||||
Client wireguard.Client
|
Client wireguard.Client
|
||||||
Status string
|
Status string
|
||||||
|
Quality string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListScreen creates a new list screen
|
// NewListScreen creates a new list screen
|
||||||
@@ -204,7 +205,6 @@ func (s *ListScreen) View() string {
|
|||||||
Foreground(lipgloss.Color("241")).
|
Foreground(lipgloss.Color("241")).
|
||||||
Render("Last updated: " + timeAgo)
|
Render("Last updated: " + timeAgo)
|
||||||
|
|
||||||
|
|
||||||
// Add keyboard shortcuts help
|
// Add keyboard shortcuts help
|
||||||
helpText := lipgloss.NewStyle().
|
helpText := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("63")).
|
Foreground(lipgloss.Color("63")).
|
||||||
@@ -225,6 +225,8 @@ func formatDuration(d time.Duration) string {
|
|||||||
return fmt.Sprintf("%d hr ago", int(d.Hours()))
|
return fmt.Sprintf("%d hr ago", int(d.Hours()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadClients loads clients from wireguard config
|
||||||
|
|
||||||
// loadClients loads clients from wireguard config
|
// loadClients loads clients from wireguard config
|
||||||
func (s *ListScreen) loadClients() tea.Msg {
|
func (s *ListScreen) loadClients() tea.Msg {
|
||||||
clients, err := wireguard.ListClients()
|
clients, err := wireguard.ListClients()
|
||||||
@@ -232,16 +234,31 @@ func (s *ListScreen) loadClients() tea.Msg {
|
|||||||
return ErrMsg{Err: err}
|
return ErrMsg{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get status for each client
|
// Get all peer statuses to retrieve quality information
|
||||||
|
peerStatuses, err := wireguard.GetAllPeers()
|
||||||
|
if err != nil {
|
||||||
|
return ErrMsg{Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match clients with their peer status
|
||||||
clientsWithStatus := make([]ClientWithStatus, len(clients))
|
clientsWithStatus := make([]ClientWithStatus, len(clients))
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
status, err := wireguard.GetClientStatus(client.PublicKey)
|
status := wireguard.StatusDisconnected
|
||||||
if err != nil {
|
quality := ""
|
||||||
status = wireguard.StatusDisconnected
|
|
||||||
|
// Find matching peer status
|
||||||
|
for _, peerStatus := range peerStatuses {
|
||||||
|
if peerStatus.PublicKey == client.PublicKey {
|
||||||
|
status = peerStatus.Status
|
||||||
|
quality = peerStatus.Quality
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientsWithStatus[i] = ClientWithStatus{
|
clientsWithStatus[i] = ClientWithStatus{
|
||||||
Client: client,
|
Client: client,
|
||||||
Status: status,
|
Status: status,
|
||||||
|
Quality: quality,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,8 +298,11 @@ func (s *ListScreen) applyFilter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// formatStatusWithIcon formats the status with a colored circle icon
|
// formatStatusWithIcon formats the status with a colored circle icon
|
||||||
func (s *ListScreen) formatStatusWithIcon(status string) string {
|
func (s *ListScreen) formatStatusWithIcon(status string, quality string) string {
|
||||||
if status == wireguard.StatusConnected {
|
if status == wireguard.StatusConnected {
|
||||||
|
if quality != "" {
|
||||||
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("●") + " " + status + " (" + quality + ")"
|
||||||
|
}
|
||||||
return lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("●") + " " + status
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("●") + " " + status
|
||||||
}
|
}
|
||||||
return lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render("●") + " " + status
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render("●") + " " + status
|
||||||
@@ -305,11 +325,30 @@ func (s *ListScreen) buildTable() {
|
|||||||
|
|
||||||
var rows []table.Row
|
var rows []table.Row
|
||||||
for _, cws := range displayClients {
|
for _, cws := range displayClients {
|
||||||
statusText := s.formatStatusWithIcon(cws.Status)
|
// Apply highlighting based on filter type
|
||||||
|
name := cws.Client.Name
|
||||||
|
ipv4 := cws.Client.IPv4
|
||||||
|
ipv6 := cws.Client.IPv6
|
||||||
|
status := cws.Status
|
||||||
|
|
||||||
|
if s.search.IsActive() {
|
||||||
|
switch s.search.GetFilterType() {
|
||||||
|
case components.FilterByName:
|
||||||
|
name = s.search.HighlightMatches(name)
|
||||||
|
case components.FilterByIPv4:
|
||||||
|
ipv4 = s.search.HighlightMatches(ipv4)
|
||||||
|
case components.FilterByIPv6:
|
||||||
|
ipv6 = s.search.HighlightMatches(ipv6)
|
||||||
|
case components.FilterByStatus:
|
||||||
|
status = s.search.HighlightMatches(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusText := s.formatStatusWithIcon(status, "")
|
||||||
row := table.Row{
|
row := table.Row{
|
||||||
cws.Client.Name,
|
name,
|
||||||
cws.Client.IPv4,
|
ipv4,
|
||||||
cws.Client.IPv6,
|
ipv6,
|
||||||
statusText,
|
statusText,
|
||||||
}
|
}
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/calmcacil/wg-admin/internal/tui/components"
|
"github.com/calmcacil/wg-admin/internal/tui/components"
|
||||||
|
"github.com/calmcacil/wg-admin/internal/tui/theme"
|
||||||
"github.com/calmcacil/wg-admin/internal/wireguard"
|
"github.com/calmcacil/wg-admin/internal/wireguard"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
@@ -102,39 +103,22 @@ func (s *QRScreen) generateQRCode() {
|
|||||||
|
|
||||||
// renderQR renders the QR code with styling
|
// renderQR renders the QR code with styling
|
||||||
func (s *QRScreen) renderQR() string {
|
func (s *QRScreen) renderQR() string {
|
||||||
styleTitle := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("62")).
|
|
||||||
Bold(true).
|
|
||||||
MarginBottom(1)
|
|
||||||
|
|
||||||
styleHelp := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("241")).
|
|
||||||
MarginTop(1)
|
|
||||||
|
|
||||||
styleQR := lipgloss.NewStyle().
|
styleQR := lipgloss.NewStyle().
|
||||||
MarginLeft(2)
|
MarginLeft(2)
|
||||||
|
|
||||||
title := styleTitle.Render(fmt.Sprintf("QR Code: %s", s.clientName))
|
title := theme.StyleTitle.MarginBottom(1).Render(fmt.Sprintf("QR Code: %s", s.clientName))
|
||||||
help := "Press [f] to toggle fullscreen • Press [q/Escape] to return"
|
help := "Press [f] to toggle fullscreen • Press [q/Escape] to return"
|
||||||
|
|
||||||
return title + "\n\n" + styleQR.Render(s.qrCode) + "\n" + styleHelp.Render(help)
|
return title + "\n\n" + styleQR.Render(s.qrCode) + "\n" + theme.StyleMuted.MarginTop(1).Render(help)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderError renders an error message
|
// renderError renders an error message
|
||||||
func (s *QRScreen) renderError() string {
|
func (s *QRScreen) renderError() string {
|
||||||
styleError := lipgloss.NewStyle().
|
title := theme.StyleError.Bold(true).Render("Error")
|
||||||
Foreground(lipgloss.Color("196")).
|
|
||||||
Bold(true)
|
|
||||||
|
|
||||||
styleHelp := lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("241")).
|
|
||||||
MarginTop(1)
|
|
||||||
|
|
||||||
title := styleError.Render("Error")
|
|
||||||
message := s.errorMsg
|
message := s.errorMsg
|
||||||
help := "Press [q/Escape] to return"
|
help := "Press [q/Escape] to return"
|
||||||
|
|
||||||
return title + "\n\n" + message + "\n" + styleHelp.Render(help)
|
return title + "\n\n" + message + "\n" + theme.StyleMuted.MarginTop(1).Render(help)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/calmcacil/wg-admin/internal/backup"
|
"github.com/calmcacil/wg-admin/internal/backup"
|
||||||
"github.com/calmcacil/wg-admin/internal/tui/components"
|
"github.com/calmcacil/wg-admin/internal/tui/components"
|
||||||
|
"github.com/calmcacil/wg-admin/internal/tui/theme"
|
||||||
"github.com/charmbracelet/bubbles/spinner"
|
"github.com/charmbracelet/bubbles/spinner"
|
||||||
"github.com/charmbracelet/bubbles/table"
|
"github.com/charmbracelet/bubbles/table"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -26,27 +27,7 @@ type RestoreScreen struct {
|
|||||||
spinner spinner.Model
|
spinner spinner.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
// Styles
|
// No local styles - all use theme package
|
||||||
var (
|
|
||||||
restoreTitleStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("62")).
|
|
||||||
Bold(true)
|
|
||||||
restoreHelpStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("63")).
|
|
||||||
MarginTop(1)
|
|
||||||
restoreSuccessStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("46")).
|
|
||||||
Bold(true)
|
|
||||||
restoreErrorStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("196")).
|
|
||||||
Bold(true)
|
|
||||||
restoreInfoStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("241")).
|
|
||||||
MarginTop(1)
|
|
||||||
restoreLoadingStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("62")).
|
|
||||||
Bold(true)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRestoreScreen creates a new restore screen
|
// NewRestoreScreen creates a new restore screen
|
||||||
func NewRestoreScreen() *RestoreScreen {
|
func NewRestoreScreen() *RestoreScreen {
|
||||||
@@ -184,12 +165,11 @@ func (s *RestoreScreen) renderContent() string {
|
|||||||
// Breadcrumb: Clients > Restore
|
// Breadcrumb: Clients > Restore
|
||||||
breadcrumb := components.RenderBreadcrumb([]components.BreadcrumbItem{{Label: "Clients", ID: "list"}, {Label: "Restore", ID: "restore"}})
|
breadcrumb := components.RenderBreadcrumb([]components.BreadcrumbItem{{Label: "Clients", ID: "list"}, {Label: "Restore", ID: "restore"}})
|
||||||
|
|
||||||
|
|
||||||
var content strings.Builder
|
var content strings.Builder
|
||||||
|
|
||||||
content.WriteString(breadcrumb)
|
content.WriteString(breadcrumb)
|
||||||
content.WriteString("\n")
|
content.WriteString("\n")
|
||||||
content.WriteString(restoreTitleStyle.Render("Restore WireGuard Configuration"))
|
content.WriteString(theme.StyleTitle.Render("Restore WireGuard Configuration"))
|
||||||
content.WriteString("\n\n")
|
content.WriteString("\n\n")
|
||||||
|
|
||||||
if len(s.backups) == 0 && !s.isRestoring && s.message == "" {
|
if len(s.backups) == 0 && !s.isRestoring && s.message == "" {
|
||||||
@@ -198,23 +178,23 @@ func (s *RestoreScreen) renderContent() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.isRestoring {
|
if s.isRestoring {
|
||||||
content.WriteString(restoreLoadingStyle.Render(s.spinner.View() + " Restoring from backup, please wait..."))
|
content.WriteString(theme.StyleTitle.Render(s.spinner.View() + " Restoring from backup, please wait..."))
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.restoreSuccess {
|
if s.restoreSuccess {
|
||||||
content.WriteString(restoreSuccessStyle.Render("✓ " + s.message))
|
content.WriteString(theme.StyleSuccess.Render("✓ " + s.message))
|
||||||
content.WriteString("\n\n")
|
content.WriteString("\n\n")
|
||||||
content.WriteString(restoreInfoStyle.Render("Press 'q' to return to client list."))
|
content.WriteString(theme.StyleMuted.Render("Press 'q' to return to client list."))
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.restoreError != nil {
|
if s.restoreError != nil {
|
||||||
content.WriteString(restoreErrorStyle.Render("✗ " + s.message))
|
content.WriteString(theme.StyleError.Render("✗ " + s.message))
|
||||||
content.WriteString("\n\n")
|
content.WriteString("\n\n")
|
||||||
content.WriteString(s.table.View())
|
content.WriteString(s.table.View())
|
||||||
content.WriteString("\n\n")
|
content.WriteString("\n\n")
|
||||||
content.WriteString(restoreHelpStyle.Render("Actions: [Enter] Restore Selected • [↑/↓] Navigate • [q] Back"))
|
content.WriteString(theme.StyleHelpKey.Render("Actions: [Enter] Restore Selected • [↑/↓] Navigate • [q] Back"))
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +204,7 @@ func (s *RestoreScreen) renderContent() string {
|
|||||||
|
|
||||||
// Show selected backup details
|
// Show selected backup details
|
||||||
if len(s.table.Rows()) > 0 && s.selectedBackup != nil {
|
if len(s.table.Rows()) > 0 && s.selectedBackup != nil {
|
||||||
content.WriteString(restoreInfoStyle.Render(
|
content.WriteString(theme.StyleMuted.Render(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"Selected: %s (%s) - %s\nSize: %s",
|
"Selected: %s (%s) - %s\nSize: %s",
|
||||||
s.selectedBackup.Operation,
|
s.selectedBackup.Operation,
|
||||||
@@ -236,7 +216,7 @@ func (s *RestoreScreen) renderContent() string {
|
|||||||
content.WriteString("\n")
|
content.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString(restoreHelpStyle.Render("Actions: [Enter] Restore Selected • [↑/↓] Navigate • [q] Back"))
|
content.WriteString(theme.StyleHelpKey.Render("Actions: [Enter] Restore Selected • [↑/↓] Navigate • [q] Back"))
|
||||||
|
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
@@ -284,15 +264,8 @@ func (s *RestoreScreen) buildTable() {
|
|||||||
// setTableStyles applies styling to the table
|
// setTableStyles applies styling to the table
|
||||||
func (s *RestoreScreen) setTableStyles() {
|
func (s *RestoreScreen) setTableStyles() {
|
||||||
styles := table.DefaultStyles()
|
styles := table.DefaultStyles()
|
||||||
styles.Header = styles.Header.
|
styles.Header = theme.StyleTableHeader
|
||||||
BorderStyle(lipgloss.NormalBorder()).
|
styles.Selected = theme.StyleTableSelected
|
||||||
BorderForeground(lipgloss.Color("240")).
|
|
||||||
BorderBottom(true).
|
|
||||||
Bold(true)
|
|
||||||
styles.Selected = styles.Selected.
|
|
||||||
Foreground(lipgloss.Color("229")).
|
|
||||||
Background(lipgloss.Color("57")).
|
|
||||||
Bold(false)
|
|
||||||
s.table.SetStyles(styles)
|
s.table.SetStyles(styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,54 @@ func ApplyTheme(theme *Theme) {
|
|||||||
StyleBackground = lipgloss.Color("235")
|
StyleBackground = lipgloss.Color("235")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modal styles
|
||||||
|
var (
|
||||||
|
// ModalBaseStyle is the base style for all modals
|
||||||
|
ModalBaseStyle = func() lipgloss.Style {
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(lipgloss.Color("196")).
|
||||||
|
Padding(1, 2).
|
||||||
|
Background(StyleBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModalTitleStyle is the style for modal titles
|
||||||
|
ModalTitleStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("226")).
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
// ModalMessageStyle is the style for modal messages
|
||||||
|
ModalMessageStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("255")).
|
||||||
|
Width(50)
|
||||||
|
|
||||||
|
// ModalHelpStyle is the style for modal help text
|
||||||
|
ModalHelpStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("241")).
|
||||||
|
MarginTop(1)
|
||||||
|
|
||||||
|
// ModalSelectedStyle is the style for selected modal options
|
||||||
|
ModalSelectedStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("57")).
|
||||||
|
Bold(true).
|
||||||
|
Underline(true)
|
||||||
|
|
||||||
|
// ModalUnselectedStyle is the style for unselected modal options
|
||||||
|
ModalUnselectedStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("241"))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status icon styles
|
||||||
|
var (
|
||||||
|
// StatusConnectedStyle is the style for connected status icons
|
||||||
|
StatusConnectedStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("46"))
|
||||||
|
|
||||||
|
// StatusDisconnectedStyle is the style for disconnected status icons
|
||||||
|
StatusDisconnectedStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("196"))
|
||||||
|
)
|
||||||
|
|
||||||
// GetThemeNames returns a list of available theme names
|
// GetThemeNames returns a list of available theme names
|
||||||
func GetThemeNames() []string {
|
func GetThemeNames() []string {
|
||||||
names := make([]string, 0, len(ThemeRegistry))
|
names := make([]string, 0, len(ThemeRegistry))
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func finalizePeerStatus(peer *PeerStatus, handshake string, transfer string) Pee
|
|||||||
// This allows for ~12 missed keepalive intervals (at 25 seconds each)
|
// This allows for ~12 missed keepalive intervals (at 25 seconds each)
|
||||||
if timeSinceHandshake < 5*time.Minute {
|
if timeSinceHandshake < 5*time.Minute {
|
||||||
peer.Status = StatusConnected
|
peer.Status = StatusConnected
|
||||||
peer.Quality = calculateQuality(timeSinceHandshake)
|
peer.Quality = CalculateQuality(timeSinceHandshake)
|
||||||
} else {
|
} else {
|
||||||
peer.Status = StatusDisconnected
|
peer.Status = StatusDisconnected
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user