feat: deploy admin panel to stage
Some checks failed
Build White Nights admin stage / build-and-deploy (push) Failing after 14s
Some checks failed
Build White Nights admin stage / build-and-deploy (push) Failing after 14s
This commit is contained in:
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.git
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Backend content/auth API and media endpoint.
|
||||||
|
VITE_API_URL="https://server.white-nights-stage.makkah.verstack.ru"
|
||||||
|
VITE_KRBL_MEDIA="https://server.white-nights-stage.makkah.verstack.ru/media/"
|
||||||
|
|
||||||
|
# Device APIs used by the embedded route demo at /demo/:id.
|
||||||
|
VITE_GEO_API_URL="https://device.white-nights-stage.makkah.verstack.ru"
|
||||||
|
VITE_WEATHER_API_URL="https://device.white-nights-stage.makkah.verstack.ru"
|
||||||
|
|
||||||
|
VITE_NEED_AUTH="true"
|
||||||
57
.gitea/workflows/build-stage.yaml
Normal file
57
.gitea/workflows/build-stage.yaml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Build White Nights admin stage
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- feature/stage-service-endpoints
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set image metadata
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
IMAGE_PREFIX="${{ secrets.REGISTRY_URL }}/krbl/whitenightsadminpanel"
|
||||||
|
echo "IMAGE_PREFIX=${IMAGE_PREFIX}" >> "$GITEA_OUTPUT"
|
||||||
|
echo "IMAGE_TAG=$(echo \"${{ gitea.sha }}\" | cut -c1-7)" >> "$GITEA_OUTPUT"
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to the container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.REGISTRY_URL }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push admin panel
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
VITE_API_URL=https://server.white-nights-stage.makkah.verstack.ru
|
||||||
|
VITE_KRBL_MEDIA=https://server.white-nights-stage.makkah.verstack.ru/media/
|
||||||
|
VITE_GEO_API_URL=https://device.white-nights-stage.makkah.verstack.ru
|
||||||
|
VITE_WEATHER_API_URL=https://device.white-nights-stage.makkah.verstack.ru
|
||||||
|
tags: |
|
||||||
|
${{ steps.meta.outputs.IMAGE_PREFIX }}/service-admin-panel:${{ steps.meta.outputs.IMAGE_TAG }}
|
||||||
|
${{ steps.meta.outputs.IMAGE_PREFIX }}/service-admin-panel:stage
|
||||||
|
|
||||||
|
- name: Restart backend pod to pick up the stage frontend
|
||||||
|
env:
|
||||||
|
KUBE_CONFIG_DATA: ${{ secrets.WNS_KUBE_CONFIG }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
KUBECONFIG_PATH="$RUNNER_TEMP/wn-kubeconfig"
|
||||||
|
printf '%s' "$KUBE_CONFIG_DATA" > "$KUBECONFIG_PATH"
|
||||||
|
chmod 600 "$KUBECONFIG_PATH"
|
||||||
|
export KUBECONFIG="$KUBECONFIG_PATH"
|
||||||
|
kubectl -n white-nights-stage rollout restart deployment/wn-backend
|
||||||
|
kubectl -n white-nights-stage rollout status deployment/wn-backend --timeout=5m
|
||||||
25
Dockerfile
25
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
# Stage 1: Build the application
|
# Stage 1: Build the application
|
||||||
FROM node:20-alpine AS build
|
FROM node:24-alpine AS build
|
||||||
|
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -7,6 +7,13 @@ WORKDIR /app
|
|||||||
# Copy package.json and yarn.lock
|
# Copy package.json and yarn.lock
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
|
# Vite embeds these values into the static bundle at build time. When an
|
||||||
|
# argument is omitted, the checked-in .env remains the fallback.
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ARG VITE_GEO_API_URL
|
||||||
|
ARG VITE_WEATHER_API_URL
|
||||||
|
ARG VITE_KRBL_MEDIA
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN yarn install --frozen-lockfile
|
RUN yarn install --frozen-lockfile
|
||||||
|
|
||||||
@@ -14,19 +21,23 @@ RUN yarn install --frozen-lockfile
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application
|
# Build the application
|
||||||
RUN yarn build
|
RUN if [ -n "${VITE_API_URL:-}" ]; then export VITE_API_URL; fi && \
|
||||||
|
if [ -n "${VITE_GEO_API_URL:-}" ]; then export VITE_GEO_API_URL; fi && \
|
||||||
|
if [ -n "${VITE_WEATHER_API_URL:-}" ]; then export VITE_WEATHER_API_URL; fi && \
|
||||||
|
if [ -n "${VITE_KRBL_MEDIA:-}" ]; then export VITE_KRBL_MEDIA; fi && \
|
||||||
|
yarn build
|
||||||
|
|
||||||
# Stage 2: Serve the application with Nginx
|
# Stage 2: Serve the application with Nginx
|
||||||
FROM nginx:alpine
|
FROM nginxinc/nginx-unprivileged:alpine
|
||||||
|
|
||||||
# Copy the built application from the build stage
|
# Copy the built application from the build stage
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
# Copy nginx configuration (optional, can be added later if needed)
|
# Keep client-side routes working on direct navigation and page refresh.
|
||||||
# COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
# Expose port 80
|
# The unprivileged image cannot bind to port 80.
|
||||||
EXPOSE 80
|
EXPOSE 8080
|
||||||
|
|
||||||
# Start Nginx server
|
# Start Nginx server
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
22
STAGE_DEPLOYMENT.md
Normal file
22
STAGE_DEPLOYMENT.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Stage deployment
|
||||||
|
|
||||||
|
The admin panel is a static Vite bundle. The backend and device remain
|
||||||
|
separate services.
|
||||||
|
|
||||||
|
Build a stage image with the service URLs embedded into the bundle:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build \
|
||||||
|
--build-arg VITE_API_URL=https://server.white-nights-stage.makkah.verstack.ru \
|
||||||
|
--build-arg VITE_KRBL_MEDIA=https://server.white-nights-stage.makkah.verstack.ru/media/ \
|
||||||
|
--build-arg VITE_GEO_API_URL=https://device.white-nights-stage.makkah.verstack.ru \
|
||||||
|
--build-arg VITE_WEATHER_API_URL=https://device.white-nights-stage.makkah.verstack.ru \
|
||||||
|
-t white-nights-admin-panel:stage .
|
||||||
|
```
|
||||||
|
|
||||||
|
The backend URL serves content, authentication, media and admin API requests.
|
||||||
|
The device URL serves geolocation and weather requests used by `/demo/:id`.
|
||||||
|
|
||||||
|
The device ingress must publish the geolocation and weather paths to the
|
||||||
|
corresponding device ports. The browser must also be authorized by Authelia
|
||||||
|
and the backend must allow the frontend origin through CORS.
|
||||||
11
nginx.conf
Normal file
11
nginx.conf
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
const envUrl = import.meta.env.VITE_API_URL || "localhost";
|
const trimTrailingSlash = (value) => value.replace(/\/+$/, "");
|
||||||
const baseURL = envUrl.includes("://") ? envUrl : `http://${envUrl}`;
|
|
||||||
const isFullUrl =
|
const serviceURL = (rawValue, defaultPort) => {
|
||||||
baseURL.includes("://") &&
|
const value = String(rawValue || "").trim() || "localhost";
|
||||||
!envUrl.includes("localhost") &&
|
const hasScheme = value.includes("://");
|
||||||
!envUrl.includes("127.0.0.1");
|
const url = new URL(hasScheme ? value : `http://${value}`);
|
||||||
|
|
||||||
|
// Bare local hosts use service-specific ports. Full URLs keep their
|
||||||
|
// configured scheme and port, which is required behind ingress.
|
||||||
|
if (!hasScheme && !url.port) {
|
||||||
|
url.port = String(defaultPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimTrailingSlash(url.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiURL = import.meta.env.VITE_API_URL || "localhost";
|
||||||
|
const geoURL = import.meta.env.VITE_GEO_API_URL || apiURL;
|
||||||
|
const weatherURL = import.meta.env.VITE_WEATHER_API_URL || apiURL;
|
||||||
|
|
||||||
const addAuthInterceptor = (instance) => {
|
const addAuthInterceptor = (instance) => {
|
||||||
instance.interceptors.request.use((config) => {
|
instance.interceptors.request.use((config) => {
|
||||||
@@ -17,9 +30,9 @@ const addAuthInterceptor = (instance) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const apiBaseURL = isFullUrl ? baseURL : `${baseURL}:8080`;
|
export const apiBaseURL = serviceURL(apiURL, 8080);
|
||||||
export const geoBaseURL = isFullUrl ? baseURL : `${baseURL}:6001`;
|
export const geoBaseURL = serviceURL(geoURL, 6001);
|
||||||
export const weatherBaseURL = isFullUrl ? baseURL : `${baseURL}:6002`;
|
export const weatherBaseURL = serviceURL(weatherURL, 6002);
|
||||||
|
|
||||||
export const getMediaUrl = (id) => {
|
export const getMediaUrl = (id) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
|
|||||||
Reference in New Issue
Block a user