add non-wasm version of tplprev

This commit is contained in:
nils måsén 2023-10-02 13:39:53 +02:00
parent 16883d21c0
commit ecf1dbb6b1
6 changed files with 246 additions and 137 deletions

View file

@ -60,6 +60,7 @@
overflow: auto; overflow: auto;
padding: 0.77em 1.18em; padding: 0.77em 1.18em;
margin:0; margin:0;
height: 540px;
} }
</style> </style>
@ -69,23 +70,24 @@
const form = document.querySelector('#tplprev'); const form = document.querySelector('#tplprev');
const input = form.template.value; const input = form.template.value;
console.log('Input: %o', input); console.log('Input: %o', input);
const actions = form.enablereport.checked ? [ const arrFromCount = (key) => Array.from(Array(form[key]?.valueAsNumber ?? 0), () => key);
[ form.skipped.valueAsNumber, "skipped" ], const states = form.enablereport.checked ? [
[ form.scanned.valueAsNumber, "scanned" ], ...arrFromCount("skipped"),
[ form.updated.valueAsNumber, "updated" ], ...arrFromCount("scanned"),
[ form.failed.valueAsNumber, "failed" ], ...arrFromCount("updated"),
[ form.fresh.valueAsNumber, "fresh" ], ...arrFromCount("failed" ),
[ form.stale.valueAsNumber, "stale" ], ...arrFromCount("fresh" ),
...arrFromCount("stale" ),
] : []; ] : [];
console.log("Actions: %o", actions); console.log("States: %o", states);
const logentries = form.enablelog.checked ? [ const levels = form.enablelog.checked ? [
form.logerrors.valueAsNumber, ...arrFromCount("error"),
form.logwarnings.valueAsNumber, ...arrFromCount("warning"),
form.loginfos.valueAsNumber, ...arrFromCount("info"),
form.logdebugs.valueAsNumber, ...arrFromCount("debug"),
] : [0, 0, 0, 0]; ] : [];
console.log("LogLevel counts: %o", logentries); console.log("Levels: %o", levels);
const output = WATCHTOWER.tplprev(input, actions, logentries); const output = WATCHTOWER.tplprev(input, states, levels);
console.log('Output: \n%o', output); console.log('Output: \n%o', output);
document.querySelector('#result').innerText = output; document.querySelector('#result').innerText = output;
} }
@ -99,6 +101,9 @@
if(debounce) clearTimeout(debounce); if(debounce) clearTimeout(debounce);
debounce = setTimeout(() => updatePreview(), 400); debounce = setTimeout(() => updatePreview(), 400);
} }
const formChanged = (e) => {
console.log('form changed: %o', e);
}
const go = new Go(); const go = new Go();
WebAssembly.instantiateStreaming(fetch("../assets/tplprev.wasm"), go.importObject).then((result) => { WebAssembly.instantiateStreaming(fetch("../assets/tplprev.wasm"), go.importObject).then((result) => {
document.querySelector('#loading').style.display = "none"; document.querySelector('#loading').style.display = "none";
@ -110,7 +115,7 @@
<form id="tplprev" onsubmit="formSubmitted(event)" style="margin: 0;display: flex; flex-direction: column; row-gap: 1rem; box-sizing: border-box; position: relative; margin-right: -13.3rem"> <form id="tplprev" onchange="inputUpdated()" onsubmit="formSubmitted(event)" style="margin: 0;display: flex; flex-direction: column; row-gap: 1rem; box-sizing: border-box; position: relative; margin-right: -13.3rem">
<pre id="loading" style="position: absolute; inset: 0; display: flex; padding: 1rem; box-sizing: border-box; background: var(--md-code-bg-color); margin-top: 0">loading wasm...</pre> <pre id="loading" style="position: absolute; inset: 0; display: flex; padding: 1rem; box-sizing: border-box; background: var(--md-code-bg-color); margin-top: 0">loading wasm...</pre>
@ -170,19 +175,19 @@ Logs:
<legend><label><input type="checkbox" name="enablelog" checked /> Log entries</label></legend> <legend><label><input type="checkbox" name="enablelog" checked /> Log entries</label></legend>
<label class="numfield"> <label class="numfield">
Error: Error:
<input type="number" name="logerrors" value="1" /> <input type="number" name="error" value="1" />
</label> </label>
<label class="numfield"> <label class="numfield">
Warning: Warning:
<input type="number" name="logwarnings" value="2" /> <input type="number" name="warning" value="2" />
</label> </label>
<label class="numfield"> <label class="numfield">
Info: Info:
<input type="number" name="loginfos" value="3" /> <input type="number" name="info" value="3" />
</label> </label>
<label class="numfield"> <label class="numfield">
Debug: Debug:
<input type="number" name="logdebugs" value="4" /> <input type="number" name="debug" value="4" />
</label> </label>
</fieldset> </fieldset>
<button type="submit" style="flex:1; min-width: 12ch; padding: 0.5rem">Update preview</button> <button type="submit" style="flex:1; min-width: 12ch; padding: 0.5rem">Update preview</button>

View file

@ -24,34 +24,43 @@ type LogEntry struct {
Level LogLevel Level LogLevel
} }
type LogLevel int type LogLevel string
const ( const (
PanicLevel LogLevel = iota TraceLevel LogLevel = "trace"
FatalLevel DebugLevel LogLevel = "debug"
ErrorLevel InfoLevel LogLevel = "info"
WarnLevel WarnLevel LogLevel = "warning"
InfoLevel ErrorLevel LogLevel = "error"
DebugLevel FatalLevel LogLevel = "fatal"
TraceLevel PanicLevel LogLevel = "panic"
) )
func (level LogLevel) String() string { func LevelsFromString(str string) []LogLevel {
switch level { levels := make([]LogLevel, 0, len(str))
case TraceLevel: for _, c := range str {
return "trace" switch c {
case DebugLevel: case 'p':
return "debug" levels = append(levels, PanicLevel)
case InfoLevel: case 'f':
return "info" levels = append(levels, FatalLevel)
case WarnLevel: case 'e':
return "warning" levels = append(levels, ErrorLevel)
case ErrorLevel: case 'w':
return "error" levels = append(levels, WarnLevel)
case FatalLevel: case 'i':
return "fatal" levels = append(levels, InfoLevel)
case PanicLevel: case 'd':
return "panic" levels = append(levels, DebugLevel)
case 't':
levels = append(levels, TraceLevel)
default:
continue
}
} }
return "" return levels
}
func (level LogLevel) String() string {
return string(level)
} }

View file

@ -1,89 +1,47 @@
//go:build !wasm
package main package main
import ( import (
"flag"
"fmt" "fmt"
"strings" "os"
"text/template"
"time"
"github.com/containrrr/watchtower/internal/meta" "github.com/containrrr/watchtower/internal/meta"
"github.com/containrrr/watchtower/pkg/notifications/templates"
"syscall/js"
) )
func main() { func main() {
fmt.Println("watchtower/tplprev v" + meta.Version) fmt.Fprintf(os.Stderr, "watchtower/tplprev v%v\n\n", meta.Version)
js.Global().Set("WATCHTOWER", js.ValueOf(map[string]any{ var states string
"tplprev": js.FuncOf(tplprev), var entries string
}))
<-make(chan bool)
} flag.StringVar(&states, "states", "cccuuueeekkktttfff", "sCanned, Updated, failEd, sKipped, sTale, Fresh")
flag.StringVar(&entries, "entries", "ewwiiidddd", "Fatal,Error,Warn,Info,Debug,Trace")
func tplprev(this js.Value, args []js.Value) any { flag.Parse()
rb := ReportBuilder() if len(flag.Args()) < 1 {
fmt.Fprintln(os.Stderr, "Missing required argument TEMPLATE")
if len(args) < 2 { flag.Usage()
return "Requires 3 argument passed" os.Exit(1)
return
} }
input := args[0].String() input, err := os.ReadFile(flag.Arg(0))
tpl, err := template.New("").Funcs(templates.Funcs).Parse(input)
if err != nil { if err != nil {
return "Failed to parse template: " + err.Error()
fmt.Fprintf(os.Stderr, "Failed to read template file %q: %v\n", flag.Arg(0), err)
os.Exit(1)
return
} }
actionsArg := args[1] result, err := TplPrev(string(input), StatesFromString(states), LevelsFromString(entries))
for i := 0; i < actionsArg.Length(); i++ {
action := actionsArg.Index(i)
if action.Length() != 2 {
return fmt.Sprintf("Invalid size of action tuple, expected 2, got %v", action.Length())
}
count := action.Index(0).Int()
state := State(action.Index(1).String())
rb.AddNContainers(count, state)
}
entriesArg := args[2]
var entries []*LogEntry
for i := 0; i < entriesArg.Length(); i++ {
count := entriesArg.Index(i).Int()
level := ErrorLevel + LogLevel(i)
for m := 0; m < count; m++ {
var msg string
if level <= WarnLevel {
msg = rb.randomEntry(logErrors)
} else {
msg = rb.randomEntry(logMessages)
}
entries = append(entries, &LogEntry{
Message: msg,
Data: map[string]any{},
Time: time.Now(),
Level: level,
})
}
}
report := rb.Build()
data := Data{
Entries: entries,
StaticData: StaticData{
Title: "Title",
Host: "Host",
},
Report: report,
}
var buf strings.Builder
err = tpl.Execute(&buf, data)
if err != nil { if err != nil {
return "Failed to execute template: " + err.Error() fmt.Fprintf(os.Stderr, "Failed to read template file %q: %v\n", flag.Arg(0), err)
os.Exit(1)
return
} }
return buf.String() fmt.Println(result)
} }

60
tplprev/main_wasm.go Normal file
View file

@ -0,0 +1,60 @@
//go:build wasm
package main
import (
"fmt"
"github.com/containrrr/watchtower/internal/meta"
"syscall/js"
)
func main() {
fmt.Println("watchtower/tplprev v" + meta.Version)
js.Global().Set("WATCHTOWER", js.ValueOf(map[string]any{
"tplprev": js.FuncOf(jsTplPrev),
}))
<-make(chan bool)
}
func jsTplPrev(this js.Value, args []js.Value) any {
if len(args) < 3 {
return "Requires 3 arguments passed"
}
input := args[0].String()
statesArg := args[1]
var states []State
if statesArg.Type() == js.TypeString {
states = StatesFromString(statesArg.String())
} else {
for i := 0; i < statesArg.Length(); i++ {
state := State(statesArg.Index(i).String())
states = append(states, state)
}
}
levelsArg := args[2]
var levels []LogLevel
if levelsArg.Type() == js.TypeString {
levels = LevelsFromString(statesArg.String())
} else {
for i := 0; i < levelsArg.Length(); i++ {
level := LogLevel(levelsArg.Index(i).String())
levels = append(levels, level)
}
}
result, err := TplPrev(input, states, levels)
if err != nil {
return "Error: " + err.Error()
}
return result
}

View file

@ -3,7 +3,6 @@ package main
import ( import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"math/rand" "math/rand"
"sort" "sort"
"strconv" "strconv"
@ -30,30 +29,27 @@ func (rb *reportBuilder) Build() types.Report {
return &rb.report return &rb.report
} }
func (rb *reportBuilder) AddNContainers(n int, state State) { func (rb *reportBuilder) AddFromState(state State) {
fmt.Printf("Adding %v containers with state %v", n, state) cid := types.ContainerID(rb.generateID())
for i := 0; i < n; i++ { old := types.ImageID(rb.generateID())
cid := types.ContainerID(rb.generateID()) new := types.ImageID(rb.generateID())
old := types.ImageID(rb.generateID()) name := rb.generateName()
new := types.ImageID(rb.generateID()) image := rb.generateImageName(name)
name := rb.generateName() var err error
image := rb.generateImageName(name) if state == FailedState {
var err error err = errors.New(rb.randomEntry(errorMessages))
if state == FailedState { } else if state == SkippedState {
err = errors.New(rb.randomEntry(errorMessages)) err = errors.New(rb.randomEntry(skippedMessages))
} else if state == SkippedState {
err = errors.New(rb.randomEntry(skippedMessages))
}
rb.AddContainer(ContainerStatus{
containerID: cid,
oldImage: old,
newImage: new,
containerName: name,
imageName: image,
error: err,
state: state,
})
} }
rb.AddContainer(ContainerStatus{
containerID: cid,
oldImage: old,
newImage: new,
containerName: name,
imageName: image,
error: err,
state: state,
})
} }
func (rb *reportBuilder) AddContainer(c ContainerStatus) { func (rb *reportBuilder) AddContainer(c ContainerStatus) {
@ -127,6 +123,29 @@ type Report struct {
fresh []types.ContainerReport fresh []types.ContainerReport
} }
func StatesFromString(str string) []State {
states := make([]State, 0, len(str))
for _, c := range str {
switch c {
case 'c':
states = append(states, ScannedState)
case 'u':
states = append(states, UpdatedState)
case 'e':
states = append(states, FailedState)
case 'k':
states = append(states, SkippedState)
case 't':
states = append(states, StaleState)
case 'f':
states = append(states, FreshState)
default:
continue
}
}
return states
}
func (r *Report) Scanned() []types.ContainerReport { func (r *Report) Scanned() []types.ContainerReport {
return r.scanned return r.scanned
} }

58
tplprev/tplprev.go Normal file
View file

@ -0,0 +1,58 @@
package main
import (
"fmt"
"strings"
"text/template"
"time"
"github.com/containrrr/watchtower/pkg/notifications/templates"
)
func TplPrev(input string, states []State, loglevels []LogLevel) (string, error) {
rb := ReportBuilder()
tpl, err := template.New("").Funcs(templates.Funcs).Parse(input)
if err != nil {
return "", fmt.Errorf("failed to parse template: %e", err)
}
for _, state := range states {
rb.AddFromState(state)
}
var entries []*LogEntry
for _, level := range loglevels {
var msg string
if level <= WarnLevel {
msg = rb.randomEntry(logErrors)
} else {
msg = rb.randomEntry(logMessages)
}
entries = append(entries, &LogEntry{
Message: msg,
Data: map[string]any{},
Time: time.Now(),
Level: level,
})
}
report := rb.Build()
data := Data{
Entries: entries,
StaticData: StaticData{
Title: "Title",
Host: "Host",
},
Report: report,
}
var buf strings.Builder
err = tpl.Execute(&buf, data)
if err != nil {
return "", fmt.Errorf("failed to execute template: %e", err)
}
return buf.String(), nil
}