Files
proxy/internal/tui/pages.go
T

177 lines
6.0 KiB
Go

package tui
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
"proxyctl/internal/profile"
)
func (p terminalPresentation) mainView(model presentationState, width, height int) terminalPage {
var header strings.Builder
header.WriteString(p.theme.title.Render("proxyctl — Target panel"))
header.WriteString("\n\n")
if model.snapshot.ActiveProfile == "" {
header.WriteString(p.theme.warning.Render("No Active Profile"))
header.WriteString("\nCreate and activate a Proxy Profile before enabling Targets.")
} else {
fmt.Fprintf(&header, "Active Profile: %s", model.snapshot.ActiveProfile)
}
list, details, focusLine := p.targetRegions(model)
page := terminalPage{
header: header.String(),
primary: list,
context: details,
footer: "Space: toggle ↑/k ↓/j: select p: manage Proxy Profiles\nr: refresh ?: help q/Esc: quit",
focusLine: focusLine,
}
if width >= wideWidth && height >= wideHeight {
leftWidth := (width - 2) * 2 / 5
rightWidth := width - leftWidth - 2
left := lipgloss.NewStyle().Width(leftWidth).Render(list)
right := lipgloss.NewStyle().Width(rightWidth).Render(details)
page.primary = lipgloss.JoinHorizontal(lipgloss.Top, left, " ", right)
page.context = ""
}
if model.targetConfirm && !model.applying {
page.footer = "y: confirm n/Esc: cancel"
}
return page
}
func (p terminalPresentation) targetRegions(model presentationState) (listView, detailsView string, focusLine int) {
targets := availableTargets(model.snapshot)
var list strings.Builder
list.WriteString(p.theme.selected.Render("Targets"))
if len(targets) == 0 {
list.WriteString("\nNo Targets are currently Available. Press ? to review detection reasons.")
}
for i, item := range targets {
cursor := " "
if i == model.selected {
cursor = "> "
}
applying := (model.progressTarget != "" && item.ID == model.progressTarget) ||
(!model.profileTransition && model.progressTarget == "" && model.applying && i == model.selected)
fmt.Fprintf(&list, "\n%s[%s] %s", cursor, p.theme.renderTargetState(item.State, applying), item.Description.Name)
}
var details strings.Builder
details.WriteString(p.theme.section.Render("Target details"))
if model.selected >= 0 && model.selected < len(targets) {
selected := targets[model.selected]
fmt.Fprintf(&details, "\n%s\n%s\n%s", selected.Description.Summary, selected.Description.Details, selected.Evidence)
} else {
details.WriteString("\nSelect an Available Target to review its details.")
}
return list.String(), details.String(), model.selected + 1
}
func (p terminalPresentation) helpView(model presentationState) terminalPage {
var body strings.Builder
for _, item := range model.snapshot.Targets {
fmt.Fprintf(&body, "%s\n%s\n%s\n", p.theme.section.Render(item.Description.Name), item.Description.Summary, item.Description.Details)
if item.Available {
fmt.Fprintf(&body, "Detected: %s\n\n", item.AvailabilityEvidence)
} else {
fmt.Fprintf(&body, "Not detected: %s\n\n", item.AvailabilityEvidence)
}
}
return terminalPage{
header: p.theme.title.Render("proxyctl Help — built-in Targets"),
primary: strings.TrimRight(body.String(), "\n"),
footer: "↑/k ↓/j: navigate Space: toggle p: manage Proxy Profiles\nr: refresh ?: help q/Escape: back",
focusLine: 0,
}
}
func (p terminalPresentation) profilesView(model presentationState) terminalPage {
var list strings.Builder
if len(model.snapshot.Profiles) == 0 {
list.WriteString("No Proxy Profiles. Press n to create one.")
}
for i, item := range model.snapshot.Profiles {
cursor, active := " ", " "
if i == model.selected {
cursor = "> "
}
if item.Name == model.snapshot.ActiveProfile {
active = "*"
}
if list.Len() > 0 {
list.WriteByte('\n')
}
fmt.Fprintf(&list, "%s%s %s", cursor, active, item.Name)
}
context := selectedProfileDetails(p, model)
footer := "n: new e: edit d: delete a/Enter: activate\n↑/k ↓/j: select q/Esc: back"
switch {
case model.profileConfirm:
footer = "y: confirm n/Esc: cancel"
case model.confirm:
footer = "y: confirm n/Esc: cancel"
if len(model.snapshot.Profiles) == 1 {
context = "Delete Profile — this is the final Proxy Profile. Managed configuration will remain in place and become Drifted."
} else {
context = "Delete Profile?"
}
}
return terminalPage{
header: p.theme.title.Render("Proxy Profiles"),
primary: list.String(),
context: context,
footer: footer,
focusLine: model.selected,
}
}
func selectedProfileDetails(p terminalPresentation, model presentationState) string {
selected, ok := profileAt(model.snapshot, model.selected)
if !ok {
return ""
}
var details strings.Builder
details.WriteString(p.theme.section.Render("Selected Profile"))
fmt.Fprintf(&details, "\nMode: %s", selected.Mode)
if selected.Mode == profile.SingleMode {
fmt.Fprintf(&details, "\nProxy: %s", profile.ObscureURL(selected.SingleURL))
} else {
fmt.Fprintf(&details, "\nHTTP: %s\nHTTPS: %s\nALL/SOCKS: %s",
profile.ObscureURL(selected.HTTPURL), profile.ObscureURL(selected.HTTPSURL), profile.ObscureURL(selected.AllURL))
}
fmt.Fprintf(&details, "\nBypass: %s", strings.Join(selected.Bypass, ", "))
return details.String()
}
func (p terminalPresentation) formView(model presentationState) terminalPage {
action := "Create"
if model.form.originalName != "" {
action = "Edit"
}
var fields strings.Builder
for i, label := range model.form.labels() {
cursor := " "
if i == model.form.field {
cursor = "> "
}
value := model.form.valueAt(i)
if label != "Name" && label != "Bypass" {
value = profile.ObscureURL(value)
}
if fields.Len() > 0 {
fields.WriteByte('\n')
}
fmt.Fprintf(&fields, "%s%s: %s", cursor, label, value)
}
return terminalPage{
header: p.theme.title.Render(fmt.Sprintf("%s Proxy Profile — %s mode", action, model.form.profile.Mode)),
primary: fields.String(),
footer: "Tab/↑/↓: move Ctrl+T: toggle Single/Advanced\nCtrl+S: save Ctrl+D: detect protocol Esc: cancel",
focusLine: model.form.field,
}
}