Initial commit

This commit is contained in:
Henri Burau 2024-06-03 18:07:27 +02:00
commit ca04cc51f3
43 changed files with 4440 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
LISTEN_ADDR=":8080"

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Created by https://www.toptal.com/developers/gitignore/api/go
# Edit at https://www.toptal.com/developers/gitignore?templates=go
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# 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/
# Go workspace file
go.work
# End of https://www.toptal.com/developers/gitignore/api/go
nn

36
Makefile Normal file
View File

@ -0,0 +1,36 @@
# run templ generation in watch mode to detect all .templ files and
# re-create _templ.txt files on change, then send reload event to browser.
# Default url: http://localhost:7331
live/templ:
templ generate --watch --proxy="http://localhost:8080" --open-browser=false -v
# run air to detect any go file changes to re-build and re-run the server.
live/server:
go run github.com/cosmtrek/air@v1.51.0 \
--build.cmd "go build -o tmp/bin/main" --build.bin "tmp/bin/main" --build.delay "100" \
--build.exclude_dir "node_modules" \
--build.include_ext "go" \
--build.stop_on_error "false" \
--misc.clean_on_exit true
# run tailwindcss to generate the styles.css bundle in watch mode.
live/tailwind:
npx tailwindcss -i ./style/input.css -o ./assets/styles.css --minify --watch
# run esbuild to generate the index.js bundle in watch mode.
live/esbuild:
npx esbuild script/index.ts --bundle --outdir=assets/ --watch=forever
# watch for any js or css change in the assets/ folder, then reload the browser via templ proxy.
live/sync_assets:
go run github.com/cosmtrek/air@v1.51.0 \
--build.cmd "templ generate --notify-proxy" \
--build.bin "true" \
--build.delay "100" \
--build.exclude_dir "" \
--build.include_dir "assets" \
--build.include_ext "js,css"
# start all 5 watch processes in parallel.
live:
make -j5 live/templ live/server live/tailwind live/esbuild live/sync_assets

BIN
assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

3093
assets/index.js Normal file

File diff suppressed because it is too large Load Diff

16
assets/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 545 KiB

1
assets/styles.css Normal file

File diff suppressed because one or more lines are too long

14
go.mod Normal file
View File

@ -0,0 +1,14 @@
module gitea.henriburau.de/haw-lan/cod4watcher
go 1.22.2
require github.com/a-h/templ v0.2.707
require github.com/mergestat/timediff v0.0.3 // indirect
require (
github.com/go-chi/chi v1.5.5
github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/gorcon/rcon v1.3.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect
)

12
go.sum Normal file
View File

@ -0,0 +1,12 @@
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/gorcon/rcon v1.3.5 h1:YE/Vrw6R99uEP08wp0EjdPAP3Jwz/ys3J8qxI1nYoeU=
github.com/gorcon/rcon v1.3.5/go.mod h1:zR1qfKZttF8vAgH1NsP6CdpachOvLDq8jE64NboTpIM=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mergestat/timediff v0.0.3 h1:ucCNh4/ZrTPjFZ081PccNbhx9spymCJkFxSzgVuPU+Y=
github.com/mergestat/timediff v0.0.3/go.mod h1:yvMUaRu2oetc+9IbPLYBJviz6sA7xz8OXMDfhBl7YSI=

51
main.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"fmt"
"log"
"log/slog"
"net/http"
"os"
"time"
"gitea.henriburau.de/haw-lan/cod4watcher/models"
"gitea.henriburau.de/haw-lan/cod4watcher/routes"
"github.com/go-chi/chi"
"github.com/joho/godotenv"
)
func main() {
if err := godotenv.Load(); err != nil {
log.Fatal(err)
}
cod4server, err := models.NewCOD4ServerStatus("80.57.28.137", "28960", 1*time.Second)
if err != nil {
log.Panic(err)
}
status, err := cod4server.GetServerStatus()
if err != nil {
log.Panic(err)
}
log.Printf("%+v", status)
router := chi.NewMux()
server := &routes.Server{}
router.Handle("/*", public())
router.Get("/health", routes.Make(server.HandleHealth))
router.Get("/captures/{captureID}", routes.Make(server.HandleCapture))
router.Get("/", routes.Make(server.HandleHome))
listenAddr := os.Getenv("LISTEN_ADDR")
slog.Info("HTTP server started", "listenAddr", listenAddr)
http.ListenAndServe(listenAddr, router)
}
func public() http.Handler {
fmt.Println("building static files for development")
return http.StripPrefix("/assets/", http.FileServerFS(os.DirFS("assets")))
}

89
models/capture.go Normal file
View File

@ -0,0 +1,89 @@
package models
import (
"cmp"
"slices"
"time"
"github.com/mergestat/timediff"
)
type MapScoreList []MapScore
type Capture struct {
Id uint
Host string
Port string
Name string
Active bool
Start time.Time
MapScores MapScoreList
}
type MapScore struct {
Id uint
StartTime time.Time
Map string
ScoreList []Score
}
type Score struct {
Id uint
Name string
Score int
Ping int
}
type ResultTable struct {
Header []ResultTableHeader
Rows []ResultTableRow
}
type ResultTableHeader struct {
Title string
Subtitle string
}
type ResultTableRow struct {
Name string
Total int
Individual []int
}
func (msl MapScoreList) BuildTable() *ResultTable {
rt := &ResultTable{
Header: []ResultTableHeader{{Title: "Name"}, {Title: "Total"}},
}
userMapRows := make(map[string]*ResultTableRow)
for mapIndex, mapScore := range msl {
rt.Header = append(rt.Header, ResultTableHeader{
Title: mapScore.Map,
Subtitle: timediff.TimeDiff(mapScore.StartTime),
})
for _, score := range mapScore.ScoreList {
if score.Ping > 0 {
if _, ok := userMapRows[score.Name]; !ok {
rt.Rows = append(rt.Rows, ResultTableRow{
Name: score.Name,
Total: 0,
Individual: make([]int, len(msl)),
})
userMapRows[score.Name] = &rt.Rows[len(rt.Rows)-1]
}
row := userMapRows[score.Name]
row.Total = row.Total + score.Score
row.Individual[mapIndex] = score.Score
}
}
}
slices.SortFunc(rt.Rows, func(a, b ResultTableRow) int {
return cmp.Compare(b.Total, a.Total)
})
return rt
}

167
models/cod4server.go Normal file
View File

@ -0,0 +1,167 @@
package models
import (
"bufio"
"bytes"
"errors"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"time"
)
var timeLayout = "Mon Jun 2 15:04:05 2006"
type CoD4Server struct {
server string
port string
protocol string
timeout time.Duration
}
type CoD4ServerStatus struct {
raw string
serverData map[string]string
MapStartTime time.Time
MapName string
Score []Score
meta map[string]interface{}
}
func NewCOD4ServerStatus(server, port string, timeout time.Duration) (*CoD4Server, error) {
if server == "" || port == "" {
return nil, errors.New("server or port is empty")
}
return &CoD4Server{
server: server,
port: port,
protocol: "udp",
timeout: timeout,
}, nil
}
func (c *CoD4Server) GetServerStatus() (*CoD4ServerStatus, error) {
data, err := c.receiveData()
if err != nil {
return nil, err
}
return parseServerData(data)
}
func (c *CoD4Server) receiveData() (string, error) {
address := fmt.Sprintf("%s:%s", c.server, c.port)
conn, err := net.DialTimeout(c.protocol, address, c.timeout)
if err != nil {
return "", fmt.Errorf("could not connect to server: %s", err)
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(c.timeout))
_, err = conn.Write([]byte("\xFF\xFF\xFF\xFFgetstatus\x00"))
if err != nil {
return "", fmt.Errorf("failed to send data to server: %s", err)
}
var buf bytes.Buffer
response := make([]byte, 8192)
reader := bufio.NewReader(conn)
for {
n, err := reader.Read(response)
if err != nil {
break
}
buf.Write(response[:n])
}
data := buf.String()
if len(strings.TrimSpace(data)) == 0 {
return "", fmt.Errorf("no data received from server: %s", err)
}
return data, nil
}
func parseServerData(data string) (*CoD4ServerStatus, error) {
lines := strings.Split(data, "\n")
if len(lines) < 2 {
return nil, fmt.Errorf("insufficient data received")
}
c := &CoD4ServerStatus{
raw: data,
serverData: make(map[string]string),
meta: make(map[string]interface{}),
}
tempPlayers := lines[2:]
tempData := strings.Split(lines[1], "\\")
for i := 1; i < len(tempData)-1; i += 2 {
c.serverData[tempData[i]] = tempData[i+1]
}
c.serverData["sv_hostname"] = colorCode(c.serverData["sv_hostname"])
c.serverData["_Maps"] = strings.Join(strings.Split(c.serverData["_Maps"], "-"), ",")
c.MapName = c.serverData["mapname"]
startTime, err := time.Parse(timeLayout, c.serverData["g_mapStartTime"])
if err != nil {
return nil, err
}
c.MapStartTime = startTime
for _, playerLine := range tempPlayers {
if len(strings.TrimSpace(playerLine)) > 1 {
temp := strings.Fields(playerLine)
if len(temp) >= 3 {
playerName := strings.Trim(playerLine, `"`)
scoreValue, err := strconv.Atoi(temp[0])
if err != nil {
return nil, err
}
pingValue, err := strconv.Atoi(temp[1])
if err != nil {
return nil, err
}
c.Score = append(c.Score, Score{
Name: playerName[strings.Index(playerName, "\"")+1:],
Score: scoreValue,
Ping: pingValue,
})
}
}
}
return c, nil
}
func colorCode(str string) string {
str += "^"
colorMap := map[string]string{
"0": "#000000",
"1": "#F65A5A",
"2": "#00F100",
"3": "#EFEE04",
"4": "#0F04E8",
"5": "#04E8E7",
"6": "#F75AF6",
"7": "#FFFFFF",
"8": "#7E7E7E",
"9": "#6E3C3C",
}
re := regexp.MustCompile(`\^(\d)(.*?)\^`)
return re.ReplaceAllStringFunc(str, func(m string) string {
matches := re.FindStringSubmatch(m)
return fmt.Sprintf(`<span style="color:%s;">%s</span>^`, colorMap[matches[1]], matches[2])
})
}

4
models/persistence.go Normal file
View File

@ -0,0 +1,4 @@
package models
type Persistence interface {
}

24
routes/capture.go Normal file
View File

@ -0,0 +1,24 @@
package routes
import (
"net/http"
"strconv"
"gitea.henriburau.de/haw-lan/cod4watcher/views/capture"
"github.com/go-chi/chi"
)
func (s *Server) HandleCapture(w http.ResponseWriter, r *http.Request) error {
captureString := chi.URLParam(r, "captureID")
captureID, err := strconv.Atoi(captureString)
if err != nil {
return err
}
foundCapture, err := s.cs.GetCaptureById(captureID)
if err != nil {
return err
}
return Render(w, r, capture.Capture(foundCapture))
}

12
routes/health.go Normal file
View File

@ -0,0 +1,12 @@
package routes
import (
"fmt"
"net/http"
)
func (s *Server) HandleHealth(w http.ResponseWriter, r *http.Request) error {
fmt.Fprint(w, "Up and running!")
return nil
}

16
routes/home.go Normal file
View File

@ -0,0 +1,16 @@
package routes
import (
"net/http"
"gitea.henriburau.de/haw-lan/cod4watcher/views/home"
)
func (s *Server) HandleHome(w http.ResponseWriter, r *http.Request) error {
captureList, err := s.cs.GetActiveCapures()
if err != nil {
return err
}
return Render(w, r, home.Index(captureList))
}

27
routes/routes.go Normal file
View File

@ -0,0 +1,27 @@
package routes
import (
"log/slog"
"net/http"
"gitea.henriburau.de/haw-lan/cod4watcher/services"
"github.com/a-h/templ"
)
type HTTPHandler func(w http.ResponseWriter, r *http.Request) error
func Make(h HTTPHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil {
slog.Error("HTTP handler error", "err", err, "path", r.URL.Path)
}
}
}
type Server struct {
cs *services.CaptureService
}
func Render(w http.ResponseWriter, r *http.Request, c templ.Component) error {
return c.Render(r.Context(), w)
}

145
script/.gitignore vendored Normal file
View File

@ -0,0 +1,145 @@
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node
n

1
script/index.ts Normal file
View File

@ -0,0 +1 @@
import 'htmx.org'

21
script/package-lock.json generated Normal file
View File

@ -0,0 +1,21 @@
{
"name": "cod4observer",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cod4observer",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"htmx.org": "^1.9.12"
}
},
"node_modules/htmx.org": {
"version": "1.9.12",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
"integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="
}
}
}

14
script/package.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "cod4observer",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"htmx.org": "^1.9.12"
}
}

44
services/capture.go Normal file
View File

@ -0,0 +1,44 @@
package services
import (
"time"
"gitea.henriburau.de/haw-lan/cod4watcher/models"
)
type CaptureService struct {
}
var captures = []models.Capture{
{Id: 1, Host: "80.57.28.137", Port: "28960", Active: true, Name: "Gungame HAW-LAN 11", Start: time.Now().Add(-1 * time.Hour)},
{Id: 1, Host: "80.57.28.137", Port: "28960", Active: true, Name: "Gungame HAW-LAN 12", Start: time.Now().Add(-5 * time.Minute)},
}
func (cs *CaptureService) GetActiveCapures() ([]models.Capture, error) {
return captures, nil
}
func (cs *CaptureService) GetCaptureById(id int) (*models.Capture, error) {
capture := captures[0]
server, err := models.NewCOD4ServerStatus(capture.Host, capture.Port, time.Second)
if err != nil {
return nil, err
}
status, err := server.GetServerStatus()
if err != nil {
return nil, err
}
capture.MapScores = []models.MapScore{
{
Id: 0,
StartTime: status.MapStartTime,
Map: status.MapName,
ScoreList: status.Score,
},
}
return &capture, nil
}

3
style/input.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

9
tailwind.config.js Normal file
View File

@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./**/*.html", "./**/*.templ", "./**/*.go"],
theme: {
extend: {},
},
plugins: [],
}

BIN
tmp/bin/main Executable file

Binary file not shown.

1
tmp/build-errors.log Normal file
View File

@ -0,0 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

View File

@ -0,0 +1,19 @@
package capture
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/layouts"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/components"
templ Capture(capture *models.Capture) {
@layouts.Base() {
<div class="flex flex-col">
<div class="block font-bold text-lg">
{ capture.Name }
</div>
<div class="block">
{ capture.Host }:{ capture.Port }
</div>
@components.CaptureTable(*capture.MapScores.BuildTable())
</div>
}
}

View File

@ -0,0 +1,101 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package capture
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/layouts"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/components"
func Capture(capture *models.Capture) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(capture.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/capture/capture.templ`, Line: 11, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(capture.Host)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/capture/capture.templ`, Line: 14, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(capture.Port)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/capture/capture.templ`, Line: 14, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CaptureTable(*capture.MapScores.BuildTable()).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = layouts.Base().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,5 @@
<div class=\"flex flex-col\"><div class=\"block font-bold text-lg\">
</div><div class=\"block\">
:
</div>
</div>

View File

@ -0,0 +1,12 @@
package components
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "github.com/mergestat/timediff"
import "fmt"
templ CaptureCard(capture models.Capture) {
<a href={ templ.URL(fmt.Sprintf("/captures/%d", capture.Id)) } class="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">{ capture.Name }</h5>
<p class="font-normal text-gray-700 dark:text-gray-400">{ timediff.TimeDiff(capture.Start) }</p>
</a>
}

View File

@ -0,0 +1,74 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package components
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "github.com/mergestat/timediff"
import "fmt"
func CaptureCard(capture models.Capture) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(fmt.Sprintf("/captures/%d", capture.Id))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(capture.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/capture_card.templ`, Line: 9, Col: 97}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(timediff.TimeDiff(capture.Start))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/capture_card.templ`, Line: 10, Col: 92}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,4 @@
<a href=\"
\" class=\"block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700\"><h5 class=\"mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white\">
</h5><p class=\"font-normal text-gray-700 dark:text-gray-400\">
</p></a>

View File

@ -0,0 +1,42 @@
package components
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "strconv"
templ CaptureTable(table models.ResultTable) {
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
for _, header := range table.Header {
<th scope="col" class="px-6 py-3">
<div>
{ header.Title }
</div>
<div class="lowercase font-light">
{ header.Subtitle }
</div>
</th>
}
</tr>
</thead>
<tbody>
for _, row := range table.Rows {
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{ row.Name }
</th>
<td class="px-6 py-4">
{ strconv.Itoa(row.Total) }
</td>
for _, score := range row.Individual {
<td class="px-6 py-4">
{ strconv.Itoa(score) }
</td>
}
</tr>
}
</tbody>
</table>
</div>
}

View File

@ -0,0 +1,133 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package components
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "strconv"
func CaptureTable(table models.ResultTable) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, header := range table.Header {
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(header.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/capture_table.templ`, Line: 14, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(header.Subtitle)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/capture_table.templ`, Line: 17, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, row := range table.Rows {
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(row.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/capture_table.templ`, Line: 27, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(row.Total))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/capture_table.templ`, Line: 30, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, score := range row.Individual {
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(score))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/capture_table.templ`, Line: 34, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 10)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 11)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 12)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,12 @@
<div class=\"relative overflow-x-auto\"><table class=\"w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400\"><thead class=\"text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400\"><tr>
<th scope=\"col\" class=\"px-6 py-3\"><div>
</div><div class=\"lowercase font-light\">
</div></th>
</tr></thead> <tbody>
<tr class=\"bg-white border-b dark:bg-gray-800 dark:border-gray-700\"><th scope=\"row\" class=\"px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white\">
</th><td class=\"px-6 py-4\">
</td>
<td class=\"px-6 py-4\">
</td>
</tr>
</tbody></table></div>

View File

@ -0,0 +1,24 @@
package components
templ Nagivation() {
<nav class="bg-gray-800">
<div class="mx-auto max-w-7xl">
<div class="relative flex h-16 items-center justify-between">
<div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div class="flex flex-shrink-0 items-center">
<span class="text-white pr-2 font-bold">Turnier-Tracker</span>
<img class="h-8 w-auto" src="/assets/logo.svg" alt="Your Company"/>
</div>
<div class="hidden sm:ml-6 sm:block">
<div class="flex space-x-4">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href="/" class="bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium" aria-current="page">Dashboard</a>
<a href="/about" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">About</a>
</div>
</div>
</div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"></div>
</div>
</div>
</nav>
}

View File

@ -0,0 +1,35 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package components
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func Nagivation() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1 @@
<nav class=\"bg-gray-800\"><div class=\"mx-auto max-w-7xl\"><div class=\"relative flex h-16 items-center justify-between\"><div class=\"flex flex-1 items-center justify-center sm:items-stretch sm:justify-start\"><div class=\"flex flex-shrink-0 items-center\"><span class=\"text-white pr-2 font-bold\">Turnier-Tracker</span> <img class=\"h-8 w-auto\" src=\"/assets/logo.svg\" alt=\"Your Company\"></div><div class=\"hidden sm:ml-6 sm:block\"><div class=\"flex space-x-4\"><!-- Current: \"bg-gray-900 text-white\", Default: \"text-gray-300 hover:bg-gray-700 hover:text-white\" --><a href=\"/\" class=\"bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium\" aria-current=\"page\">Dashboard</a> <a href=\"/about\" class=\"text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium\">About</a></div></div></div><div class=\"absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0\"></div></div></div></nav>

13
views/home/index.templ Normal file
View File

@ -0,0 +1,13 @@
package home
import "gitea.henriburau.de/haw-lan/cod4watcher/views/layouts"
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/components"
templ Index(captures []models.Capture) {
@layouts.Base() {
for _, capture := range captures {
@components.CaptureCard(capture)
}
}
}

56
views/home/index_templ.go Normal file
View File

@ -0,0 +1,56 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package home
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/layouts"
import "gitea.henriburau.de/haw-lan/cod4watcher/models"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/components"
func Index(captures []models.Capture) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
for _, capture := range captures {
templ_7745c5c3_Err = components.CaptureCard(capture).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = layouts.Base().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

24
views/layouts/base.templ Normal file
View File

@ -0,0 +1,24 @@
package layouts
import "gitea.henriburau.de/haw-lan/cod4watcher/views/components"
templ Base() {
<!DOCTYPE html>
<html lang="en">
<head>
<title>CoD 4 Turnier-Tracker</title>
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico"/>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/assets/styles.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/js/all.min.js"></script>
</head>
<body class="antialiased">
@components.Nagivation()
<div class="mx-auto max-w-7xl mt-10 flex gap-3">
{ children... }
</div>
<script src="/assets/index.js"></script>
</body>
</html>
}

View File

@ -0,0 +1,53 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package layouts
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "gitea.henriburau.de/haw-lan/cod4watcher/views/components"
func Base() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.Nagivation().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,3 @@
<!doctype html><html lang=\"en\"><head><title>CoD 4 Turnier-Tracker</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/assets/favicon.ico\"><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/assets/styles.css\"><script src=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/js/all.min.js\"></script></head><body class=\"antialiased\">
<div class=\"mx-auto max-w-7xl mt-10 flex gap-3\">
</div><script src=\"/assets/index.js\"></script></body></html>