mirror of
https://github.com/asimonson1125/asimonson1125.github.io.git
synced 2026-02-25 05:09:49 -06:00
fix architectural issues: SPA nav, error handling, CSS bugs, perf
- responsive.js: fix scroll race condition (scroll after innerHTML),
add error handling for fetch failures, fix implicit global `eid`,
dispatch `beforenavigate` event for cleanup hooks
- chessbed.js: fix implicit global `ratings` variable
- status.js: clear polling interval on SPA navigation via
`beforenavigate` event to prevent leak
- App.css: add font-display:swap to all @font-face, fix broken
media query (missing px unit), consolidate duplicate selectors
(.concentratedHead, .relative, strong), fix hardcoded bookshelf
background-image path to use relative URL
- header.html: defer chessbed.js, use p5.min.js instead of p5.js
- monitor.py: use ThreadPoolExecutor for concurrent service checks
- config.py: fix __import__('envs.py') → __import__('envs')
- app.py: rename misleading error handlers (page404→handle_http_error,
page500→handle_generic_error), fix error info leakage by not passing
raw exception to InternalServerError, fix hardcoded canonical "404"
https://claude.ai/code/session_01FUhPqQLahEoL6FMxhXkDKa
This commit is contained in:
16
src/app.py
16
src/app.py
@@ -60,9 +60,9 @@ def goto(location='home'):
|
|||||||
page = None
|
page = None
|
||||||
try:
|
try:
|
||||||
page = flask.render_template(pagevars["template"], var=pagevars)
|
page = flask.render_template(pagevars["template"], var=pagevars)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
e = HTTPerror.InternalServerError(None, e)
|
e = HTTPerror.InternalServerError()
|
||||||
page = page404(e)
|
page = handle_http_error(e)
|
||||||
return [pagevars, page]
|
return [pagevars, page]
|
||||||
|
|
||||||
def funcGen(pagename, pages):
|
def funcGen(pagename, pages):
|
||||||
@@ -72,7 +72,7 @@ def funcGen(pagename, pages):
|
|||||||
except Exception:
|
except Exception:
|
||||||
e = HTTPerror.InternalServerError()
|
e = HTTPerror.InternalServerError()
|
||||||
print(e)
|
print(e)
|
||||||
return page404(e)
|
return handle_http_error(e)
|
||||||
return dynamicRule
|
return dynamicRule
|
||||||
|
|
||||||
for i in pages:
|
for i in pages:
|
||||||
@@ -86,14 +86,14 @@ def resume():
|
|||||||
return flask.send_file("./static/Resume_Simonson_Andrew.pdf")
|
return flask.send_file("./static/Resume_Simonson_Andrew.pdf")
|
||||||
|
|
||||||
@app.errorhandler(HTTPerror.HTTPException)
|
@app.errorhandler(HTTPerror.HTTPException)
|
||||||
def page404(e):
|
def handle_http_error(e):
|
||||||
eCode = e.code
|
eCode = e.code
|
||||||
message = e.description
|
message = e.description
|
||||||
pagevars = {
|
pagevars = {
|
||||||
"template": "error.html",
|
"template": "error.html",
|
||||||
"title": f"{eCode} - Simonson",
|
"title": f"{eCode} - Simonson",
|
||||||
"description": "Error on Andrew Simonson's Digital Portfolio",
|
"description": "Error on Andrew Simonson's Digital Portfolio",
|
||||||
"canonical": "404",
|
"canonical": f"/{eCode}",
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
flask.render_template(
|
flask.render_template(
|
||||||
@@ -107,12 +107,12 @@ def page404(e):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@app.errorhandler(Exception)
|
@app.errorhandler(Exception)
|
||||||
def page500(e):
|
def handle_generic_error(e):
|
||||||
pagevars = {
|
pagevars = {
|
||||||
"template": "error.html",
|
"template": "error.html",
|
||||||
"title": "500 - Simonson",
|
"title": "500 - Simonson",
|
||||||
"description": "Error on Andrew Simonson's Digital Portfolio",
|
"description": "Error on Andrew Simonson's Digital Portfolio",
|
||||||
"canonical": "404",
|
"canonical": "/500",
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
flask.render_template(
|
flask.render_template(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from os import environ as env
|
from os import environ as env
|
||||||
# automatically updates some dev envs. need to remove for production.
|
# automatically updates some dev envs. need to remove for production.
|
||||||
try:
|
try:
|
||||||
__import__('envs.py')
|
__import__('envs')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Checks service availability and tracks uptime statistics
|
|||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from threading import Thread, Lock
|
from threading import Thread, Lock
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -125,10 +126,13 @@ class ServiceMonitor:
|
|||||||
"""Check all services and update status data"""
|
"""Check all services and update status data"""
|
||||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Checking all services...")
|
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Checking all services...")
|
||||||
|
|
||||||
# Perform all network checks OUTSIDE the lock to avoid blocking API calls
|
# Perform all network checks concurrently and OUTSIDE the lock
|
||||||
results = {}
|
results = {}
|
||||||
for service in SERVICES:
|
with ThreadPoolExecutor(max_workers=len(SERVICES)) as executor:
|
||||||
result = self.check_service(service)
|
futures = {executor.submit(self.check_service, s): s for s in SERVICES}
|
||||||
|
for future in futures:
|
||||||
|
service = futures[future]
|
||||||
|
result = future.result()
|
||||||
results[service['id']] = result
|
results[service['id']] = result
|
||||||
print(f" {service['name']}: {result['status']} ({result['response_time']}ms)")
|
print(f" {service['name']}: {result['status']} ({result['response_time']}ms)")
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "neon-future";
|
font-family: "neon-future";
|
||||||
src: url("../fonts/NeonFuture.ttf")
|
src: url("../fonts/NeonFuture.ttf");
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "shuttlex";
|
font-family: "shuttlex";
|
||||||
src: url("../fonts/SHUTTLE-X.ttf");
|
src: url("../fonts/SHUTTLE-X.ttf");
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "sunset-club";
|
font-family: "sunset-club";
|
||||||
src: url("../fonts/SunsetClub.otf")
|
src: url("../fonts/SunsetClub.otf");
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "robotoreg";
|
font-family: "robotoreg";
|
||||||
src: url("../fonts/RobotoCondensed-Regular.ttf")
|
src: url("../fonts/RobotoCondensed-Regular.ttf");
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
@@ -117,10 +121,6 @@ li {
|
|||||||
color: #a8a8a8;
|
color: #a8a8a8;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
|
||||||
color: #ecebeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
p, li, span {
|
p, li, span {
|
||||||
color: rgb(212, 212, 212);
|
color: rgb(212, 212, 212);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -289,6 +289,9 @@ tr {
|
|||||||
padding-right: 4rem;
|
padding-right: 4rem;
|
||||||
border-bottom: #0f0f0f solid 5px;
|
border-bottom: #0f0f0f solid 5px;
|
||||||
color: white;
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.foreground {
|
.foreground {
|
||||||
@@ -313,12 +316,6 @@ tr {
|
|||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.concentratedHead {
|
|
||||||
position: relative;
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.concentratedHead::after {
|
.concentratedHead::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -442,10 +439,6 @@ tr {
|
|||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onRight {
|
.onRight {
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -955,7 +948,7 @@ tr {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: solid 2px #553;
|
border: solid 2px #553;
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
background-image: url("/static/photos/wood.jpg");
|
background-image: url("../photos/wood.jpg");
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
@@ -1125,9 +1118,10 @@ tr {
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: Chess New;
|
font-family: Chess New;
|
||||||
src: url('../fonts/chessglyph-new.0cc8115c.woff2');
|
src: url('../fonts/chessglyph-new.0cc8115c.woff2');
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600) {
|
@media screen and (max-width: 600px) {
|
||||||
#nametagContainer {
|
#nametagContainer {
|
||||||
width: unset;
|
width: unset;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ async function addChessEmbed(username) {
|
|||||||
if (user.status === 200) {
|
if (user.status === 200) {
|
||||||
user = await user.json();
|
user = await user.json();
|
||||||
stats = await stats.json();
|
stats = await stats.json();
|
||||||
ratings = {
|
const ratings = {
|
||||||
rapid: stats.chess_rapid.last.rating,
|
rapid: stats.chess_rapid.last.rating,
|
||||||
blitz: stats.chess_blitz.last.rating,
|
blitz: stats.chess_blitz.last.rating,
|
||||||
bullet: stats.chess_bullet.last.rating,
|
bullet: stats.chess_bullet.last.rating,
|
||||||
|
|||||||
@@ -13,21 +13,28 @@ function toggleMenu(collapse=false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function goto(location, { push = true } = {}) {
|
async function goto(location, { push = true } = {}) {
|
||||||
let a = await fetch("/api/goto/" + location, {
|
let a;
|
||||||
|
try {
|
||||||
|
a = await fetch("/api/goto/" + location, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
});
|
});
|
||||||
const response = await a.json();
|
if (!a.ok) {
|
||||||
if (!window.location.href.includes("#")) {
|
console.error(`Navigation failed: HTTP ${a.status}`);
|
||||||
window.scrollTo({top: 0, left: 0, behavior:"instant"});
|
return;
|
||||||
} else {
|
|
||||||
eid = decodeURIComponent(window.location.hash.substring(1))
|
|
||||||
document.getElementById(eid).scrollIntoView()
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Navigation fetch failed:", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.dispatchEvent(new Event('beforenavigate'));
|
||||||
|
|
||||||
|
const response = await a.json();
|
||||||
const metadata = response[0];
|
const metadata = response[0];
|
||||||
const content = response[1];
|
const content = response[1];
|
||||||
let root = document.getElementById("root");
|
const root = document.getElementById("root");
|
||||||
root.innerHTML = content;
|
root.innerHTML = content;
|
||||||
root.querySelectorAll("script").forEach((oldScript) => {
|
root.querySelectorAll("script").forEach((oldScript) => {
|
||||||
const newScript = document.createElement("script");
|
const newScript = document.createElement("script");
|
||||||
@@ -37,6 +44,15 @@ async function goto(location, { push = true } = {}) {
|
|||||||
newScript.textContent = oldScript.textContent;
|
newScript.textContent = oldScript.textContent;
|
||||||
oldScript.parentNode.replaceChild(newScript, oldScript);
|
oldScript.parentNode.replaceChild(newScript, oldScript);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!window.location.href.includes("#")) {
|
||||||
|
window.scrollTo({top: 0, left: 0, behavior:"instant"});
|
||||||
|
} else {
|
||||||
|
const eid = decodeURIComponent(window.location.hash.substring(1));
|
||||||
|
const el = document.getElementById(eid);
|
||||||
|
if (el) el.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
toggleMenu(collapse=true);
|
toggleMenu(collapse=true);
|
||||||
document.querySelector("title").textContent = metadata["title"];
|
document.querySelector("title").textContent = metadata["title"];
|
||||||
if (push) {
|
if (push) {
|
||||||
|
|||||||
@@ -248,6 +248,14 @@ function initStatusPage() {
|
|||||||
statusIntervalId = setInterval(fetchStatus, 300000);
|
statusIntervalId = setInterval(fetchStatus, 300000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up interval when navigating away via SPA
|
||||||
|
document.addEventListener('beforenavigate', () => {
|
||||||
|
if (statusIntervalId !== null) {
|
||||||
|
clearInterval(statusIntervalId);
|
||||||
|
statusIntervalId = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Start when page loads
|
// Start when page loads
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', initStatusPage);
|
document.addEventListener('DOMContentLoaded', initStatusPage);
|
||||||
|
|||||||
@@ -53,9 +53,9 @@
|
|||||||
<link rel="canonical" href="{{ var['canonical'] }}" />
|
<link rel="canonical" href="{{ var['canonical'] }}" />
|
||||||
<script defer src="{{ url_for('static', filename='js/checkbox.js') }}"></script>
|
<script defer src="{{ url_for('static', filename='js/checkbox.js') }}"></script>
|
||||||
<script defer src="{{ url_for('static', filename='js/responsive.js') }}"></script>
|
<script defer src="{{ url_for('static', filename='js/responsive.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/chessbed.js') }}"></script>
|
<script defer src="{{ url_for('static', filename='js/chessbed.js') }}"></script>
|
||||||
<script defer src="{{ url_for('static', filename='js/idler.js') }}"></script>
|
<script defer src="{{ url_for('static', filename='js/idler.js') }}"></script>
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.js"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.min.js"></script>
|
||||||
<title>{{ var['title'] }}</title>
|
<title>{{ var['title'] }}</title>
|
||||||
</head>
|
</head>
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|||||||
Reference in New Issue
Block a user