Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 32 additions & 29 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"unic/internal/config"
"unic/internal/domain"
awsservice "unic/internal/services/aws"
Expand Down Expand Up @@ -825,12 +823,13 @@ func (m Model) View() string {

func (m Model) viewServiceList() string {
var b strings.Builder
var panel strings.Builder
b.WriteString(m.renderStatusBar())
b.WriteString(titleStyle.Render("Select AWS Service"))
b.WriteString("\n\n")

// overhead: status bar (2 lines) + title (1) + blank (1) + blank (1) + footer (1) = 6
visibleLines := max(m.height-6, 3)
// overhead: status bar (2) + title (1) + blank (1) + list panel (2) + blank (1) + help bar (1) = 8
visibleLines := max(m.height-8, 3)
start := 0
if m.svcIdx >= visibleLines {
start = m.svcIdx - visibleLines + 1
Expand All @@ -845,25 +844,27 @@ func (m Model) viewServiceList() string {
cursor = "> "
style = selectedStyle
}
b.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, svc.Name)))
b.WriteString("\n")
panel.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, svc.Name)))
panel.WriteString("\n")
}

b.WriteString("\n")
b.WriteString(dimStyle.Render("↑/↓: navigate • enter: select • esc: context • q: quit"))
b.WriteString(m.renderListPanel(panel.String()))
b.WriteString("\n\n")
b.WriteString(m.renderHelpBar("↑/↓: navigate • enter: select • esc: context • q: quit"))
return b.String()
}

func (m Model) viewFeatureList() string {
var b strings.Builder
var panel strings.Builder
b.WriteString(m.renderStatusBar())
svcName := m.services[m.svcIdx].Name
b.WriteString(titleStyle.Render(fmt.Sprintf("%s > Select Feature", svcName)))
b.WriteString("\n\n")

// Each selected item takes 2 lines (name + description), others take 1.
// overhead: status bar (2) + title (1) + blank (1) + blank (1) + footer (1) = 6
visibleLines := max(m.height-6, 3)
// overhead: status bar (2) + title (1) + blank (1) + list panel (2) + blank (1) + help bar (1) = 8
visibleLines := max(m.height-8, 3)
start := 0
// Count lines from start to cursor to determine if we need to scroll
linesFromStart := 0
Expand Down Expand Up @@ -905,17 +906,18 @@ func (m Model) viewFeatureList() string {
cursor = "> "
style = selectedStyle
}
b.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, feat.Kind)))
b.WriteString("\n")
panel.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, feat.Kind)))
panel.WriteString("\n")
if i == m.featIdx {
b.WriteString(dimStyle.Render(fmt.Sprintf(" %s", feat.Description)))
b.WriteString("\n")
panel.WriteString(dimStyle.Render(fmt.Sprintf(" %s", feat.Description)))
panel.WriteString("\n")
}
linesUsed += needed
}

b.WriteString("\n")
b.WriteString(dimStyle.Render("↑/↓: navigate • enter: select • esc: back"))
b.WriteString(m.renderListPanel(panel.String()))
b.WriteString("\n\n")
b.WriteString(m.renderHelpBar("↑/↓: navigate • enter: select • esc: back"))
return b.String()
}
func (m Model) loadSecrets() tea.Cmd {
Expand Down Expand Up @@ -996,6 +998,7 @@ func (m Model) updateSecretDetail(msg tea.KeyMsg) (tea.Model, tea.Cmd) {

func (m Model) viewSecretList() string {
var b strings.Builder
var panel strings.Builder
b.WriteString(m.renderStatusBar())
b.WriteString(titleStyle.Render("Secrets Manager"))
b.WriteString("\n")
Expand All @@ -1004,10 +1007,10 @@ func (m Model) viewSecretList() string {
b.WriteString("\n\n")

if len(m.filteredSecrets) == 0 {
b.WriteString(dimStyle.Render(" No matching secrets"))
b.WriteString("\n")
panel.WriteString(dimStyle.Render(" No matching secrets"))
panel.WriteString("\n")
} else {
visibleLines := max(m.height-8, 5)
visibleLines := max(m.height-10, 5)
start := 0
if m.secretIdx >= visibleLines {
start = m.secretIdx - visibleLines + 1
Expand All @@ -1022,16 +1025,17 @@ func (m Model) viewSecretList() string {
cursor = "> "
style = selectedStyle
}
b.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, s.DisplayTitle())))
b.WriteString("\n")
panel.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, s.DisplayTitle())))
panel.WriteString("\n")
}

b.WriteString("\n")
b.WriteString(dimStyle.Render(fmt.Sprintf(" %d/%d secrets", len(m.filteredSecrets), len(m.secrets))))
panel.WriteString("\n")
panel.WriteString(dimStyle.Render(fmt.Sprintf(" %d/%d secrets", len(m.filteredSecrets), len(m.secrets))))
}

b.WriteString("\n")
b.WriteString(dimStyle.Render("↑/↓: navigate • /: filter • enter: detail • esc: back • H: home"))
b.WriteString(m.renderListPanel(panel.String()))
b.WriteString("\n\n")
b.WriteString(m.renderHelpBar("↑/↓: navigate • /: filter • enter: detail • esc: back • H: home"))
return b.String()
}

Expand All @@ -1045,15 +1049,14 @@ func (m Model) viewSecretDetail() string {
b.WriteString(titleStyle.Render("Secret Detail"))
b.WriteString("\n\n")

labelStyle := lipgloss.NewStyle().Width(14)
b.WriteString(normalStyle.Render(fmt.Sprintf(" %s%s", labelStyle.Render("Name"), d.Name)))
b.WriteString(renderDetailLine("Name", normalStyle.Render(d.Name)))
b.WriteString("\n")

kmsKey := d.KMSKeyID
if kmsKey == "" {
kmsKey = dimStyle.Render("(aws/secretsmanager)")
}
b.WriteString(normalStyle.Render(fmt.Sprintf(" %s%s", labelStyle.Render("Encryption Key"), kmsKey)))
b.WriteString(renderDetailLine("Encryption Key", kmsKey))
b.WriteString("\n\n")

if len(d.Values) > 0 {
Expand Down Expand Up @@ -1081,6 +1084,6 @@ func (m Model) viewSecretDetail() string {
}

b.WriteString("\n")
b.WriteString(dimStyle.Render("esc: back • H: home"))
b.WriteString(m.renderHelpBar("esc: back • H: home"))
return b.String()
}
6 changes: 3 additions & 3 deletions internal/app/context_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func (m Model) viewContextAdd() string {
b.WriteString("\n")
}
b.WriteString("\n")
b.WriteString(dimStyle.Render(" ↑/↓: navigate • enter: select • esc: cancel"))
b.WriteString(m.renderHelpBar("↑/↓: navigate • enter: select • esc: cancel"))
return b.String()
}

Expand Down Expand Up @@ -189,10 +189,10 @@ func (m Model) viewContextAdd() string {
b.WriteString("\n")
b.WriteString(normalStyle.Render(" Save this context?"))
b.WriteString("\n\n")
b.WriteString(dimStyle.Render(" enter: save • esc: cancel"))
b.WriteString(m.renderHelpBar("enter: save • esc: cancel"))
} else if m.addStep > 0 {
b.WriteString("\n")
b.WriteString(dimStyle.Render(" enter: next • esc: back"))
b.WriteString(m.renderHelpBar("enter: next • esc: back"))
}

return b.String()
Expand Down
13 changes: 10 additions & 3 deletions internal/app/context_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
const (
defaultContextTableWidth = 52
defaultContextTableHeight = 4
contextTableColumnCount = 4
contextTableCellPadding = 2
)

func newContextTable() table.Model {
Expand Down Expand Up @@ -93,12 +95,17 @@ func contextTableHeight(terminalHeight int) int {
if terminalHeight <= 0 {
return defaultContextTableHeight
}
visibleRows := max(terminalHeight-6, 3)
return visibleRows + 1
// Context picker layout overhead:
// title/filter block (3) + panel border (2) + separator/help bar (2) = 7.
// The table height itself must fit inside the remaining rows.
return max(terminalHeight-7, 3)
}

func contextTableColumns(terminalWidth int) []table.Column {
available := contextTableWidth(terminalWidth)
available := contextTableWidth(terminalWidth) - contextTableColumnCount*contextTableCellPadding
if available < 16 {
available = 16
}

currentWidth := 7
regionWidth := 12
Expand Down
2 changes: 1 addition & 1 deletion internal/app/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (m Model) viewHelp() string {
b.WriteString("\n")
}

b.WriteString(dimStyle.Render("?: close help • esc: close help • enter: close help"))
b.WriteString(m.renderHelpBar("?: close help • esc: close help • enter: close help"))
return strings.TrimRight(b.String(), "\n")
}

Expand Down
50 changes: 27 additions & 23 deletions internal/app/screen_cloudwatchlogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ func (m Model) updateCWLogGroupList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {

func (m Model) viewCWLogGroupList() string {
var b strings.Builder
var panel strings.Builder
b.WriteString(m.renderStatusBar())
b.WriteString(titleStyle.Render("CloudWatch Log Groups"))
b.WriteString("\n")
Expand All @@ -194,8 +195,8 @@ func (m Model) viewCWLogGroupList() string {
b.WriteString("\n\n")

if len(m.filteredCWLogGroups) == 0 {
b.WriteString(dimStyle.Render(" No matching log groups"))
b.WriteString("\n")
panel.WriteString(dimStyle.Render(" No matching log groups"))
panel.WriteString("\n")
} else {
maxName := 4 // "NAME"
for _, g := range m.filteredCWLogGroups {
Expand All @@ -209,10 +210,10 @@ func (m Model) viewCWLogGroupList() string {
nameCol := lipgloss.NewStyle().Width(maxName + 2)
retCol := lipgloss.NewStyle().Width(14)

b.WriteString(dimStyle.Render(" " + nameCol.Render("NAME") + retCol.Render("RETENTION") + "SIZE"))
b.WriteString("\n")
panel.WriteString(dimStyle.Render(" " + nameCol.Render("NAME") + retCol.Render("RETENTION") + "SIZE"))
panel.WriteString("\n")

visibleLines := max(m.height-9, 5)
visibleLines := max(m.height-11, 5)
start := 0
if m.cwLogGroupIdx >= visibleLines {
start = m.cwLogGroupIdx - visibleLines + 1
Expand All @@ -239,20 +240,21 @@ func (m Model) viewCWLogGroupList() string {
nameCol.Inherit(style).Render(name) +
retCol.Inherit(dimStyle).Render(retention) +
dimStyle.Render(awsservice.FormatBytes(g.StoredBytes))
b.WriteString(row)
b.WriteString("\n")
panel.WriteString(row)
panel.WriteString("\n")
}

b.WriteString("\n")
panel.WriteString("\n")
countLine := fmt.Sprintf(" %d/%d log groups", len(m.filteredCWLogGroups), len(m.cwLogGroups))
if m.cwLogGroupNextToken != nil {
countLine += " • more available"
}
b.WriteString(dimStyle.Render(countLine))
panel.WriteString(dimStyle.Render(countLine))
}

b.WriteString("\n")
b.WriteString(dimStyle.Render("↑/↓: navigate • /: filter • n: load more • enter: streams • esc: back • H: home"))
b.WriteString(m.renderListPanel(panel.String()))
b.WriteString("\n\n")
b.WriteString(m.renderHelpBar("↑/↓: navigate • /: filter • n: load more • enter: streams • esc: back • H: home"))
return b.String()
}

Expand Down Expand Up @@ -302,6 +304,7 @@ func (m Model) updateCWLogStreamList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {

func (m Model) viewCWLogStreamList() string {
var b strings.Builder
var panel strings.Builder
b.WriteString(m.renderStatusBar())
groupName := ""
if m.selectedCWLogGroup != nil {
Expand All @@ -314,8 +317,8 @@ func (m Model) viewCWLogStreamList() string {
b.WriteString("\n\n")

if len(m.filteredCWLogStreams) == 0 {
b.WriteString(dimStyle.Render(" No matching log streams"))
b.WriteString("\n")
panel.WriteString(dimStyle.Render(" No matching log streams"))
panel.WriteString("\n")
} else {
maxName := 4
for _, s := range m.filteredCWLogStreams {
Expand All @@ -328,10 +331,10 @@ func (m Model) viewCWLogStreamList() string {
}
nameCol := lipgloss.NewStyle().Width(maxName + 2)

b.WriteString(dimStyle.Render(" " + nameCol.Render("NAME") + "LAST EVENT"))
b.WriteString("\n")
panel.WriteString(dimStyle.Render(" " + nameCol.Render("NAME") + "LAST EVENT"))
panel.WriteString("\n")

visibleLines := max(m.height-9, 5)
visibleLines := max(m.height-11, 5)
start := 0
if m.cwLogStreamIdx >= visibleLines {
start = m.cwLogStreamIdx - visibleLines + 1
Expand All @@ -357,20 +360,21 @@ func (m Model) viewCWLogStreamList() string {
row := cursor +
nameCol.Inherit(style).Render(name) +
dimStyle.Render(lastEvent)
b.WriteString(row)
b.WriteString("\n")
panel.WriteString(row)
panel.WriteString("\n")
}

b.WriteString("\n")
panel.WriteString("\n")
countLine := fmt.Sprintf(" %d/%d streams", len(m.filteredCWLogStreams), len(m.cwLogStreams))
if m.cwLogStreamNextToken != nil {
countLine += " • more available"
}
b.WriteString(dimStyle.Render(countLine))
panel.WriteString(dimStyle.Render(countLine))
}

b.WriteString("\n")
b.WriteString(dimStyle.Render("↑/↓: navigate • /: filter • n: load more • enter: view logs • esc: back • H: home"))
b.WriteString(m.renderListPanel(panel.String()))
b.WriteString("\n\n")
b.WriteString(m.renderHelpBar("↑/↓: navigate • /: filter • n: load more • enter: view logs • esc: back • H: home"))
return b.String()
}

Expand Down Expand Up @@ -519,7 +523,7 @@ func (m Model) viewCWLogViewer() string {
hint += fmt.Sprintf(" • h/l: horizontal (%d)", m.cwLogHorizontalOffset)
}
hint += " • n: load more • esc: back"
b.WriteString(dimStyle.Render(hint))
b.WriteString(m.renderHelpBar(hint))
return b.String()
}

Expand Down
24 changes: 13 additions & 11 deletions internal/app/screen_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,30 +172,32 @@ func (m Model) doFinalizeContextSwitch() tea.Cmd {

func (m Model) viewContextPicker() string {
var b strings.Builder
var panel strings.Builder
b.WriteString(titleStyle.Render("Select Context"))
b.WriteString("\n")

b.WriteString(m.renderFilterValue(filterContexts))
b.WriteString("\n\n")

if len(m.ctxList) == 0 {
b.WriteString(normalStyle.Render(" No contexts defined."))
b.WriteString("\n\n")
b.WriteString(dimStyle.Render(" Press 'a' to add your first context."))
b.WriteString("\n")
panel.WriteString(normalStyle.Render(" No contexts defined."))
panel.WriteString("\n\n")
panel.WriteString(dimStyle.Render(" Press 'a' to add your first context."))
panel.WriteString("\n")
} else if len(m.filteredCtxList) == 0 {
b.WriteString(dimStyle.Render(" No matching contexts"))
b.WriteString("\n")
panel.WriteString(dimStyle.Render(" No matching contexts"))
panel.WriteString("\n")
} else {
b.WriteString(m.contextTable.View())
b.WriteString("\n")
panel.WriteString(m.contextTable.View())
panel.WriteString("\n")
}

b.WriteString("\n")
b.WriteString(m.renderListPanel(panel.String()))
b.WriteString("\n\n")
if m.cfg.ContextName != "" {
b.WriteString(dimStyle.Render("↑/↓: navigate • /: filter • enter: select • a: add • esc: back • q: quit"))
b.WriteString(m.renderHelpBar("↑/↓: navigate • /: filter • enter: select • a: add • esc: back • q: quit"))
} else {
b.WriteString(dimStyle.Render("↑/↓: navigate • /: filter • enter: select • a: add • q: quit"))
b.WriteString(m.renderHelpBar("↑/↓: navigate • /: filter • enter: select • a: add • q: quit"))
}
return b.String()
}
Loading
Loading