Initialize project with DIUN Webhook Dashboard: core Go app, Docker setup, static assets, and documentation.

This commit is contained in:
2026-02-23 16:47:50 +01:00
commit 2077d4132b
13 changed files with 290 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@@ -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

10
.idea/.gitignore generated vendored Normal file
View File

@@ -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/

9
.idea/awesomeProject.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

11
.idea/go.imports.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="github.com/pkg/errors" />
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/awesomeProject.iml" filepath="$PROJECT_DIR$/.idea/awesomeProject.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

11
Dockerfile Normal file
View File

@@ -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"]

21
LICENSE Normal file
View File

@@ -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.

69
README.md Normal file
View File

@@ -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`.

7
docker-compose.yml Normal file
View File

@@ -0,0 +1,7 @@
version: "3.9"
services:
app:
build: .
ports:
- "8080:8080"
restart: unless-stopped

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module awesomeProject
go 1.26

68
main.go Normal file
View File

@@ -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))
}

48
static/index.html Normal file
View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>DIUN Updates</title>
<style>
body { font-family: sans-serif; background: #111; color: #eee; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px; border-bottom: 1px solid #333; }
th { text-align: left; }
</style>
</head>
<body>
<h2>Available Image Updates</h2>
<table id="updates">
<thead>
<tr>
<th>Image</th>
<th>Tag</th>
<th>Status</th>
<th>Last Seen</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script>
async function load() {
const res = await fetch('/api/updates');
const data = await res.json();
const tbody = document.querySelector('tbody');
tbody.innerHTML = '';
Object.values(data).forEach(update => {
const row = `<tr>
<td>${update.image}</td>
<td>${update.tag}</td>
<td>${update.status}</td>
<td>${update.time}</td>
</tr>`;
tbody.innerHTML += row;
});
}
setInterval(load, 5000);
load();
</script>
</body>
</html>