Compare commits
12 Commits
007611fb4f
...
39ed9280e1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39ed9280e1 | ||
|
|
ac6bf30042 | ||
|
|
4eb2117f21 | ||
|
|
6411011e62 | ||
|
|
444ca5d0ca | ||
|
|
bb72ff4bfa | ||
|
|
e8d52274e7 | ||
|
|
8a67f505ce | ||
|
|
f4b04e01d3 | ||
|
|
e33d1a6a4b | ||
|
|
4ac55e2eea | ||
|
|
b0c9c1cf9e |
119
report.go
119
report.go
@@ -3,7 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -12,7 +16,28 @@ import (
|
|||||||
var reportTMPL string
|
var reportTMPL string
|
||||||
|
|
||||||
func ReportSince(ctx context.Context, w io.Writer, s Storage, t time.Time) error {
|
func ReportSince(ctx context.Context, w io.Writer, s Storage, t time.Time) error {
|
||||||
tmpl, err := template.New("report").Parse(reportTMPL)
|
tmpl := template.New("report").Funcs(map[string]any{
|
||||||
|
"time": func(foo string, args ...any) (any, error) {
|
||||||
|
switch foo {
|
||||||
|
case "Unix":
|
||||||
|
seconds, _ := args[0].(uint64)
|
||||||
|
return time.Unix(int64(seconds), 0), nil
|
||||||
|
case "Time.Format":
|
||||||
|
t, _ := args[1].(time.Time)
|
||||||
|
return t.Format(args[0].(string)), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
},
|
||||||
|
"json": func(foo string, args ...any) (any, error) {
|
||||||
|
switch foo {
|
||||||
|
case "Marshal":
|
||||||
|
b, err := json.Marshal(args[0])
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
tmpl, err := tmpl.Parse(reportTMPL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -22,26 +47,102 @@ func ReportSince(ctx context.Context, w io.Writer, s Storage, t time.Time) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
threads, err := s.ThreadsSince(ctx, t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
eventNames, err := s.EventNamesSince(ctx, t)
|
eventNames, err := s.EventNamesSince(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := s.EventsSince(ctx, t)
|
eventIDs, err := s.EventsSince(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type aThread struct {
|
||||||
|
Thread string
|
||||||
|
Messages []Message
|
||||||
|
First Message
|
||||||
|
Last Message
|
||||||
|
}
|
||||||
|
type anEvent struct {
|
||||||
|
Event string
|
||||||
|
Threads []aThread
|
||||||
|
First Message
|
||||||
|
Last Message
|
||||||
|
}
|
||||||
|
type someEvents struct {
|
||||||
|
Events []anEvent
|
||||||
|
}
|
||||||
|
|
||||||
return tmpl.Execute(w, map[string]any{
|
return tmpl.Execute(w, map[string]any{
|
||||||
"since": t.Format("2006-01-02"),
|
"since": t.Format("2006-01-02"),
|
||||||
|
"events": func() someEvents {
|
||||||
|
events := make([]anEvent, len(eventIDs))
|
||||||
|
for i, event := range eventIDs {
|
||||||
|
events[i] = func() anEvent {
|
||||||
|
threadNames := []string{}
|
||||||
|
for _, m := range messages {
|
||||||
|
if m.Event == event {
|
||||||
|
threadNames = append(threadNames, m.Thread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(threadNames)
|
||||||
|
slices.Compact(threadNames)
|
||||||
|
threads := make([]aThread, len(threadNames))
|
||||||
|
for i, thread := range threadNames {
|
||||||
|
threads[i] = func() aThread {
|
||||||
|
someMessages := []Message{}
|
||||||
|
for _, m := range messages {
|
||||||
|
if m.Thread == thread {
|
||||||
|
someMessages = append(someMessages, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(someMessages, func(i, j int) bool {
|
||||||
|
return someMessages[i].TS < someMessages[j].TS
|
||||||
|
})
|
||||||
|
return aThread{
|
||||||
|
Thread: thread,
|
||||||
|
Messages: someMessages,
|
||||||
|
First: func() Message {
|
||||||
|
if len(someMessages) == 0 {
|
||||||
|
return Message{}
|
||||||
|
}
|
||||||
|
return someMessages[0]
|
||||||
|
}(),
|
||||||
|
Last: func() Message {
|
||||||
|
if len(someMessages) == 0 {
|
||||||
|
return Message{}
|
||||||
|
}
|
||||||
|
return someMessages[len(someMessages)-1]
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
sort.Slice(threads, func(i, j int) bool {
|
||||||
|
return threads[i].First.TS < threads[j].First.TS
|
||||||
|
})
|
||||||
|
return anEvent{
|
||||||
|
Event: event,
|
||||||
|
Threads: threads,
|
||||||
|
First: func() Message {
|
||||||
|
if len(threads) == 0 {
|
||||||
|
return Message{}
|
||||||
|
}
|
||||||
|
return threads[0].First
|
||||||
|
}(),
|
||||||
|
Last: func() Message {
|
||||||
|
if len(threads) == 0 {
|
||||||
|
return Message{}
|
||||||
|
}
|
||||||
|
return threads[len(threads)-1].Last
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return someEvents{
|
||||||
|
Events: events,
|
||||||
|
}
|
||||||
|
}(),
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"threads": threads,
|
|
||||||
"events": events,
|
|
||||||
"eventNames": eventNames,
|
"eventNames": eventNames,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
110
report.tmpl
110
report.tmpl
@@ -1,11 +1,115 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<header>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
||||||
</header>
|
<script type="module">
|
||||||
<body>
|
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const allMessages = {{ json "Marshal" .messages }};
|
||||||
|
console.log(allMessages);
|
||||||
|
|
||||||
|
function fillForm() {
|
||||||
|
const filterableFields = [
|
||||||
|
"EventName",
|
||||||
|
"Asset",
|
||||||
|
];
|
||||||
|
const fieldsToOptions = {};
|
||||||
|
filterableFields.map((field) => {fieldsToOptions[field] = {}});
|
||||||
|
allMessages.map((message) => {
|
||||||
|
Object.keys(fieldsToOptions).map((field) => {fieldsToOptions[field][message[field]] = true});
|
||||||
|
});
|
||||||
|
Object.keys(fieldsToOptions).map((field) => {fieldsToOptions[field] = Object.keys(fieldsToOptions[field])});
|
||||||
|
|
||||||
|
document.getElementById("form").innerHTML = Object.keys(fieldsToOptions).map((field) => {
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
console.log(fieldsToOptions);
|
||||||
|
`
|
||||||
|
<select name="filter1" multiple>
|
||||||
|
<option selected>a</option>
|
||||||
|
<option selected>b</option>
|
||||||
|
</select>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
function drawAll() {
|
||||||
|
const messages = filterMessages(allMessages)
|
||||||
|
drawEventVolumeByName(messages)
|
||||||
|
drawEventVolumeByWeekday(messages)
|
||||||
|
drawEventVolumeByHour(messages)
|
||||||
|
drawEventVolumeByAsset(messages)
|
||||||
|
}
|
||||||
|
function filterMessages(messages) {
|
||||||
|
}
|
||||||
|
function drawEventVolumeByName() {}
|
||||||
|
function drawEventVolumeByWeekday() {}
|
||||||
|
function drawEventVolumeByHour() {}
|
||||||
|
function drawEventVolumeByAsset() {}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
columns {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
rows, columns { border: 1px solid red; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body onload="fillForm()">
|
||||||
<h1>Report</h1>
|
<h1>Report</h1>
|
||||||
|
<columns>
|
||||||
|
<form id="form" style="width: 10em; flex-shrink: 0;" onchange="drawAll()">
|
||||||
|
</form>
|
||||||
|
<rows>
|
||||||
|
<rows>
|
||||||
|
<rows>
|
||||||
|
<h2>Event Volume by Name</h2>
|
||||||
|
<div>DRAW ME</div>
|
||||||
|
</rows>
|
||||||
|
<columns>
|
||||||
|
<rows>
|
||||||
|
<h3>by Weekday</h3>
|
||||||
|
<div>DRAW ME</div>
|
||||||
|
</rows>
|
||||||
|
<rows>
|
||||||
|
<h3>by Hour</h3>
|
||||||
|
<div>DRAW ME</div>
|
||||||
|
</rows>
|
||||||
|
</columns>
|
||||||
|
<rows>
|
||||||
|
<h3>by Asset</h3>
|
||||||
|
<div>DRAW ME</div>
|
||||||
|
</rows>
|
||||||
|
</rows>
|
||||||
|
<rows>
|
||||||
|
<div>
|
||||||
|
<h2>Events</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>TS</th>
|
||||||
|
<th>Event</th>
|
||||||
|
<th>EventName</th>
|
||||||
|
<th>Latest</th>
|
||||||
|
</tr>
|
||||||
|
{{ range .events.Events }}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ .First.Source }}">{{ time "Unix" .First.TS | time "Time.Format" "Mon Jan 02" }}</a></td>
|
||||||
|
<td><a href="https://TODO">{{ .Event }}</a></td>
|
||||||
|
<td>{{ .First.EventName }}</td>
|
||||||
|
<td><a href="{{ .Last.Source }}">{{ .Last.Plaintext }}</a></td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</rows>
|
||||||
|
</rows>
|
||||||
|
</columns>
|
||||||
</body>
|
</body>
|
||||||
<footer>
|
<footer>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user