Compare commits
6 Commits
b56c62ed3c
...
presejpack
| Author | SHA1 | Date | |
|---|---|---|---|
| 16b0568bf1 | |||
| afd29c0a23 | |||
| c7c6bed56c | |||
| f106ba0248 | |||
| bd0672e49d | |||
| 7f4b463e4c |
@@ -1,6 +1,12 @@
|
||||
name: Build and Push Presejpacky Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Image tag'
|
||||
required: true
|
||||
default: 'latest'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -12,35 +18,29 @@ on:
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Gitea Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.home.hrajfrisbee.cz
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
gitea.home.hrajfrisbee.cz/${{ github.repository_owner }}/flat-stack-presejpacky
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=match,pattern=presejpacky-v(.*),group=1
|
||||
- name: Login to Gitea registry
|
||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login -u ${{ github.actor }} --password-stdin gitea.home.hrajfrisbee.cz
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: drills/flat-stack-presejpacky
|
||||
file: drills/flat-stack-presejpacky/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
run: |
|
||||
TAG=${{ github.ref_name }}
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
TAG=${{ inputs.tag }}
|
||||
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
TAG="latest"
|
||||
fi
|
||||
IMAGE=gitea.home.hrajfrisbee.cz/${{ github.repository_owner }}/flat-stack-presejpacky:$TAG
|
||||
docker build -f drills/flat-stack-presejpacky/Dockerfile \
|
||||
--build-arg GIT_TAG=$TAG \
|
||||
--build-arg GIT_COMMIT=${{ github.sha }} \
|
||||
--build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
|
||||
-t $IMAGE drills/flat-stack-presejpacky
|
||||
docker push $IMAGE
|
||||
|
||||
113
drills/flat-stack-presejpacky/docs/nginx-reverse-proxy.md
Normal file
113
drills/flat-stack-presejpacky/docs/nginx-reverse-proxy.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Nginx Reverse Proxy Configuration Guide
|
||||
|
||||
This guide describes how to configure an Nginx reverse proxy to host the `flat-stack-presejpacky` container, either at the root domain or under a specific subpath (e.g., `/presejpacky`), using dynamic path rewriting based on HTTP headers.
|
||||
|
||||
## Overview
|
||||
|
||||
The application is built to be path-agnostic at compile time. At runtime, the Express server inspects HTTP headers to dynamically rewrite script and style references in `index.html` and strip the path prefix from incoming resource requests.
|
||||
|
||||
This enables running the same Docker image under any subpath without rebuilds.
|
||||
|
||||
The server checks for the presence of the following headers (in order):
|
||||
1. `X-Forwarded-Prefix`
|
||||
2. `X-Base-Path`
|
||||
|
||||
---
|
||||
|
||||
## 1. Hosting under a Subpath (e.g., `/presejpacky`)
|
||||
|
||||
To serve the application on a subpath, configure Nginx to pass the request path prefix in the `X-Forwarded-Prefix` header.
|
||||
|
||||
### Option A: Stripping the prefix in Nginx (Recommended)
|
||||
If your `proxy_pass` directive has a trailing slash, Nginx automatically strips the matched URI prefix before forwarding the request to the container.
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name drills.home.hrajfrisbee.cz;
|
||||
|
||||
location /presejpacky/ {
|
||||
# Forward requests to the container, stripping "/presejpacky"
|
||||
proxy_pass http://presejpacky-container:3000/;
|
||||
|
||||
# Forward host and IP headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Pass the subpath prefix so the container can dynamically prefix asset paths
|
||||
proxy_set_header X-Forwarded-Prefix /presejpacky;
|
||||
}
|
||||
|
||||
# Redirect requests missing a trailing slash (e.g., /presejpacky -> /presejpacky/)
|
||||
# to ensure relative browser assets resolve correctly.
|
||||
location = /presejpacky {
|
||||
return 301 $scheme://$host$request_uri/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option B: Preserving the prefix in Nginx
|
||||
If your `proxy_pass` directive does not have a trailing slash, Nginx forwards the request with the subpath prefix intact. The Express server's built-in middleware will strip it.
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name drills.home.hrajfrisbee.cz;
|
||||
|
||||
location /presejpacky/ {
|
||||
# Forward requests with "/presejpacky" intact
|
||||
proxy_pass http://presejpacky-container:3000;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_set_header X-Forwarded-Prefix /presejpacky;
|
||||
}
|
||||
|
||||
location = /presejpacky {
|
||||
return 301 $scheme://$host$request_uri/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Hosting on a Root Domain (e.g., `https://presejpacky.example.com`)
|
||||
|
||||
If you want to dedicate an entire domain or subdomain to the application, no prefix stripping or rewriting is required.
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name presejpacky.example.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://presejpacky-container:3000;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Verifying the Setup
|
||||
|
||||
You can verify that the headers are being set and processed correctly by inspecting the source of the returned HTML in the browser:
|
||||
- If Nginx is correctly sending `X-Forwarded-Prefix: /presejpacky`, the references in the `<head>` and `<body>` tags of `index.html` should be rendered with the prefix:
|
||||
```html
|
||||
<link rel="stylesheet" href="/presejpacky/assets/index-XYZ.css">
|
||||
<script type="module" src="/presejpacky/assets/index-XYZ.js"></script>
|
||||
```
|
||||
- If the headers are not set or not forwarded, the paths will fallback to the root:
|
||||
```html
|
||||
<link rel="stylesheet" href="/assets/index-XYZ.css">
|
||||
<script type="module" src="/assets/index-XYZ.js"></script>
|
||||
```
|
||||
@@ -1,6 +1,7 @@
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -8,12 +9,58 @@ const __dirname = path.dirname(__filename);
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Serve static files from the 'dist' directory
|
||||
app.use(express.static(path.join(__dirname, 'dist')));
|
||||
// Log all incoming request methods, URLs, and headers
|
||||
app.use((req, res, next) => {
|
||||
console.log(`[Express Request] ${req.method} ${req.url}`);
|
||||
console.log('Headers:', JSON.stringify(req.headers, null, 2));
|
||||
next();
|
||||
});
|
||||
|
||||
// Fallback to index.html for Single Page Application routing
|
||||
// Middleware to strip base path prefix from request headers if present
|
||||
app.use((req, res, next) => {
|
||||
const prefix = (req.headers['x-forwarded-prefix'] as string) || (req.headers['x-base-path'] as string) || '';
|
||||
const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
||||
|
||||
if (cleanPrefix && req.url.startsWith(cleanPrefix)) {
|
||||
req.url = req.url.substring(cleanPrefix.length);
|
||||
if (!req.url.startsWith('/')) {
|
||||
req.url = '/' + req.url;
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// Serve static files from the 'dist' directory, but bypass index.html requests
|
||||
// so they can be handled dynamically by our fallback route.
|
||||
app.use((req, res, next) => {
|
||||
if (req.url === '/index.html') {
|
||||
return next();
|
||||
}
|
||||
next();
|
||||
});
|
||||
app.use(express.static(path.join(__dirname, 'dist'), { index: false }));
|
||||
|
||||
// Fallback to index.html for Single Page Application routing, injecting base path from headers
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
||||
const indexPath = path.join(__dirname, 'dist', 'index.html');
|
||||
fs.readFile(indexPath, 'utf8', (err, html) => {
|
||||
if (err) {
|
||||
return res.status(500).send('Error reading index.html');
|
||||
}
|
||||
|
||||
const prefix = (req.headers['x-forwarded-prefix'] as string) || (req.headers['x-base-path'] as string) || '';
|
||||
const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
||||
|
||||
// Dynamically prefix asset URLs in index.html with the base path
|
||||
let modifiedHtml = html;
|
||||
if (cleanPrefix) {
|
||||
modifiedHtml = html
|
||||
.replace(/(href|src)="\/assets\//g, `$1="${cleanPrefix}/assets/`)
|
||||
.replace(/(href|src)="\/vite.svg"/g, `$1="${cleanPrefix}/vite.svg"`);
|
||||
}
|
||||
|
||||
res.send(modifiedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
|
||||
@@ -12,8 +12,15 @@ export default defineConfig(() => {
|
||||
},
|
||||
},
|
||||
server: {
|
||||
configureServer(server) {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
console.log(`[Vite Request] ${req.method} ${req.url}`);
|
||||
console.log('Headers:', JSON.stringify(req.headers, null, 2));
|
||||
next();
|
||||
});
|
||||
},
|
||||
// HMR is disabled in AI Studio via DISABLE_HMR env var.
|
||||
// Do not modifyâfile watching is disabled to prevent flickering during agent edits.
|
||||
// Do not modify—file watching is disabled to prevent flickering during agent edits.
|
||||
hmr: process.env.DISABLE_HMR !== 'true',
|
||||
// Disable file watching when DISABLE_HMR is true to save CPU during agent edits.
|
||||
watch: process.env.DISABLE_HMR === 'true' ? null : {},
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 650px;
|
||||
height: 80%;
|
||||
max-width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
@@ -133,8 +134,8 @@
|
||||
<div class="speed-row">
|
||||
<label for="speedSelect">Animation Speed:</label>
|
||||
<select id="speedSelect">
|
||||
<option value="1">Normal</option>
|
||||
<option value="2" selected>Faster</option>
|
||||
<option value="1" selected>Normal</option>
|
||||
<option value="2">Faster</option>
|
||||
<option value="4">High Speed</option>
|
||||
<option value="8">Insane (No Skips)</option>
|
||||
</select>
|
||||
|
||||
@@ -1,25 +1 @@
|
||||
Can you create ultimate frisbee drill animation for me?
|
||||
|
||||
```
|
||||
Drill: Paddy's endzone
|
||||
Players: 2 short vertical stacks in the endzone separated by slightly bigger space in the middle, players in the stack are close together, one at the back and other on the front each of them moving independently on the other, one player in front of the endzone, about 10-20 metres away and 10 metres from the sideline
|
||||
Field:
|
||||
- Field has lines as ultimate frisbee field would have
|
||||
- make proportions of the endzone and rest of the field realistic
|
||||
- Field takes up whole width of the canvas
|
||||
- It is enough to have only half of the field in the picture
|
||||
Setup: H1 at the disc, H2,H4,H6 in the back stack - starting with H2 closest to back endzone libe, H3, H5, H7 in the front stack - starting with H3 closest to player with disc, whole front stack is fully in the endzone
|
||||
Extra details:
|
||||
- when you are showing pass in the animation make it at least in 3 frames
|
||||
- make default speed faster and faster even faster, possibly add 2 more speed options, faster speed should not skip frames, it just should move quicker from one to another
|
||||
- make sure players do not cut out of bounds
|
||||
- allow to go through animation one step at a time
|
||||
Steps:
|
||||
1. H2 cuts to the oposite side of H1 with cut parallel to the back line and H1 throws around forehand over both stacks and hits H2 about 3 metres off the sideline curving to the receiver
|
||||
2. H1 moves into position closest to the front of end zone of the back stack, whole back stack moves so that player closest to the back of endzone is on the same position last player was on the start of the drill
|
||||
3. H3 cuts out of the endzone to similar place where H1 was, but on the other side and gets pass from H2
|
||||
4. H2 moves to the back of the front stack which moves in the way it now starts on the same spot as when we begun the drill
|
||||
5. H4 cuts in the same way H2 cut in the step 1, only to the oposite side and H3 throws around backhand both stacks and hits H4 about 3 metres off the sideline curving to the receiver
|
||||
7. H3 moves into position closest to the front of end zone of the back stack, whole back stack moves so that player closest to the back of endzone is on the same position last player was on the start of the drill
|
||||
6. H5 cuts out of the endzone to place where H1 was, gets pass from H4, H4 moves to the back of the front stack
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user