Merge pull request #35 from asimonson1125/claude/review-architecture-hxQz0

Claude/review architecture hx qz0
This commit is contained in:
2026-02-12 10:15:35 -05:00
committed by GitHub
8 changed files with 86 additions and 55 deletions

View File

@@ -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(

View File

@@ -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

View File

@@ -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)")

View File

@@ -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,15 +121,15 @@ 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;
} }
strong {
color: #ecebeb;
}
span { span {
font-size: .8rem; font-size: .8rem;
} }
@@ -289,6 +293,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 +320,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 +443,6 @@ tr {
height: 0; height: 0;
} }
.relative {
position: relative;
}
.onRight { .onRight {
overflow: scroll; overflow: scroll;
margin: auto; margin: auto;
@@ -456,6 +453,11 @@ tr {
margin-bottom: 5px; margin-bottom: 5px;
} }
.relative {
position: relative;
}
.bgi { .bgi {
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -955,7 +957,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 +1127,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;
} }

View File

@@ -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,

View File

@@ -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) {

View File

@@ -236,7 +236,7 @@ function refreshStatus() {
/** /**
* Initialize on page load * Initialize on page load
*/ */
let statusIntervalId = null; var statusIntervalId = null;
function initStatusPage() { function initStatusPage() {
// Clear any existing interval from a previous SPA navigation // Clear any existing interval from a previous SPA navigation
@@ -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);

View File

@@ -55,7 +55,7 @@
<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 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 %}