mirror of
https://github.com/asimonson1125/asimonson1125.github.io.git
synced 2026-09-22 11:35:33 -05:00
review from gsq-rco-iq3_xxs
This commit is contained in:
@@ -22,6 +22,7 @@ SERVICES = [
|
||||
CHECK_INTERVAL = 60 # seconds between checks
|
||||
RETENTION_DAYS = 90 # how long to keep records
|
||||
CLEANUP_INTERVAL = 86400 # seconds between purge runs
|
||||
STALE_AFTER = CHECK_INTERVAL * 5 # flag cached data as stale if no check has landed in this long
|
||||
|
||||
DATABASE_URL = os.environ.get('DATABASE_URL')
|
||||
|
||||
@@ -331,9 +332,21 @@ class ServiceMonitor:
|
||||
|
||||
def get_status_summary(self):
|
||||
"""Return the cached status summary, refreshed once per check cycle
|
||||
(see check_all_services), so this never itself touches the database."""
|
||||
(see check_all_services), so this never itself touches the database.
|
||||
|
||||
Adds a `stale` flag computed against wall-clock time, since a summary
|
||||
that stops updating (e.g. the monitor loop hit a persistent error) would
|
||||
otherwise keep reporting its last-known last_check as if it were fresh.
|
||||
"""
|
||||
with self.lock:
|
||||
return self._cached_summary
|
||||
summary = dict(self._cached_summary)
|
||||
|
||||
stale = False
|
||||
if summary.get('last_check'):
|
||||
age = (datetime.now() - datetime.fromisoformat(summary['last_check'])).total_seconds()
|
||||
stale = age > STALE_AFTER
|
||||
summary['stale'] = stale
|
||||
return summary
|
||||
|
||||
# ── Background loop ───────────────────────────────────────────
|
||||
|
||||
@@ -352,21 +365,37 @@ class ServiceMonitor:
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _run_check_cycle(self):
|
||||
"""Run one check_all_services() call, catching any exception so a
|
||||
transient failure (DB hiccup, network blip) can't kill the daemon
|
||||
thread -- without this, the thread dies silently and /api/status
|
||||
keeps serving an ever-staler cached summary with nothing to say so."""
|
||||
try:
|
||||
self.check_all_services()
|
||||
except Exception as e:
|
||||
print(f"[monitor] check cycle failed, will retry next interval: {e}")
|
||||
|
||||
def _run_cleanup(self):
|
||||
try:
|
||||
self._purge_old_records()
|
||||
except Exception as e:
|
||||
print(f"[monitor] cleanup failed, will retry next interval: {e}")
|
||||
|
||||
def start_monitoring(self):
|
||||
"""Start the background daemon thread for periodic checks and cleanup."""
|
||||
def monitor_loop():
|
||||
self.check_all_services()
|
||||
self._purge_old_records()
|
||||
self._run_check_cycle()
|
||||
self._run_cleanup()
|
||||
|
||||
checks_since_cleanup = 0
|
||||
checks_per_cleanup = CLEANUP_INTERVAL // CHECK_INTERVAL
|
||||
|
||||
while True:
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
self.check_all_services()
|
||||
self._run_check_cycle()
|
||||
checks_since_cleanup += 1
|
||||
if checks_since_cleanup >= checks_per_cleanup:
|
||||
self._purge_old_records()
|
||||
self._run_cleanup()
|
||||
checks_since_cleanup = 0
|
||||
|
||||
thread = Thread(target=monitor_loop, daemon=True)
|
||||
|
||||
@@ -1137,6 +1137,13 @@ figcaption {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.status-notice {
|
||||
color: var(--warn);
|
||||
border: 1px solid var(--warn);
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
Books
|
||||
------------------------------------------------------------------ */
|
||||
@@ -1396,6 +1403,19 @@ figcaption {
|
||||
transition: width 0.2s ease, opacity 0.4s ease 0.1s;
|
||||
}
|
||||
|
||||
.nav-notice {
|
||||
border-bottom: 1px solid var(--bad);
|
||||
color: var(--bad);
|
||||
font-family: var(--font-data);
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
.nav-notice[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: fixed;
|
||||
top: -100%;
|
||||
|
||||
@@ -17,9 +17,21 @@ function markCurrentPage(location) {
|
||||
});
|
||||
}
|
||||
|
||||
function showNavNotice(message) {
|
||||
const notice = document.getElementById('nav-notice');
|
||||
if (!notice) return;
|
||||
notice.textContent = message;
|
||||
notice.hidden = false;
|
||||
}
|
||||
|
||||
function hideNavNotice() {
|
||||
const notice = document.getElementById('nav-notice');
|
||||
if (notice) notice.hidden = true;
|
||||
}
|
||||
|
||||
async function goto(location, { push = true, hash = "" } = {}) {
|
||||
const loadingBar = document.getElementById('loading-bar');
|
||||
|
||||
|
||||
if (loadingBar) {
|
||||
loadingBar.style.width = ''; // Clear inline style from previous run
|
||||
}
|
||||
@@ -47,6 +59,7 @@ async function goto(location, { push = true, hash = "" } = {}) {
|
||||
const [metadata, content] = await response.json();
|
||||
|
||||
document.dispatchEvent(new Event('beforenavigate'));
|
||||
hideNavNotice();
|
||||
|
||||
const root = document.getElementById("root");
|
||||
root.innerHTML = content;
|
||||
@@ -79,6 +92,7 @@ async function goto(location, { push = true, hash = "" } = {}) {
|
||||
|
||||
} catch (err) {
|
||||
console.error("Navigation failed:", err);
|
||||
showNavNotice("Couldn't load that page. Check your connection and try again.");
|
||||
} finally {
|
||||
clearTimeout(loadingTimeout);
|
||||
if (loadingBar && loadingBar.classList.contains('active')) {
|
||||
|
||||
@@ -19,6 +19,8 @@ async function fetchStatus() {
|
||||
}
|
||||
|
||||
function updateStatusDisplay(data) {
|
||||
showStaleNotice(!!data.stale);
|
||||
|
||||
if (data.last_check) {
|
||||
const lastCheck = new Date(data.last_check);
|
||||
const lastUpdateEl = document.getElementById('lastUpdate');
|
||||
@@ -188,7 +190,7 @@ function showError(message) {
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'status-error';
|
||||
errorDiv.textContent = message;
|
||||
|
||||
|
||||
|
||||
const container = document.querySelector('.page');
|
||||
if (container) {
|
||||
@@ -197,6 +199,28 @@ function showError(message) {
|
||||
}
|
||||
}
|
||||
|
||||
// Persistent (not auto-dismissed) notice that the last successful check is
|
||||
// older than expected -- the monitor may have hit a snag, so don't let the
|
||||
// page keep implying the numbers below are fresh.
|
||||
function showStaleNotice(isStale) {
|
||||
const container = document.querySelector('.page');
|
||||
if (!container) return;
|
||||
let noticeEl = document.getElementById('staleNotice');
|
||||
|
||||
if (!isStale) {
|
||||
if (noticeEl) noticeEl.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!noticeEl) {
|
||||
noticeEl = document.createElement('div');
|
||||
noticeEl.id = 'staleNotice';
|
||||
noticeEl.className = 'status-notice';
|
||||
noticeEl.textContent = 'Data below may be out of date -- the last successful check was longer ago than expected.';
|
||||
container.insertBefore(noticeEl, container.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshStatus() {
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
if (refreshBtn) {
|
||||
|
||||
@@ -80,12 +80,12 @@
|
||||
"No, They Can't": {
|
||||
"filename": "no-they-cant.jpeg",
|
||||
"link": "https://www.goodreads.com/book/show/13260131-no-they-can-t",
|
||||
"review": "I much preferred Give Me A Break (which I read first). Sossel's writing style in this book is less developed - it feels aimless and with an intent to lecture."
|
||||
"review": "I much preferred Give Me A Break (which I read first). Stossel's writing style in this book is less developed - it feels aimless and with an intent to lecture."
|
||||
},
|
||||
"Give Me a Break": {
|
||||
"filename": "giveMeABreak.jpeg",
|
||||
"link": "https://www.amazon.com/Give-Me-Break-Exposed-Hucksters-ebook/dp/B000FC2NF8/",
|
||||
"review": "I expected a boring autobiography-type book, but instead is a glimpse inside Stossel's work that transformed itself as it transformed his view. Was very happy to see a figure of similar personal ideology. Probably made it a little too easy to swallow that pill."
|
||||
"review": "I expected a boring autobiography-type book, but instead is a glimpse inside Stossel's work that transformed itself as it transformed his view."
|
||||
},
|
||||
"Reign of Terror": {
|
||||
"filename": "reignofterror.jpg",
|
||||
@@ -125,7 +125,7 @@
|
||||
"The Scout Mindset": {
|
||||
"filename": "scoutMindset.png",
|
||||
"link": "https://www.amazon.com/Scout-Mindset-People-Things-Clearly-ebook/dp/B07L2HQ26K/",
|
||||
"review": "Felt like a list of things that I already do that I should be more mindful of. Maybe that's just me. There was some interesting mental probablism sprinkled in the first half but the second half did not have much new to say. Good but not eye-opening."
|
||||
"review": "Felt like a list of things that I already do that I should be more mindful of. Maybe that's just me. There was some interesting mental probabilism sprinkled in the first half but the second half did not have much new to say. Good but not eye-opening."
|
||||
},
|
||||
"Verbal Judo": {
|
||||
"filename": "verbalJudo.png",
|
||||
@@ -150,7 +150,7 @@
|
||||
"Where Good Ideas Come From": {
|
||||
"filename": "where-good-ideas-come-from.png",
|
||||
"link": "https://www.goodreads.com/book/show/8034188-where-good-ideas-come-from",
|
||||
"review": "I got this book at a recycling center. I didn't want to read it or like it. Unfortnuately, it's pretty good. 200 pages of considerate review of how innovation comes to be + suggestions to expand the utility of your ideas (I've adopted several!)"
|
||||
"review": "I got this book at a recycling center. I didn't want to read it or like it. Unfortunately, it's pretty good. 200 pages of considerate review of how innovation comes to be + suggestions to expand the utility of your ideas (I've adopted several!)"
|
||||
},
|
||||
"12 Rules for Life": {
|
||||
"filename": "12RulesForLife.jpg",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"links": [
|
||||
[
|
||||
"globe",
|
||||
"http://files.asimonson.com/u/AIcodeSmells.pdf",
|
||||
"https://files.asimonson.com/u/AIcodeSmells.pdf",
|
||||
"Paper"
|
||||
]
|
||||
]
|
||||
@@ -20,7 +20,7 @@
|
||||
"links": [
|
||||
[
|
||||
"globe",
|
||||
"http://files.asimonson.com/u/blanketTrust.pdf",
|
||||
"https://files.asimonson.com/u/blanketTrust.pdf",
|
||||
"Paper"
|
||||
]
|
||||
]
|
||||
@@ -83,7 +83,7 @@
|
||||
"Portfolio Website": {
|
||||
"status": "complete",
|
||||
"classes": "programming",
|
||||
"content": "This website is my personal sandbox where I've integrated some of my data projects via docker cluster. It is self hosted and zero-trust secure while remaining dynamic and free of the tech debt that comes with pre-designed sites and excessive framework application. Yeah, I can do E2E.",
|
||||
"content": "This website is my personal sandbox where I've integrated some of my data projects via docker cluster. It is self hosted and dynamic, generally free of the tech debt that comes with pre-designed sites and excessive framework application. Yeah, I can do E2E.",
|
||||
"links": [
|
||||
["globe", "https://asimonson.com", "Homepage"],
|
||||
[
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
<svg width="350" height="165" viewBox="0 0 350 165" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="descId">
|
||||
<title id="titleId"/>
|
||||
<desc id="descId"/>
|
||||
<style>
|
||||
.header {
|
||||
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
|
||||
fill: #fabd2f;
|
||||
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
||||
}
|
||||
@supports(-moz-appearance: auto) {
|
||||
/* Selector detects Firefox */
|
||||
.header { font-size: 15.5px; }
|
||||
}
|
||||
|
||||
@keyframes slideInAnimation {
|
||||
from {
|
||||
width: 0;
|
||||
}
|
||||
to {
|
||||
width: calc(100%-100px);
|
||||
}
|
||||
}
|
||||
@keyframes growWidthAnimation {
|
||||
from {
|
||||
width: 0;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.lang-name {
|
||||
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
|
||||
fill: #8ec07c;
|
||||
}
|
||||
.stagger {
|
||||
opacity: 0;
|
||||
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
#rect-mask rect{
|
||||
animation: slideInAnimation 1s ease-in-out forwards;
|
||||
}
|
||||
.lang-progress{
|
||||
animation: growWidthAnimation 0.6s ease-in-out forwards;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Animations */
|
||||
@keyframes scaleInAnimation {
|
||||
from {
|
||||
transform: translate(-5px, 5px) scale(0);
|
||||
}
|
||||
to {
|
||||
transform: translate(-5px, 5px) scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInAnimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<rect data-testid="card-bg" x="0.5" y="0.5" rx="4.5" height="99%" stroke="#e4e2e2" width="349" fill="#282828" stroke-opacity="1"/>
|
||||
|
||||
|
||||
<g data-testid="card-title" transform="translate(25, 35)">
|
||||
<g transform="translate(0, 0)">
|
||||
<text x="0" y="0" class="header" data-testid="header">Most Used Languages</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
||||
<g data-testid="main-card-body" transform="translate(0, 55)">
|
||||
|
||||
<svg data-testid="lang-items" x="25">
|
||||
|
||||
<mask id="rect-mask">
|
||||
<rect x="0" y="0" width="300" height="8" fill="white" rx="5"/>
|
||||
</mask>
|
||||
|
||||
<rect mask="url(#rect-mask)" data-testid="lang-progress" x="0" y="0" width="128.34" height="8" fill="#f1e05a"/>
|
||||
|
||||
<rect mask="url(#rect-mask)" data-testid="lang-progress" x="128.34" y="0" width="63.46" height="8" fill="#f34b7d"/>
|
||||
|
||||
<rect mask="url(#rect-mask)" data-testid="lang-progress" x="191.8" y="0" width="49.7" height="8" fill="#3572A5"/>
|
||||
|
||||
<rect mask="url(#rect-mask)" data-testid="lang-progress" x="241.5" y="0" width="33.69" height="8" fill="#e34c26"/>
|
||||
|
||||
<rect mask="url(#rect-mask)" data-testid="lang-progress" x="275.19" y="0" width="24.81" height="8" fill="#563d7c"/>
|
||||
|
||||
|
||||
<g transform="translate(0, 25)">
|
||||
<g transform="translate(0, 0)"><g transform="translate(0, 0)">
|
||||
<g class="stagger" style="animation-delay: 450ms">
|
||||
<circle cx="5" cy="6" r="5" fill="#f1e05a"/>
|
||||
<text data-testid="lang-name" x="15" y="10" class="lang-name">
|
||||
JavaScript 42.78%
|
||||
</text>
|
||||
</g>
|
||||
</g><g transform="translate(0, 25)">
|
||||
<g class="stagger" style="animation-delay: 600ms">
|
||||
<circle cx="5" cy="6" r="5" fill="#f34b7d"/>
|
||||
<text data-testid="lang-name" x="15" y="10" class="lang-name">
|
||||
C++ 21.15%
|
||||
</text>
|
||||
</g>
|
||||
</g><g transform="translate(0, 50)">
|
||||
<g class="stagger" style="animation-delay: 750ms">
|
||||
<circle cx="5" cy="6" r="5" fill="#3572A5"/>
|
||||
<text data-testid="lang-name" x="15" y="10" class="lang-name">
|
||||
Python 16.57%
|
||||
</text>
|
||||
</g>
|
||||
</g></g><g transform="translate(150, 0)"><g transform="translate(0, 0)">
|
||||
<g class="stagger" style="animation-delay: 450ms">
|
||||
<circle cx="5" cy="6" r="5" fill="#e34c26"/>
|
||||
<text data-testid="lang-name" x="15" y="10" class="lang-name">
|
||||
HTML 11.23%
|
||||
</text>
|
||||
</g>
|
||||
</g><g transform="translate(0, 25)">
|
||||
<g class="stagger" style="animation-delay: 600ms">
|
||||
<circle cx="5" cy="6" r="5" fill="#563d7c"/>
|
||||
<text data-testid="lang-name" x="15" y="10" class="lang-name">
|
||||
CSS 8.27%
|
||||
</text>
|
||||
</g>
|
||||
</g></g>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,11 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://asimonson.com</loc>
|
||||
<loc>https://asimonson.com/projects</loc>
|
||||
<loc>https://asimonson.com/Resume</loc>
|
||||
<loc>https://asimonson.com/duck</loc>
|
||||
<loc>https://asimonson.com/status</loc>
|
||||
<lastmod>2026-02-12</lastmod>
|
||||
<loc>https://asimonson.com/</loc>
|
||||
<lastmod>2026-09-22</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
<url>
|
||||
<loc>https://asimonson.com/about</loc>
|
||||
<lastmod>2026-09-22</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://asimonson.com/projects</loc>
|
||||
<lastmod>2026-09-22</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://asimonson.com/books</loc>
|
||||
<lastmod>2026-09-22</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://asimonson.com/status</loc>
|
||||
<lastmod>2026-09-22</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://asimonson.com/resume</loc>
|
||||
<lastmod>2026-09-22</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<h1 class="display">About</h1>
|
||||
<p class="lede">
|
||||
I'm Andrew Simonson, a data scientist at Ecolab and a graduate data
|
||||
science student at Rochester Institute of Technology, where I recently
|
||||
finished a B.S. in Computer Science with a minor in international
|
||||
relations and a focus on probability theory.
|
||||
science student at Rochester Institute of Technology, pursuing an M.S.
|
||||
in Data Science after finishing a B.S. in Computer Science with a
|
||||
minor in international relations and a focus on probability theory.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="cert-group">
|
||||
<h3>Data Science MicroMasters</h3>
|
||||
<p class="cert-provider">UC San Diego on edX ·
|
||||
<a href="http://credentials.edx.org/credentials/4b7e78dca8154c0d88ca9abc5aedb4ac" rel="noopener noreferrer">program certificate</a>
|
||||
<a href="https://credentials.edx.org/credentials/4b7e78dca8154c0d88ca9abc5aedb4ac" rel="noopener noreferrer">program certificate</a>
|
||||
</p>
|
||||
<ul class="cert-list">
|
||||
<li><a href="https://courses.edx.org/certificates/b6deccc56e5344ae84cb55f9ad81fd79" rel="noopener noreferrer">Python for Data Science</a> <span class="cert-code">DSE200x</span></li>
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
<body onpopstate="backButton()">
|
||||
<a class="skip-link" href="#root">Skip to content</a>
|
||||
<div id="loading-bar" aria-hidden="true"></div>
|
||||
<div id="nav-notice" class="nav-notice" role="alert" hidden></div>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="bg" aria-hidden="true"></div>
|
||||
<div class="site">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<section class="section" aria-labelledby="now-heading">
|
||||
<div class="section-head">
|
||||
<h2 id="now-heading">Now</h2>
|
||||
<p class="section-note">September 2026</p>
|
||||
<p class="section-note">Today</p>
|
||||
</div>
|
||||
<dl class="now-list">
|
||||
<div class="now-item">
|
||||
@@ -97,7 +97,8 @@
|
||||
<div class="colophon-text">
|
||||
<p>
|
||||
I wasn't really expecting anyone to read this far.
|
||||
If you're looking for shared resources you've taken a wrong turn.
|
||||
If you're looking for shared resources you've taken a wrong turn.
|
||||
</p>
|
||||
<p>
|
||||
From here, we'll rekindle the future.
|
||||
</p>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/hotspots.css') }}" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin=""
|
||||
/>
|
||||
<script
|
||||
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""
|
||||
></script>
|
||||
<script src="{{ url_for('static', filename='js/lib/leaflet-providers.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/lib/CUSTOM.leaflet.curve.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<script src="{{ url_for('static', filename='js/hotspots.js') }}"></script>
|
||||
</body>
|
||||
@@ -1,4 +0,0 @@
|
||||
{% extends "header.html" %}
|
||||
{% block header %}{% endblock %}
|
||||
{% block footer %}{% endblock %}
|
||||
{% block content %}<iframe id="fullIframe" src="{{ url }}" title="HotspotsRIT"></iframe>{% endblock %}
|
||||
Reference in New Issue
Block a user