Extract Vue frontend into separate container and add stp_wasm crate
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m58s

Move Vue app from nginx/vue/ to top-level vue/ with its own Dockerfile,
update docker-compose configs and nginx proxy to serve from the new
container, and add initial Rust WASM crate (stp_wasm). Also fix .gitignore
to exclude Rust target/ directories.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 16:40:45 +00:00
parent 2b84730126
commit d3d3269d49
182 changed files with 215 additions and 34 deletions

View File

@@ -1,5 +1,2 @@
vue/node_modules
vue/.vite
vue/dist
**/.git
**/.DS_Store

View File

@@ -1,21 +1,9 @@
# Stage 1: Build Vue app
FROM node:22-slim AS build
RUN apt-get update && apt-get install -y make git && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY vue/package.json vue/package-lock.json ./
RUN npm ci
COPY vue/ ./
RUN npm run build
# Stage 2: Serve with nginx
FROM nginx:latest
RUN rm -rf /etc/nginx/html/* && \
apt-get update && apt-get install -y gettext-base && \
rm -rf /var/lib/apt/lists/*
COPY --from=build /app/dist /etc/nginx/html/
COPY nginx.conf.template /etc/nginx/nginx.conf.template
COPY nginx_setup.conf.template /etc/nginx/nginx_setup.conf.template
COPY nginx_dev.conf.template /etc/nginx/nginx_dev.conf.template
COPY robots.txt /etc/nginx/html/robots.txt
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -20,5 +20,20 @@ fi
# Ensure upload directory is traversable by nginx worker
chmod 755 /uploads 2>/dev/null || true
# Wait for Vue assets in production mode
if [ "$DEV_MODE" != "true" ]; then
echo "Waiting for Vue assets..."
elapsed=0
while [ ! -f /etc/nginx/html/index.html ] && [ $elapsed -lt 120 ]; do
sleep 1
elapsed=$((elapsed + 1))
done
if [ ! -f /etc/nginx/html/index.html ]; then
echo "WARNING: Vue assets not found after 120s, starting nginx anyway"
else
echo "Vue assets ready."
fi
fi
# Start nginx
nginx -g 'daemon off;'

View File

@@ -28,9 +28,6 @@ http {
listen 80;
server_name $DOMAIN www.$DOMAIN;
root /etc/nginx/html;
index index.html;
location /uploads/ {
alias /uploads/;
add_header X-Content-Type-Options nosniff always;
@@ -39,17 +36,11 @@ http {
}
location / {
try_files $uri $uri/ /index.html;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /img/stamps/mine.gif {
add_header Access-Control-Allow-Origin *;
proxy_pass http://vue:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
location $BACKEND_ENDPOINT {

View File

@@ -1,2 +0,0 @@
User-agent: *
Allow: /

View File

@@ -1,41 +0,0 @@
# My Web - Frontend
Vue 3 SPA for [adam-french.co.uk](https://adam-french.co.uk). Built with Vite, Tailwind CSS v4, Pinia, and Vue Router.
## Setup
```sh
npm install
```
## Development
```sh
npm run dev
```
The Vite dev server proxies API requests:
- `/api` -> `http://localhost:8080` (Go backend)
- `/gitea` -> `http://localhost:3000` (Gitea)
- `/radio` -> `http://localhost:8000` (Icecast2)
## Production Build
```sh
npm run build
```
In production, the built `dist/` is served by Nginx inside a Docker container (see `../Dockerfile`).
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Recommended Browser Setup
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)

View File

@@ -1,12 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AF</title>
<link rel="icon" type="/img/x-icon" href="/img/favicon.ico" />
</head>
<body id="app">
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -1,8 +0,0 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +0,0 @@
{
"name": "nginx-html",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@mdit/plugin-katex": "^0.24.1",
"@tailwindcss/vite": "^4.1.18",
"@vueuse/core": "^14.2.1",
"axios": "^1.13.2",
"katex": "^0.16.27",
"markdown-it": "^14.1.0",
"markdown-it-wikilinks": "^1.4.0",
"pinia": "^3.0.4",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"vite": "^7.1.11",
"vite-plugin-vue-devtools": "^8.0.3"
}
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 573 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,12 +0,0 @@
<script setup>
import { RouterView } from "vue-router";
import Navbar from "@/components/Navbar.vue";
import Footer from "@/components/Footer.vue";
</script>
<template>
<Navbar class="no-print sticky" />
<RouterView />
<!-- <Footer style="height: 10vh" /> -->
</template>

View File

@@ -1,299 +0,0 @@
@import "tailwindcss";
/* PRINTING */
@media print {
.no-print,
.no-print * {
display: none !important;
margin: 0px;
padding: 0px;
width: 0x;
height: 0px;
}
}
/* END OF PRINTING */
/* FONTS */
@font-face {
font-family: "big_noodle_titling";
src: url("/fonts/big_noodle_titling.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "CreatoDisplay";
src: url("/fonts/CreatoDisplay-Bold.otf") format("opentype");
font-weight: normal;
font-style: normal;
}
/* END OF FONTS */
/* VARIABLES */
:root {
/* RED, WHITE, BLACK are standard*/
--portal_grey: #dddddd;
--portal_orange: #ff9a00;
--portal_light_orange: #ff5d00;
--portal_blue: #0065ff;
--portal_light_blue: #00a2ff;
/* MAIN COLORS */
--primary: #55ffbb;
--secondary: #62ff57;
--tertiary: #ff579a;
--quaternary: #024942;
/* BACKGROUND COLORS */
--bg_primary: #1b110e;
--bg_secondary: #000;
--link: #222;
--bdr: 2px;
--spacing: 3px;
/* FONTS USED */
--font_heading: big_noodle_titling;
--font_default: CreatoDisplay;
}
@theme {
--color-primary: var(--primary);
--color-secondary: var(--secondary);
--color-tertiary: var(--tertiary);
--color-quaternary: var(--quaternary);
--color-bg_primary: var(--bg_primary);
--color-bg_secondary: var(--bg_secondary);
--color-link: var(--link);
--borderWidth-primary: var(--primary);
--borderWidth-secondary: var(--secondary);
--borderWidth-tertiary: var(--tertiary);
--font-heading: var(--font_heading);
--default-font-family: var(--font_default);
}
/* END OF VARIABLES */
/* ELEMENTS */
body {
margin: 0 auto;
width: 100vw;
height: 100vh;
}
main {
@apply overflow-y-scroll w-full h-full p-10;
}
input {
@apply text-secondary border-primary border;
}
small {
@apply text-tertiary;
}
code {
@apply text-tertiary;
}
ul {
@apply text-tertiary;
}
li {
@apply text-tertiary;
}
h1,
h2,
h3,
h4 {
@apply m-1 font-heading text-primary;
}
h3,
h4 {
@apply text-lg;
}
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
p {
@apply text-secondary;
}
a {
@apply text-primary bg-link text-center font-heading tracking-wide;
}
input,
textarea {
@apply text-primary border p-2 w-full;
}
input::placeholder,
textarea::placeholder {
@apply text-secondary opacity-50;
}
table {
@apply border-primary border text-primary;
}
td {
@apply gap-1;
}
tr {
@apply border-primary border-b text-primary;
}
th {
@apply pr-3 pl-3 border-r border-dotted border-tertiary;
}
td {
@apply pr-3 pl-3;
}
/* END OF ELEMENTS */
/* CLASSES */
.img-stamp {
width: 99px;
height: 55px;
}
/* BORDERS */
.bdr-1 {
@apply border-30;
border-image: url("/img/borders/border1.gif") 30 round;
}
.bdr-1-inv {
@apply border-30;
border-image: url("/img/borders/border1inv.gif") 30 round;
}
.bdr-2 {
@apply border-5;
border-image: url("/img/borders/border4.gif") 7 round;
}
.bdr-cv {
@apply border-30;
border-image: url("/img/borders/bordercv.png") 30 round;
}
/* A5 Page */
.a5page-landscape {
@apply m-0 box-content;
height: 148mm;
width: 210mm;
}
.a5page-portrait {
@apply m-0 box-content;
width: 148mm;
height: 210mm;
}
/* A4 Page */
.a4page-portrait {
@apply m-0 box-content;
width: 210mm;
height: 297mm;
}
.a4page-landscape {
@apply m-0 box-content;
height: 210mm;
width: 297mm;
}
/* END OF CLASSES */
/* PHONE */
@media (max-width: 850px) {
.a4page-portrait {
width: 100%;
/* fill mobile width */
height: fit-content;
margin: 0 auto;
/* center horizontally */
box-sizing: border-box;
}
.a4page-landscape {
width: 100%;
/* fill mobile width */
height: fit-content;
margin: 0 auto;
/* center horizontally */
box-sizing: border-box;
}
}
@media (max-width: 600px) {
.a5page-portrait {
width: 100%;
height: fit-content;
margin: 0 auto;
box-sizing: border-box;
}
.a5page-landscape {
width: 100%;
height: fit-content;
margin: 0 auto;
box-sizing: border-box;
}
}
.tl {
@apply absolute top-0 left-0;
}
.tr {
@apply absolute top-0 right-0;
}
.bl {
@apply absolute bottom-0 left-0;
}
.br {
@apply absolute bottom-0 right-0;
}
.background {
@apply fixed;
}
.halftone {
--dot_size: 1px;
--bg_size: 3px;
--bg_pos: calc(var(--bg_size) / 2);
--blur: 0%;
background-color: var(--bg_secondary);
background-image: radial-gradient(circle at center,
var(--bg_primary) var(--dot_size),
transparent var(--blur));
background-size: var(--bg_size) var(--bg_size);
background-position: 0 0;
}

View File

@@ -1,3 +0,0 @@
<template>
<footer></footer>
</template>

View File

@@ -1,67 +0,0 @@
<script setup>
import Headline from "@/components/text/Headline.vue";
import { computed } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const parentPath = computed(() => {
const segments = route.path.split("/").filter(Boolean);
if (segments.length == 1) {
return "/";
} else {
segments.pop();
return segments.length ? "/" + segments.join("/") : null;
}
});
const inHome = computed(() => {
return route.path == "/" || route.path == "/stp";
});
const faces = [
"^_^",
"¯\\_(ツ)_/¯",
"(◕‿◕✿)",
"ಠ_ಠ",
"ʘ‿ʘ",
"^̮^",
">_>",
"¬_¬",
"˙ ͜ʟ˙",
"( ͡° ͜ʖ ͡°)",
"[̲̅$̲̅(̲̅5̲̅)̲̅$̲̅]",
"(ง'̀-'́)ง",
"\ (•◡•) /",
"( ͡ᵔ ͜ʖ ͡ᵔ )",
"ᕙ(⇀‸↼‶)ᕗ",
"⚆ _ ⚆",
"(。◕‿◕。)",
"(╯°□°)╯︵ ʞooqǝɔɐɟ",
"̿ ̿ ̿'̿'\̵͇̿̿\з=(•_•)=ε/̵͇̿̿/'̿'̿ ̿",
"(☞゚ヮ゚)☞ ☜(゚ヮ゚☜)",
];
const faces_string = faces.join(" ");
</script>
<template>
<nav class="flex flex-row w-full h-fit border border-primary bg-bg_primary">
<RouterLink class="bdr-2 bg-bg_primary" to="/" v-if="!inHome">
<span>HOME</span>
</RouterLink>
<RouterLink class="bdr-2 bg-bg_primary" v-if="parentPath" :to="parentPath">
<span>UP</span>
</RouterLink>
<Headline class="border flex-1 max-w-full">
<code class="whitespace-pre">{{ faces_string }}</code>
</Headline>
</nav>
</template>
<style scoped>
.left {
position: fixed;
top: 0;
left: 0;
}
</style>

Some files were not shown because too many files have changed in this diff Show More