diff --git a/bin/build-static.sh b/bin/build-static.sh new file mode 100755 index 00000000..47cdb31d --- /dev/null +++ b/bin/build-static.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +#------------------------------------------------------------------------------ +#- build-static.sh +#- Copies all necessary x0 JavaScript source files and static web assets +#- from /www into the /static directory, preparing it for static deployment. +#- +#- Usage: +#- bin/build-static.sh [OUTPUT_DIR] +#- +#- OUTPUT_DIR defaults to /static when not specified. +#------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" + +WWW_DIR="${REPO_ROOT}/www" +OUTPUT_DIR="${1:-${REPO_ROOT}/static}" + +echo "x0 static build" +echo " source : ${WWW_DIR}" +echo " output : ${OUTPUT_DIR}" + +mkdir -p "${OUTPUT_DIR}" + +#- JavaScript source files +echo " copying JS files ..." +cp -a "${WWW_DIR}"/*.js "${OUTPUT_DIR}/" + +#- CSS and font assets (bootstrap, globalstyles, fontawesome) +echo " copying static assets ..." +cp -ra "${WWW_DIR}/static/." "${OUTPUT_DIR}/static/" + +#- Favicon and image assets +echo " copying image assets ..." +mkdir -p "${OUTPUT_DIR}/image" +cp -ra "${WWW_DIR}/image/." "${OUTPUT_DIR}/image/" + +echo " done." +echo "" +echo " Place your metadata files in ${OUTPUT_DIR}/data/:" +echo " object.json - x0 object definitions" +echo " skeleton.json - x0 screen skeleton" +echo " menu.json - x0 menu definition" +echo " text-data.json - x0 UI text strings" diff --git a/conf/vhost-x0-static.conf b/conf/vhost-x0-static.conf new file mode 100644 index 00000000..3eb299ff --- /dev/null +++ b/conf/vhost-x0-static.conf @@ -0,0 +1,22 @@ + + ServerName x0-static.x0.localnet + ServerAdmin admin@x0.localnet + DocumentRoot /var/www/vhosts/x0-static + LogLevel warn + + + Options -Indexes +FollowSymLinks + AllowOverride None + Require all granted + # Allow POST requests so the x0 JS loader can fetch static JSON data files + AllowMethods GET POST HEAD OPTIONS + + + # Serve JSON metadata files with correct content type + + Header set Content-Type "application/json; charset=UTF-8" + + + ErrorLog /var/log/apache2/x0-static.error.log + CustomLog /var/log/apache2/x0-static.access.log vhost_combined + diff --git a/docker/build-x0-static.sh b/docker/build-x0-static.sh new file mode 100755 index 00000000..3a08533d --- /dev/null +++ b/docker/build-x0-static.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +workdir="$(echo $PWD)" + +cd ../../ +docker build --progress=plain -t x0-static --file ./x0/docker/x0-static.dockerfile . &> x0-build-static.log +cd ${workdir} diff --git a/docker/x0-start-static.sh b/docker/x0-start-static.sh new file mode 100755 index 00000000..3c52a755 --- /dev/null +++ b/docker/x0-start-static.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +docker stop x0-static +docker container rm x0-static + +docker run -i \ +--log-driver=none \ +-a stdin -a stdout -a stderr \ +--name x0-static \ +-p 8080:80 x0-static diff --git a/docker/x0-static.dockerfile b/docker/x0-static.dockerfile new file mode 100644 index 00000000..2de7123b --- /dev/null +++ b/docker/x0-static.dockerfile @@ -0,0 +1,39 @@ +FROM ubuntu:24.04 +MAINTAINER Claus Prüfer + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get -qq update -y && \ + apt-get -qq install -y apache2 + +# Copy x0 JS source and static assets for the build step +COPY www/ /build/www/ + +# Copy static deployment directory (contains index.html and data/ templates) +COPY static/ /build/static/ + +# Copy the build script +COPY bin/build-static.sh /build/bin/build-static.sh + +# Run build: populate /build/static with JS files and web assets from /build/www +RUN chmod +x /build/bin/build-static.sh && \ + /build/bin/build-static.sh /build/static + +# Deploy static files to the Apache document root +RUN mkdir -p /var/www/vhosts/x0-static && \ + cp -ra /build/static/. /var/www/vhosts/x0-static/ + +# Configure Apache virtual host +COPY conf/vhost-x0-static.conf /etc/apache2/sites-available/x0-static.conf + +RUN a2dissite 000-default.conf && \ + a2ensite x0-static.conf && \ + a2enmod headers allowmethods + +CMD ["apache2ctl", "-D", "FOREGROUND"] + +EXPOSE 80 + +LABEL org.opencontainers.image.source=https://github.com/clauspruefer/x0 +LABEL org.opencontainers.image.description="x0 docker container image - static variant" +LABEL org.opencontainers.image.licenses=AGPL-3.0-or-later diff --git a/static/data/menu.json b/static/data/menu.json new file mode 100644 index 00000000..1d72700f --- /dev/null +++ b/static/data/menu.json @@ -0,0 +1,8 @@ +[ + { + "MenuEntry1": + { + "RefID": "sysMenu" + } + } +] diff --git a/static/data/object.json b/static/data/object.json new file mode 100644 index 00000000..b883fd07 --- /dev/null +++ b/static/data/object.json @@ -0,0 +1,11 @@ +{ + "Header1": + { + "Type": "Div", + "Attributes": { + "DOMType": "h1", + "Value": "x0 Static", + "Style": "border rounded p-3 fw-bold" + } + } +} diff --git a/static/data/skeleton.json b/static/data/skeleton.json new file mode 100644 index 00000000..4df5f29e --- /dev/null +++ b/static/data/skeleton.json @@ -0,0 +1,11 @@ +{ + "Screen1": + [ + { + "Header1": + { + "RefID": "Screen1" + } + } + ] +} diff --git a/static/data/text-data.json b/static/data/text-data.json new file mode 100644 index 00000000..86044254 --- /dev/null +++ b/static/data/text-data.json @@ -0,0 +1,7 @@ +{ + "TXT.SYS.OBJECT.DEFAULT.TEXT": + { + "value_de": "Standard Text", + "value_en": "Default Text" + } +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 00000000..ed6be319 --- /dev/null +++ b/static/index.html @@ -0,0 +1,79 @@ + + + + x0 Static + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/sysInitOnLoad.js b/www/sysInitOnLoad.js index fd109aed..326bd3a1 100644 --- a/www/sysInitOnLoad.js +++ b/www/sysInitOnLoad.js @@ -116,7 +116,8 @@ function InitOk(XHR) { sysFactory.DataObject.setLoaderObj(sysObjLoader); sysFactory.DataSkeleton.setLoaderObj(sysObjLoader); - sysFactory.ObjText.requestXMLRPCData('/python/getText.py'); + const TextDataURL = (typeof sysVarTextDataFile !== 'undefined') ? sysVarTextDataFile : '/python/getText.py'; + sysFactory.ObjText.requestXMLRPCData(TextDataURL); sysFactory.DataMenu.requestXMLRPCData(sysVarAppSubdir + '/' + sysVarConfigMenuFile); sysFactory.DataObject.requestXMLRPCData(sysVarAppSubdir + '/' + sysVarConfigObjectFile); sysFactory.DataSkeleton.requestXMLRPCData(sysVarAppSubdir + '/' + sysVarConfigSkeletonFile);