Frontend/Deployment work.

Deployment now serves minified GZipped files.

Frontend now has a responsive station title size.

Need to: Change to an nginx container version that supports Brotli - I am after all the speed!

Signed-off-by: Fred Boniface <fred@fjla.uk>
This commit is contained in:
Fred Boniface 2023-01-19 16:53:18 +00:00
parent efb202577e
commit 302b749834
10 changed files with 329 additions and 168 deletions

33
.test-tools/ferry-vc.json Normal file
View File

@ -0,0 +1,33 @@
{"service":
[
{"sta":"16:07",
"eta":"On time",
"operator":"South Western Railway",
"operatorCode":"SW",
"serviceType":"ferry",
"serviceID":"37782PHBR____",
"origin":
{"location":
{"locationName":
"Ryde Pier Head","crs":"RYP"
}
},
"destination":
{"location":
{"locationName":"Portsmouth Harbour",
"crs":"PMH"
}
},
"previousCallingPoints":
{"callingPointList":
{"callingPoint":
{"locationName":"Ryde Pier Head",
"crs":"RYP",
"st":"15:45",
"et":"On time"
}
}
}
},
{"std":"16:15","etd":"On time","operator":"South Western Railway","operatorCode":"SW","serviceType":"ferry","serviceID":"37746PHBR____","origin":{"location":{"locationName":"Portsmouth Harbour","crs":"PMH"}},"destination":{"location":{"locationName":"Ryde Pier Head","crs":"RYP"}},"subsequentCallingPoints":{"callingPointList":{"callingPoint":
{"locationName":"Ryde Pier Head","crs":"RYP","st":"16:37","et":"On time"}}}}]}

View File

@ -1,2 +1,3 @@
.dockerignore .dockerignore
Dockerfile Dockerfile
*.xcf

View File

@ -1,14 +1,10 @@
FROM fedora:latest as compressor FROM fedora:latest as compressor
RUN dnf install brotli nodejs npm -y && npm i uglify-js -g RUN dnf install brotli nodejs npm jq -y
COPY . /data/ RUN npm i uglifyjs-folder uglifycss html-minifier-terser -g
RUN rm -r /data/nginx COPY . /data/in
RUN uglifyjs /data/js/*.js --compress --mangle RUN bash /data/in/conf/deploy.sh
RUN gzip -k -9 /data/*.json
RUN gzip -k -9 /data/styles/*.css
RUN gzip -k -9 /data/js/*.js
RUN gzip -k -9 /data/*.html
FROM nginx:mainline-alpine-slim FROM nginx:mainline-alpine-slim
RUN rm /etc/nginx/nginx.conf RUN rm /etc/nginx/nginx.conf
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf COPY ./conf/nginx.conf /etc/nginx/nginx.conf
COPY --from=compressor /data/ /site-static/ COPY --from=compressor /data/out/ /site-static/

View File

@ -14,6 +14,7 @@
<link rel="manifest" type="application/json" href="./manifest.json"/> <link rel="manifest" type="application/json" href="./manifest.json"/>
<script src="./js/main.js" defer></script> <script src="./js/main.js" defer></script>
<script src="./js/boards.js" defer></script> <script src="./js/boards.js" defer></script>
<script src="./js/public-board.js" defer></script>
</head> </head>
<body> <body>
<div id="loading"> <div id="loading">
@ -24,12 +25,16 @@
<div id="content"> <div id="content">
<div id="header"> <div id="header">
<h1 id="station_name"></h1> <div id="station_name">
<p class="header-right">Data from:</p> <h1 id="stn_name" class="header-large"></h1>
<p id="fetch_time" class="header-right">Loading...</p> </div>
<div id="header-right">
<p class="header-small">Data from:</p>
<p id="fetch_time" class="header-small">Loading...</p>
</div>
</div> </div>
<div id="nrcc_notices" class="hidden-while-loading"> <div id="nrcc_notices">
<p> <p>
Example NRCC Message Here... ... ... Example NRCC Message Here... ... ...
NRCC Messages can sometimes be long, and sometimes NRCC Messages can sometimes be long, and sometimes
@ -51,7 +56,11 @@
</table> </table>
</div> </div>
<div id="error_notice" class="main-notice hidden-while-loading"> <div id="ferry" class="hide-when-loading">
<p>Ferry Services</p>
</div>
<div id="error_notice" class="main-notice hide-when-loading">
<h1 class="error">Oops</h1> <h1 class="error">Oops</h1>
<p class="error">There was an error with your request</p> <p class="error">There was an error with your request</p>
<p id="err_not_found" class="notices-hidden">The station you are searching for cannot be found</p> <p id="err_not_found" class="notices-hidden">The station you are searching for cannot be found</p>

37
static/conf/deploy.sh Normal file
View File

@ -0,0 +1,37 @@
#!/bin/bash
echo "Running UglifyJS"
JSIN="/data/in/js/"
JSOUT="/data/out/js"
uglifyjs-folder "$JSIN" -x ".js" -eo "$JSOUT"
echo "Running UglifyCSS"
CSSIN="/data/in/styles/"
CSSOUT="/data/out/styles"
cd $CSSIN
echo "Changed directory"
pwd
for f in *
do
if [ -f "$f" ]; then
uglifycss "$f" --output "$f";
fi
done
echo "Moving 'styles' to 'out'"
cp -r /data/in/styles /data/out/styles
echo "Running html-minifier-terser"
HTMLIN="/data/in/"
HTMLOUT="/data/out"
html-minifier-terser --collapse-whitespace --remove-comments --file-ext html --input-dir /data/in/ --output-dir /data/out/
echo "Moving JSON Manifest file from root to output"
cat /data/in/manifest.json | jq -c > /data/out/manifest.json
echo "Moving images folder from in/ to out/"
cp -r /data/in/images /data/out/images
echo "Running GZIP & Brotli on all HTML, JS, CSS, JSON & SVG files"
find /data/out -type f -name \*.html -or -name \*.js -or -name \*.css -or -name \*.json -or -name \*.svg -or -name \*.ttf | while read file; do gzip -k -9 $file; brotli -k -q 11 $file; done

View File

@ -1,29 +1,4 @@
// Enable delays // Fetch a known query parameter from the pages URL
const delay = ms => new Promise(res => setTimeout(res, ms));
init()
/* Supporting Functions */
async function init() {
var stn = await getQuery("stn");
log(`init: Looking up: ${stn}`);
var sv = await getQuery("sv");
log(`init: Staff Version: ${sv}`);
if (sv === 'true') {
log("init: Staff Version not supported yet.")
log("init: Unable to proceed.")
} else {
try {
var data = await publicLdb(stn)
log("init: Fetched LDB Data")
parseLdb(data)
} catch (err) {
var data = null
log("init: Unable to fetch LDB data")
}
}
}
async function getQuery(param) { async function getQuery(param) {
var params = new URLSearchParams(window.location.search) var params = new URLSearchParams(window.location.search)
var query = params.get(param) var query = params.get(param)
@ -34,124 +9,15 @@ async function getQuery(param) {
} }
} }
async function publicLdb(stn) { // Set page headers
var url = `${window.location.origin}/api/v1/ldb/${stn}`;
var resp = await fetch(url);
return await resp.json();
}
async function setHeaders(title,time) { async function setHeaders(title,time) {
var prefix = `OwlBoard - ` var prefix = `OwlBoard - `
document.title = `${prefix}${title}` document.title = `${prefix}${title}`
document.getElementById("station_name").innerHTML = title document.getElementById("stn_name").innerHTML = title
document.getElementById("fetch_time").innerHTML = time.toLocaleTimeString() document.getElementById("fetch_time").innerHTML = time.toLocaleTimeString()
} }
async function parseLdb(data) { // Determine what should display in 'platform' column
if (data.ERROR == "NOT_FOUND") { // Station not found
clearLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_not_found").style = "display: block;";
setHeaders("Not Found",new Date())
} else if (data == false) { // No data for station
clearLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_no_data").style = "display:block;";
setHeaders("No Data",new Date())
} else if (data == "err") { // Connection Error
clearLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_conn").style = "display: block;";
setHeaders("Connection Error",new Date())
await delay(5000);
log(`parseLdb: Waited five seconds, reloading`)
location.reload()
} else {
buildPage(data);
}
}
// Build and Display Functions
async function buildPage(data) {
var stationName = data.GetStationBoardResult.locationName;
log(`buildPage: Data ready for ${stationName}`);
var generateTime = new Date(await data.GetStationBoardResult.generatedAt);
log(`buildPage: Data prepared at ${generateTime.toLocaleString()}`)
setHeaders(stationName, generateTime);
// Check for notices and if true pass to function
if (data.GetStationBoardResult.nrccMessages) {
displayNotices(data.GetStationBoardResult.nrccMessages.message)
}
if (typeof data.GetStationBoardResult.trainServices == 'undefined') {
displayNoTrains()
} else {
displayTrains(data)
}
}
async function displayNotices(notices) {
// Input: data.GetStationBoardResult.nrccMessages.messages
// Processing: For each message, create a <p> inside #notices.
// If there is more than one notice, scroll between them.
// Output: Only to DOM.
//document.getElementById("notices").innerHTML = notices;
}
async function displayNoTrains() {
document.getElementById('no_services').style = "display: block;";
clearLoading();
}
async function displayTrains(data) {
log(`Inserting data in DOM`)
for(var i = 0; i < data.GetStationBoardResult.trainServices.service.length; i++) {
// Reset Vars
var svc = data.GetStationBoardResult.trainServices.service[i];
displayService(svc);
}
clearLoading();
log(`Insertion complete`)
}
async function displayService(svc) {
var table = document.getElementById("output");
// Determine Time Message
var sta = await parseTime(svc.sta);
var eta = await parseTime(svc.eta);
var std = await parseTime(svc.std);
var etd = await parseTime(svc.etd);
// Determine Platform Message
//if (svc.platform != undefined){var plt = svc.platform} else {var plt = "-"};
var plt = await parsePlatform(svc);
// Define Table Row
var row = `
<table>
<tr>
<td class="name">${svc.origin.location.locationName}</td>
<td class="name">${svc.destination.location.locationName}</td>
<td class="plat ${plat.changed}">${plt.num}</td>
<td class="time">${sta.data}</td>
<td class="time ${eta.changed}">${eta.data}</td>
<td class="time">${std.data}</td>
<td class="time ${etd.changed}">${etd.data}</td>
</tr>
</table>`
// Put Table Row
table.insertAdjacentHTML("beforeend", row)
// Parse cancelReason & delayReason
if (svc.cancelReason) {
var cancelRow = `<p class="msg">${svc.cancelReason}</p>`
table.insertAdjacentHTML("beforeend", cancelRow);
}
if (svc.delayReason) {
var delayRow = `<p class="msg">${svc.delayReason}</p>`
table.insertAdjacentHTML("beforeend", delayRow);
}
}
async function parsePlatform(svc){ async function parsePlatform(svc){
if (svc.platform != undefined) { if (svc.platform != undefined) {
var platform = svc.platform; var platform = svc.platform;
@ -166,6 +32,7 @@ async function parsePlatform(svc){
return {num: platform, change: changed} return {num: platform, change: changed}
} }
// Use different time strings to default to make better on small screens
async function parseTime(string){ async function parseTime(string){
switch (string) { switch (string) {
case "Delayed": case "Delayed":
@ -185,7 +52,7 @@ async function parseTime(string){
var change = ""; var change = "";
break; break;
case undefined: case undefined:
var output = ""; var output = "-";
var change = ""; var change = "";
break; break;
default: default:
@ -195,11 +62,7 @@ async function parseTime(string){
return {data: output, changed: change}; return {data: output, changed: change};
} }
// Sometimes the origin or destination names are undefined, need to catch that
async function parseName(){ async function parseName(){
} return;
// Log Helper
function log(msg) {
var time = new Date().toISOString();
console.log(`${time} - ${msg}`)
} }

View File

@ -1,7 +1,17 @@
// Toggle Loading Box
async function clearLoading() { async function clearLoading() {
document.getElementById("loading").style = "display: none;"; document.getElementById("loading").style = "display: none;";
} }
async function showLoading() { async function showLoading() {
document.getElementById("loading").style = "display: block;"; document.getElementById("loading").style = "display: block;";
}
// Enable delays
const delay = ms => new Promise(res => setTimeout(res, ms));
// Log Helper
function log(msg) {
var time = new Date().toISOString();
console.log(`${time} - ${msg}`)
} }

180
static/js/public-board.js Normal file
View File

@ -0,0 +1,180 @@
/* Page Init: */
init()
/* Supporting Functions */
async function init() {
var stn = await getQuery("stn");
log(`init: Looking up: ${stn}`);
var sv = await getQuery("sv");
log(`init: Staff Version: ${sv}`);
if (sv === 'true') {
log("init: Staff Version not supported yet.")
log("init: Unable to proceed.")
} else {
try {
var data = await publicLdb(stn)
log("init: Fetched LDB Data")
parseLdb(data)
} catch (err) {
var data = null
log("init: Unable to fetch LDB data")
}
}
}
async function publicLdb(stn) {
var url = `${window.location.origin}/api/v1/ldb/${stn}`;
var resp = await fetch(url);
return await resp.json();}
async function parseLdb(data) {
if (data.ERROR == "NOT_FOUND") { // Station not found
clearLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_not_found").style = "display: block;";
setHeaders("Not Found",new Date())
} else if (data == false) { // No data for station
clearLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_no_data").style = "display:block;";
setHeaders("No Data",new Date())
} else if (data == "err") { // Connection Error
clearLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_conn").style = "display: block;";
setHeaders("Connection Error",new Date())
await delay(5000);
log(`parseLdb: Waited five seconds, reloading`)
location.reload()
} else {
buildPage(data);
}
}
// Build and Display Functions
async function buildPage(data) {
var stationName = data.GetStationBoardResult.locationName;
log(`buildPage: Data ready for ${stationName}`);
var generateTime = new Date(await data.GetStationBoardResult.generatedAt);
log(`buildPage: Data prepared at ${generateTime.toLocaleString()}`)
setHeaders(stationName, generateTime);
// Check for notices and if true pass to function
if (data.GetStationBoardResult.nrccMessages) {
displayNotices(data.GetStationBoardResult.nrccMessages.message)
}
if (typeof data.GetStationBoardResult.trainServices == 'undefined') {
displayNoTrains()
} else {
displayTrains(data)
}
if (data.GetStationBoardResult.ferryServices) {
displayFerry(data.GetStationBoardResult.ferryServices)
}
}
async function displayNotices(notices) {
// Input: data.GetStationBoardResult.nrccMessages.messages
// Processing: For each message, create a <p> inside #notices.
// If there is more than one notice, scroll between them.
// Output: Only to DOM.
//document.getElementById("notices").innerHTML = notices;
}
async function displayNoTrains() {
document.getElementById('no_services').style = "display: block;";
clearLoading();
}
async function displayTrains(data) {
log(`Inserting data in DOM`)
for(var i = 0; i < data.GetStationBoardResult.trainServices.service.length; i++) {
// Reset Vars
var svc = data.GetStationBoardResult.trainServices.service[i];
await displayService(svc);
}
clearLoading();
log(`Insertion complete`)
}
async function displayFerry(ferrySvc) {
log(JSON.stringify(ferrySvc))
for(var i = 0; i < ferrySvc.service.length; i++) {
displayFerryService(ferrySvc.service[i])
}
}
async function displayService(svc) {
var table = document.getElementById("output");
// Determine Time Message
var sta = await parseTime(svc.sta);
var eta = await parseTime(svc.eta);
var std = await parseTime(svc.std);
var etd = await parseTime(svc.etd);
// Determine Platform Message
//if (svc.platform != undefined){var plt = svc.platform} else {var plt = "-"};
var plt = await parsePlatform(svc);
// Define Table Row
var row = `
<table>
<tr>
<td class="name name-item">${svc.origin.location.locationName}</td>
<td class="name name-item">${svc.destination.location.locationName}</td>
<td class="plat ${plt.changed}">${plt.num}</td>
<td class="time">${sta.data}</td>
<td class="time ${eta.changed}">${eta.data}</td>
<td class="time">${std.data}</td>
<td class="time ${etd.changed}">${etd.data}</td>
</tr>
</table>`
// Put Table Row
table.insertAdjacentHTML("beforeend", row)
// Parse cancelReason & delayReason
if (svc.cancelReason) {
var cancelRow = `<p class="msg">${svc.cancelReason}</p>`
table.insertAdjacentHTML("beforeend", cancelRow);
}
if (svc.delayReason) {
var delayRow = `<p class="msg">${svc.delayReason}</p>`
table.insertAdjacentHTML("beforeend", delayRow);
}
}
async function displayFerryService(svc) {
var table = document.getElementById("ferry");
log(JSON.stringify(svc))
// Determine Time Message
var sta = await parseTime(svc.sta);
var eta = await parseTime(svc.eta);
var std = await parseTime(svc.std);
var etd = await parseTime(svc.etd);
// Determine Platform Message
var plt = "";
// Define Table Row
var row = `
<table>
<tr>
<td class="name name-item">${svc.origin.location.locationName}</td>
<td class="name name-item">${svc.destination.location.locationName}</td>
<td class="plat}">${plt}</td>
<td class="time">${sta.data}</td>
<td class="time ${eta.changed}">${eta.data}</td>
<td class="time">${std.data}</td>
<td class="time ${etd.changed}">${etd.data}</td>
</tr>
</table>`
// Put Table Row
table.insertAdjacentHTML("beforeend", row)
// Parse cancelReason & delayReason
if (svc.cancelReason) {
var cancelRow = `<p class="msg">${svc.cancelReason}</p>`
table.insertAdjacentHTML("beforeend", cancelRow);
}
if (svc.delayReason) {
var delayRow = `<p class="msg">${svc.delayReason}</p>`
table.insertAdjacentHTML("beforeend", delayRow);
}
document.getElementById("ferry").style = "display:block"
}

View File

@ -29,13 +29,36 @@
#station_name { #station_name {
position: absolute; position: absolute;
margin-top: 10px; max-width: 50%;
font-size: 15pt; left: 7px;
left: 10px; top: 3px;
}
.header-large{
left: 0;
text-align: left;
font-size: 13pt;
margin-top: -2px;
overflow-wrap: anywhere;
text-transform: capitalize; text-transform: capitalize;
} }
.header-right { @media (min-width: 380px){
.header-large{
font-size: 13pt;
margin-top: 9px;
white-space: nowrap;
}
}
@media (min-width: 580px){
.header-large{
font-size: 19pt;
margin-top: 5px;
white-space: nowrap;
}
}
.header-small {
text-align: right; text-align: right;
padding-right: 5px; padding-right: 5px;
margin: 3px; margin: 3px;
@ -74,19 +97,28 @@
} }
table { table {
color: white;
width: 100%; width: 100%;
margin-top: 3px; margin-top: 3px;
font-size: 10.5px; font-size: 10.5px;
} }
#ferry{
margin-top: 45px;
}
.name{ .name{
width: 25%; width: 25%;
text-align: left; text-align: left;
} }
.name-item {
color: yellow;
}
.plat{ .plat{
width: 4%; width: 4%;
text-align: center; text-align: center;
} }
.time{ .time{
width: 11.5%; width: 11.5%;
text-align: center; text-align: center;
@ -98,7 +130,7 @@ table {
margin: 0; margin: 0;
margin-left: 3px; margin-left: 3px;
text-align: left; text-align: left;
color: white; color: lightblue;
} }
.changed{ .changed{
@ -108,6 +140,6 @@ table {
/* Animations */ /* Animations */
@keyframes pulse { @keyframes pulse {
50% { 50% {
opacity: 0; color: orange;
} }
} }