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
|
||||
try:
|
||||
page = flask.render_template(pagevars["template"], var=pagevars)
|
||||
except Exception as e:
|
||||
e = HTTPerror.InternalServerError(None, e)
|
||||
page = page404(e)
|
||||
except Exception:
|
||||
e = HTTPerror.InternalServerError()
|
||||
page = handle_http_error(e)
|
||||
return [pagevars, page]
|
||||
|
||||
def funcGen(pagename, pages):
|
||||
@@ -72,7 +72,7 @@ def funcGen(pagename, pages):
|
||||
except Exception:
|
||||
e = HTTPerror.InternalServerError()
|
||||
print(e)
|
||||
return page404(e)
|
||||
return handle_http_error(e)
|
||||
return dynamicRule
|
||||
|
||||
for i in pages:
|
||||
@@ -86,14 +86,14 @@ def resume():
|
||||
return flask.send_file("./static/Resume_Simonson_Andrew.pdf")
|
||||
|
||||
@app.errorhandler(HTTPerror.HTTPException)
|
||||
def page404(e):
|
||||
def handle_http_error(e):
|
||||
eCode = e.code
|
||||
message = e.description
|
||||
pagevars = {
|
||||
"template": "error.html",
|
||||
"title": f"{eCode} - Simonson",
|
||||
"description": "Error on Andrew Simonson's Digital Portfolio",
|
||||
"canonical": "404",
|
||||
"canonical": f"/{eCode}",
|
||||
}
|
||||
return (
|
||||
flask.render_template(
|
||||
@@ -107,12 +107,12 @@ def page404(e):
|
||||
)
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def page500(e):
|
||||
def handle_generic_error(e):
|
||||
pagevars = {
|
||||
"template": "error.html",
|
||||
"title": "500 - Simonson",
|
||||
"description": "Error on Andrew Simonson's Digital Portfolio",
|
||||
"canonical": "404",
|
||||
"canonical": "/500",
|
||||
}
|
||||
return (
|
||||
flask.render_template(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from os import environ as env
|
||||
# automatically updates some dev envs. need to remove for production.
|
||||
try:
|
||||
__import__('envs.py')
|
||||
__import__('envs')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -5,6 +5,7 @@ Checks service availability and tracks uptime statistics
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Thread, Lock
|
||||
from pathlib import Path
|
||||
@@ -125,12 +126,15 @@ class ServiceMonitor:
|
||||
"""Check all services and update status data"""
|
||||
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 = {}
|
||||
for service in SERVICES:
|
||||
result = self.check_service(service)
|
||||
results[service['id']] = result
|
||||
print(f" {service['name']}: {result['status']} ({result['response_time']}ms)")
|
||||
with ThreadPoolExecutor(max_workers=len(SERVICES)) as executor:
|
||||
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
|
||||
print(f" {service['name']}: {result['status']} ({result['response_time']}ms)")
|
||||
|
||||
# Only acquire lock when updating the shared data structure
|
||||
with self.lock:
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
@font-face {
|
||||
font-family: "neon-future";
|
||||
src: url("../fonts/NeonFuture.ttf")
|
||||
src: url("../fonts/NeonFuture.ttf");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "shuttlex";
|
||||
src: url("../fonts/SHUTTLE-X.ttf");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "sunset-club";
|
||||
src: url("../fonts/SunsetClub.otf")
|
||||
src: url("../fonts/SunsetClub.otf");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "robotoreg";
|
||||
src: url("../fonts/RobotoCondensed-Regular.ttf")
|
||||
src: url("../fonts/RobotoCondensed-Regular.ttf");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
html, body {
|
||||
@@ -117,10 +121,6 @@ li {
|
||||
color: #a8a8a8;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #ecebeb;
|
||||
}
|
||||
|
||||
p, li, span {
|
||||
color: rgb(212, 212, 212);
|
||||
font-size: 1rem;
|
||||
@@ -289,6 +289,9 @@ tr {
|
||||
padding-right: 4rem;
|
||||
border-bottom: #0f0f0f solid 5px;
|
||||
color: white;
|
||||
position: relative;
|
||||
padding-bottom: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.foreground {
|
||||
@@ -313,12 +316,6 @@ tr {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.concentratedHead {
|
||||
position: relative;
|
||||
padding-bottom: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.concentratedHead::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -442,10 +439,6 @@ tr {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.onRight {
|
||||
overflow: scroll;
|
||||
margin: auto;
|
||||
@@ -955,7 +948,7 @@ tr {
|
||||
padding: 10px;
|
||||
border: solid 2px #553;
|
||||
border-radius: 0.5em;
|
||||
background-image: url("/static/photos/wood.jpg");
|
||||
background-image: url("../photos/wood.jpg");
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
@@ -1125,9 +1118,10 @@ tr {
|
||||
@font-face {
|
||||
font-family: Chess New;
|
||||
src: url('../fonts/chessglyph-new.0cc8115c.woff2');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600) {
|
||||
@media screen and (max-width: 600px) {
|
||||
#nametagContainer {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ async function addChessEmbed(username) {
|
||||
if (user.status === 200) {
|
||||
user = await user.json();
|
||||
stats = await stats.json();
|
||||
ratings = {
|
||||
const ratings = {
|
||||
rapid: stats.chess_rapid.last.rating,
|
||||
blitz: stats.chess_blitz.last.rating,
|
||||
bullet: stats.chess_bullet.last.rating,
|
||||
|
||||
@@ -13,30 +13,46 @@ function toggleMenu(collapse=false) {
|
||||
}
|
||||
|
||||
async function goto(location, { push = true } = {}) {
|
||||
let a = await fetch("/api/goto/" + location, {
|
||||
credentials: "include",
|
||||
method: "GET",
|
||||
mode: "cors",
|
||||
});
|
||||
let a;
|
||||
try {
|
||||
a = await fetch("/api/goto/" + location, {
|
||||
credentials: "include",
|
||||
method: "GET",
|
||||
mode: "cors",
|
||||
});
|
||||
if (!a.ok) {
|
||||
console.error(`Navigation failed: HTTP ${a.status}`);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Navigation fetch failed:", err);
|
||||
return;
|
||||
}
|
||||
|
||||
document.dispatchEvent(new Event('beforenavigate'));
|
||||
|
||||
const response = await a.json();
|
||||
const metadata = response[0];
|
||||
const content = response[1];
|
||||
const root = document.getElementById("root");
|
||||
root.innerHTML = content;
|
||||
root.querySelectorAll("script").forEach((oldScript) => {
|
||||
const newScript = document.createElement("script");
|
||||
Array.from(oldScript.attributes).forEach(attr => {
|
||||
newScript.setAttribute(attr.name, attr.value);
|
||||
});
|
||||
newScript.textContent = oldScript.textContent;
|
||||
oldScript.parentNode.replaceChild(newScript, oldScript);
|
||||
});
|
||||
|
||||
if (!window.location.href.includes("#")) {
|
||||
window.scrollTo({top: 0, left: 0, behavior:"instant"});
|
||||
} else {
|
||||
eid = decodeURIComponent(window.location.hash.substring(1))
|
||||
document.getElementById(eid).scrollIntoView()
|
||||
const eid = decodeURIComponent(window.location.hash.substring(1));
|
||||
const el = document.getElementById(eid);
|
||||
if (el) el.scrollIntoView();
|
||||
}
|
||||
const metadata = response[0];
|
||||
const content = response[1];
|
||||
let root = document.getElementById("root");
|
||||
root.innerHTML = content;
|
||||
root.querySelectorAll("script").forEach((oldScript) => {
|
||||
const newScript = document.createElement("script");
|
||||
Array.from(oldScript.attributes).forEach(attr => {
|
||||
newScript.setAttribute(attr.name, attr.value);
|
||||
});
|
||||
newScript.textContent = oldScript.textContent;
|
||||
oldScript.parentNode.replaceChild(newScript, oldScript);
|
||||
});
|
||||
|
||||
toggleMenu(collapse=true);
|
||||
document.querySelector("title").textContent = metadata["title"];
|
||||
if (push) {
|
||||
|
||||
@@ -248,6 +248,14 @@ function initStatusPage() {
|
||||
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
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initStatusPage);
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
<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/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="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>
|
||||
</head>
|
||||
{% block header %}
|
||||
|
||||
Reference in New Issue
Block a user