diff --git a/sysdump/eventSummary.html b/sysdump/eventSummary.html
new file mode 100644
index 0000000000..0fea2fba60
--- /dev/null
+++ b/sysdump/eventSummary.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Events
+
+
+
+
+
+
+
+
+ Time |
+ Namespace |
+ Component |
+ Host |
+ RelatedObject |
+ Reason |
+ Message |
+
+
+
+ {{range . }}
+
+ {{formatTime .ObjectMeta.CreationTimestamp .FirstTimestamp .LastTimestamp .Count}} |
+ {{.Namespace}} |
+ {{.Source.Component}} |
+ {{.Source.Host}} |
+ {{.InvolvedObject.Name}} |
+ {{.Reason}} |
+ {{.Message}} |
+
+ {{end}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sysdump/sysdump.go b/sysdump/sysdump.go
index 443066f6c5..9f281f5325 100644
--- a/sysdump/sysdump.go
+++ b/sysdump/sysdump.go
@@ -301,6 +301,9 @@ func (c *Collector) Run() error {
if err := c.WriteYAML(kubernetesEventsFileName, v); err != nil {
return fmt.Errorf("failed to collect Kubernetes events: %w", err)
}
+ if err := c.WriteBytes("events.html", makeEventTable(v.Items)); err != nil {
+ return fmt.Errorf("failed to write event tble: %w", err)
+ }
return nil
},
},
diff --git a/sysdump/writers.go b/sysdump/writers.go
index 16871391c9..d077ecd240 100644
--- a/sysdump/writers.go
+++ b/sysdump/writers.go
@@ -5,14 +5,24 @@ package sysdump
import (
"bytes"
+ _ "embed"
+ "fmt"
+ "html/template"
"os"
+ "sort"
+ "strings"
+ "time"
+ corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/client-go/kubernetes/scheme"
)
+//go:embed eventSummary.html
+var eventSummaryHTML string
+
func writeBytes(p string, b []byte) error {
return os.WriteFile(p, b, fileMode)
}
@@ -44,3 +54,49 @@ func writeYaml(p string, o runtime.Object) error {
}
return writeString(p, b.String())
}
+
+// writeEventTable writes a html summary of cluster events.
+func makeEventTable(events []corev1.Event) []byte {
+ // sort events by time
+ sort.Slice(events, func(i, j int) bool {
+ return events[i].LastTimestamp.Time.Before(events[j].LastTimestamp.Time)
+ })
+
+ t := template.Must(template.New("events").Funcs(template.FuncMap{
+ "formatTime": func(created, firstSeen, lastSeen metav1.Time, count int32) template.HTML {
+ countMsg := ""
+ if count > 1 {
+ countMsg = fmt.Sprintf(" (x%d)", count)
+ }
+ if lastSeen.IsZero() {
+ lastSeen = created
+ }
+ firstSeenUTC := firstSeen.In(time.UTC)
+ lastSeenUTC := lastSeen.In(time.UTC)
+ //nolint:gosec
+ return template.HTML(fmt.Sprintf(`%s`, lastSeenUTC.String(), firstSeenUTC.Format("15:04:05Z"), lastSeenUTC.Format("15:04:05Z"), countMsg))
+ },
+ "reasonClass": func(r string) template.HTMLAttr {
+ cssClass := "text-muted"
+ switch {
+ case strings.Contains(strings.ToLower(r), "fail"),
+ strings.Contains(strings.ToLower(r), "error"),
+ strings.Contains(strings.ToLower(r), "kill"),
+ strings.Contains(strings.ToLower(r), "backoff"):
+ cssClass = "text-danger"
+ case strings.Contains(strings.ToLower(r), "notready"),
+ strings.Contains(strings.ToLower(r), "unhealthy"),
+ strings.Contains(strings.ToLower(r), "missing"):
+ cssClass = "text-warning"
+ }
+ //nolint:gosec
+ return template.HTMLAttr(fmt.Sprintf(`class="%s"`, cssClass))
+ },
+ }).Parse(eventSummaryHTML))
+
+ out := bytes.NewBuffer([]byte{})
+ if err := t.Execute(out, events); err != nil {
+ panic(err) // unreachable
+ }
+ return out.Bytes()
+}