diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..667e390 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +# -------- Dockerfile (Multi-Stage Build) -------- +# 1. Builder-Stage +FROM golang:1.24-alpine AS builder + +WORKDIR /app +COPY go.* ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /bin/vcc + +# 2. Runtime-Stage +FROM alpine:3.22 + +# HTTPS-Callouts in Alpine brauchen ca-certificates +RUN apk add --no-cache ca-certificates +RUN mkdir /web +COPY --from=builder /bin/vcc /bin/vcc +COPY ./web /web +# Default listens on :8090 – siehe main.go +EXPOSE 8090 + +# Environment defaults; können per compose überschrieben werden +ENV CLIPBOARD_TOKEN="" \ +CLIPBOARD_DATA="" \ +PORT=":8090" \ +MAX_PER_ROOM=200 + +ENTRYPOINT ["/bin/vcc"] \ No newline at end of file diff --git a/clipboard.json b/clipboard.json index dfa4d58..1bd7e15 100644 --- a/clipboard.json +++ b/clipboard.json @@ -3,12 +3,46 @@ "rooms": { "default": [ { - "id": "20250905T103347.497751500Z-f12bf341402200b2", + "id": "20250906T094234.159189600Z-51dab5cf1cc000fb", "room": "default", "type": "text", - "content": "demo", - "author": "Jan", - "created_at": "2025-09-05T10:33:47.4977515Z" + "content": "^9rhME!\u003e-Q#6k969og^d", + "created_at": "2025-09-06T09:42:34.1591896Z" + }, + { + "id": "20250906T094239.420737500Z-3884c4d0cb8a9ac4", + "room": "default", + "type": "text", + "content": "f,u6g-r8E2:gNTu/w)b)", + "created_at": "2025-09-06T09:42:39.4207375Z" + }, + { + "id": "20250906T094240.237113600Z-0a9fe15a381b458f", + "room": "default", + "type": "text", + "content": "nyG\u003eC2eq[M4RP2;+u9Tz", + "created_at": "2025-09-06T09:42:40.2371136Z" + }, + { + "id": "20250906T094241.057489000Z-544852c456ff133e", + "room": "default", + "type": "text", + "content": ".kC49}YqE7P~P5i7dadk", + "created_at": "2025-09-06T09:42:41.057489Z" + }, + { + "id": "20250906T142832.444200200Z-5cbf503227067e52", + "room": "default", + "type": "text", + "content": "n?x2Pr!d929FK9!7Ls5L", + "created_at": "2025-09-06T14:28:32.4442002Z" + }, + { + "id": "20250906T142832.444720600Z-ac6a369b19a2ca3a", + "room": "default", + "type": "text", + "content": "UoT$YvL%eAKh\u0026SG9VS\u0026$", + "created_at": "2025-09-06T14:28:32.4447206Z" } ] } diff --git a/main.go b/main.go index f23bd6e..0f58139 100644 --- a/main.go +++ b/main.go @@ -480,14 +480,14 @@ func (s *Server) routes() http.Handler { mux.HandleFunc("/status", s.handleStatus) // Static UI - sub, _ := fs.Sub(embedded, "web") + sub, _ := fs.Sub(embedded, "/web") mux.Handle("/", http.FileServer(http.FS(sub))) return mux } func main() { - port := getenv("PORT", "8080") + port := getenv("PORT", ":8080") maxPerRoom := getenvInt("MAX_PER_ROOM", 200) secret := os.Getenv("CLIPBOARD_TOKEN") persist := os.Getenv("CLIPBOARD_DATA") @@ -498,7 +498,7 @@ func main() { } srv := &http.Server{ - Addr: ":" + port, + Addr: port, Handler: s.routes(), ReadHeaderTimeout: 10 * time.Second, } diff --git a/web/index.html b/web/index.html index af66a49..7009d32 100644 --- a/web/index.html +++ b/web/index.html @@ -5,26 +5,128 @@ Virtuelle Zwischenablage + :root { + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif; + } + + body { + margin: 0; + background: #f9fafb; /* sehr helles Grau statt fast Schwarz */ + color: #222; /* dunkle Schrift */ + } + + header { + padding: 16px 20px; + border-bottom: 1px solid #e5e7eb; /* helles Grau */ + position: sticky; + top: 0; + background: #ffffff; /* weißer Header */ + z-index: 1; + } + + h1 { + margin: 0; + font-size: 18px; + } + + main { + max-width: 900px; + margin: 0 auto; + padding: 20px; + } + + .row { + display: grid; + grid-template-columns: 1fr auto; + gap: 10px; + } + + .grid { + display: grid; + gap: 12px; + } + + input, textarea, select, button { + background: #ffffff; /* weißer Hintergrund */ + border: 1px solid #d1d5db; /* hellgraue Umrandung */ + color: #111; /* dunkler Text */ + border-radius: 10px; + padding: 10px; + font-size: 14px; + } + + button { + cursor: pointer; + } + + button.primary { + background: #3b82f6; /* freundliches Blau */ + border-color: #3b82f6; + color: #fff; + } + + .card { + border: 1px solid #e5e7eb; + background: #ffffff; /* weiße Karten */ + border-radius: 14px; + padding: 14px; + box-shadow: 0 1px 2px rgba(0,0,0,0.05); /* leichter Schatten */ + } + + .list { + display: grid; + gap: 10px; + } + + .clip { + display: grid; + gap: 6px; + border: 1px solid #e5e7eb; + padding: 10px; + border-radius: 12px; + background: #f3f4f6; /* hellgrauer Hintergrund */ + } + + .meta { + color: #6b7280; /* mittleres Grau */ + font-size: 12px; + display: flex; + gap: 10px; + align-items: center; + } + + .actions { + display: flex; + gap: 8px; + } + + .muted { + color: #6b7280; + } + + .inline { + display: inline-flex; + gap: 8px; + align-items: center; + } + + .pill { + background: #e5e7eb; /* graue Badge */ + border: 1px solid #d1d5db; + padding: 2px 8px; + border-radius: 999px; + font-size: 12px; + } + + a { + color: #2563eb; /* satteres Blau */ + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } +