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>
@@ -1,5 +1,2 @@
|
||||
vue/node_modules
|
||||
vue/.vite
|
||||
vue/dist
|
||||
**/.git
|
||||
**/.DS_Store
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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;'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
@@ -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/)
|
||||
@@ -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>
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
3788
nginx/vue/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 756 KiB |
|
Before Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 1021 KiB |
|
Before Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 948 B |
|
Before Width: | Height: | Size: 970 B |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 4.7 MiB |
|
Before Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 573 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 650 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 536 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 59 KiB |
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<footer></footer>
|
||||
</template>
|
||||
@@ -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>
|
||||