diff options
Diffstat (limited to 'internal/goprecords/report.go')
| -rw-r--r-- | internal/goprecords/report.go | 132 |
1 files changed, 109 insertions, 23 deletions
diff --git a/internal/goprecords/report.go b/internal/goprecords/report.go index 12c3951..8916caa 100644 --- a/internal/goprecords/report.go +++ b/internal/goprecords/report.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "sort" + "strings" ) type metricExtractor struct { @@ -61,38 +62,33 @@ func WriteReports(w io.Writer, aggregates *Aggregates, cfg ReportConfig) error { if cfg.Category != CategoryHost && (cfg.Metric == MetricDowntime || cfg.Metric == MetricLifespan) { return fmt.Errorf("Category %s only supports: Boots, Uptime, Score", cfg.Category) } - if cfg.Category == CategoryHost { - if err := writeReportString(w, NewHostReporter(aggregates, cfg.Limit, cfg.Metric, cfg.OutputFormat, 1).Report()); err != nil { - return err - } - } else { - if err := writeReportString(w, NewReporter(aggregates, cfg.Category, cfg.Limit, cfg.Metric, cfg.OutputFormat, 1).Report()); err != nil { - return err - } - } - return nil + s := reportForPair(aggregates, cfg.Category, cfg.Metric, cfg.Limit, 1, cfg.OutputFormat) + return writeReportString(w, wrapIfHTML(s, cfg.OutputFormat)) } order, err := StatsOrderList(cfg.StatsOrder) if err != nil { return err } headerIndent := uint(2) - for _, pair := range order { - c, m := pair.Category, pair.Metric - if !cfg.IncludeKernel && c == CategoryKernel { - continue + if cfg.OutputFormat == FormatHTML { + var parts []string + for _, pair := range order { + if skipPair(cfg, pair.Category, pair.Metric) { + continue + } + if s := reportForPair(aggregates, pair.Category, pair.Metric, cfg.Limit, headerIndent, cfg.OutputFormat); s != "" { + parts = append(parts, s) + } } - if c != CategoryHost && (m == MetricDowntime || m == MetricLifespan) { + return writeReportString(w, wrapHTMLDocument(strings.Join(parts, "\n"))) + } + for _, pair := range order { + if skipPair(cfg, pair.Category, pair.Metric) { continue } - if c == CategoryHost { - if err := writeReportString(w, NewHostReporter(aggregates, cfg.Limit, m, cfg.OutputFormat, headerIndent).Report()); err != nil { - return err - } - } else { - if err := writeReportString(w, NewReporter(aggregates, c, cfg.Limit, m, cfg.OutputFormat, headerIndent).Report()); err != nil { - return err - } + s := reportForPair(aggregates, pair.Category, pair.Metric, cfg.Limit, headerIndent, cfg.OutputFormat) + if err := writeReportString(w, s); err != nil { + return err } if err := writeReportString(w, "\n"); err != nil { return err @@ -101,6 +97,96 @@ func WriteReports(w io.Writer, aggregates *Aggregates, cfg ReportConfig) error { return nil } +func reportForPair(aggregates *Aggregates, c Category, m Metric, limit, headerIndent uint, outputFormat OutputFormat) string { + if c == CategoryHost { + return NewHostReporter(aggregates, limit, m, outputFormat, headerIndent).Report() + } + return NewReporter(aggregates, c, limit, m, outputFormat, headerIndent).Report() +} + +func skipPair(cfg ReportConfig, c Category, m Metric) bool { + if !cfg.IncludeKernel && c == CategoryKernel { + return true + } + if c != CategoryHost && (m == MetricDowntime || m == MetricLifespan) { + return true + } + return false +} + +func wrapIfHTML(s string, f OutputFormat) string { + if f != FormatHTML { + return s + } + return wrapHTMLDocument(s) +} + +func wrapHTMLDocument(body string) string { + var b strings.Builder + b.WriteString("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n") + b.WriteString("<meta charset=\"UTF-8\">\n") + b.WriteString("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n") + b.WriteString("<title>goprecords uptime report</title>\n") + b.WriteString(htmlStyle) + b.WriteString("</head>\n<body>\n") + b.WriteString(body) + b.WriteString("</body>\n</html>\n") + return b.String() +} + +const htmlStyle = `<style> + /* Compact, full-width layout */ + :root { + --pad: 8px; + } + html, body { + height: 100%; + } + body { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + line-height: 1.3; + margin: 0; + padding: var(--pad); + background: #fff; + color: #000; + } + /* Headings: smaller and tighter */ + h1, h2, h3 { margin: 0.5em 0 0.25em; font-weight: 600; } + h1 { font-size: 1em; } + h2 { font-size: 0.95em; } + h3 { font-size: 0.9em; } + /* Paragraphs and lists: minimal vertical rhythm */ + p { margin: 0.2em 0; } + ul { margin: 0.3em 0; padding-left: 1.2em; } + li { margin: 0.1em 0; } + /* Code blocks and tables */ + pre { + overflow-x: auto; + white-space: pre; + margin: 0.3em 0; + } + table { + border-collapse: collapse; + table-layout: auto; /* size columns by content */ + width: auto; /* do not stretch to full width */ + max-width: 100%; + margin: 0.5em 0; + font-size: 0.95em; + display: inline-table; /* keep as compact as content allows */ + } + th, td { + padding: 0.1em 0.3em; + text-align: left; + white-space: nowrap; /* avoid wide columns caused by wrapping */ + } + /* Links */ + a { color: #06c; text-decoration: underline; } + a:visited { color: #639; } + /* Rules */ + hr { border: none; border-top: 1px solid #ccc; margin: 0.5em 0; } + </style> +` + func writeReportString(w io.Writer, s string) error { _, err := io.WriteString(w, s) return err |
