Attempt 2

This commit is contained in:
2025-09-12 02:13:57 +02:00
parent 3ea7308af0
commit e749ec772e
30 changed files with 143 additions and 86 deletions

View File

@@ -25,7 +25,7 @@ WORKDIR /root/
# Get # Get
COPY --from=builder /app-server/build ./build COPY --from=builder /app-server/build ./build
COPY ./certs ./certs COPY ./certs ./certs
COPY ./*.env ./build/ COPY ./*.env ./
# Run the app # Run the app
CMD ["./build/app_server"] CMD ["./build/app_server"]

View File

@@ -9,6 +9,7 @@ require (
require ( require (
github.com/chirpstack/chirpstack/api/go/v4 v4.14.1 github.com/chirpstack/chirpstack/api/go/v4 v4.14.1
github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.39.0 golang.org/x/crypto v0.39.0
google.golang.org/grpc v1.75.0 google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8 google.golang.org/protobuf v1.36.8

View File

@@ -25,6 +25,8 @@ github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@@ -29,6 +29,7 @@ import (
serder "server/serder" serder "server/serder"
chirp_api "github.com/chirpstack/chirpstack/api/go/v4/api" chirp_api "github.com/chirpstack/chirpstack/api/go/v4/api"
"github.com/joho/godotenv"
mqtt "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
@@ -282,6 +283,8 @@ func main() {
ExitSig: quit_signal, ExitSig: quit_signal,
} }
godotenv.Load("./.env")
// === Logger settings // === Logger settings
log.SetFlags(log.Ldate | log.Ltime | log.Lmsgprefix | log.Lshortfile) log.SetFlags(log.Ldate | log.Ltime | log.Lmsgprefix | log.Lshortfile)
log.SetPrefix(FindEnv("LOG_PREFIX") + " ") log.SetPrefix(FindEnv("LOG_PREFIX") + " ")

Binary file not shown.

View File

@@ -57,7 +57,7 @@
[network] [network]
# Network identifier (NetID, 3 bytes) encoded as HEX (e.g. 010203). # Network identifier (NetID, 3 bytes) encoded as HEX (e.g. 010203).
net_id="10204" net_id="102040"
# Enabled regions. # Enabled regions.
# #

Binary file not shown.

View File

@@ -26,10 +26,8 @@ RUN apk add --no-cache ca-certificates
## Final build ## Final build
# Get # Get
COPY --from=builder /web-server/build ./build/ COPY --from=builder /web-server/build ./build/
COPY ./build/stylesheet.css ./layouts/ COPY ./src/layouts ./layouts
COPY ./src/layouts ./layouts/ COPY ./.env ./config/.env
COPY ./config ./config/
COPY ./shared.env ./config/shared.env
# Run # Run
CMD ["./build/web_server"] CMD ["./build/web_server"]

View File

@@ -1,12 +1,12 @@
# Variables # Variables
CSS_INPUT:=./src/layouts/styles/global.css CSS_INPUT:=./src/layouts/static/styles/global.css
CSS_OUTPUT:=./build/stylesheet.css CSS_OUTPUT:=./build/stylesheet.css
NAME:=web-app NAME:=web-app
all: css all: css
# Build Tailwind # Build Tailwind
css: ./src/layouts/**/*.css ./src/layouts/**/*.tmpl css: $(CSS_INPUT) ./src/layouts/**/*.tmpl
npx tailwindcss -i $(CSS_INPUT) -o $(CSS_OUTPUT) --minify npx tailwindcss -i $(CSS_INPUT) -o $(CSS_OUTPUT) --minify
# Build App # Build App

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,8 @@ services:
networks: networks:
pagerino_net: pagerino_net:
restart: on-failure:3 restart: on-failure:3
ports:
- "8222:8222"
networks: networks:
pagerino_net: pagerino_net:

View File

@@ -1,8 +1,8 @@
{{ define "meta" -}} {{ define "meta" -}}
<title>{{ .meta.Title }}</title> <title>{{ .meta.Title }}</title>
<link rel="icon" type="image/x-icon" href="/static/{{ if .meta.icon }}{{ .meta.icon }}{{ else }}favicon.ico{{ end }}"> <link rel="icon" type="image/x-icon" href="/static/{{ if .meta.Icon }}{{ .meta.Icon }}{{ else }}favicon.ico{{ end }}">
<meta name="author" content="Olek" /> <meta name="author" content="Olek" />
<meta name="description" <meta name="description"
content={{ .meta.description }} /> content={{ .meta.Description }} />
{{- end }} {{- end }}

View File

@@ -1,17 +1,32 @@
{{ define "navbar" }} {{ define "navbar" }}
<nav class="fixed top-0 w-full bg-[var(--primary-bg)] shadow-md z-50 transition-all duration-300"> <nav class="sticky top-0 w-full bg-[var(--primary-bg)] shadow-md z-50 transition-all duration-300">
<div id="navbar-inner" class="max-w-7xl mx-auto flex items-center justify-between px-4 py-3 md:py-4 transition-all duration-300"> <div id="navbar-inner" class="max-w-7xl mx-auto flex items-center justify-between px-4 py-3 md:py-4 transition-all duration-300">
<!-- Logo --> <!-- Logo -->
<a href="/" class="flex items-center transition-all duration-300" id="logo-container"> <a href="/" class="flex items-start transition-all duration-300" id="logo-container">
<img src="/static/logo.png" alt="Logo" class="h-12 md:h-16 transition-all duration-300" id="logo-img"/> <img src="/static/GORAKLOGO.png" alt="Logo" class="h-12 md:h-16 transition-all duration-300" id="logo-img"/>
<span class="ml-2 text-[var(--accent)] font-bold text-xl md:text-2xl">MyApp</span> <span class="ml-2 text-[var(--accent)] font-bold text-xl md:text-2xl">Pagerino</span>
</a> </a>
<!-- Nav Links --> <!-- Nav Links -->
<ul class="hidden md:flex space-x-6 text-[var(--text-primary)] font-medium"> <ul class="hidden md:flex space-x-10 text-[var(--text-primary)] font-semibold">
<li><a href="/network" class="hover:text-[var(--accent)] transition-colors">Network</a></li> <li>
<li><a href="/map" class="hover:text-[var(--accent)] transition-colors">Map</a></li> <a href="/network"
<li><a href="/analytics" class="hover:text-[var(--accent)] transition-colors">Analytics</a></li> class="px-4 py-2 rounded-lg hover:bg-[var(--accent)] hover:text-white transition-colors duration-200">
Network
</a>
</li>
<li>
<a href="/map"
class="px-4 py-2 rounded-lg hover:bg-[var(--accent)] hover:text-white transition-colors duration-200">
Map
</a>
</li>
<li>
<a href="/analytics"
class="px-4 py-2 rounded-lg hover:bg-[var(--accent)] hover:text-white transition-colors duration-200">
Analytics
</a>
</li>
</ul> </ul>
<!-- User / Login --> <!-- User / Login -->

View File

@@ -0,0 +1,29 @@
{{ define "websocket" -}}
<script>
let socket; // Global socket variable
function initWebSocket() { //TODO Make connection persistent across all pages
if (socket) return; // Single connection
socket = new WebSocket("ws://" + window.location.host + "/ws");
socket.onopen = function() {
console.log("Connected to server");
};
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log("Message from server:", data);
};
let retries = 0
socket.onclose = function() {
console.log("Disconnected");
socket = null; // For reconnection
setTimeout(initWebSocket, 1000)
};
}
window.addEventListener("load", initWebSocket);
</script>
{{- end }}

View File

@@ -1,51 +0,0 @@
{{ define "layout" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="../../build/stylesheet.css" rel="stylesheet">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
{{ template "meta" . }}
</head>
<body class="bg-gray-100">
{{ template "navbar" . }}
<main>
{{ template "content" . }}
</main>
<footer>
{{ template "footer" . }}
</footer>
<script>
let socket; // Global socket variable
function initWebSocket() { //TODO Make connection persistent across all pages
if (socket) return; // Single connection
socket = new WebSocket("ws://" + window.location.host + "/ws");
socket.onopen = function() {
console.log("Connected to server");
};
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log("Message from server:", data);
};
let retries = 0
socket.onclose = function() {
console.log("Disconnected");
socket = null; // For reconnection
setTimeout(initWebSocket, 1000)
};
}
window.addEventListener("load", initWebSocket);
</script>
</body>
</html>
{{ end }}

View File

@@ -1,4 +1,4 @@
{{ define "content" -}} {{ define "home" -}}
<h1> <h1>
Hello, Pagerino User! Hello, Pagerino User!
</h1> </h1>

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/static/styles/stylesheet.css" rel="stylesheet">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<link rel="manifest" href="/static/site.webmanifest" />
{{ template "meta" . }}
</head>
<body class="bg-gray-100">
{{ template "navbar" . }}
<main>
{{ if eq .Page "login" }}
{{ template "login" . }}
{{ else if eq .Page "home" }}
{{ template "home" . }}
{{ end }}
</main>
<footer>
{{ template "footer" . }}
</footer>
</body>
</html>

View File

@@ -1,11 +1,35 @@
{{ define "content" -}} {{ define "login" -}}
<form method="POST" action="/login"> <div class="flex items-center justify-center min-h-screen bg-gray-100">
<label>Username</label> <form method="POST" action="/login"
<input type="text" name="username" required /> class="bg-white shadow-lg rounded-2xl p-8 w-full max-w-md space-y-6">
<h2 class="text-2xl font-bold text-center text-gray-800">Login</h2>
<label>Password</label> <!-- Username -->
<input type="password" name="password" required /> <div>
<label for="username" class="block text-sm font-medium text-gray-600">Username</label>
<input type="text" id="username" name="username" required
class="mt-1 w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none" />
</div>
<button type="submit">Login</button> <!-- Password -->
</form> <div>
<label for="password" class="block text-sm font-medium text-gray-600">Password</label>
<input type="password" id="password" name="password" required
class="mt-1 w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none" />
</div>
<!-- Submit -->
<button type="submit"
class="w-full bg-blue-600 text-white py-2 rounded-lg font-medium hover:bg-blue-700 transition-colors">
Login
</button>
<!-- Optional link -->
<p class="text-sm text-center text-gray-500">
Dont have an account?
<a href="/register" class="text-blue-600 hover:underline">Sign up</a>
</p>
</form>
</div>
{{- end }} {{- end }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

File diff suppressed because one or more lines are too long

View File

@@ -58,7 +58,7 @@ type UserData struct {
type PageMeta struct { type PageMeta struct {
Title string Title string
Description string Description string
IconFile string Icon string
} }
// === Utilities === // === Utilities ===
@@ -176,7 +176,7 @@ func main() {
signal.Notify(Data.ExitSig, syscall.SIGINT, syscall.SIGTERM) signal.Notify(Data.ExitSig, syscall.SIGINT, syscall.SIGTERM)
// = Load configs // = Load configs
godotenv.Load("./config/app_config.env", "./config/shared.env", "./config/socket_config.env") godotenv.Load("./config/.env")
// = Setup logger // = Setup logger
log.SetFlags(log.Lmsgprefix | log.LUTC | log.Lshortfile) log.SetFlags(log.Lmsgprefix | log.LUTC | log.Lshortfile)
@@ -186,7 +186,7 @@ func main() {
router := gin.Default() router := gin.Default()
// Load assets and HTML templates // Load assets and HTML templates
router.Static("./layouts/static", "./layouts/static") router.Static("/static", "./layouts/static")
router.LoadHTMLGlob("./layouts/**/*.tmpl") router.LoadHTMLGlob("./layouts/**/*.tmpl")
HOME_NAME := os.Getenv("HOME_NAME") HOME_NAME := os.Getenv("HOME_NAME")
@@ -215,7 +215,7 @@ func main() {
dial_opts..., dial_opts...,
) )
if err != nil { if err != nil {
log.Println("Failed to connect to gRPC API") log.Println("Failed to connect to gRPC API: " + err.Error())
return return
} }
Data.ApiUserClient = api_user.NewUserServiceClient(grpc_client) Data.ApiUserClient = api_user.NewUserServiceClient(grpc_client)
@@ -232,21 +232,25 @@ func main() {
title = strings.ToTitle(HOME_NAME) title = strings.ToTitle(HOME_NAME)
} }
ctx.HTML(http.StatusOK, "home.tmpl", gin.H{ ctx.HTML(http.StatusOK, "index.tmpl", gin.H{
"meta": PageMeta{ "meta": PageMeta{
Title: title, Title: title,
Description: "Pagerino system home page", Description: "Pagerino system home page",
//Icon: "",
}, },
"Page": "home",
}) })
}) })
// Login page (escape auth handler) // Login page (escape auth handler)
router.GET("/login", func(ctx *gin.Context) { router.GET("/login", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "login.tmpl", gin.H{ ctx.HTML(http.StatusOK, "index.tmpl", gin.H{
"meta": PageMeta{ "meta": PageMeta{
Title: "Login", Title: "Login",
Description: "Pagerino system login page", Description: "Pagerino system login page",
//Icon: "",
}, },
"Page": "login",
}) })
}) })
@@ -257,7 +261,7 @@ func main() {
// Run server in another thread for graceful shutdown // Run server in another thread for graceful shutdown
go func() { go func() {
if err := router.Run("localhost:" + Data.FindEnv("WEB_PORT")); err != nil && err != http.ErrServerClosed { if err := router.Run(":" + Data.FindEnv("WEB_PORT")); err != nil && err != http.ErrServerClosed {
log.Println("HTTP Server error: " + err.Error()) log.Println("HTTP Server error: " + err.Error())
} }
log.Println("HTTP server shutdown.") log.Println("HTTP server shutdown.")

Binary file not shown.