init
All checks were successful
release-tag / release-image (push) Successful in 4m28s

This commit is contained in:
2025-07-22 16:33:11 +02:00
parent 9c89e250c1
commit 0ada0c135d
9 changed files with 687 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
name: release-tag
on:
push:
branches:
- 'main'
jobs:
release-image:
runs-on: ubuntu-fast
env:
DOCKER_ORG: ${{ vars.DOCKER_ORG }}
DOCKER_LATEST: latest
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
with: # replace it with your local IP
config-inline: |
[registry."${{ vars.DOCKER_REGISTRY }}"]
http = true
insecure = true
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ vars.DOCKER_REGISTRY }} # replace it with your local IP
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
push: true
tags: | # replace it with your local IP and tags
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}

28
Dockerfile Normal file
View File

@@ -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/sctradingtool
# 2. Runtime-Stage
FROM alpine:3.20
# HTTPS-Callouts in Alpine brauchen ca-certificates
RUN apk add --no-cache ca-certificates
COPY --from=builder /bin/sctradingtool /bin/sctradingtool
# Default listens on :8080 siehe main.go
EXPOSE 8080
# Environment defaults; können per compose überschrieben werden
ENV REDIS_ADDR=redis:6379 \
BLOCKLIST_MODE=slave \
HASH_NAME=bl:manual \
MASTER_URL=https://flod-proxy.send.nrw
ENTRYPOINT ["/bin/sctradingtool"]

82
License.md Normal file
View File

@@ -0,0 +1,82 @@
# # CommonsProtect Lizenz Nicht-kommerzielle Edition (CPL-NC)
**Version 1.0 Stand: April 2025**
https://git.send.nrw/b1tsblog/trading
---
**Lizenztyp:** Quelloffen, aber nicht kommerziell nutzbar
**Autorenrecht:** © 2025 Jan Bergner
**Lizenzkürzel:** CPL-NC-1.0
---
Diese Lizenz regelt die Bedingungen für Nutzung, Veränderung und Weiterverbreitung der in diesem Repository enthaltenen Software. Ziel ist es, offene Zusammenarbeit zu ermöglichen, gleichzeitig aber eine kommerzielle Ausbeutung ohne Erlaubnis zu verhindern.
---
## 1. Erlaubte Nutzung
Diese Software darf kostenlos genutzt, kopiert, verändert und weiterverbreitet werden **ausschließlich für nicht-kommerzielle Zwecke**.
---
## 2. Namensnennungspflicht
Wenn du den Quellcode dieser Software ganz oder in Teilen nutzt sei es im Original oder verändert , **musst du deutlich auf das ursprüngliche Projekt hinweisen**, inklusive:
- Name des ursprünglichen Autors oder Projekts
- Link zur Originalquelle (z.B. Git-Repository)
**Beispiel:**
> "Diese Software basiert auf dem Trading-Produkt von B1tK1ll3r, verfügbar unter https://git.send.nrw/b1tsblog/trading."
---
## 3. Verbot kommerzieller Nutzung
Die Nutzung dieser Software oder abgeleiteter Werke ist **ausschließlich für nicht-kommerzielle Zwecke gestattet**. Eine kommerzielle Nutzung in jeglicher Form ist **streng untersagt**.
### Dies umfasst unter anderem:
- Den Verkauf, die Vermietung oder das Anbieten kostenpflichtiger Lizenzen oder Zugänge zur Software
- Die Integration oder Verwendung der Software in Produkten oder Dienstleistungen, die verkauft oder lizenziert werden
- Die Bereitstellung einer gehosteten Version (z.B. als Webservice oder API), bei der direkt oder indirekt Einnahmen erzielt werden (z.B. über Werbung, Abonnements, Spendenplattformen wie Patreon, Zugang gegen Bezahlung)
- Die Nutzung der Software zur Generierung von Einnahmen durch Content-Plattformen (z.B. Streaming, YouTube, Social Media), wenn das Projekt, die Plattform oder die Inhalte **auf irgendeine Weise monetarisiert** werden sei es durch Werbung, Sponsoring, Bezahlabos, Spenden oder bezahlte Partnerschaften
**Auch teilweise Monetarisierung** etwa durch freiwillige Spenden, bezahlten Premium-Zugang oder Monetarisierung begleitender Inhalte fällt unter diese Regelung und ist **nicht erlaubt**.
### Ausnahmen:
Eine kommerzielle Nutzung kann nur mit ausdrücklicher, schriftlicher Erlaubnis des ursprünglichen Autors erfolgen.
---
## 4. Keine Garantie und Haftungsausschluss
Diese Software wird **„wie besehen“** ohne ausdrückliche oder stillschweigende Garantien bereitgestellt. Insbesondere werden **keine Zusicherungen hinsichtlich Funktionalität, Eignung für einen bestimmten Zweck, Fehlerfreiheit oder Verfügbarkeit** gegeben.
Die Nutzung dieser Software erfolgt **auf eigenes Risiko**. Der Autor übernimmt keine Verantwortung für direkte oder indirekte Schäden, Datenverlust, Ausfallzeiten, Sicherheitsprobleme oder sonstige Folgen, die sich aus der Nutzung oder dem Missbrauch der Software ergeben.
### Produkthaftungsausschluss
**Insbesondere ausgeschlossen ist jegliche Haftung nach dem Produkthaftungsgesetz (§1 ProdHaftG)** oder vergleichbaren Regelungen in anderen Ländern. Diese Software wird **nicht als Produkt im Sinne der Produkthaftung bereitgestellt**, da sie ohne Gegenleistung, ohne Prüfverfahren und ohne Gewähr zur Verfügung steht.
### Nutzung in kritischen Systemen
Diese Software ist **nicht vorgesehen oder geeignet für sicherheitskritische Anwendungen** wie:
- Medizinische Geräte
- Luft- und Raumfahrttechnik
- Automatisierte Verkehrs- und Steuerungssysteme
- Industrieanlagen mit hohem Gefährdungspotenzial
- Systeme, bei denen ein Softwarefehler zu Personenschäden oder schweren Sachschäden führen könnte
Die Verwendung in solchen Bereichen erfolgt ausdrücklich auf eigene Gefahr.
---
## Hinweis
Diese Lizenz ist keine anerkannte Open-Source-Lizenz im Sinne der [Open Source Definition](https://opensource.org/osd). Sie stellt eine sogenannte **„source-available“-Lizenz** dar.

28
compose.yml Normal file
View File

@@ -0,0 +1,28 @@
services:
api:
image: git.send.nrw/b1tsblog/trading:latest
container_name: trading
networks:
- tradingnw
environment:
# HIER BEARBEITEN
KT_USERNAME: Nutzername
KT_PASSWORD: Passwort
KT_MEMBER: InGame-Name
# Sollten Ports extern verfügbar gemacht werden müssen (nicht empfohlen)
#ports:
#- "8080:8080" # <host>:<container>
restart: unless-stopped
# Newt-Client für eine Pangolin-Integration
#newt:
#image: fosrl/newt
#container_name: newt
#networks:
#- tradingnw
#restart: unless-stopped
#environment:
#- PANGOLIN_ENDPOINT=
#- NEWT_ID=
#- NEWT_SECRET=
networks:
tradingnw:

BIN
data.db Normal file

Binary file not shown.

18
go.mod Normal file
View File

@@ -0,0 +1,18 @@
module git.send.nrw/b1tsblog/trading
go 1.24.4
require modernc.org/sqlite v1.38.0
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.33.0 // indirect
modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

23
go.sum Normal file
View File

@@ -0,0 +1,23 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=

451
main.go Normal file
View File

@@ -0,0 +1,451 @@
package main
import (
"database/sql"
"html/template"
"log"
"net/http"
"os"
"strconv"
"strings"
_ "modernc.org/sqlite" // statt github.com/mattn/go-sqlite3
)
func GetENV(k, d string) string {
if v := os.Getenv(k); v != "" {
return v
}
return d
}
func Enabled(k string, def bool) bool {
b, err := strconv.ParseBool(strings.ToLower(os.Getenv(k)))
if err != nil {
return def
}
return b
}
var (
username = GetENV("KT_USERNAME", "root")
password = GetENV("KT_PASSWORD", "root")
membername = GetENV("KT_MEMBER", "guest")
)
type Entry struct {
ID int
Anfangsbestand float64
Endbestand float64
Prozentwert float64
Abgabe float64
Gesamtwert float64
Bezahlt bool
}
type Abteilung struct {
Name string
Anteil float64 // in Prozent
Wert float64 // berechnet: Anteil * Summe / 100
WertOffen float64 // berechnet: Anteil * Summe / 100 (von offen)
}
var tmpl = template.Must(template.New("form").Funcs(template.FuncMap{
"formatNumber": formatNumber,
}).Parse(htmlTemplate))
// Tausendertrenner für deutsche Zahlendarstellung (z.B. 12345 → "12.345")
func formatNumber(n float64) string {
intVal := int64(n + 0.5) // runden
s := strconv.FormatInt(intVal, 10)
nStr := ""
for i, r := range reverse(s) {
if i > 0 && i%3 == 0 {
nStr = "." + nStr
}
nStr = string(r) + nStr
}
return nStr
}
func reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
func isAuthenticated(r *http.Request) bool {
cookie, err := r.Cookie("session")
return err == nil && cookie.Value == "authenticated"
}
func main() {
db, err := sql.Open("sqlite", "data.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
createTable(db)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
r.ParseForm()
user := r.FormValue("username")
pass := r.FormValue("password")
if user == username && pass == password {
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: "authenticated",
Path: "/",
})
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
http.Error(w, "Login fehlgeschlagen", http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(loginForm))
})
http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: "",
Path: "/",
MaxAge: -1,
})
http.Redirect(w, r, "/", http.StatusSeeOther)
})
http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) {
if !isAuthenticated(r) {
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
return
}
id := r.URL.Query().Get("id")
if id != "" {
db.Exec("DELETE FROM eintraege WHERE id = ?", id)
}
http.Redirect(w, r, "/", http.StatusSeeOther)
})
http.HandleFunc("/reset", func(w http.ResponseWriter, r *http.Request) {
if !isAuthenticated(r) {
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
return
}
// Tabelle leeren
db.Exec("DELETE FROM eintraege")
// Auto-Increment-Zähler zurücksetzen
db.Exec("DELETE FROM sqlite_sequence WHERE name='eintraege'")
http.Redirect(w, r, "/", http.StatusSeeOther)
})
http.HandleFunc("/markaspaid", func(w http.ResponseWriter, r *http.Request) {
if !isAuthenticated(r) {
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
return
}
id := r.URL.Query().Get("id")
if id != "" {
db.Exec("UPDATE eintraege SET bezahlt = 1 WHERE id = ?", id)
}
http.Redirect(w, r, "/", http.StatusSeeOther)
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
if !isAuthenticated(r) {
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
return
}
r.ParseForm()
anfang, _ := strconv.ParseFloat(r.FormValue("anfangsbestand"), 64)
ende, _ := strconv.ParseFloat(r.FormValue("endbestand"), 64)
prozent, _ := strconv.ParseFloat(r.FormValue("prozentwert"), 64)
diff := ende - anfang
abgabe := (diff / 100) * prozent
_, err := db.Exec(`INSERT INTO eintraege (anfangsbestand, endbestand, prozentwert, abgabe) VALUES (?, ?, ?, ?)`,
anfang, ende, prozent, abgabe)
if err != nil {
http.Error(w, "Fehler beim Einfügen", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
rows, err := db.Query(`SELECT id, anfangsbestand, endbestand, prozentwert, abgabe, bezahlt FROM eintraege`)
if err != nil {
http.Error(w, "Fehler beim Abrufen", http.StatusInternalServerError)
return
}
defer rows.Close()
var eintraege []Entry
var summe float64
var offeneSumme float64
for rows.Next() {
var e Entry
var bezahlt int
err := rows.Scan(&e.ID, &e.Anfangsbestand, &e.Endbestand, &e.Prozentwert, &e.Abgabe, &bezahlt)
if err != nil {
log.Println("Fehler beim Scan:", err)
continue
}
e.Gesamtwert = e.Endbestand - e.Anfangsbestand
e.Bezahlt = bezahlt == 1
eintraege = append(eintraege, e)
if !e.Bezahlt {
offeneSumme += e.Abgabe
} else {
summe += e.Abgabe
}
}
// Dynamische Abteilungen frei anpassbar
abteilungen := []Abteilung{
{Name: "Raumkampf", Anteil: 15},
{Name: "Bodenkampf", Anteil: 8},
{Name: "Racing", Anteil: 3},
{Name: "Medical", Anteil: 5},
{Name: "Exploration", Anteil: 3},
{Name: "Rettung", Anteil: 5},
{Name: "Logistik", Anteil: 8},
{Name: "Mining", Anteil: 3},
{Name: "Salvaging", Anteil: 3},
{Name: "Trading", Anteil: 3},
{Name: "Basebuilding", Anteil: 10},
{Name: "Crafting", Anteil: 8},
{Name: "Forschung", Anteil: 5},
{Name: "Events", Anteil: 15},
{Name: "Roleplay", Anteil: 3},
{Name: "Kunstflug", Anteil: 3},
}
for i := range abteilungen {
abteilungen[i].Wert = (abteilungen[i].Anteil / 100) * summe
abteilungen[i].WertOffen = (abteilungen[i].Anteil / 100) * offeneSumme
}
tmpl.Execute(w, struct {
Entries []Entry
Summe float64
OffeneSumme float64
Abteilungen []Abteilung
LoggedIn bool
Member string
}{
Entries: eintraege,
Summe: summe,
OffeneSumme: offeneSumme,
Abteilungen: abteilungen,
LoggedIn: isAuthenticated(r),
Member: membername,
})
})
log.Println("Server läuft auf http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
func createTable(db *sql.DB) {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS eintraege (
id INTEGER PRIMARY KEY AUTOINCREMENT,
anfangsbestand REAL,
endbestand REAL,
prozentwert REAL,
abgabe REAL,
bezahlt INTEGER DEFAULT 0
);
`)
if err != nil {
log.Fatal(err)
}
// Falls die Tabelle schon existiert, aber die Spalte "bezahlt" fehlt (z.B. nach Update)
_, err = db.Exec(`ALTER TABLE eintraege ADD COLUMN bezahlt INTEGER DEFAULT 0;`)
if err != nil && !strings.Contains(err.Error(), "duplicate column") {
log.Fatal(err)
}
}
const loginForm = `
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<h2>Login</h2>
<form method="POST" class="card p-4 shadow-sm">
<div class="mb-3">
<label class="form-label">Benutzername</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Passwort</label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</body>
</html>
`
const htmlTemplate = `
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Abgabe-Berechnung</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="d-flex justify-content-end">
{{if .LoggedIn}}
<form action="/logout" method="POST">
<button type="submit" class="btn btn-sm btn-secondary">Logout</button>
</form>
{{else}}
<a href="/login" class="btn btn-sm btn-outline-primary">Login</a>
{{end}}
</div>
<h1 class="mb-4">Beitrag zur Community von der Trading-Staffel ({{.Member}})</h1>
{{if .LoggedIn}}
<form method="POST" class="card p-4 mb-4 shadow-sm">
<div class="row mb-3">
<div class="col">
<label class="form-label">Anfangsbestand</label>
<input type="number" step="0.01" name="anfangsbestand" class="form-control" required>
</div>
<div class="col">
<label class="form-label">Endbestand</label>
<input type="number" step="0.01" name="endbestand" class="form-control" required>
</div>
<div class="col">
<label class="form-label">Prozentwert</label>
<select name="prozentwert" class="form-select">
<option value="30">30%</option>
<option value="10">10%</option>
<option value="20">20%</option>
<option value="30">30%</option>
<option value="40">40%</option>
<option value="50">50%</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">Berechnen & Speichern</button>
</form>
{{end}}
<h2 class="mb-3">Gespeicherte Einträge</h2>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>Anfang</th>
<th>Ende</th>
<th>Profit</th>
<th>Prozent</th>
<th>Abgabe</th>
<th>Status</th>
{{if .LoggedIn}}<th>Aktion</th>{{end}}
</tr>
</thead>
<tbody>
{{range .Entries}}
<tr>
<td>{{.ID}}</td>
<td>{{formatNumber .Anfangsbestand}}</td>
<td>{{formatNumber .Endbestand}}</td>
<td>{{formatNumber .Gesamtwert}}</td>
<td>{{formatNumber .Prozentwert}}%</td>
<td>
{{formatNumber .Abgabe}}
</td>
<td>
{{if .Bezahlt}}
<span class="badge bg-success">✓ verteilt</span>
{{else}}
{{if $.LoggedIn}}
<a href="/markaspaid?id={{.ID}}" class="btn btn-sm btn-outline-success">Als verteilt markieren</a>
{{else}}
<span class="badge bg-danger">✗ nicht verteilt</span>
{{end}}
{{end}}
</td>
{{if $.LoggedIn}}
<td>
<a href="/delete?id={{.ID}}" class="btn btn-sm btn-danger" onclick="return confirm('Eintrag wirklich löschen?')">Löschen</a>
</td>
{{end}}
</tr>
{{end}}
</tbody>
</table>
<div class="alert alert-warning">
<strong>Offen zur Abgabe:</strong> {{formatNumber .OffeneSumme}} UEC
</div>
<div class="alert alert-success">
<strong>Summe aller Abgaben an die Knebel-Community:</strong> {{formatNumber .Summe}} UEC
</div>
<hr />
<div class="alert alert-info">
<strong>Die tatsächlichen Werte können abweichen.</strong> Die dargestellten Werte sind meine Vorstellung einer sinnvollen Verteilung.<br>
Die Summe wird an die Orga-Leitung entrichtet. Die entgültige Entscheidung über die Verteilung obliegt der Orga-Leitung.
</div>
<h4 class="mt-4">Verteilung auf Abteilungen:</h4>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Abteilung</th>
<th>Verteilungsschlüssel</th>
<th>Summe verteilt</th>
<th>Summe offen</th>
</tr>
</thead>
<tbody>
{{range .Abteilungen}}
<tr>
<td>{{.Name}}</td>
<td>{{formatNumber .Anteil}}%</td>
<td>{{formatNumber .Wert}} UEC</td>
<td>{{formatNumber .WertOffen}} UEC</td>
</tr>
{{end}}
</tbody>
</table>
{{if .LoggedIn}}
<form action="/reset" method="POST" onsubmit="return confirm('Alle Einträge wirklich löschen?')">
<button type="submit" class="btn btn-outline-danger mt-3">Alle Einträge löschen</button>
</form>
{{end}}
</div>
</body>
</html>
`

6
static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long