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
Dockerfile
*.xcf

View File

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

View File

@ -14,6 +14,7 @@
<link rel="manifest" type="application/json" href="./manifest.json"/>
<script src="./js/main.js" defer></script>
<script src="./js/boards.js" defer></script>
<script src="./js/public-board.js" defer></script>
</head>
<body>
<div id="loading">
@ -24,12 +25,16 @@
<div id="content">
<div id="header">
<h1 id="station_name"></h1>
<p class="header-right">Data from:</p>
<p id="fetch_time" class="header-right">Loading...</p>
<div id="station_name">
<h1 id="stn_name" class="header-large"></h1>
</div>
<div id="header-right">
<p class="header-small">Data from:</p>
<p id="fetch_time" class="header-small">Loading...</p>
</div>
</div>
<div id="nrcc_notices" class="hidden-while-loading">
<div id="nrcc_notices">
<p>
Example NRCC Message Here... ... ...
NRCC Messages can sometimes be long, and sometimes
@ -51,7 +56,11 @@
</table>
</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>
<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>

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
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")
}
}
}
// Fetch a known query parameter from the pages URL
async function getQuery(param) {
var params = new URLSearchParams(window.location.search)
var query = params.get(param)
@ -34,124 +9,15 @@ async function getQuery(param) {
}
}
async function publicLdb(stn) {
var url = `${window.location.origin}/api/v1/ldb/${stn}`;
var resp = await fetch(url);
return await resp.json();
}
// Set page headers
async function setHeaders(title,time) {
var prefix = `OwlBoard - `
document.title = `${prefix}${title}`
document.getElementById("station_name").innerHTML = title
document.getElementById("stn_name").innerHTML = title
document.getElementById("fetch_time").innerHTML = time.toLocaleTimeString()
}
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)
}
}
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);
}
}
// Determine what should display in 'platform' column
async function parsePlatform(svc){
if (svc.platform != undefined) {
var platform = svc.platform;
@ -166,6 +32,7 @@ async function parsePlatform(svc){
return {num: platform, change: changed}
}
// Use different time strings to default to make better on small screens
async function parseTime(string){
switch (string) {
case "Delayed":
@ -185,7 +52,7 @@ async function parseTime(string){
var change = "";
break;
case undefined:
var output = "";
var output = "-";
var change = "";
break;
default:
@ -195,11 +62,7 @@ async function parseTime(string){
return {data: output, changed: change};
}
// Sometimes the origin or destination names are undefined, need to catch that
async function parseName(){
}
// Log Helper
function log(msg) {
var time = new Date().toISOString();
console.log(`${time} - ${msg}`)
return;
}

View File

@ -1,3 +1,4 @@
// Toggle Loading Box
async function clearLoading() {
document.getElementById("loading").style = "display: none;";
}
@ -5,3 +6,12 @@ async function clearLoading() {
async function showLoading() {
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 {
position: absolute;
margin-top: 10px;
font-size: 15pt;
left: 10px;
max-width: 50%;
left: 7px;
top: 3px;
}
.header-large{
left: 0;
text-align: left;
font-size: 13pt;
margin-top: -2px;
overflow-wrap: anywhere;
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;
padding-right: 5px;
margin: 3px;
@ -74,19 +97,28 @@
}
table {
color: white;
width: 100%;
margin-top: 3px;
font-size: 10.5px;
}
#ferry{
margin-top: 45px;
}
.name{
width: 25%;
text-align: left;
}
.name-item {
color: yellow;
}
.plat{
width: 4%;
text-align: center;
}
.time{
width: 11.5%;
text-align: center;
@ -98,7 +130,7 @@ table {
margin: 0;
margin-left: 3px;
text-align: left;
color: white;
color: lightblue;
}
.changed{
@ -108,6 +140,6 @@ table {
/* Animations */
@keyframes pulse {
50% {
opacity: 0;
color: orange;
}
}