- Fix 60 DOM ID mismatches in admin.js (editor, dashboard, API keys, theme, services, navigation, links)
- Add 51 missing CSS classes to admin.css (sidebar, topbar, login, editor, tables, settings, backups, etc)
- Fix 5 undefined onclick methods in admin.html (saveContact, saveSEO, remove unused save buttons)
- Fix API payload mismatches: services/nav/links send single object, apikeys nested {group, data} format
- Replace Promise.all with Promise.allSettled in loadDashboard for resilient loading
- Fix /api/services timeout: ThreadPoolExecutor parallel checks + timeout=2s
- Add /etc/hosts entries on VPS for subdomain resolution from localhost
- Add JSON error handlers (400, 401, 404, 500) to Flask API
- Suppress InsecureRequestWarning in Flask
- Fix dashThreats container ID mismatch in admin.html
- Delete stale root-level JS files from VPS
30 KiB
JAESWIFT.XYZ — FULL SITE AUDIT REPORT
Date: 2026-04-01
Auditor: Agent Zero Master Developer
Scope: All HTML, CSS, JS, Flask API, nginx config, live endpoint testing
EXECUTIVE SUMMARY
The site's core infrastructure works: nginx serves pages (200 OK), Flask API runs on port 5000, all 9 blog post slugs resolve correctly, and static assets load. However, the admin panel is critically broken due to a systematic naming mismatch between admin.js and admin.html — 60 DOM element IDs referenced in JavaScript don't exist in the HTML. Additionally, 5 onclick handlers call undefined methods, 51 CSS classes are used in HTML but never defined, and several API payload shapes don't match between frontend and backend.
Issue Count by Severity
| Severity | Count |
|---|---|
| CRITICAL | 8 |
| HIGH | 12 |
| MEDIUM | 15 |
| LOW | 7 |
1. CRITICAL ISSUES
1.1 ❌ CRITICAL: 60 DOM ID Mismatches — admin.js ↔ admin.html
Impact: The entire admin panel is non-functional. Every section that reads/writes form fields silently fails because getElementById() returns null.
1.1.1 Editor Section (16 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
postId |
editorPostId |
admin.js:451,524 |
editTitle |
editorTitle |
admin.js:66,452,525 |
editSlug |
editorSlug |
admin.js:453,526 |
editDate |
editorDate |
admin.js:460,463,527 |
editTime |
editorTime |
admin.js:461,464,528 |
editTags |
editorTags |
admin.js:467 |
editExcerpt |
editorExcerpt |
admin.js:468 |
editContent |
editorContent |
admin.js:72,469 |
editMood |
hudMood |
admin.js:472 |
editEnergy |
hudEnergy |
admin.js:85,476 |
editMotivation |
hudMotivation |
admin.js:85,477 |
editFocus |
hudFocus |
admin.js:85,478 |
editDifficulty |
hudDifficulty |
admin.js:85,479 |
editCoffee |
hudCoffee |
admin.js:490 |
editBPM |
hudBPM |
admin.js:493 |
editThreat |
hudThreat |
admin.js:496 |
Fix: In admin.js, rename all editX → editorX and editMood/Energy/etc → hudMood/Energy/etc, postId → editorPostId.
1.1.2 Dashboard Section (8 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
dashCPU |
statCPU |
admin.js:318 |
dashMEM |
statMEM |
admin.js:319 |
dashDISK |
statDISK |
admin.js:320 |
dashPosts |
statPosts |
admin.js:330 |
dashWords |
statWords |
admin.js:331 |
dashTracks |
statTracks |
admin.js:343 |
dashServicesGrid |
servicesGrid |
admin.js:351 |
dashThreats |
(not in HTML) | admin.js:370 |
Fix: In admin.js, rename dashX → statX, dashServicesGrid → servicesGrid. Add a threats container <div id="dashThreats"> to the dashboard section of admin.html, OR rename the JS reference.
1.1.3 API Keys Section (19 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
weatherApiKey |
apiWeatherKey |
admin.js:1294 |
spotifyClientId |
apiSpotifyClientId |
admin.js:1297 |
spotifyClientSecret |
apiSpotifyClientSecret |
admin.js:1298 |
spotifyRefreshToken |
apiSpotifyRefreshToken |
admin.js:1299 |
smtpHost |
apiSmtpHost |
admin.js:1302 |
smtpPort |
apiSmtpPort |
admin.js:1303 |
smtpUser |
apiSmtpUser |
admin.js:1304 |
smtpPass |
apiSmtpPass |
admin.js:1305 |
discordWebhook |
apiDiscordWebhook |
admin.js:1308 |
githubToken |
apiGithubToken |
admin.js:1312 |
customApi1Name |
apiCustom1Name |
admin.js:1315 |
customApi1Key |
apiCustom1Key |
admin.js:1316 |
customApi1Url |
apiCustom1URL |
admin.js:1317 |
customApi2Name |
apiCustom2Name |
admin.js:1320 |
customApi2Key |
apiCustom2Key |
admin.js:1321 |
customApi2Url |
apiCustom2URL |
admin.js:1322 |
customApi3Name |
apiCustom3Name |
admin.js:1325 |
customApi3Key |
apiCustom3Key |
admin.js:1326 |
customApi3Url |
apiCustom3URL |
admin.js:1327 |
Fix: In admin.js, rename all to match HTML IDs (add api prefix, use URL not Url). OR rename all HTML IDs to match JS convention.
1.1.4 Theme Section (7 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
themeAccent |
themeAccentColor |
admin.js:1351+ |
themeAccentHex |
themeAccentColorHex |
admin.js:1351+ |
themeBg |
themeBgColor |
admin.js:1351+ |
themeBgHex |
themeBgColorHex |
admin.js:1351+ |
themeText |
themeTextColor |
admin.js:1351+ |
themeTextHex |
themeTextColorHex |
admin.js:1351+ |
themeGrid |
themeGridBg |
admin.js:1351+ |
valFontSize |
themeFontSizeVal |
admin.js:1351+ |
Fix: In admin.js, add Color suffix to colour IDs, themeGrid → themeGridBg, valFontSize → themeFontSizeVal.
1.1.5 Services Section (2 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
managedServicesList |
servicesList |
admin.js:1009 |
serviceUrl |
serviceURL |
admin.js:1034 |
Fix: managedServicesList → servicesList, serviceUrl → serviceURL.
1.1.6 Navigation Section (2 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
navItemsList |
navList |
admin.js:1089 |
navUrl |
navURL |
admin.js:1118 |
Fix: navItemsList → navList, navUrl → navURL.
1.1.7 Links Section (2 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
managedLinksList |
linksList |
admin.js:1175 |
linkUrl |
linkURL |
admin.js:1201 |
Fix: managedLinksList → linksList, linkUrl → linkURL.
1.1.8 Other Mismatches (4)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
loginForm |
(no form element) | admin.js:29 |
notifications |
notification |
admin.js:259 |
trackUrl |
trackURL |
admin.js:826+ |
| (none) | editorWordCount (JS-created #bpmValue in post.js) |
— |
Fix: Either add <form id="loginForm"> wrapper in HTML or change JS to use loginBtn click handler only. notifications → notification, trackUrl → trackURL.
1.2 ❌ CRITICAL: 5 Undefined onclick Methods
Impact: Clicking Save buttons in 5 admin sections throws AdminApp.X is not a function error.
| Method Called (admin.html) | Correct Method in admin.js | HTML Line |
|---|---|---|
AdminApp.saveServices() |
(does not exist) | admin.html:608 |
AdminApp.saveNavigation() |
(does not exist) | admin.html:643 |
AdminApp.saveLinks() |
(does not exist) | admin.html:684 |
AdminApp.saveSEO() |
(does not exist) | admin.html:961 |
AdminApp.saveContact() |
AdminApp.saveContactSettings() |
admin.html:996 |
Fix for saveContact: Change admin.html:996 from AdminApp.saveContact() to AdminApp.saveContactSettings().
Fix for saveServices/saveNavigation/saveLinks/saveSEO: These methods need to be implemented in admin.js. Currently, the admin.js CRUD for services/nav/links works per-item (add/delete) but has no bulk "Save" method. Either:
- (a) Remove the Save buttons from HTML (since add/delete already persist via API), OR
- (b) Add PUT methods in admin.js that POST the full array
For saveSEO, admin.js has saveSeo() (lowercase 'eo'). Change admin.html:961 to AdminApp.saveSeo().
1.3 ❌ CRITICAL: API Payload Shape Mismatches (Frontend ↔ Backend)
Impact: POST requests from admin panel silently fail or corrupt data.
1.3.1 Services/Navigation/Links — Array vs Single Object
- admin.js
addService()(line 1047): SendsJSON.stringify(this.servicesData)— the full array - app.py
add_managed_service()(line 342): Doesd = request.get_json(); svcs.append({name: d.get('name')})— expects a single object - Result: Flask receives an array, calls
.get('name')on it → returnsNone, appends{name: '', url: ''}empty entry
Same pattern for:
addNavItem()(admin.js:1129) vsadd_navigation()(app.py:374)addLink()(admin.js:1213) vsadd_link()(app.py:409)
Fix: Change admin.js to send only the new item:
// In addService():
body: JSON.stringify({ name, url }) // NOT this.servicesData
1.3.2 API Keys — Flat vs Nested Payload
- admin.js
saveApiKey()(line 1334): Sends{ group: 'weather', weather_api_key: 'xxx' }— flat object with group field - app.py
save_apikeys()(line 474-476): Expects{ group: 'weather', data: { api_key: 'xxx' } }— nested withdatadict - Result:
data = d.get('data', {})returns empty dict → nothing saved
Fix: Change admin.js saveApiKey() to wrap fields in a data object:
const payload = { group };
const data = {};
switch (group) {
case 'weather': data.api_key = this.getVal('weatherApiKey'); break;
// ...
}
payload.data = data;
1.4 ❌ CRITICAL: /api/services Endpoint Hangs (Timeout)
File: api/app.py:181-200
Impact: Dashboard never finishes loading. Nginx logs show upstream timed out (110: Connection timed out). The admin dashboard calls this at load time via loadDashboard().
Root Cause: The services() function makes synchronous HTTP requests to 7 external URLs (git.jaeswift.xyz, plex.jaeswift.xyz, etc.) from inside the Flask app. The VPS cannot resolve its own subdomains from localhost — curl -sk https://git.jaeswift.xyz returns code 000 (DNS/connection failure) with 3+ second timeout per service. Total: 7 × 5s timeout = 35s, exceeding nginx's default 60s proxy timeout.
Fix:
- Add
/etc/hostsentries on VPS:127.0.0.1 git.jaeswift.xyz plex.jaeswift.xyz archive.jaeswift.xyz - OR reduce timeout in app.py:
req.get(s['url'], timeout=2, ...) - OR use
concurrent.futures.ThreadPoolExecutorto check all services in parallel - OR move service checks to a background task/cache
1.5 ❌ CRITICAL: /api/git-activity Endpoint Hangs
File: api/app.py:260-282
Impact: Same DNS issue as /api/services. Requests to https://git.jaeswift.xyz/api/v1/users/jae/heatmap fail from VPS localhost.
Fix: Same as 1.4 — add hosts entries or reduce timeouts.
1.6 ❌ CRITICAL: 51 CSS Classes Missing from admin.css
Impact: Major layout breakage in admin panel — sidebar, login screen, backups, settings, notifications, and many components render unstyled.
Missing classes referenced in admin.html but not defined in admin.css OR style.css:
| Class | Used In | Category |
|---|---|---|
.sidebar |
Layout wrapper | Layout |
.sidebar-brand |
Brand logo area | Layout |
.sidebar-close |
Mobile close btn | Layout |
.sidebar-divider |
Section dividers | Layout |
.sidebar-header |
Header area | Layout |
.sidebar-nav |
Nav container | Layout |
.sidebar-section-label |
Group labels | Layout |
.sidebar-version |
Version footer | Layout |
.topbar |
Top bar | Layout |
.topbar-title |
Title text | Layout |
.topbar-logout |
Logout btn | Layout |
.login-screen |
Login wrapper | Auth |
.login-form |
Form container | Auth |
.login-logo |
Logo area | Auth |
.notification |
Toast container | UI |
.operator-hud |
Editor HUD | Editor |
.hud-grid |
HUD metrics grid | Editor |
.hud-range |
Range sliders | Editor |
.hud-val |
Value labels | Editor |
.editor-split |
Split view | Editor |
.editor-pane |
Editor pane | Editor |
.preview-pane |
Preview pane | Editor |
.editor-meta-bar |
Meta info bar | Editor |
.editor-textarea-sm |
Small textareas | Editor |
.section-toolbar |
Toolbar | Editor |
.toolbar-btn |
Toolbar buttons | Editor |
.toolbar-sep |
Toolbar separator | Editor |
.admin-table |
Posts table | Posts |
.table-wrap |
Table wrapper | Posts |
.tracks-add-form |
Add track form | Tracks |
.tracks-list |
Tracks container | Tracks |
.settings-grid |
Settings layout | Settings |
.setting-item |
Setting row | Settings |
.toggle-slider |
Toggle control | Settings |
.homepage-section-name |
Section label | Homepage |
.homepage-section-controls |
Controls | Homepage |
.services-manage-list |
Services list | Services |
.nav-manage-list |
Nav items list | Navigation |
.links-manage-list |
Links list | Links |
.apikey-group |
Key group card | API Keys |
.color-input |
Colour picker | Theme |
.color-hex |
Hex input | Theme |
.sm |
Small variant | Utility |
.lg |
Large variant | Utility |
.backup-card-icon |
Card icon | Backups |
.backup-card-title |
Card title | Backups |
.backup-card-desc |
Card description | Backups |
.backup-desc |
Description text | Backups |
.backup-export |
Export section | Backups |
.backup-status |
Status area | Backups |
.threat-feed |
Threats container | Dashboard |
Fix: These classes must be defined in admin.css. Many were apparently planned (the HTML uses them consistently) but never written.
1.7 ❌ CRITICAL: Stale Root-Level JS Files on VPS
Impact: Potential confusion; the root-level /var/www/jaeswift-homepage/admin.js (66,567 bytes) is an OLDER version than /var/www/jaeswift-homepage/js/admin.js (66,766 bytes). The diff shows the root copy is missing the showSection() normalization fix.
Diff:
# Root admin.js is MISSING these 3 lines present in js/admin.js:
+ // Normalize: ensure name has section- prefix
+ if (!name.startsWith('section-')) name = 'section-' + name;
# Root admin.js has outdated sidebar active-link selector:
- const activeLink = document.querySelector(`[onclick*="'${name}'"]`);
# js/admin.js has fixed version:
+ const shortName = name.replace('section-', '');
+ const activeLink = document.querySelector(`.sidebar-link[data-section="${shortName}"]`);
Affected root files (permissions also wrong — 600 vs 644):
| File | Root Size | js/ Size | Match? |
|---|---|---|---|
| admin.js | 66,567 (perms: 600) | 66,766 (perms: 644) | ❌ DIFFERENT |
| blog.js | 7,325 (perms: 600) | 7,325 (perms: 644) | ✅ Same |
| main.js | 36,092 (perms: 600) | 36,092 (perms: 644) | ✅ Same |
| post.js | 13,473 (perms: 600) | 13,473 (perms: 644) | ✅ Same |
Fix: Delete the stale root-level JS files:
rm /var/www/jaeswift-homepage/admin.js /var/www/jaeswift-homepage/blog.js \
/var/www/jaeswift-homepage/main.js /var/www/jaeswift-homepage/post.js
1.8 ❌ CRITICAL: showSection() Broken in Root admin.js
File: Root /var/www/jaeswift-homepage/admin.js (stale copy)
Impact: If somehow the root copy were served (e.g., nginx try_files fallback), the sidebar section switching would not work because the old code lacks the section- prefix normalization.
Note: Currently nginx serves from /js/admin.js (the correct file), so this is only a risk if the stale file is served by accident. Still, it should be cleaned up.
Fix: Delete root-level JS files (see 1.7).
2. HIGH ISSUES
2.1 ⚠️ HIGH: blog.html and post.html — 4 CSS Classes Undefined
Impact: Navbar, clock, and signal animation don't render correctly on blog pages.
| Class | File | Not in |
|---|---|---|
.navbar |
blog.html, post.html | blog.css, style.css |
.nav-clock |
blog.html, post.html | blog.css, style.css |
.signal-flicker |
blog.html, post.html | blog.css, style.css |
.blog-header-content |
blog.html only | blog.css, style.css |
Fix: Define these classes in blog.css or style.css. The index.html uses different class names for the navbar (likely defined in style.css under different names).
2.2 ⚠️ HIGH: No CORS Headers on API
Impact: If any external/subdomain frontend tries to call the API, requests will be blocked.
Test result: curl -sI /api/posts | grep access-control returned nothing.
Fix: Add Flask-CORS or manual headers:
from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": "https://jaeswift.xyz"}})
2.3 ⚠️ HIGH: Auth Endpoints Return HTML Error Pages
File: api/app.py:41-52 (require_auth decorator)
Impact: Admin.js expects JSON responses but gets HTML <title>401 Unauthorized</title>.
Evidence:
curl http://127.0.0.1:5000/api/apikeys →
<!doctype html><title>401 Unauthorized</title><h1>Unauthorized</h1><p>Missing token</p>
Fix: The require_auth decorator should use abort() with JSON, or Flask should be configured for JSON error responses:
@app.errorhandler(401)
def unauthorized(e):
return jsonify({'error': 'Unauthorized', 'message': str(e)}), 401
2.4 ⚠️ HIGH: /api/login Returns 404 (Route is /api/auth/login)
Impact: If anything calls /api/login (the intuitive URL), it 404s.
Analysis: Flask route is @app.route('/api/auth/login') (line 62). admin.js correctly uses this.API + '/auth/login' (line 132). No functional issue currently, but the non-standard auth path could confuse future integrations.
Status: Working correctly but unconventional. Consider adding an alias route.
2.5 ⚠️ HIGH: admin.js Sends Full Array on Add Operations
File: js/admin.js:1044-1048 (addService), 1129 (addNavItem), 1213 (addLink)
Impact: Even though the POST creates a garbage entry (see 1.3.1), the admin.js then calls loadX() which re-fetches from the API, showing the corrupted data.
Root Cause: The JS pattern is:
- Push new item to local array:
this.servicesData.push({name, url}) - Send ENTIRE array to POST endpoint:
body: JSON.stringify(this.servicesData) - Flask
add_managed_service()treats the array as a single object, calling.get('name')on a list → empty entry appended
Fix: Send only the new item:
body: JSON.stringify({ name, url })
2.6 ⚠️ HIGH: admin.js saveApiKey() Sends Wrong Payload Shape
File: js/admin.js:1289-1334
Impact: API keys are never saved. The Flask endpoint returns 400 {"error": "Invalid request: need group and data"}.
Current JS payload: { group: 'weather', weather_api_key: 'xxx' }
Expected by Flask: { group: 'weather', data: { api_key: 'xxx' } }
Fix: Restructure the saveApiKey() method to wrap fields in data:
async saveApiKey(group) {
const data = {};
switch (group) {
case 'weather':
data.api_key = this.getVal('apiWeatherKey'); // also fix ID
break;
// etc.
}
const res = await fetch(this.API + '/apikeys', {
method: 'POST',
headers: this.authHeaders(),
body: JSON.stringify({ group, data })
});
}
2.7 ⚠️ HIGH: dashThreats Container Missing from admin.html
File: admin.html (dashboard section, ~line 126-167)
Impact: admin.js:370 tries document.getElementById('dashThreats') — returns null, threats never display on dashboard.
Fix: Add to dashboard section in admin.html:
<h3 class="section-subheading">▲ THREAT FEED</h3>
<div id="dashThreats" class="threat-feed"></div>
2.8 ⚠️ HIGH: /api/services Causes Dashboard Load Cascade Failure
File: js/admin.js:308-312
Impact: loadDashboard() calls Promise.all([stats, posts, tracks, services, threats]). When /api/services hangs for 35+ seconds, the entire dashboard appears frozen even though other data loaded fine.
Fix: Use Promise.allSettled() instead of Promise.all(), and handle each result independently:
const results = await Promise.allSettled([
fetch(this.API + '/stats', ...),
fetch(this.API + '/posts', ...),
// ...
]);
results.forEach((result, i) => {
if (result.status === 'fulfilled') { /* process */ }
else { console.warn('Dashboard fetch failed:', result.reason); }
});
2.9 ⚠️ HIGH: nginx Warning — Duplicate MIME Type
File: /etc/nginx/sites-enabled/cat.jaeswift.xyz:19
Impact: Minor — duplicate text/html MIME type definition. No functional impact but generates warnings on every nginx -t and reload.
Fix: Remove duplicate text/html from the types block in cat.jaeswift.xyz config.
2.10 ⚠️ HIGH: nginx Warning — Protocol Options Redefined
File: /etc/nginx/sites-enabled/jaeswift.xyz:26
Impact: SSL protocol options redefined for [::]:443. First server block to define wins; subsequent definitions are ignored.
Fix: Move SSL protocol directives to http {} block in /etc/nginx/nginx.conf or use ssl_protocols only once across all server blocks sharing the same listen address.
2.11 ⚠️ HIGH: Flask Serves with Debug/Development Server
File: VPS systemd service runs python3 /var/www/jaeswift-homepage/api/app.py
Impact: Flask development server is single-threaded, not suitable for production. A single slow /api/services request blocks ALL other API requests.
Fix: Use gunicorn or waitress:
# In systemd service:
ExecStart=/usr/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app
2.12 ⚠️ HIGH: Flask Makes Unverified HTTPS Requests
File: api/app.py:195,263,267 — verify=False
Impact: Suppressed SSL certificate verification for requests to git.jaeswift.xyz and plex.jaeswift.xyz. Generates InsecureRequestWarning in logs.
Fix: Either install proper CA certs or use verify='/path/to/cert.pem'.
3. MEDIUM ISSUES
3.1 🔶 MEDIUM: post.js Creates #bpmValue Dynamically
File: js/post.js:153-154,171
Impact: No current bug — post.js creates the element via innerHTML (line 153) then references it (line 171). Works because innerHTML is synchronous. However, static analysis flags this as a missing ID because post.html contains no such element.
Note: Not a bug, but the pattern is fragile. If the innerHTML template changes, the getElementById will silently break.
3.2 🔶 MEDIUM: admin.html Has No <form> Tag for Login
File: admin.html:16-31
Impact: admin.js:29 looks for loginForm by ID and finds null. The fallback (line 33-35) uses loginBtn click handler, which works. But Enter key on username field doesn't submit — only works on password field (line 38-41).
Fix: Wrap login fields in <form id="loginForm"> with onsubmit="return false;".
3.3 🔶 MEDIUM: admin.js Notification Container Mismatch
File: js/admin.js:259, admin.html:1045
Impact: JS looks for #notifications (plural), HTML has #notification (singular). Fallback || document.body means notifications append to body — they'll appear but may not be styled correctly.
Fix: Change JS to use notification (matching HTML) or rename HTML ID.
3.4 🔶 MEDIUM: blog.html Nav Links Point to Index Anchors
File: blog.html navbar
Impact: Links like /#development, /#links, /#contact navigate away from blog page back to index. This is intentional for cross-page navigation but there's no way back to blog from those sections.
Fix: Consider adding a "BLOG" link in index.html navbar (currently only an anchor #blog section exists).
3.5 🔶 MEDIUM: index.html Has href="#" Links
File: index.html — various social/placeholder links
Impact: Clicking scrolls to page top, poor UX.
Fix: Replace href="#" with actual URLs or javascript:void(0).
3.6 🔶 MEDIUM: No favicon Referenced
File: All HTML files
Impact: Browser shows default icon, 404 in network tab for /favicon.ico.
Fix: Add <link rel="icon" href="/assets/favicon.ico"> to all pages.
3.7 🔶 MEDIUM: admin.css is 41KB / admin.js is 66KB
Impact: Large unminified files on every admin page load.
Fix: Consider minification for production deployment.
3.8 🔶 MEDIUM: No Cache-Busting for Static Assets
File: All HTML files reference /js/main.js, /css/style.css etc. with no query string version.
Impact: Browser caching may serve stale JS/CSS after deployments.
Fix: Add version query strings: /js/main.js?v=20260401.
3.9 🔶 MEDIUM: app.py /api/stats Calls shell() for System Stats
File: api/app.py:134 (via shell() function at line 54)
Impact: Executes shell commands (uptime, df, free, etc.) on every stats request. Not a security issue per se (no user input), but slow and could be cached.
Fix: Cache stats with a TTL (e.g., 30 seconds).
3.10 🔶 MEDIUM: No Rate Limiting on Login Endpoint
File: api/app.py:62
Impact: Brute force attacks possible against /api/auth/login.
Fix: Add Flask-Limiter: @limiter.limit("5/minute").
3.11 🔶 MEDIUM: JWT Secret Hardcoded
File: api/app.py (JWT_SECRET referenced)
Impact: Secret is in source code committed to git. Anyone with repo access can forge tokens.
Fix: Move to environment variable: JWT_SECRET = os.environ.get('JWT_SECRET', 'fallback').
3.12 🔶 MEDIUM: No Error Boundary in admin.js
Impact: Any unhandled JS error in one section breaks the entire admin panel. No global error handler.
Fix: Add:
window.addEventListener('unhandledrejection', (e) => {
console.error('Unhandled promise:', e.reason);
AdminApp.notify('An error occurred: ' + e.reason?.message, 'error');
});
3.13 🔶 MEDIUM: admin.html Editor Toolbar References HUD IDs
File: admin.html:275-345 (Operator HUD section)
Impact: The range slider event listeners in admin.js:85 reference editEnergy/editMotivation/editFocus/editDifficulty but HTML has hudEnergy/hudMotivation/hudFocus/hudDifficulty. The slider value display labels won't update live.
Fix: Part of the broader ID mismatch (1.1.1).
3.14 🔶 MEDIUM: blog.html and post.html Missing <meta charset>
Impact: Character encoding not explicitly set. Unicode characters (used extensively in cyberpunk UI) may render incorrectly in edge cases.
Fix: Add <meta charset="UTF-8"> to <head> of both files.
3.15 🔶 MEDIUM: Google Fonts Loaded Without font-display: swap
File: All HTML files
Impact: Text invisible during font load (FOIT — Flash of Invisible Text).
Fix: Append &display=swap to Google Fonts URL (already present in some files, verify all).
4. LOW ISSUES
4.1 ℹ️ LOW: Root-Level JS Files Have Wrong Permissions (600)
File: /var/www/jaeswift-homepage/*.js
Impact: Files are owner-read-only. nginx runs as www-data and cannot read them. Not currently an issue because HTML references /js/*.js (the correct copies with 644), but if try_files falls through, these would be served as 403.
Fix: chmod 644 /var/www/jaeswift-homepage/*.js or delete them (preferred — see 1.7).
4.2 ℹ️ LOW: nginx Ghost Blog Catch-All Comment but No Block
File: /etc/nginx/sites-enabled/jaeswift.xyz — line with # Ghost Blog (catch-all) comment
Impact: Vestigial comment from previous Ghost setup. No functional issue.
Fix: Remove the comment for cleanliness.
4.3 ℹ️ LOW: admin.html Uses javascript:void(0) hrefs
File: admin.html sidebar links
Impact: Minor accessibility concern — screen readers announce these as links.
Fix: Use <button> elements instead of <a> tags for non-navigation actions.
4.4 ℹ️ LOW: No <meta viewport> in admin.html
Impact: Admin panel may not render correctly on mobile without viewport meta tag.
Fix: Add <meta name="viewport" content="width=device-width, initial-scale=1.0"> (check if already present).
4.5 ℹ️ LOW: InsecureRequestWarning Spam in Flask Logs
File: Flask service journal
Impact: Log noise from verify=False in requests to git/plex subdomains.
Fix: Add import urllib3; urllib3.disable_warnings() or fix the SSL verification.
4.6 ℹ️ LOW: admin.css Missing from Local Repo
File: /a0/usr/workdir/jaeswift-website/css/admin.css
Impact: The local copy exists but VPS copy has different content (VPS: 41,541 bytes). Ensure git is the source of truth.
Fix: Sync local and VPS copies via git.
4.7 ℹ️ LOW: posts.json Contains 9 Posts but No Pagination
Impact: As posts grow, /api/posts returns all posts in a single response. Currently fine with 9 posts.
Fix: Add optional ?page=1&limit=10 query parameters to the posts endpoint.
5. WHAT'S WORKING CORRECTLY ✅
| Component | Status |
|---|---|
| nginx → static HTML serving | ✅ All pages return 200 |
| All 9 blog post slug URLs | ✅ /blog/post/<slug> resolves correctly |
| Flask API on port 5000 | ✅ Active and running |
/api/posts GET |
✅ Returns 9 posts with valid JSON |
/api/stats GET |
✅ Returns server stats |
/api/weather GET |
✅ Returns weather data |
/api/nowplaying GET |
✅ Returns now playing info |
/api/tracks GET |
✅ Returns 35 tracks |
/api/threats GET |
✅ Returns CVE data |
/api/settings GET |
✅ Returns settings |
/api/homepage GET |
✅ Returns homepage config |
/api/navigation GET |
✅ Returns 3 nav items |
/api/links GET |
✅ Returns 2 links |
/api/theme GET |
✅ Returns theme config |
/api/seo GET |
✅ Returns SEO config |
/api/services/managed GET |
✅ Returns 3 managed services |
/api/auth/login POST |
✅ Returns JWT token |
| Auth protection on endpoints | ✅ Returns 401 without token |
| All 12 JSON data files | ✅ Valid JSON, correct types |
| CSS files (style/blog/post) | ✅ All load with 200 |
| JS files (main/blog/post) | ✅ All load with 200 |
| main.js ↔ index.html IDs | ✅ All IDs match |
| blog.js ↔ blog.html IDs | ✅ All IDs match |
| SSL certificates | ✅ Valid Let's Encrypt |
| nginx config syntax | ✅ Test passes (with warnings) |
| Blog clean URLs | ✅ /blog/post/.+ → post.html |
| try_files fallback | ✅ $uri $uri.html $uri/ |
6. PRIORITY FIX ORDER
- Fix 60 DOM ID mismatches in admin.js (or admin.html) — this alone fixes ~80% of admin issues
- Add 51 missing CSS classes to admin.css — makes admin panel visually functional
- Fix 5 undefined onclick methods — either implement or rename to match existing
- Fix API payload mismatches — services/nav/links send single object, apikeys wrap in data
- Fix /api/services timeout — add /etc/hosts entries or parallelize with timeout
- Delete stale root-level JS files on VPS
- Add CORS headers to Flask API
- Return JSON error responses from Flask (not HTML)
- Switch to gunicorn for production
- Add missing CSS classes for blog.html/post.html navbar
End of Audit Report