Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e04cfcf433 |
@@ -66,3 +66,16 @@ go build -ldflags="-X main.version=v0.1.0" -o terdut-tui .
|
|||||||
| 3 | Alert detail: acknowledge, comment, statistics charts |
|
| 3 | Alert detail: acknowledge, comment, statistics charts |
|
||||||
| 4 | On-call schedule calendar view |
|
| 4 | On-call schedule calendar view |
|
||||||
| 5 | User management and API key lifecycle |
|
| 5 | User management and API key lifecycle |
|
||||||
|
|
||||||
|
<!-- graymatter:instructions:begin — managed by `graymatter init`; edits inside this block are overwritten -->
|
||||||
|
## Memory (GrayMatter)
|
||||||
|
|
||||||
|
This project has persistent agent memory via the `graymatter` MCP tools:
|
||||||
|
|
||||||
|
- `memory_search` (`agent_id`, `query`) — call at the **start of a task** when prior context might matter.
|
||||||
|
- `memory_add` (`agent_id`, `text`) — call whenever you learn something **durable**: user preferences, decisions, conventions, gotchas.
|
||||||
|
- `memory_reflect` (`action`, `agent`, `text`/`target`) — update or forget stale facts. ⚠ takes `agent`, not `agent_id`.
|
||||||
|
- `checkpoint_save` / `checkpoint_resume` (`agent_id`) — snapshot/restore session state before major refactors or across restarts.
|
||||||
|
|
||||||
|
Use a stable `agent_id` of the form `<project>-<role>` (e.g. `myapp-backend`). Store conclusions, not conversation logs. Err on the side of remembering.
|
||||||
|
<!-- graymatter:instructions:end -->
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ type Alert struct {
|
|||||||
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"`
|
ArchivedAt *time.Time `json:"archived_at,omitempty"`
|
||||||
|
|
||||||
|
// ResolutionSource records why a resolved alert left the firing state:
|
||||||
|
// "alertmanager" for a real resolved webhook, "expiry" when the server
|
||||||
|
// inferred it after the alert stopped being refreshed.
|
||||||
|
ResolutionSource *string `json:"resolution_source,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertStats struct {
|
type AlertStats struct {
|
||||||
|
|||||||
@@ -340,18 +340,21 @@ func (m Model) detailViewportHeight() int {
|
|||||||
// ── Column definitions ─────────────────────────────────────────────────────
|
// ── Column definitions ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
func alertColumns(width int) []table.Column {
|
func alertColumns(width int) []table.Column {
|
||||||
nameW := width/2 - 8
|
const statusW, timeW = 10, 12
|
||||||
|
nameW := width/2 - 14
|
||||||
if nameW < 20 {
|
if nameW < 20 {
|
||||||
nameW = 20
|
nameW = 20
|
||||||
}
|
}
|
||||||
ackW := width - nameW - 10 - 12 - 6
|
// 10 = bubbles' Padding(0, 1) on each of the five cells.
|
||||||
|
ackW := width - nameW - statusW - 2*timeW - 10
|
||||||
if ackW < 8 {
|
if ackW < 8 {
|
||||||
ackW = 8
|
ackW = 8
|
||||||
}
|
}
|
||||||
return []table.Column{
|
return []table.Column{
|
||||||
{Title: "Name", Width: nameW},
|
{Title: "Name", Width: nameW},
|
||||||
{Title: "Status", Width: 10},
|
{Title: "Status", Width: statusW},
|
||||||
{Title: "Started", Width: 12},
|
{Title: "Started", Width: timeW},
|
||||||
|
{Title: "Last Seen", Width: timeW},
|
||||||
{Title: "Ack By", Width: ackW},
|
{Title: "Ack By", Width: ackW},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +403,7 @@ func alertRows(alerts []api.Alert) []table.Row {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
rows := make([]table.Row, len(alerts))
|
rows := make([]table.Row, len(alerts))
|
||||||
for i, a := range alerts {
|
for i, a := range alerts {
|
||||||
rows[i] = table.Row{a.Name, a.Status, humanAgo(now, a.StartsAt), a.AcknowledgedBy}
|
rows[i] = table.Row{a.Name, a.Status, humanAgo(now, a.StartsAt), humanAgo(now, a.ReceivedAt), a.AcknowledgedBy}
|
||||||
}
|
}
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,7 +381,11 @@ func buildDetailContent(alert api.Alert, comments []api.Comment, cursor, width i
|
|||||||
if alert.Status == "firing" {
|
if alert.Status == "firing" {
|
||||||
statusStr = styleFiring.Render("● FIRING")
|
statusStr = styleFiring.Render("● FIRING")
|
||||||
} else {
|
} else {
|
||||||
statusStr = styleResolved.Render("✓ RESOLVED")
|
label := "✓ RESOLVED"
|
||||||
|
if alert.ResolutionSource != nil {
|
||||||
|
label += " · " + *alert.ResolutionSource
|
||||||
|
}
|
||||||
|
statusStr = styleResolved.Render(label)
|
||||||
}
|
}
|
||||||
nameGap := contentW - lipgloss.Width(name) - lipgloss.Width(statusStr)
|
nameGap := contentW - lipgloss.Width(name) - lipgloss.Width(statusStr)
|
||||||
if nameGap < 1 {
|
if nameGap < 1 {
|
||||||
@@ -392,6 +396,8 @@ func buildDetailContent(alert api.Alert, comments []api.Comment, cursor, width i
|
|||||||
// Timeline
|
// Timeline
|
||||||
b.WriteString(fmt.Sprintf(" Started: %s (%s)\n",
|
b.WriteString(fmt.Sprintf(" Started: %s (%s)\n",
|
||||||
alert.StartsAt.UTC().Format("2006-01-02 15:04 UTC"), humanAgo(now, alert.StartsAt)))
|
alert.StartsAt.UTC().Format("2006-01-02 15:04 UTC"), humanAgo(now, alert.StartsAt)))
|
||||||
|
b.WriteString(fmt.Sprintf(" Last Seen: %s (%s)\n",
|
||||||
|
alert.ReceivedAt.UTC().Format("2006-01-02 15:04 UTC"), humanAgo(now, alert.ReceivedAt)))
|
||||||
if alert.EndsAt != nil {
|
if alert.EndsAt != nil {
|
||||||
b.WriteString(fmt.Sprintf(" Ended: %s\n", alert.EndsAt.UTC().Format("2006-01-02 15:04 UTC")))
|
b.WriteString(fmt.Sprintf(" Ended: %s\n", alert.EndsAt.UTC().Format("2006-01-02 15:04 UTC")))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user