- 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
658 lines
30 KiB
Markdown
658 lines
30 KiB
Markdown
# 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): Sends `JSON.stringify(this.servicesData)` — the **full array**
|
||
- **app.py `add_managed_service()`** (line 342): Does `d = request.get_json(); svcs.append({name: d.get('name')})` — expects a **single object**
|
||
- Result: Flask receives an array, calls `.get('name')` on it → returns `None`, appends `{name: '', url: ''}` empty entry
|
||
|
||
Same pattern for:
|
||
- `addNavItem()` (admin.js:1129) vs `add_navigation()` (app.py:374)
|
||
- `addLink()` (admin.js:1213) vs `add_link()` (app.py:409)
|
||
|
||
**Fix:** Change admin.js to send only the new item:
|
||
```javascript
|
||
// 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 with `data` dict
|
||
- Result: `data = d.get('data', {})` returns empty dict → nothing saved
|
||
|
||
**Fix:** Change admin.js `saveApiKey()` to wrap fields in a `data` object:
|
||
```javascript
|
||
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:**
|
||
1. Add `/etc/hosts` entries on VPS: `127.0.0.1 git.jaeswift.xyz plex.jaeswift.xyz archive.jaeswift.xyz`
|
||
2. OR reduce timeout in app.py: `req.get(s['url'], timeout=2, ...)`
|
||
3. OR use `concurrent.futures.ThreadPoolExecutor` to check all services in parallel
|
||
4. 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:**
|
||
```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:
|
||
```bash
|
||
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:
|
||
```python
|
||
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:
|
||
```python
|
||
@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:
|
||
1. Push new item to local array: `this.servicesData.push({name, url})`
|
||
2. Send ENTIRE array to POST endpoint: `body: JSON.stringify(this.servicesData)`
|
||
3. 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:
|
||
```javascript
|
||
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`:
|
||
```javascript
|
||
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:
|
||
```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:
|
||
```javascript
|
||
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:
|
||
```bash
|
||
# 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:
|
||
```javascript
|
||
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
|
||
|
||
1. **Fix 60 DOM ID mismatches in admin.js** (or admin.html) — this alone fixes ~80% of admin issues
|
||
2. **Add 51 missing CSS classes to admin.css** — makes admin panel visually functional
|
||
3. **Fix 5 undefined onclick methods** — either implement or rename to match existing
|
||
4. **Fix API payload mismatches** — services/nav/links send single object, apikeys wrap in data
|
||
5. **Fix /api/services timeout** — add /etc/hosts entries or parallelize with timeout
|
||
6. **Delete stale root-level JS files on VPS**
|
||
7. **Add CORS headers to Flask API**
|
||
8. **Return JSON error responses from Flask (not HTML)**
|
||
9. **Switch to gunicorn for production**
|
||
10. **Add missing CSS classes for blog.html/post.html navbar**
|
||
|
||
---
|
||
|
||
*End of Audit Report*
|