Compare commits
28 Commits
308505a588
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 134e601d57 | |||
| 842c541c13 | |||
| 41cee8af1d | |||
| 190cded4e5 | |||
| 4ecdd40613 | |||
| 7c73ac7749 | |||
| afc6bcae83 | |||
| ecf5cf9773 | |||
| 33b2a2d966 | |||
| e12f631c4a | |||
| 3c71785849 | |||
| 9308b914ff | |||
| 4fccd54805 | |||
| 83d8644ef9 | |||
| 24cfcd61ed | |||
| 58b7552714 | |||
| 92bff5640f | |||
| b3a35f7e74 | |||
| ca7eadfcb2 | |||
| d6d5b4b647 | |||
| 15d4bffb72 | |||
| ec3899677a | |||
| 5bd89aa32b | |||
| 4bd351cd6d | |||
| fdd080523d | |||
| 4ad790de64 | |||
| 73ea4b4040 | |||
| 07e19bd89f |
15
Dockerfile
15
Dockerfile
@@ -14,16 +14,23 @@ FROM alpine:3.20
|
|||||||
# HTTPS-Callouts in Alpine brauchen ca-certificates
|
# HTTPS-Callouts in Alpine brauchen ca-certificates
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
RUN mkdir /data
|
RUN mkdir /data
|
||||||
|
RUN mkdir /dynamicsrc
|
||||||
|
RUN mkdir /tempsrc
|
||||||
COPY --from=builder /bin/sctradingtool /bin/sctradingtool
|
COPY --from=builder /bin/sctradingtool /bin/sctradingtool
|
||||||
COPY ./static /data/static
|
COPY ./static /data/static
|
||||||
|
COPY ./static /tempsrc/static
|
||||||
|
COPY ./dynamicsrc /dynamicsrc
|
||||||
|
|
||||||
# Default listens on :8080 – siehe main.go
|
# Default listens on :8080 – siehe main.go
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Environment defaults; können per compose überschrieben werden
|
# Environment defaults; können per compose überschrieben werden
|
||||||
ENV REDIS_ADDR=redis:6379 \
|
ENV KT_USERNAME=admin \
|
||||||
BLOCKLIST_MODE=slave \
|
KT_PASSWORD=admin \
|
||||||
HASH_NAME=bl:manual \
|
KT_MEMBER=guest \
|
||||||
MASTER_URL=https://flod-proxy.send.nrw
|
KT_HASIMPRESSUM=true \
|
||||||
|
KT_IMPRESSUM=https://www.google.de \
|
||||||
|
KT_PRODUCTIVE=true
|
||||||
|
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sctradingtool"]
|
ENTRYPOINT ["/bin/sctradingtool"]
|
||||||
BIN
dynamicsrc/footerlower.webp
Normal file
BIN
dynamicsrc/footerlower.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
dynamicsrc/footerupper.webp
Normal file
BIN
dynamicsrc/footerupper.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
1
dynamicsrc/pois.json
Normal file
1
dynamicsrc/pois.json
Normal file
File diff suppressed because one or more lines are too long
3
go.mod
3
go.mod
@@ -10,8 +10,9 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/crypto v0.40.0
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
modernc.org/libc v1.65.10 // indirect
|
modernc.org/libc v1.65.10 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
|||||||
30
go.sum
30
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
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/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -8,16 +10,40 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
|
|||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
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 h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
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/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||||
|
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||||
|
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||||
|
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
|
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||||
|
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
|
||||||
|
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
|
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
|
||||||
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
|
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 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
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 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||||
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
648
main.go
648
main.go
@@ -1,7 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
@@ -10,11 +14,16 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
_ "modernc.org/sqlite" // statt github.com/mattn/go-sqlite3
|
_ "modernc.org/sqlite" // statt github.com/mattn/go-sqlite3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* Quellen */
|
||||||
|
/* https://starmap.space/api/v3/oc/index.php */
|
||||||
|
|
||||||
func GetENV(k, d string) string {
|
func GetENV(k, d string) string {
|
||||||
if v := os.Getenv(k); v != "" {
|
if v := os.Getenv(k); v != "" {
|
||||||
return v
|
return v
|
||||||
@@ -35,8 +44,81 @@ var (
|
|||||||
password = GetENV("KT_PASSWORD", "root")
|
password = GetENV("KT_PASSWORD", "root")
|
||||||
membername = GetENV("KT_MEMBER", "demo")
|
membername = GetENV("KT_MEMBER", "demo")
|
||||||
productive = Enabled("KT_PRODUCTIVE", false)
|
productive = Enabled("KT_PRODUCTIVE", false)
|
||||||
|
hasimpressum = Enabled("KT_HASIMPRESSUM", false)
|
||||||
|
impressum = GetENV("KT_IMPRESSUM", "")
|
||||||
|
hashedPassword = ""
|
||||||
|
orte = []string{}
|
||||||
|
schiffe = []string{
|
||||||
|
"", "100i", "125a", "135c", "Arrow", "Aurora CL", "Aurora ES", "Aurora LN", "Aurora LX", "Aurora MR",
|
||||||
|
"Avenger Stalker", "Avenger Titan", "Avenger Titan Renegade", "Avenger Warlock",
|
||||||
|
"Blade", "Buccaneer", "C1 Spirit", "C2 Hercules Starlifter", "M2 Hercules Starlifter", "A2 Hercules Starlifter", "C8 Pisces", "C8R Pisces Rescue",
|
||||||
|
"C8X Pisces Expedition", "Carrack", "Caterpillar", "Constellation Andromeda",
|
||||||
|
"Constellation Aquila", "Constellation Phoenix", "Constellation Taurus", "Corsair",
|
||||||
|
"Cutlass Black", "Cutlass Blue", "Cutlass Red", "Cutter", "Defender", "Eclipse",
|
||||||
|
"Freelancer", "Freelancer DUR", "Freelancer MAX", "Gladiator", "Gladius", "Glaive",
|
||||||
|
"Hammerhead", "Hawk", "Herald", "Hurricane", "Idris-P", "Idris-K", "Idris-M", "Javelin",
|
||||||
|
"Khartu-Al", "Kraken", "M50", "Merchantman", "Mercury Star Runner", "Mustang Alpha",
|
||||||
|
"Mustang Beta", "Mustang Delta", "Mustang Gamma", "Mustang Omega", "Nomad",
|
||||||
|
"Orion", "P-52 Merlin", "P-72 Archimedes", "Prospector", "Prowler", "Prowler Utility", "Raft",
|
||||||
|
"Reclaimer", "Redeemer", "Reliant Kore", "Reliant Mako", "Reliant Sen", "Reliant Tana",
|
||||||
|
"Retaliator", "Sabre Peregrine", "Starfarer Gemini", "Talon", "Talon Shrike",
|
||||||
|
"Terrapin", "Vulture", "Hull-A", "Hull-C", "Zeus ES", "Zeus CL",
|
||||||
|
// …weitere Capital- und Concept-Schiffe sind ebenfalls bekannt!
|
||||||
|
}
|
||||||
|
waren = []string{
|
||||||
|
"", "Laranite", "Titanium", "Medical Supplies", "Gold", "Agricium", "Hydrogen", "Hydrogen Fuel", "Nitrogen", "Astatine", "Processed Food", "Scrap", "Recycled Material Composite", "Agricultural Supplies",
|
||||||
|
"Iodine", "Aluminium", "Copper", "Lithium", "Silicon", "Tungsten", "Aphorite", "Beryl", "Bexalite", "Waste", "Osoian hides", "Borase", "WiDoW", "Fresh Food", "Heart of the Woods", "Pressurized Ice", "Atlasium",
|
||||||
|
"Corundum", "Diamond", "Dolivine", "Hadanite", "Hephaestanite", "Laranite", "Quartz", "Taranite", "Stims", "Carbon", "Slam", "Distilled Spirits", "Maze", "Gasping Weevil Eggs", "E'tam", "Iron", "Methane",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type POI struct {
|
||||||
|
ItemID int `json:"item_id"`
|
||||||
|
System string `json:"System"`
|
||||||
|
Planet string `json:"Planet"`
|
||||||
|
PoiName string `json:"PoiName"`
|
||||||
|
Type string `json:"Type"`
|
||||||
|
Classification string `json:"Classification"`
|
||||||
|
Latitude float64 `json:"Latitude"`
|
||||||
|
Longitude float64 `json:"Longitude"`
|
||||||
|
Longitude360 float64 `json:"Longitude360"`
|
||||||
|
Height float64 `json:"Height"`
|
||||||
|
XCoord float64 `json:"XCoord"`
|
||||||
|
YCoord float64 `json:"YCoord"`
|
||||||
|
ZCoord float64 `json:"ZCoord"`
|
||||||
|
QTMarker int `json:"QTMarker"`
|
||||||
|
NextPOI string `json:"NextPOI"`
|
||||||
|
NextQTMarker string `json:"NextQTMarker"`
|
||||||
|
Comment string `json:"Comment"`
|
||||||
|
Submitted string `json:"Submitted"` // oder time.Time, falls du umwandelst
|
||||||
|
Introduced string `json:"Introduced"` // z. B. "Unknown"
|
||||||
|
GUID string `json:"GUID"`
|
||||||
|
POISize *string `json:"POI_Size"`
|
||||||
|
POIType *string `json:"POI_Type"`
|
||||||
|
POIEntries string `json:"POI_Entries"`
|
||||||
|
POIAccessableFoot string `json:"POI_Accessable_Foot"`
|
||||||
|
POIAccessableVehicle string `json:"POI_Accessable_Vehicle"`
|
||||||
|
POIAccessableShip string `json:"POI_Accessable_Ship"`
|
||||||
|
POIDefenses string `json:"POI_Defenses"`
|
||||||
|
POILandingPads string `json:"POI_LandingPads"`
|
||||||
|
POIVehiclePads string `json:"POI_VehiclePads"`
|
||||||
|
POITerminals string `json:"POI_Terminals"`
|
||||||
|
POIMedBay string `json:"POI_MedBay"`
|
||||||
|
POIServices string `json:"POI_Services"`
|
||||||
|
POIAtmosphere *string `json:"POI_Atmosphere"`
|
||||||
|
POINPCs string `json:"POI_NPCs"`
|
||||||
|
ZoneArmistice int `json:"Zone_Armistice"`
|
||||||
|
ZoneNoFly int `json:"Zone_NoFly"`
|
||||||
|
ZoneTrespassing int `json:"Zone_Trespassing"`
|
||||||
|
ZoneBiome string `json:"Zone_Biome"`
|
||||||
|
ZoneGravitation int `json:"Zone_Gravitation"`
|
||||||
|
ZoneTemperatureMin string `json:"Zone_Temperature_Min"`
|
||||||
|
ZoneTemperatureMax string `json:"Zone_Temperature_Max"`
|
||||||
|
Minerals *string `json:"Minerals"`
|
||||||
|
SpecialLoot string `json:"SpecialLoot"`
|
||||||
|
Missions string `json:"Missions"`
|
||||||
|
}
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
ID int
|
ID int
|
||||||
Anfangsbestand float64
|
Anfangsbestand float64
|
||||||
@@ -46,6 +128,11 @@ type Entry struct {
|
|||||||
Gesamtwert float64
|
Gesamtwert float64
|
||||||
Bezahlt bool
|
Bezahlt bool
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
|
Startort string
|
||||||
|
Zielort string
|
||||||
|
Schiff string
|
||||||
|
Ware string
|
||||||
|
Zeitaufwand float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Abteilung struct {
|
type Abteilung struct {
|
||||||
@@ -61,6 +148,9 @@ type Monatsstatistik struct {
|
|||||||
Monat string // z. B. "07.2025"
|
Monat string // z. B. "07.2025"
|
||||||
Summe float64 // bezahlte
|
Summe float64 // bezahlte
|
||||||
SummeOffen float64 // noch nicht bezahlt
|
SummeOffen float64 // noch nicht bezahlt
|
||||||
|
Prozent float64
|
||||||
|
ProzentOffen float64
|
||||||
|
Eintraege []Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmpl = template.Must(template.New("form").Funcs(template.FuncMap{
|
var tmpl = template.Must(template.New("form").Funcs(template.FuncMap{
|
||||||
@@ -104,18 +194,78 @@ func reverse(s string) string {
|
|||||||
|
|
||||||
func isAuthenticated(r *http.Request) bool {
|
func isAuthenticated(r *http.Request) bool {
|
||||||
cookie, err := r.Cookie("session")
|
cookie, err := r.Cookie("session")
|
||||||
return err == nil && cookie.Value == "authenticated"
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Prüfen, ob der Token im sessionStore existiert
|
||||||
|
_, ok := sessionStore[cookie.Value]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionStore = make(map[string]string) // token → username
|
||||||
|
var loginAttempts = make(map[string]int)
|
||||||
|
var loginLastAttempt = make(map[string]time.Time)
|
||||||
|
var loginBlockedUntil = make(map[string]time.Time)
|
||||||
|
var loginMutex sync.Mutex
|
||||||
|
|
||||||
|
func hashPassword(pw string) string {
|
||||||
|
hash, _ := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
|
||||||
|
return string(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPasswordHash(pw, hash string) bool {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSessionToken() string {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return "" // handle error besser im echten Code
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
err error
|
err error
|
||||||
|
data []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
if productive {
|
if productive {
|
||||||
db, err = sql.Open("sqlite", "/data/data.db")
|
db, err = sql.Open("sqlite", "/data/data.db")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
data, err = os.ReadFile("/dynamicsrc/pois.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
db, err = sql.Open("sqlite", "./data.db")
|
db, err = sql.Open("sqlite", "./data.db")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
data, err = os.ReadFile("./dynamicsrc/pois.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword = hashPassword(password)
|
||||||
|
|
||||||
|
var pois []POI
|
||||||
|
if err := json.Unmarshal(data, &pois); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
orte = append(orte, "")
|
||||||
|
for _, poi := range pois {
|
||||||
|
if poi.System == "Stanton" || poi.System == "Pyro" {
|
||||||
|
formatted := fmt.Sprintf("%s - %s - %s (%s)", poi.System, poi.Planet, poi.PoiName, poi.Type)
|
||||||
|
orte = append(orte, formatted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -132,34 +282,86 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ip := strings.Split(r.RemoteAddr, ":")[0]
|
||||||
|
|
||||||
|
loginMutex.Lock()
|
||||||
|
blockUntil, blocked := loginBlockedUntil[ip]
|
||||||
|
if blocked && time.Now().Before(blockUntil) {
|
||||||
|
loginMutex.Unlock()
|
||||||
|
http.Error(w, "Zu viele Fehlversuche. Bitte versuch es später erneut.", http.StatusTooManyRequests)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loginMutex.Unlock()
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
user := r.FormValue("username")
|
user := r.FormValue("username")
|
||||||
pass := r.FormValue("password")
|
pass := r.FormValue("password")
|
||||||
if user == username && pass == password {
|
|
||||||
|
if user == username && checkPasswordHash(pass, hashedPassword) {
|
||||||
|
token := generateSessionToken()
|
||||||
|
|
||||||
|
// Speichere Session
|
||||||
|
sessionStore[token] = user
|
||||||
|
|
||||||
|
// Cookie setzen
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "session",
|
Name: "session",
|
||||||
Value: "authenticated",
|
Value: token,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
|
HttpOnly: true,
|
||||||
|
Secure: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Erfolgreich -> Versuche zurücksetzen
|
||||||
|
loginMutex.Lock()
|
||||||
|
delete(loginAttempts, ip)
|
||||||
|
delete(loginLastAttempt, ip)
|
||||||
|
delete(loginBlockedUntil, ip)
|
||||||
|
loginMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fehlversuch behandeln
|
||||||
|
loginMutex.Lock()
|
||||||
|
loginAttempts[ip]++
|
||||||
|
loginLastAttempt[ip] = time.Now()
|
||||||
|
if loginAttempts[ip] >= 5 {
|
||||||
|
loginBlockedUntil[ip] = time.Now().Add(10 * time.Minute)
|
||||||
|
}
|
||||||
|
loginMutex.Unlock()
|
||||||
|
|
||||||
http.Error(w, "Login fehlgeschlagen", http.StatusUnauthorized)
|
http.Error(w, "Login fehlgeschlagen", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: Login-Formular
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(loginForm))
|
w.Write([]byte(loginForm))
|
||||||
})
|
})
|
||||||
|
|
||||||
http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cookie, err := r.Cookie("session")
|
||||||
|
if err == nil {
|
||||||
|
token := cookie.Value
|
||||||
|
// Token aus dem serverseitigen Store löschen
|
||||||
|
delete(sessionStore, token)
|
||||||
|
|
||||||
|
// Cookie ungültig machen
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "session",
|
Name: "session",
|
||||||
Value: "",
|
Value: "",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
|
HttpOnly: true,
|
||||||
|
Secure: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -172,6 +374,11 @@ func main() {
|
|||||||
if id != "" {
|
if id != "" {
|
||||||
db.Exec("DELETE FROM eintraege WHERE id = ?", id)
|
db.Exec("DELETE FROM eintraege WHERE id = ?", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
cache.LastComputed = time.Time{} // auf null zurücksetzen
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -185,6 +392,11 @@ func main() {
|
|||||||
|
|
||||||
// Auto-Increment-Zähler zurücksetzen
|
// Auto-Increment-Zähler zurücksetzen
|
||||||
db.Exec("DELETE FROM sqlite_sequence WHERE name='eintraege'")
|
db.Exec("DELETE FROM sqlite_sequence WHERE name='eintraege'")
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
cache.LastComputed = time.Time{} // auf null zurücksetzen
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -198,6 +410,11 @@ func main() {
|
|||||||
if id != "" {
|
if id != "" {
|
||||||
db.Exec("UPDATE eintraege SET bezahlt = 1 WHERE id = ?", id)
|
db.Exec("UPDATE eintraege SET bezahlt = 1 WHERE id = ?", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
cache.LastComputed = time.Time{} // auf null zurücksetzen
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -211,6 +428,11 @@ func main() {
|
|||||||
if id != "" {
|
if id != "" {
|
||||||
db.Exec("UPDATE eintraege SET bezahlt = 0 WHERE id = ?", id)
|
db.Exec("UPDATE eintraege SET bezahlt = 0 WHERE id = ?", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
cache.LastComputed = time.Time{} // auf null zurücksetzen
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -226,17 +448,39 @@ func main() {
|
|||||||
prozent, _ := strconv.ParseFloat(r.FormValue("prozentwert"), 64)
|
prozent, _ := strconv.ParseFloat(r.FormValue("prozentwert"), 64)
|
||||||
diff := ende - anfang
|
diff := ende - anfang
|
||||||
abgabe := (diff / 100) * prozent
|
abgabe := (diff / 100) * prozent
|
||||||
|
startort := r.FormValue("startort")
|
||||||
|
zielort := r.FormValue("zielort")
|
||||||
|
schiff := r.FormValue("schiff")
|
||||||
|
ware := r.Form["ware"]
|
||||||
|
wareStr := strings.Join(ware, ", ")
|
||||||
|
zeitaufwand, _ := strconv.ParseFloat(r.FormValue("zeitaufwand"), 64)
|
||||||
|
|
||||||
_, err := db.Exec(`INSERT INTO eintraege (anfangsbestand, endbestand, prozentwert, abgabe, created_at) VALUES (?, ?, ?, ?, datetime('now'))`, anfang, ende, prozent, abgabe)
|
_, err := db.Exec(`INSERT INTO eintraege (anfangsbestand, endbestand, prozentwert, abgabe, created_at, startort, zielort, schiff, ware, zeitaufwand) VALUES (?, ?, ?, ?, datetime('now'), ?, ?, ?, ?, ?)`, anfang, ende, prozent, abgabe, startort, zielort, schiff, wareStr, zeitaufwand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Fehler beim Einfügen", http.StatusInternalServerError)
|
http.Error(w, "Fehler beim Einfügen", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
cache.LastComputed = time.Time{} // auf null zurücksetzen
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := db.Query(`SELECT id, anfangsbestand, endbestand, prozentwert, abgabe, bezahlt, created_at FROM eintraege`)
|
cacheMutex.RLock()
|
||||||
|
validCache := time.Since(cache.LastComputed) < 6*time.Hour
|
||||||
|
cachedData := cache.Data
|
||||||
|
cacheMutex.RUnlock()
|
||||||
|
|
||||||
|
if validCache {
|
||||||
|
cachedData.LoggedIn = isAuthenticated(r)
|
||||||
|
tmpl.Execute(w, cachedData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT id, anfangsbestand, endbestand, prozentwert, abgabe, bezahlt, created_at, startort, zielort, schiff, ware, zeitaufwand FROM eintraege`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Fehler beim Abrufen", http.StatusInternalServerError)
|
http.Error(w, "Fehler beim Abrufen", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -250,7 +494,13 @@ func main() {
|
|||||||
var e Entry
|
var e Entry
|
||||||
var bezahlt int
|
var bezahlt int
|
||||||
var createdAt sql.NullString
|
var createdAt sql.NullString
|
||||||
err := rows.Scan(&e.ID, &e.Anfangsbestand, &e.Endbestand, &e.Prozentwert, &e.Abgabe, &bezahlt, &createdAt)
|
var startort sql.NullString
|
||||||
|
var zielort sql.NullString
|
||||||
|
var schiff sql.NullString
|
||||||
|
var ware sql.NullString
|
||||||
|
var zeitaufwand sql.NullFloat64
|
||||||
|
|
||||||
|
err := rows.Scan(&e.ID, &e.Anfangsbestand, &e.Endbestand, &e.Prozentwert, &e.Abgabe, &bezahlt, &createdAt, &startort, &zielort, &schiff, &ware, &zeitaufwand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Fehler beim Scan:", err)
|
log.Println("Fehler beim Scan:", err)
|
||||||
continue
|
continue
|
||||||
@@ -269,6 +519,36 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
e.CreatedAt = "unbekannt"
|
e.CreatedAt = "unbekannt"
|
||||||
}
|
}
|
||||||
|
/**/
|
||||||
|
if startort.Valid {
|
||||||
|
e.Startort = startort.String
|
||||||
|
} else {
|
||||||
|
e.Startort = "unbekannt"
|
||||||
|
}
|
||||||
|
|
||||||
|
if zielort.Valid {
|
||||||
|
e.Zielort = zielort.String
|
||||||
|
} else {
|
||||||
|
e.Zielort = "unbekannt"
|
||||||
|
}
|
||||||
|
|
||||||
|
if schiff.Valid {
|
||||||
|
e.Schiff = schiff.String
|
||||||
|
} else {
|
||||||
|
e.Schiff = "unbekannt"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ware.Valid {
|
||||||
|
e.Ware = ware.String
|
||||||
|
} else {
|
||||||
|
e.Ware = "unbekannt"
|
||||||
|
}
|
||||||
|
|
||||||
|
if zeitaufwand.Valid {
|
||||||
|
e.Zeitaufwand = zeitaufwand.Float64
|
||||||
|
} else {
|
||||||
|
e.Zeitaufwand = 0
|
||||||
|
}
|
||||||
|
|
||||||
eintraege = append(eintraege, e)
|
eintraege = append(eintraege, e)
|
||||||
|
|
||||||
@@ -285,11 +565,14 @@ func main() {
|
|||||||
if _, ok := monatsMap[monatKey]; !ok {
|
if _, ok := monatsMap[monatKey]; !ok {
|
||||||
monatsMap[monatKey] = &Monatsstatistik{Monat: monatKey}
|
monatsMap[monatKey] = &Monatsstatistik{Monat: monatKey}
|
||||||
}
|
}
|
||||||
|
monatsMap[monatKey].Eintraege = append(monatsMap[monatKey].Eintraege, e)
|
||||||
if e.Bezahlt {
|
if e.Bezahlt {
|
||||||
monatsMap[monatKey].Summe += e.Abgabe
|
monatsMap[monatKey].Summe += e.Abgabe
|
||||||
} else {
|
} else {
|
||||||
monatsMap[monatKey].SummeOffen += e.Abgabe
|
monatsMap[monatKey].SummeOffen += e.Abgabe
|
||||||
}
|
}
|
||||||
|
monatsMap[monatKey].Prozent = monatsMap[monatKey].Summe / (monatsMap[monatKey].Summe + monatsMap[monatKey].SummeOffen) * 100
|
||||||
|
monatsMap[monatKey].ProzentOffen = monatsMap[monatKey].SummeOffen / (monatsMap[monatKey].Summe + monatsMap[monatKey].SummeOffen) * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
var monatsStat []Monatsstatistik
|
var monatsStat []Monatsstatistik
|
||||||
@@ -303,10 +586,10 @@ func main() {
|
|||||||
tj, _ := time.Parse("01.2006", monatsStat[j].Monat)
|
tj, _ := time.Parse("01.2006", monatsStat[j].Monat)
|
||||||
return ti.Before(tj)
|
return ti.Before(tj)
|
||||||
})
|
})
|
||||||
|
//test
|
||||||
// Dynamische Abteilungen – frei anpassbar
|
// Dynamische Abteilungen – frei anpassbar
|
||||||
abteilungen := []Abteilung{
|
abteilungen := []Abteilung{
|
||||||
{Name: "Raumkampf", Anteil: 15, Beispiel: "CF-337 Panther", WertItem: 36308},
|
{Name: "Raumkampf (+8)", Anteil: 23, Beispiel: "CF-337 Panther", WertItem: 36308},
|
||||||
{Name: "Bodenkampf", Anteil: 8, Beispiel: "P4-AR Rifle", WertItem: 5900},
|
{Name: "Bodenkampf", Anteil: 8, Beispiel: "P4-AR Rifle", WertItem: 5900},
|
||||||
{Name: "Racing", Anteil: 3, Beispiel: "LumaCore - Power Plant", WertItem: 69300},
|
{Name: "Racing", Anteil: 3, Beispiel: "LumaCore - Power Plant", WertItem: 69300},
|
||||||
{Name: "Medical", Anteil: 5, Beispiel: "ParaMed Medical Device", WertItem: 1250},
|
{Name: "Medical", Anteil: 5, Beispiel: "ParaMed Medical Device", WertItem: 1250},
|
||||||
@@ -319,7 +602,7 @@ func main() {
|
|||||||
{Name: "Basebuilding (+10)", Anteil: 0, Beispiel: "CF-337 Panther", WertItem: 36308},
|
{Name: "Basebuilding (+10)", Anteil: 0, Beispiel: "CF-337 Panther", WertItem: 36308},
|
||||||
{Name: "Crafting (+8)", Anteil: 0, Beispiel: "CF-337 Panther", WertItem: 36308},
|
{Name: "Crafting (+8)", Anteil: 0, Beispiel: "CF-337 Panther", WertItem: 36308},
|
||||||
{Name: "Forschung (+5)", Anteil: 0, Beispiel: "CF-337 Panther", WertItem: 36308},
|
{Name: "Forschung (+5)", Anteil: 0, Beispiel: "CF-337 Panther", WertItem: 36308},
|
||||||
{Name: "Events (-23)", Anteil: 38, Beispiel: "CF-337 Panther", WertItem: 36308},
|
{Name: "Events (-15)", Anteil: 30, Beispiel: "CF-337 Panther", WertItem: 36308},
|
||||||
{Name: "Roleplay", Anteil: 3, Beispiel: "Clothing", WertItem: 8400},
|
{Name: "Roleplay", Anteil: 3, Beispiel: "Clothing", WertItem: 8400},
|
||||||
{Name: "Kunstflug", Anteil: 3, Beispiel: "Beacon Undersuit Crimson", WertItem: 1000},
|
{Name: "Kunstflug", Anteil: 3, Beispiel: "Beacon Undersuit Crimson", WertItem: 1000},
|
||||||
}
|
}
|
||||||
@@ -329,7 +612,28 @@ func main() {
|
|||||||
abteilungen[i].WertOffen = (abteilungen[i].Anteil / 100) * offeneSumme
|
abteilungen[i].WertOffen = (abteilungen[i].Anteil / 100) * offeneSumme
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl.Execute(w, struct {
|
computed := TemplateData{
|
||||||
|
Entries: eintraege,
|
||||||
|
Summe: summe,
|
||||||
|
OffeneSumme: offeneSumme,
|
||||||
|
Abteilungen: abteilungen,
|
||||||
|
Monatsstatistik: monatsStat,
|
||||||
|
Member: membername,
|
||||||
|
HasImpressum: hasimpressum,
|
||||||
|
Impressum: impressum,
|
||||||
|
Orte: orte,
|
||||||
|
Schiffe: schiffe,
|
||||||
|
Waren: waren,
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
cache.Data = computed
|
||||||
|
cache.LastComputed = time.Now()
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
|
tmpl.Execute(w, computed)
|
||||||
|
|
||||||
|
/*tmpl.Execute(w, struct {
|
||||||
Entries []Entry
|
Entries []Entry
|
||||||
Summe float64
|
Summe float64
|
||||||
OffeneSumme float64
|
OffeneSumme float64
|
||||||
@@ -337,6 +641,11 @@ func main() {
|
|||||||
Monatsstatistik []Monatsstatistik
|
Monatsstatistik []Monatsstatistik
|
||||||
LoggedIn bool
|
LoggedIn bool
|
||||||
Member string
|
Member string
|
||||||
|
HasImpressum bool
|
||||||
|
Impressum string
|
||||||
|
Orte []string
|
||||||
|
Schiffe []string
|
||||||
|
Waren []string
|
||||||
}{
|
}{
|
||||||
Entries: eintraege,
|
Entries: eintraege,
|
||||||
Summe: summe,
|
Summe: summe,
|
||||||
@@ -345,13 +654,42 @@ func main() {
|
|||||||
Monatsstatistik: monatsStat,
|
Monatsstatistik: monatsStat,
|
||||||
LoggedIn: isAuthenticated(r),
|
LoggedIn: isAuthenticated(r),
|
||||||
Member: membername,
|
Member: membername,
|
||||||
})
|
HasImpressum: hasimpressum,
|
||||||
|
Impressum: impressum,
|
||||||
|
Orte: orte,
|
||||||
|
Schiffe: schiffe,
|
||||||
|
Waren: waren,
|
||||||
|
})*/
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Println("Server läuft auf http://0.0.0.0:8080")
|
log.Println("Server läuft auf http://0.0.0.0:8080")
|
||||||
http.ListenAndServe(":8080", nil)
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TemplateData struct {
|
||||||
|
Entries []Entry
|
||||||
|
Summe float64
|
||||||
|
OffeneSumme float64
|
||||||
|
Abteilungen []Abteilung
|
||||||
|
Monatsstatistik []Monatsstatistik
|
||||||
|
LoggedIn bool
|
||||||
|
Member string
|
||||||
|
HasImpressum bool
|
||||||
|
Impressum string
|
||||||
|
Orte []string
|
||||||
|
Schiffe []string
|
||||||
|
Waren []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CachedData struct {
|
||||||
|
Data TemplateData // das Struct, das du an tmpl.Execute übergibst
|
||||||
|
LastComputed time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var cache CachedData
|
||||||
|
var cacheMutex sync.RWMutex
|
||||||
|
|
||||||
func createTable(db *sql.DB) {
|
func createTable(db *sql.DB) {
|
||||||
_, err := db.Exec(`
|
_, err := db.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS eintraege (
|
CREATE TABLE IF NOT EXISTS eintraege (
|
||||||
@@ -360,22 +698,34 @@ func createTable(db *sql.DB) {
|
|||||||
endbestand REAL,
|
endbestand REAL,
|
||||||
prozentwert REAL,
|
prozentwert REAL,
|
||||||
abgabe REAL,
|
abgabe REAL,
|
||||||
bezahlt INTEGER DEFAULT 0
|
bezahlt INTEGER DEFAULT 0,
|
||||||
|
created_at TEXT,
|
||||||
|
startort TEXT,
|
||||||
|
zielort TEXT,
|
||||||
|
schiff TEXT,
|
||||||
|
ware TEXT,
|
||||||
|
zeitaufwand INTEGER
|
||||||
);
|
);
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Falls die Tabelle schon existiert, aber die Spalte "bezahlt" fehlt (z. B. nach Update)
|
// Ergänze ALTER TABLE nur für Migration bestehender Tabellen
|
||||||
_, err = db.Exec(`ALTER TABLE eintraege ADD COLUMN bezahlt INTEGER DEFAULT 0;`)
|
addColumn := func(column, colType string) {
|
||||||
|
_, err := db.Exec(`ALTER TABLE eintraege ADD COLUMN ` + column + ` ` + colType)
|
||||||
if err != nil && !strings.Contains(err.Error(), "duplicate column") {
|
if err != nil && !strings.Contains(err.Error(), "duplicate column") {
|
||||||
log.Fatal(err)
|
log.Fatalf("Fehler beim Hinzufügen der Spalte %s: %v", column, err)
|
||||||
}
|
}
|
||||||
_, err = db.Exec(`ALTER TABLE eintraege ADD COLUMN created_at TEXT`)
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "duplicate column") {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addColumn("bezahlt", "INTEGER DEFAULT 0")
|
||||||
|
addColumn("created_at", "TEXT")
|
||||||
|
addColumn("startort", "TEXT")
|
||||||
|
addColumn("zielort", "TEXT")
|
||||||
|
addColumn("schiff", "TEXT")
|
||||||
|
addColumn("ware", "TEXT")
|
||||||
|
addColumn("zeitaufwand", "INTEGER")
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginForm = `
|
const loginForm = `
|
||||||
@@ -385,6 +735,7 @@ const loginForm = `
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Login</title>
|
<title>Login</title>
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
@@ -412,6 +763,8 @@ const htmlTemplate = `
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Abgabe-Berechnung</title>
|
<title>Abgabe-Berechnung</title>
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/static/css/tom-select.default.min.css" rel="stylesheet">
|
||||||
|
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
@@ -426,7 +779,7 @@ const htmlTemplate = `
|
|||||||
</div>
|
</div>
|
||||||
<h1 class="mb-4">Beitrag zur Community vom Mitglied der Trading-Staffel ({{.Member}})</h1>
|
<h1 class="mb-4">Beitrag zur Community vom Mitglied der Trading-Staffel ({{.Member}})</h1>
|
||||||
|
|
||||||
<div class="alert alert-light">
|
<div class="alert alert-info">
|
||||||
<strong>Folgender Wert wurde erwirtschaftet und wird bald zur Verfügung gestellt:</strong> {{formatNumber .OffeneSumme}} UEC
|
<strong>Folgender Wert wurde erwirtschaftet und wird bald zur Verfügung gestellt:</strong> {{formatNumber .OffeneSumme}} UEC
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -450,7 +803,7 @@ const htmlTemplate = `
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<label class="form-label">Prozentwert</label>
|
<label class="form-label">Prozentwert</label>
|
||||||
<select name="prozentwert" class="form-select">
|
<select name="prozentwert" class="form-select">
|
||||||
<option value="30">30%</option>
|
<option value="30">30% (Standard)</option>
|
||||||
<option value="10">10%</option>
|
<option value="10">10%</option>
|
||||||
<option value="15">15%</option>
|
<option value="15">15%</option>
|
||||||
<option value="20">20%</option>
|
<option value="20">20%</option>
|
||||||
@@ -463,17 +816,85 @@ const htmlTemplate = `
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label">Startort</label>
|
||||||
|
<select id="startort" name="startort" class="form-select">
|
||||||
|
{{range .Orte}}
|
||||||
|
<option value="{{.}}">{{.}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label">Zielort</label>
|
||||||
|
<select id="zielort" name="zielort" class="form-select">
|
||||||
|
{{range .Orte}}
|
||||||
|
<option value="{{.}}">{{.}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label">Schiff</label>
|
||||||
|
<select id="schiff" name="schiff" class="form-select">
|
||||||
|
{{range .Schiffe}}
|
||||||
|
<option value="{{.}}">{{.}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label">Ware</label>
|
||||||
|
<select id="ware" name="ware" class="form-select" multiple>
|
||||||
|
{{range .Waren}}
|
||||||
|
<option value="{{.}}">{{.}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label">Zeitaufwand (min)</label>
|
||||||
|
<input type="number" name="zeitaufwand" class="form-control" min="1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Berechnen & Speichern</button>
|
<button type="submit" class="btn btn-primary">Berechnen & Speichern</button>
|
||||||
</form>
|
</form>
|
||||||
|
<hr />
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<h4 class="mt-4">Monatliche Übersicht</h4>
|
<h2 class="mb-3">Auswertungen</h2>
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs" id="auswertungTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="monat-tab" data-bs-toggle="tab" data-bs-target="#monat" type="button" role="tab" aria-controls="monat" aria-selected="true">
|
||||||
|
Monatliche Übersicht
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="abteilung-tab" data-bs-toggle="tab" data-bs-target="#abteilung" type="button" role="tab" aria-controls="abteilung" aria-selected="false">
|
||||||
|
Verteilung auf Abteilungen
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="items-tab" data-bs-toggle="tab" data-bs-target="#items" type="button" role="tab" aria-controls="items" aria-selected="false">
|
||||||
|
Gegenwert in Items
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content border border-top-0 p-4 bg-white" id="auswertungTabsContent">
|
||||||
|
|
||||||
|
<!-- Monatliche Übersicht -->
|
||||||
|
<div class="tab-pane fade show active" id="monat" role="tabpanel" aria-labelledby="monat-tab">
|
||||||
|
<h5 class="mb-3">Monatliche Übersicht</h5>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Monat</th>
|
<th>Monat</th>
|
||||||
<th>Abgaben verteilt</th>
|
<th>Abgaben verteilt</th>
|
||||||
<th>Abgaben offen</th>
|
<th>Abgaben offen</th>
|
||||||
|
<th>Statistik</th>
|
||||||
|
<th>Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -482,30 +903,40 @@ const htmlTemplate = `
|
|||||||
<td>{{.Monat}}</td>
|
<td>{{.Monat}}</td>
|
||||||
<td>{{formatNumber .Summe}} UEC</td>
|
<td>{{formatNumber .Summe}} UEC</td>
|
||||||
<td>{{formatNumber .SummeOffen}} UEC</td>
|
<td>{{formatNumber .SummeOffen}} UEC</td>
|
||||||
|
<td>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar progress-bar-striped bg-success" role="progressbar" style="width: {{.Prozent}}%" aria-valuenow="{{.Prozent}}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
<div class="progress-bar progress-bar-striped bg-danger" role="progressbar" style="width: {{.ProzentOffen}}%" aria-valuenow="{{.ProzentOffen}}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#monat-{{.Monat}}" aria-expanded="false" aria-controls="monat-{{.Monat}}">
|
||||||
|
Details
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<hr />
|
<tr>
|
||||||
|
<td colspan="9" class="p-0 border-0">
|
||||||
<h2 class="mb-3">Gespeicherte Einträge</h2>
|
<div class="collapse" id="monat-{{.Monat}}">
|
||||||
|
<div class="bg-light p-3 border-top">
|
||||||
|
<strong>Interne Infos (Details):</strong>
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>#</th>
|
<th>#</th>
|
||||||
<th>Datum</th>
|
<th>Datum</th>
|
||||||
<th>Anfang</th>
|
<th>UEC Anfang</th>
|
||||||
<th>Ende</th>
|
<th>UEC Ende</th>
|
||||||
<th>Profit</th>
|
<th>UEC Profit</th>
|
||||||
<th>Prozent</th>
|
<th>Prozent</th>
|
||||||
<th>Abgabe</th>
|
<th>UEC Abgabe</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
{{if .LoggedIn}}<th>Aktion</th>{{end}}
|
{{if $.LoggedIn}}<th>Aktion</th>{{else}}<th>Erweitert</th>{{end}}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="eintragsTabelle">
|
||||||
{{range .Entries}}
|
{{range .Eintraege}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.ID}}</td>
|
<td>{{.ID}}</td>
|
||||||
<td>{{formatDate .CreatedAt}}</td>
|
<td>{{formatDate .CreatedAt}}</td>
|
||||||
@@ -513,42 +944,79 @@ const htmlTemplate = `
|
|||||||
<td>{{formatNumber .Endbestand}}</td>
|
<td>{{formatNumber .Endbestand}}</td>
|
||||||
<td>{{formatNumber .Gesamtwert}}</td>
|
<td>{{formatNumber .Gesamtwert}}</td>
|
||||||
<td>{{formatNumber .Prozentwert}}%</td>
|
<td>{{formatNumber .Prozentwert}}%</td>
|
||||||
<td>
|
<td>{{formatNumber .Abgabe}}</td>
|
||||||
{{formatNumber .Abgabe}}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
{{if .Bezahlt}}
|
{{if .Bezahlt}}
|
||||||
{{if $.LoggedIn}}
|
{{if $.LoggedIn}}
|
||||||
<a href="/unmarkaspaid?id={{.ID}}" class="btn btn-sm btn-outline-success">Als unverteilt markieren</a>
|
<a href="/unmarkaspaid?id={{.ID}}" class="btn btn-sm btn-outline-danger">✗ stornieren</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="badge bg-success">✓ verteilt</span>
|
<span class="badge bg-success">✓ Erledigt</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{if $.LoggedIn}}
|
{{if $.LoggedIn}}
|
||||||
<a href="/markaspaid?id={{.ID}}" class="btn btn-sm btn-outline-success">Als verteilt markieren</a>
|
<a href="/markaspaid?id={{.ID}}" class="btn btn-sm btn-outline-success">✓ abgeben</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="badge bg-danger">✗ nicht verteilt</span>
|
<span class="badge bg-danger">✗ Offen</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
{{if $.LoggedIn}}
|
|
||||||
<td>
|
<td>
|
||||||
|
<button class="btn btn-sm btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#details-{{.ID}}" aria-expanded="false" aria-controls="details-{{.ID}}">
|
||||||
|
Details
|
||||||
|
</button>
|
||||||
|
{{if $.LoggedIn}}
|
||||||
<a href="/delete?id={{.ID}}" class="btn btn-sm btn-danger" onclick="return confirm('Eintrag wirklich löschen?')">Löschen</a>
|
<a href="/delete?id={{.ID}}" class="btn btn-sm btn-danger" onclick="return confirm('Eintrag wirklich löschen?')">Löschen</a>
|
||||||
</td>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="9" class="p-0 border-0">
|
||||||
|
<div class="collapse" id="details-{{.ID}}">
|
||||||
|
<div class="bg-light p-3 border-top">
|
||||||
|
<strong>Interne Infos (Details):</strong>
|
||||||
|
<table class="table table-sm table-bordered mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Startort</th>
|
||||||
|
<th>Zielort</th>
|
||||||
|
<th>Schiff</th>
|
||||||
|
<th>Ware</th>
|
||||||
|
<th>Zeit (min)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{.Startort}}</td>
|
||||||
|
<td>{{.Zielort}}</td>
|
||||||
|
<td>{{.Schiff}}</td>
|
||||||
|
<td>{{.Ware}}</td>
|
||||||
|
<td>{{formatNumber .Zeitaufwand}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<h4 class="mt-4">Verteilung auf Abteilungen:</h4>
|
<!-- Verteilung auf Abteilungen -->
|
||||||
|
<div class="tab-pane fade" id="abteilung" role="tabpanel" aria-labelledby="abteilung-tab">
|
||||||
|
<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 endgültige Entscheidung über die Verteilung obliegt der Orga-Leitung.
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-3">Verteilung auf Abteilungen:</h5>
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -569,8 +1037,11 @@ const htmlTemplate = `
|
|||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 class="mt-4">Gegenwert in Items:</h4>
|
<!-- Gegenwert in Items -->
|
||||||
|
<div class="tab-pane fade" id="items" role="tabpanel" aria-labelledby="items-tab">
|
||||||
|
<h5 class="mb-3">Gegenwert in Items:</h5>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -599,13 +1070,88 @@ const htmlTemplate = `
|
|||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{if .LoggedIn}}
|
{{if .LoggedIn}}
|
||||||
<form action="/reset" method="POST" onsubmit="return confirm('Alle Einträge wirklich löschen?')">
|
<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>
|
<button type="submit" class="btn btn-outline-danger mt-3">Alle Einträge löschen</button>
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
<hr />
|
||||||
|
{{if .HasImpressum}}
|
||||||
|
<div class="alert alert-light">
|
||||||
|
<strong><a href="{{.Impressum}}">Impressum</a></strong>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.max {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/static/js/tom-select.complete.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new TomSelect("#zielort", {
|
||||||
|
create: true, // erlaubt Freitext
|
||||||
|
sortField: "text"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new TomSelect("#startort", {
|
||||||
|
create: true, // erlaubt Freitext
|
||||||
|
sortField: "text"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new TomSelect("#schiff", {
|
||||||
|
create: true, // erlaubt Freitext
|
||||||
|
sortField: "text"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new TomSelect("#ware", {
|
||||||
|
create: true, // erlaubt Freitext
|
||||||
|
sortField: "text",
|
||||||
|
plugins: ['remove_button'] // ← erlaubt Entfernen per „x“-Button
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const tabKey = "lastActiveTab";
|
||||||
|
|
||||||
|
// Tabs initialisieren - sicherstellen, dass Bootstrap geladen ist
|
||||||
|
const triggerElList = [].slice.call(document.querySelectorAll('#auswertungTabs button[data-bs-toggle="tab"]'));
|
||||||
|
const tabList = triggerElList.map(function (triggerEl) {
|
||||||
|
return new bootstrap.Tab(triggerEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Falls gespeicherter Tab vorhanden ist, anzeigen
|
||||||
|
const lastTabId = localStorage.getItem(tabKey);
|
||||||
|
if (lastTabId) {
|
||||||
|
const selector = '#auswertungTabs button[data-bs-target="' + lastTabId + '"]';
|
||||||
|
const lastTabTrigger = document.querySelector(selector);
|
||||||
|
if (lastTabTrigger) {
|
||||||
|
new bootstrap.Tab(lastTabTrigger).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab-Wechsel speichern
|
||||||
|
triggerElList.forEach(function (triggerEl) {
|
||||||
|
triggerEl.addEventListener("shown.bs.tab", function (event) {
|
||||||
|
const target = event.target.getAttribute("data-bs-target");
|
||||||
|
localStorage.setItem(tabKey, target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
|||||||
2
static/css/tom-select.default.min.css
vendored
Normal file
2
static/css/tom-select.default.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
static/img/footerlower.webp
Normal file
BIN
static/img/footerlower.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
static/img/footerupper.webp
Normal file
BIN
static/img/footerupper.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5021
static/js/tom-select.complete.min.js
vendored
Normal file
5021
static/js/tom-select.complete.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user