feat: implement service history view and tracking

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-28 16:01:45 +01:00
parent 4b41a85a5b
commit d33b4e8676
7 changed files with 239 additions and 26 deletions

View File

@@ -15,17 +15,28 @@ import (
)
var baseStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("240"))
type viewState int
const (
viewList viewState = iota
viewDetail
)
type model struct {
table table.Model
services []models.Service
user string
err error
message string
width int
height int
table table.Model
services []models.Service
user string
err error
message string
width int
height int
state viewState
history []models.HistoryEntry
historyTable table.Model
}
type tickMsg time.Time
@@ -37,6 +48,10 @@ func tick() tea.Cmd {
}
func (m model) Init() tea.Cmd {
// If in detail view, only tick (no auto refresh of history for now to keep it simple)
if m.state == viewDetail {
return tick()
}
return tea.Batch(fetchServices, tick())
}
@@ -48,8 +63,25 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch msg.String() {
case "esc", "q", "ctrl+c":
if m.state == viewDetail {
m.state = viewList
m.table.Focus()
return m, nil
}
return m, tea.Quit
case "d":
if m.state == viewList {
selected := m.table.SelectedRow()
if selected != nil {
serviceName := selected[0]
m.state = viewDetail
return m, fetchHistoryCmd(serviceName)
}
}
case "enter":
if m.state == viewDetail {
return m, nil
}
selected := m.table.SelectedRow()
if selected == nil {
return m, nil
@@ -139,10 +171,29 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.table.SetRows(rows)
m.message = "Refreshed"
case []models.HistoryEntry:
m.history = msg
rows := []table.Row{}
for _, h := range msg {
rows = append(rows, table.Row{
h.Action,
h.User,
h.Timestamp.Format("2006-01-02 15:04:05"),
})
}
m.historyTable.SetRows(rows)
// Update footer logic to resize history table if needed
m.historyTable.SetWidth(m.width - 10)
m.historyTable.SetHeight(m.table.Height())
case error:
m.err = msg
}
m.table, cmd = m.table.Update(msg)
if m.state == viewList {
m.table, cmd = m.table.Update(msg)
} else {
m.historyTable, cmd = m.historyTable.Update(msg)
}
return m, cmd
}
@@ -191,12 +242,23 @@ func (m model) View() string {
Align(lipgloss.Center).
Width(contentWidth)
var mainView string
if m.state == viewList {
mainView = m.table.View()
} else {
// History View
mainView = lipgloss.JoinVertical(lipgloss.Left,
"History for selected service:",
m.historyTable.View(),
)
}
// Render Content
content := lipgloss.JoinVertical(lipgloss.Left,
headerStyle.Render("🛡️ ENV-GUARD v1.0"),
infoStyle.Render(userInfo),
m.table.View(),
footerStyle.Render("[↑/↓] Navigate [Enter] Toggle Lock [r] Refresh [q] Quit"),
mainView,
footerStyle.Render("[↑/↓] Navigate [Enter] Toggle Lock [d] History [r] Refresh [q] Quit"),
)
// Create Border Style locally
@@ -221,6 +283,16 @@ func fetchServices() tea.Msg {
return services
}
func fetchHistoryCmd(serviceName string) tea.Cmd {
return func() tea.Msg {
hist, err := api.GetHistory(serviceName)
if err != nil {
return err
}
return hist
}
}
func main() {
serverURL := flag.String("server", "http://localhost:8080", "URL of the EnvGuard server")
flag.Parse()
@@ -257,9 +329,23 @@ func main() {
Bold(false)
t.SetStyles(s)
// History Table
hCols := []table.Column{
{Title: "ACTION", Width: 10},
{Title: "USER", Width: 15},
{Title: "TIME", Width: 20},
}
ht := table.New(
table.WithColumns(hCols),
table.WithFocused(true),
table.WithHeight(10),
)
ht.SetStyles(s)
m := model{
table: t,
user: user,
table: t,
historyTable: ht,
user: user,
}
if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {