feat: Archived alerts tab with archive/unarchive actions
Release / build (amd64, linux) (push) Failing after 6s
Release / release (push) Has been skipped
Release / build (amd64, darwin) (push) Failing after 5s
Release / build (arm64, darwin) (push) Failing after 6s
Release / build (arm64, linux) (push) Failing after 11s
Release / build (amd64, linux) (push) Failing after 6s
Release / release (push) Has been skipped
Release / build (amd64, darwin) (push) Failing after 5s
Release / build (arm64, darwin) (push) Failing after 6s
Release / build (arm64, linux) (push) Failing after 11s
Add a fourth tab (Alerts | Archived | Schedule | Users). Archived alerts are fetched lazily on first visit using the archived=true query param on GET /api/alerts. Press x from the Alerts list or detail to archive an alert; the non-archived list refreshes immediately. Press x from the Archived list or detail to unarchive; the archived list refreshes. Ack/unack are disabled in the Archived detail view. New API methods: ArchiveAlert (POST), UnarchiveAlert (DELETE). ArchivedAt field added to the Alert type.
This commit is contained in:
+23
-2
@@ -61,12 +61,16 @@ func (c *Client) do(req *http.Request, out any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAlerts fetches alerts from the server. status may be "firing", "resolved", or "" for all.
|
// ListAlerts fetches alerts. status may be "firing", "resolved", or "" for all.
|
||||||
func (c *Client) ListAlerts(status string, limit int) ([]Alert, error) {
|
// Set archived=true to fetch only archived alerts; false returns only non-archived.
|
||||||
|
func (c *Client) ListAlerts(status string, archived bool, limit int) ([]Alert, error) {
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
if status != "" {
|
if status != "" {
|
||||||
q.Set("status", status)
|
q.Set("status", status)
|
||||||
}
|
}
|
||||||
|
if archived {
|
||||||
|
q.Set("archived", "true")
|
||||||
|
}
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
q.Set("limit", strconv.Itoa(limit))
|
q.Set("limit", strconv.Itoa(limit))
|
||||||
}
|
}
|
||||||
@@ -83,6 +87,23 @@ func (c *Client) ListAlerts(status string, limit int) ([]Alert, error) {
|
|||||||
return alerts, c.do(req, &alerts)
|
return alerts, c.do(req, &alerts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ArchiveAlert(id int64) (*Alert, error) {
|
||||||
|
req, err := c.newRequest(http.MethodPost, fmt.Sprintf("/api/alerts/%d/archive", id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var alert Alert
|
||||||
|
return &alert, c.do(req, &alert)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UnarchiveAlert(id int64) error {
|
||||||
|
req, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/api/alerts/%d/archive", id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.do(req, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// GetAlertStats fetches aggregate alert counts.
|
// GetAlertStats fetches aggregate alert counts.
|
||||||
func (c *Client) GetAlertStats() (*AlertStats, error) {
|
func (c *Client) GetAlertStats() (*AlertStats, error) {
|
||||||
req, err := c.newRequest(http.MethodGet, "/api/stats/alerts")
|
req, err := c.newRequest(http.MethodGet, "/api/stats/alerts")
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type Alert struct {
|
|||||||
AcknowledgedByID *int64 `json:"acknowledged_by_id"`
|
AcknowledgedByID *int64 `json:"acknowledged_by_id"`
|
||||||
AcknowledgedBy string `json:"acknowledged_by"`
|
AcknowledgedBy string `json:"acknowledged_by"`
|
||||||
AcknowledgedAt *time.Time `json:"acknowledged_at"`
|
AcknowledgedAt *time.Time `json:"acknowledged_at"`
|
||||||
|
ArchivedAt *time.Time `json:"archived_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertStats struct {
|
type AlertStats struct {
|
||||||
|
|||||||
+61
-2
@@ -18,7 +18,8 @@ import (
|
|||||||
type section int
|
type section int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sectionAlerts section = iota
|
sectionAlerts section = iota
|
||||||
|
sectionArchived
|
||||||
sectionSchedule
|
sectionSchedule
|
||||||
sectionUsers
|
sectionUsers
|
||||||
)
|
)
|
||||||
@@ -53,6 +54,9 @@ const (
|
|||||||
type connectedMsg struct{}
|
type connectedMsg struct{}
|
||||||
type connectErrMsg struct{ err error }
|
type connectErrMsg struct{ err error }
|
||||||
type alertsFetchedMsg struct{ alerts []api.Alert }
|
type alertsFetchedMsg struct{ alerts []api.Alert }
|
||||||
|
type archivedAlertsFetchedMsg struct{ alerts []api.Alert }
|
||||||
|
type alertArchivedMsg struct{ alerts []api.Alert }
|
||||||
|
type alertUnarchivedMsg struct{ alerts []api.Alert }
|
||||||
type statsFetchedMsg struct{ stats api.AlertStats }
|
type statsFetchedMsg struct{ stats api.AlertStats }
|
||||||
type fetchDataErrMsg struct{ err error }
|
type fetchDataErrMsg struct{ err error }
|
||||||
type tickMsg time.Time
|
type tickMsg time.Time
|
||||||
@@ -113,6 +117,11 @@ type Model struct {
|
|||||||
filterStatus string
|
filterStatus string
|
||||||
alertTable table.Model
|
alertTable table.Model
|
||||||
|
|
||||||
|
// Archived alerts
|
||||||
|
archivedAlerts []api.Alert
|
||||||
|
archivedLoading bool
|
||||||
|
archivedTable table.Model
|
||||||
|
|
||||||
// Detail
|
// Detail
|
||||||
selectedAlert api.Alert
|
selectedAlert api.Alert
|
||||||
comments []api.Comment
|
comments []api.Comment
|
||||||
@@ -168,6 +177,9 @@ func NewModel(client *api.Client, serverURL string, refreshInterval time.Duratio
|
|||||||
alertT := table.New(table.WithFocused(true))
|
alertT := table.New(table.WithFocused(true))
|
||||||
alertT.SetStyles(ts)
|
alertT.SetStyles(ts)
|
||||||
|
|
||||||
|
archivedT := table.New(table.WithFocused(true))
|
||||||
|
archivedT.SetStyles(ts)
|
||||||
|
|
||||||
schedT := table.New(table.WithFocused(true))
|
schedT := table.New(table.WithFocused(true))
|
||||||
schedT.SetStyles(ts)
|
schedT.SetStyles(ts)
|
||||||
|
|
||||||
@@ -215,6 +227,7 @@ func NewModel(client *api.Client, serverURL string, refreshInterval time.Duratio
|
|||||||
filterStatus: "firing",
|
filterStatus: "firing",
|
||||||
commentCursor: -1,
|
commentCursor: -1,
|
||||||
alertTable: alertT,
|
alertTable: alertT,
|
||||||
|
archivedTable: archivedT,
|
||||||
commentInput: ti,
|
commentInput: ti,
|
||||||
scheduleWindow: window,
|
scheduleWindow: window,
|
||||||
scheduleTable: schedT,
|
scheduleTable: schedT,
|
||||||
@@ -254,6 +267,16 @@ func (m *Model) rebuildTable() {
|
|||||||
m.alertTable.SetHeight(h)
|
m.alertTable.SetHeight(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) rebuildArchivedTable() {
|
||||||
|
m.archivedTable.SetColumns(alertColumns(m.width))
|
||||||
|
m.archivedTable.SetRows(alertRows(m.archivedAlerts))
|
||||||
|
h := m.height - 8
|
||||||
|
if h < 1 {
|
||||||
|
h = 1
|
||||||
|
}
|
||||||
|
m.archivedTable.SetHeight(h)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) rebuildScheduleTable() {
|
func (m *Model) rebuildScheduleTable() {
|
||||||
m.scheduleTable.SetColumns(scheduleColumns(m.width))
|
m.scheduleTable.SetColumns(scheduleColumns(m.width))
|
||||||
m.scheduleTable.SetRows(scheduleRows(m.scheduleDays))
|
m.scheduleTable.SetRows(scheduleRows(m.scheduleDays))
|
||||||
@@ -466,7 +489,7 @@ func connectCmd(client *api.Client) tea.Cmd {
|
|||||||
|
|
||||||
func fetchAlertsCmd(client *api.Client, status string) tea.Cmd {
|
func fetchAlertsCmd(client *api.Client, status string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
alerts, err := client.ListAlerts(status, 500)
|
alerts, err := client.ListAlerts(status, false, 500)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fetchDataErrMsg{err}
|
return fetchDataErrMsg{err}
|
||||||
}
|
}
|
||||||
@@ -474,6 +497,42 @@ func fetchAlertsCmd(client *api.Client, status string) tea.Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchArchivedAlertsCmd(client *api.Client) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
alerts, err := client.ListAlerts("", true, 500)
|
||||||
|
if err != nil {
|
||||||
|
return fetchDataErrMsg{err}
|
||||||
|
}
|
||||||
|
return archivedAlertsFetchedMsg{alerts}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func archiveAlertCmd(client *api.Client, alertID int64, filterStatus string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
if _, err := client.ArchiveAlert(alertID); err != nil {
|
||||||
|
return actionErrMsg{err}
|
||||||
|
}
|
||||||
|
alerts, err := client.ListAlerts(filterStatus, false, 500)
|
||||||
|
if err != nil {
|
||||||
|
return actionErrMsg{err}
|
||||||
|
}
|
||||||
|
return alertArchivedMsg{alerts}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unarchiveAlertCmd(client *api.Client, alertID int64) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
if err := client.UnarchiveAlert(alertID); err != nil {
|
||||||
|
return actionErrMsg{err}
|
||||||
|
}
|
||||||
|
alerts, err := client.ListAlerts("", true, 500)
|
||||||
|
if err != nil {
|
||||||
|
return actionErrMsg{err}
|
||||||
|
}
|
||||||
|
return alertUnarchivedMsg{alerts}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fetchStatsCmd(client *api.Client) tea.Cmd {
|
func fetchStatsCmd(client *api.Client) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
stats, err := client.GetAlertStats()
|
stats, err := client.GetAlertStats()
|
||||||
|
|||||||
+81
-2
@@ -16,6 +16,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.width = msg.Width
|
m.width = msg.Width
|
||||||
m.height = msg.Height
|
m.height = msg.Height
|
||||||
m.rebuildTable()
|
m.rebuildTable()
|
||||||
|
m.rebuildArchivedTable()
|
||||||
m.rebuildScheduleTable()
|
m.rebuildScheduleTable()
|
||||||
m.rebuildUserPickerTable()
|
m.rebuildUserPickerTable()
|
||||||
m.rebuildUserManageTable()
|
m.rebuildUserManageTable()
|
||||||
@@ -49,6 +50,28 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.rebuildTable()
|
m.rebuildTable()
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
|
case archivedAlertsFetchedMsg:
|
||||||
|
m.archivedAlerts = msg.alerts
|
||||||
|
m.archivedLoading = false
|
||||||
|
m.rebuildArchivedTable()
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case alertArchivedMsg:
|
||||||
|
m.alerts = msg.alerts
|
||||||
|
m.loading = false
|
||||||
|
m.rebuildTable()
|
||||||
|
m.mode = modeDashboard
|
||||||
|
m.statusMsg = "Alert archived"
|
||||||
|
return m, clearStatusCmd()
|
||||||
|
|
||||||
|
case alertUnarchivedMsg:
|
||||||
|
m.archivedAlerts = msg.alerts
|
||||||
|
m.archivedLoading = false
|
||||||
|
m.rebuildArchivedTable()
|
||||||
|
m.mode = modeDashboard
|
||||||
|
m.statusMsg = "Alert unarchived"
|
||||||
|
return m, clearStatusCmd()
|
||||||
|
|
||||||
case statsFetchedMsg:
|
case statsFetchedMsg:
|
||||||
m.stats = &msg.stats
|
m.stats = &msg.stats
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -215,6 +238,11 @@ func (m Model) routeKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
m.alertTable, tableCmd = m.alertTable.Update(msg)
|
m.alertTable, tableCmd = m.alertTable.Update(msg)
|
||||||
m2, ourCmd := m.handleKey(msg)
|
m2, ourCmd := m.handleKey(msg)
|
||||||
return m2, tea.Batch(tableCmd, ourCmd)
|
return m2, tea.Batch(tableCmd, ourCmd)
|
||||||
|
case sectionArchived:
|
||||||
|
var tableCmd tea.Cmd
|
||||||
|
m.archivedTable, tableCmd = m.archivedTable.Update(msg)
|
||||||
|
m2, ourCmd := m.handleKey(msg)
|
||||||
|
return m2, tea.Batch(tableCmd, ourCmd)
|
||||||
case sectionSchedule:
|
case sectionSchedule:
|
||||||
var tableCmd tea.Cmd
|
var tableCmd tea.Cmd
|
||||||
m.scheduleTable, tableCmd = m.scheduleTable.Update(msg)
|
m.scheduleTable, tableCmd = m.scheduleTable.Update(msg)
|
||||||
@@ -266,8 +294,12 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
|
||||||
case "tab":
|
case "tab":
|
||||||
next := section((int(m.activeSection) + 1) % 3)
|
next := section((int(m.activeSection) + 1) % 4)
|
||||||
m.activeSection = next
|
m.activeSection = next
|
||||||
|
if next == sectionArchived && len(m.archivedAlerts) == 0 {
|
||||||
|
m.archivedLoading = true
|
||||||
|
return m, fetchArchivedAlertsCmd(m.client)
|
||||||
|
}
|
||||||
if next == sectionSchedule && len(m.scheduleDays) == 0 {
|
if next == sectionSchedule && len(m.scheduleDays) == 0 {
|
||||||
m.scheduleLoading = true
|
m.scheduleLoading = true
|
||||||
from := m.scheduleWindow
|
from := m.scheduleWindow
|
||||||
@@ -284,6 +316,10 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
if !m.connected {
|
if !m.connected {
|
||||||
return m, connectCmd(m.client)
|
return m, connectCmd(m.client)
|
||||||
}
|
}
|
||||||
|
if m.activeSection == sectionArchived {
|
||||||
|
m.archivedLoading = true
|
||||||
|
return m, fetchArchivedAlertsCmd(m.client)
|
||||||
|
}
|
||||||
if m.activeSection == sectionSchedule {
|
if m.activeSection == sectionSchedule {
|
||||||
m.scheduleLoading = true
|
m.scheduleLoading = true
|
||||||
from := m.scheduleWindow
|
from := m.scheduleWindow
|
||||||
@@ -319,7 +355,7 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
case "enter":
|
case "enter":
|
||||||
if m.activeSection == sectionAlerts && len(m.alerts) > 0 {
|
if m.activeSection == sectionAlerts && len(m.alerts) > 0 {
|
||||||
cursor := m.alertTable.Cursor()
|
cursor := m.alertTable.Cursor()
|
||||||
if cursor < len(m.alerts) {
|
if cursor >= 0 && cursor < len(m.alerts) {
|
||||||
m.selectedAlert = m.alerts[cursor]
|
m.selectedAlert = m.alerts[cursor]
|
||||||
m.mode = modeDetail
|
m.mode = modeDetail
|
||||||
m.commentCursor = -1
|
m.commentCursor = -1
|
||||||
@@ -328,6 +364,34 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
return m, fetchAlertDetailCmd(m.client, m.selectedAlert.ID)
|
return m, fetchAlertDetailCmd(m.client, m.selectedAlert.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if m.activeSection == sectionArchived && len(m.archivedAlerts) > 0 {
|
||||||
|
cursor := m.archivedTable.Cursor()
|
||||||
|
if cursor >= 0 && cursor < len(m.archivedAlerts) {
|
||||||
|
m.selectedAlert = m.archivedAlerts[cursor]
|
||||||
|
m.mode = modeDetail
|
||||||
|
m.commentCursor = -1
|
||||||
|
m.detailLoading = true
|
||||||
|
m.detailViewport = viewport.New(m.width, m.detailViewportHeight())
|
||||||
|
return m, fetchAlertDetailCmd(m.client, m.selectedAlert.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case "x":
|
||||||
|
switch m.activeSection {
|
||||||
|
case sectionAlerts:
|
||||||
|
cursor := m.alertTable.Cursor()
|
||||||
|
if cursor < 0 || cursor >= len(m.alerts) {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m, archiveAlertCmd(m.client, m.alerts[cursor].ID, m.filterStatus)
|
||||||
|
case sectionArchived:
|
||||||
|
cursor := m.archivedTable.Cursor()
|
||||||
|
if cursor < 0 || cursor >= len(m.archivedAlerts) {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m, unarchiveAlertCmd(m.client, m.archivedAlerts[cursor].ID)
|
||||||
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
// Schedule-specific keys
|
// Schedule-specific keys
|
||||||
@@ -447,6 +511,9 @@ func (m Model) handleDetailKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case "a":
|
case "a":
|
||||||
|
if m.activeSection != sectionAlerts {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
if m.selectedAlert.AcknowledgedByID != nil {
|
if m.selectedAlert.AcknowledgedByID != nil {
|
||||||
m.statusMsg = "already acknowledged"
|
m.statusMsg = "already acknowledged"
|
||||||
return m, clearStatusCmd()
|
return m, clearStatusCmd()
|
||||||
@@ -454,12 +521,24 @@ func (m Model) handleDetailKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
return m, acknowledgeCmd(m.client, m.selectedAlert.ID)
|
return m, acknowledgeCmd(m.client, m.selectedAlert.ID)
|
||||||
|
|
||||||
case "A":
|
case "A":
|
||||||
|
if m.activeSection != sectionAlerts {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
if m.selectedAlert.AcknowledgedByID == nil {
|
if m.selectedAlert.AcknowledgedByID == nil {
|
||||||
m.statusMsg = "not acknowledged"
|
m.statusMsg = "not acknowledged"
|
||||||
return m, clearStatusCmd()
|
return m, clearStatusCmd()
|
||||||
}
|
}
|
||||||
return m, unacknowledgeCmd(m.client, m.selectedAlert.ID)
|
return m, unacknowledgeCmd(m.client, m.selectedAlert.ID)
|
||||||
|
|
||||||
|
case "x":
|
||||||
|
if m.activeSection == sectionAlerts {
|
||||||
|
return m, archiveAlertCmd(m.client, m.selectedAlert.ID, m.filterStatus)
|
||||||
|
}
|
||||||
|
if m.activeSection == sectionArchived {
|
||||||
|
return m, unarchiveAlertCmd(m.client, m.selectedAlert.ID)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
|
||||||
case "c":
|
case "c":
|
||||||
m.mode = modeComment
|
m.mode = modeComment
|
||||||
m.commentInput.Reset()
|
m.commentInput.Reset()
|
||||||
|
|||||||
+37
-2
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/yeniklas/terdut-tui/internal/api"
|
"github.com/yeniklas/terdut-tui/internal/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sectionNames = []string{"Alerts", "Schedule", "Users"}
|
var sectionNames = []string{"Alerts", "Archived", "Schedule", "Users"}
|
||||||
|
|
||||||
func (m Model) View() string {
|
func (m Model) View() string {
|
||||||
if m.width == 0 {
|
if m.width == 0 {
|
||||||
@@ -92,7 +92,12 @@ func (m Model) renderBody() string {
|
|||||||
func (m Model) renderFooter() string {
|
func (m Model) renderFooter() string {
|
||||||
switch m.mode {
|
switch m.mode {
|
||||||
case modeDetail:
|
case modeDetail:
|
||||||
actions := styleFooter.Render(" a·ack A·unack c·comment [/]·select d·del s·assign S·stats esc·back")
|
var actions string
|
||||||
|
if m.activeSection == sectionArchived {
|
||||||
|
actions = styleFooter.Render(" x·unarchive c·comment [/]·select d·del S·stats esc·back")
|
||||||
|
} else {
|
||||||
|
actions = styleFooter.Render(" a·ack A·unack x·archive c·comment [/]·select d·del s·assign S·stats esc·back")
|
||||||
|
}
|
||||||
if m.statusMsg != "" {
|
if m.statusMsg != "" {
|
||||||
return styleStatus.Render(" "+m.statusMsg) + "\n" + actions
|
return styleStatus.Render(" "+m.statusMsg) + "\n" + actions
|
||||||
}
|
}
|
||||||
@@ -163,6 +168,13 @@ func (m Model) renderFooter() string {
|
|||||||
return "\n" + styleFooter.Render(" enter·revoke esc·back")
|
return "\n" + styleFooter.Render(" enter·revoke esc·back")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
if m.activeSection == sectionArchived {
|
||||||
|
actions := styleFooter.Render(" x·unarchive enter·detail r·refresh tab·section q·quit")
|
||||||
|
if m.statusMsg != "" {
|
||||||
|
return styleStatus.Render(" "+m.statusMsg) + "\n" + actions
|
||||||
|
}
|
||||||
|
return "\n" + actions
|
||||||
|
}
|
||||||
if m.activeSection == sectionSchedule {
|
if m.activeSection == sectionSchedule {
|
||||||
actions := styleFooter.Render(" +·assign day W·assign week d·del ←/→·shift week tab·section r·refresh q·quit")
|
actions := styleFooter.Render(" +·assign day W·assign week d·del ←/→·shift week tab·section r·refresh q·quit")
|
||||||
if m.statusMsg != "" {
|
if m.statusMsg != "" {
|
||||||
@@ -177,6 +189,10 @@ func (m Model) renderFooter() string {
|
|||||||
}
|
}
|
||||||
return "\n" + actions
|
return "\n" + actions
|
||||||
}
|
}
|
||||||
|
var footerLeft string
|
||||||
|
if m.activeSection == sectionAlerts {
|
||||||
|
footerLeft = styleFooter.Render(" x·archive")
|
||||||
|
}
|
||||||
helpView := styleFooter.Render(m.help.ShortHelpView(m.keys.ShortHelp()))
|
helpView := styleFooter.Render(m.help.ShortHelpView(m.keys.ShortHelp()))
|
||||||
if m.statusMsg != "" {
|
if m.statusMsg != "" {
|
||||||
status := styleStatus.Render(m.statusMsg)
|
status := styleStatus.Render(m.statusMsg)
|
||||||
@@ -186,6 +202,13 @@ func (m Model) renderFooter() string {
|
|||||||
}
|
}
|
||||||
return "\n" + status + strings.Repeat(" ", gap) + helpView
|
return "\n" + status + strings.Repeat(" ", gap) + helpView
|
||||||
}
|
}
|
||||||
|
if footerLeft != "" {
|
||||||
|
gap := m.width - lipgloss.Width(footerLeft) - lipgloss.Width(helpView)
|
||||||
|
if gap < 0 {
|
||||||
|
gap = 0
|
||||||
|
}
|
||||||
|
return "\n" + footerLeft + strings.Repeat(" ", gap) + helpView
|
||||||
|
}
|
||||||
return "\n" + helpView
|
return "\n" + helpView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,6 +219,8 @@ func (m Model) renderDashboard() string {
|
|||||||
switch m.activeSection {
|
switch m.activeSection {
|
||||||
case sectionAlerts:
|
case sectionAlerts:
|
||||||
return m.renderAlerts()
|
return m.renderAlerts()
|
||||||
|
case sectionArchived:
|
||||||
|
return m.renderArchived()
|
||||||
case sectionSchedule:
|
case sectionSchedule:
|
||||||
return m.renderSchedule()
|
return m.renderSchedule()
|
||||||
case sectionUsers:
|
case sectionUsers:
|
||||||
@@ -284,6 +309,16 @@ func (m Model) renderAlerts() string {
|
|||||||
return lipgloss.JoinVertical(lipgloss.Left, statsBar, content)
|
return lipgloss.JoinVertical(lipgloss.Left, statsBar, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Model) renderArchived() string {
|
||||||
|
if m.archivedLoading {
|
||||||
|
return styleMuted.Render(" Loading archived alerts…")
|
||||||
|
}
|
||||||
|
if len(m.archivedAlerts) == 0 {
|
||||||
|
return styleMuted.Render(" No archived alerts.")
|
||||||
|
}
|
||||||
|
return m.archivedTable.View()
|
||||||
|
}
|
||||||
|
|
||||||
func (m Model) renderStatsBar() string {
|
func (m Model) renderStatsBar() string {
|
||||||
total, firing, resolved := 0, 0, 0
|
total, firing, resolved := 0, 0, 0
|
||||||
if m.stats != nil {
|
if m.stats != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user