From 2077d4132b017e769bddb0b10c95c2463da1aadc Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 23 Feb 2026 16:47:50 +0100 Subject: [PATCH] Initialize project with DIUN Webhook Dashboard: core Go app, Docker setup, static assets, and documentation. --- .gitignore | 19 +++++++++++ .idea/.gitignore | 10 ++++++ .idea/awesomeProject.iml | 9 ++++++ .idea/go.imports.xml | 11 +++++++ .idea/modules.xml | 8 +++++ .idea/vcs.xml | 6 ++++ Dockerfile | 11 +++++++ LICENSE | 21 ++++++++++++ README.md | 69 ++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 7 ++++ go.mod | 3 ++ main.go | 68 +++++++++++++++++++++++++++++++++++++++ static/index.html | 48 ++++++++++++++++++++++++++++ 13 files changed, 290 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/awesomeProject.iml create mode 100644 .idea/go.imports.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 go.mod create mode 100644 main.go create mode 100644 static/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4652ff1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Docker build artifacts +app +.dockerignore diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/awesomeProject.iml b/.idea/awesomeProject.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/awesomeProject.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml new file mode 100644 index 0000000..d7202f0 --- /dev/null +++ b/.idea/go.imports.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..cc47053 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bffcb3d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.26-alpine AS builder +WORKDIR /app +COPY . . +RUN go build -o app + +FROM alpine:latest +WORKDIR /app +COPY --from=builder /app/app . +COPY static ./static +EXPOSE 8080 +CMD ["./app"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c6dd179 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Jean-Luc Makiola + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b81b6c1 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# DIUN Webhook Dashboard + +A tiny Go web app that receives [DIUN](https://crazymax.dev/diun/) webhook events and shows the latest image updates in a simple UI. + +- Receives DIUN webhooks at `POST /webhook` +- Serves a minimal dashboard at `/` +- Exposes a read-only API at `GET /api/updates` +- Stores events in memory (ephemeral) + +## Quick start + +### Run locally (Go 1.26+) +```bash +go run . +# open http://localhost:8080 +``` + +### Docker +```bash +docker build -t diun-webhook-dashboard . +docker run --rm -p 8080:8080 diun-webhook-dashboard +# open http://localhost:8080 +``` + +### Docker Compose +```bash +docker compose up -d +# open http://localhost:8080 +``` + +## DIUN configuration example +Configure DIUN to send webhooks to this app. Example (YAML): + +```yaml +notif: + webhook: + enable: true + endpoint: http://your-host-or-ip:8080/webhook +``` + +Expected JSON payload (simplified): +```json +{ + "image": "library/nginx", + "tag": "1.27.0", + "status": "new", + "time": "2026-02-23T16:00:00Z" +} +``` + +## API +- `POST /webhook` — accept a DIUN event JSON body. +- `GET /api/updates` — return the latest events as a JSON object keyed by image name. +- `/` — static HTML dashboard. + +Note: data is only kept in-memory and will be reset on restart. + +## Development +- Code: `main.go` +- Static assets: `static/` +- Container image: `Dockerfile` + +## Production notes +- Behind a reverse proxy, ensure the app is reachable at `/webhook` from DIUN. +- Persistence is not implemented; hook up a store (e.g., BoltDB/Redis/Postgres) if you need durability. +- Consider adding auth, rate limiting, or a secret/token on the webhook endpoint if exposed publicly. + +## License +MIT — see `LICENSE`. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..01dcf0f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +version: "3.9" +services: + app: + build: . + ports: + - "8080:8080" + restart: unless-stopped diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c7d2827 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module awesomeProject + +go 1.26 diff --git a/main.go b/main.go new file mode 100644 index 0000000..99769a7 --- /dev/null +++ b/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + "sync" + "time" +) + +type DiunEvent struct { + DiunVersion string `json:"diun_version"` + Hostname string `json:"hostname"` + Status string `json:"status"` + Provider string `json:"provider"` + Image string `json:"image"` + HubLink string `json:"hub_link"` + MimeType string `json:"mime_type"` + Digest string `json:"digest"` + Created time.Time `json:"created"` + Platform string `json:"platform"` + Metadata struct { + ContainerName string `json:"ctn_names"` + ContainerID string `json:"ctn_id"` + State string `json:"ctn_state"` + Status string `json:"ctn_status"` + } `json:"metadata"` +} + +var ( + mu sync.Mutex + updates = make(map[string]DiunEvent) +) + +func webhookHandler(w http.ResponseWriter, r *http.Request) { + var event DiunEvent + + if err := json.NewDecoder(r.Body).Decode(&event); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + mu.Lock() + // Use image as key (or container name if preferred) + updates[event.Image] = event + mu.Unlock() + + log.Printf("Update received: %s (%s)", event.Image, event.Status) + + w.WriteHeader(http.StatusOK) +} + +func updatesHandler(w http.ResponseWriter, r *http.Request) { + mu.Lock() + defer mu.Unlock() + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(updates) +} + +func main() { + http.HandleFunc("/webhook", webhookHandler) + http.HandleFunc("/api/updates", updatesHandler) + http.Handle("/", http.FileServer(http.Dir("./static"))) + + log.Println("Listening on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..4f21f83 --- /dev/null +++ b/static/index.html @@ -0,0 +1,48 @@ + + + + DIUN Updates + + + +

Available Image Updates

+ + + + + + + + + + +
ImageTagStatusLast Seen
+ + + + \ No newline at end of file