Initial skills documentation — 25 categories, all SKILL.md + references + scripts
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# Apex Vehicle Registration — Final Embeddable Snippet
|
||||
|
||||
**File:** `/root/.hermes/references/apex-vreg-final.html`
|
||||
**Format:** Self-contained HTML with inline vehicle data JSON + CSS + JS
|
||||
**Size:** ~16KB
|
||||
**Target:** Elementor HTML widget on `http://apextrackexperience.com/nasa`
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open the NASA page in Elementor
|
||||
2. Add an **HTML** widget
|
||||
3. Open `apex-vreg-final.html` in a text editor
|
||||
4. Copy the entire contents
|
||||
5. Paste into the Elementor HTML widget
|
||||
6. Save
|
||||
|
||||
## Features
|
||||
|
||||
- Cascading make → model → year dropdowns (50+ makes, 200+ models)
|
||||
- Factory horsepower ratings inline for every make/model/year
|
||||
- Color selector (16 presets + "Other" with free-text input)
|
||||
- Specs panel locked until color is selected
|
||||
- Light theme (white bg, blue accent #2563eb) matching the Apex site
|
||||
- **Email field omitted** — part of overall driver registration
|
||||
- No external dependencies (vehicle data is embedded as JSON)
|
||||
- Button has 18px margin-top (equidistant with field gaps)
|
||||
|
||||
## Vehicle Database
|
||||
|
||||
The snippet includes ~200 exotic/sports car entries with factory HP. The data is embedded in the `<script>` tag as `vrData`. To add/update:
|
||||
|
||||
```javascript
|
||||
const vrData = {
|
||||
"Porsche": {
|
||||
"911 GT3 RS": {"2023": 518, "2024": 518}
|
||||
},
|
||||
"Ferrari": {
|
||||
"296 GTB": {"2023": 819, "2024": 819}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
A daily cron job (`exotic-vehicle-scout`) checks for new 2025-2026 exotic vehicle announcements and reports them here.
|
||||
|
||||
## Maintenance
|
||||
|
||||
- **Data updates:** Edit the JSON object in the `<script>` tag
|
||||
- **Theme changes:** CSS is namespaced under `#apex-vreg`
|
||||
- **To rebuild from scratch:** See `portal-ui-mockups` skill for the vehicle registration pattern
|
||||
- **Live preview available at:** `https://vaultwarden.tailc2f3b0.ts.net/portal/apex-vreg-final.html`
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Apex Vehicle Registration — Embeddable HTML Snippet
|
||||
|
||||
A self-contained HTML/JS snippet for vehicle registration forms on the apextrackexperience.com website. Cascading make/model/year dropdowns with factory horsepower ratings, color field, and a specs panel that only unlocks after color is selected.
|
||||
|
||||
## Files
|
||||
- **Vehicle database:** `/root/portal-mockup/vehicles.json` — 50+ makes, 200+ models with HP ratings
|
||||
- **Full snippet:** `/root/.hermes/references/apex-vreg-final.html` — ~16KB, self-contained, no external dependencies
|
||||
- **Hosted preview:** `https://vaultwarden.tailc2f3b0.ts.net/portal/apex-vreg-final.html`
|
||||
|
||||
## Installation
|
||||
Paste the entire contents of `apex-vreg-final.html` into an Elementor HTML widget on the NASA page. No additional files needed — the vehicle database is inlined as JSON in the `<script>` tag.
|
||||
|
||||
## Light theme details
|
||||
The snippet uses a light/white theme:
|
||||
- Background: `#ffffff`, Cards: `#f8f9fa`, borders: `#e2e8f0`
|
||||
- Accent: `#2563eb` (blue) — matches NASA branding
|
||||
- Font: Inter, email-safe stack
|
||||
|
||||
## Vehicle database maintenance
|
||||
The JSON file is static — no API calls, no monthly fees. The user edits the file to add vehicles.
|
||||
|
||||
### Adding vehicles
|
||||
```json
|
||||
{
|
||||
"Ferrari": {
|
||||
"F80": {"2026": 1184}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exotic vehicle update cron
|
||||
A cron job (`exotic-vehicle-scout`) runs daily at 9 AM searching for newly announced exotic/performance vehicles. Silent if nothing new. Focus on Ferrari, Lamborghini, McLaren, Porsche, Bugatti, Koenigsegg, Pagani, Aston Martin, Rimac, Lotus, Maserati + high-end BMW M, Mercedes-AMG, Audi RS.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Cloudflare Integration Reference
|
||||
|
||||
## API Token Permissions
|
||||
|
||||
| Feature | Permission Required | Endpoint |
|
||||
|---|---|---|
|
||||
| DNS read/write | Zone → DNS → Edit | `GET/POST /zones/{id}/dns_records` |
|
||||
| List zones | Zone → DNS → Read | `GET /zones` |
|
||||
| View domains + expiry | Account → Registrar → Read | `GET /accounts/{id}/registrar/domains` |
|
||||
| Domain renewal | Account → Registrar → Admin | `POST /accounts/{id}/registrar/domains/{name}/renew` |
|
||||
| Domain registration | Account → Registrar → Admin | `POST /accounts/{id}/registrar/domains/{name}/register` |
|
||||
| Domain availability check | Account → Registrar → Admin | `GET /accounts/{id}/registrar/domains/{name}/check` |
|
||||
|
||||
## Domain Pricing (Cloudflare at cost, verified Jul 6, 2026)
|
||||
|
||||
- .com registration: $10.46/yr
|
||||
- .com renewal: $10.46/yr
|
||||
- .com transfer: $10.46
|
||||
- No premium markup on standard TLDs
|
||||
|
||||
## Availability Check Response Fields
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "example.com",
|
||||
"supported_tld": true,
|
||||
"premium": false,
|
||||
"available": true,
|
||||
"can_register": true,
|
||||
"fees": {
|
||||
"registration_fee": 10.46,
|
||||
"renewal_fee": 10.46,
|
||||
"transfer_fee": 10.46,
|
||||
"redemption_fee": 44.0,
|
||||
"currency": "USD"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `GET /accounts/{id}/registrar/domains/{name}/check` to check availability (NOT POST, NOT query params). The endpoint returns fees, premium status, and availability in a single call.
|
||||
|
||||
## Domain List Response Fields
|
||||
|
||||
Each domain has:
|
||||
- `name` - domain name
|
||||
- `expires_at` - ISO 8601 timestamp (use `datetime.fromisoformat`)
|
||||
- `auto_renew` - boolean
|
||||
- `cloudflare_registration` - boolean (true = registered through Cloudflare)
|
||||
- `permissions` - list of allowed actions for this token
|
||||
- `registry_statuses` - includes `clienttransferprohibited`
|
||||
@@ -0,0 +1,82 @@
|
||||
# Config Page — Session Reference
|
||||
|
||||
Created: 2026-07-08
|
||||
File: `/var/www/ops/config.html`
|
||||
Domain: `ops.itpropartner.com`
|
||||
|
||||
## Page structure (4 cards)
|
||||
|
||||
### 1. Versions
|
||||
Grid of version cards showing software versions. Populated from live API (`data.versions`) with embedded fallbacks:
|
||||
|
||||
| Component | Embedded | API-override |
|
||||
|-----------|----------|-------------|
|
||||
| Hermes | v0.18.0 | data.versions.hermes |
|
||||
| Caddy | v2.11.4 | data.versions.caddy |
|
||||
| Python | 3.13.5 | data.versions.python |
|
||||
| Docker | 26.1.5 | data.versions.docker |
|
||||
| OS | Debian 13.5 | data.versions.os |
|
||||
|
||||
### 2. Active Config Files
|
||||
Table of key config files that are part of the hermetic backup:
|
||||
|
||||
| Path | Size | Modified | Backed Up |
|
||||
|------|------|----------|-----------|
|
||||
| /etc/caddy/Caddyfile | 2.8 KB | 2026-07-08 | ✓ |
|
||||
| /root/.hermes/config.yaml | 3.3 KB | 2026-07-08 | ✓ |
|
||||
| /etc/systemd/system/hermes-assistant.service | 295 B | 2026-07-08 | ✓ |
|
||||
| /etc/systemd/system/hermes-browser.service | 495 B | 2026-07-07 | ✓ |
|
||||
| /root/.hermes/scripts/ | 592 KB (51 files) | — | ✓ |
|
||||
|
||||
All backed up via `hermes-backup.sh` → Wasabi S3 (`hermes-vps-backups`).
|
||||
|
||||
### 3. Environment
|
||||
Grid of system attributes (hostname, OS, kernel, uptime, CPU, memory, disk, Docker).
|
||||
|
||||
### 4. Infrastructure Diagram
|
||||
Link card pointing to `/dependency-diagram.html` (teal-accented) — placeholder for future dependency diagram.
|
||||
|
||||
## Static data + API fallback architecture
|
||||
|
||||
The config page is the first ops portal page to use **embedded static data** rather than relying solely on the API. Rationale:
|
||||
|
||||
- File system stats (size, modified time, backup status) are not provided by the ops-data-collector
|
||||
- These change infrequently — rebuilding the HTML is acceptable
|
||||
- The page works fully when the API is offline (embedded data is always shown)
|
||||
|
||||
**Render sequence:**
|
||||
1. Embedded `CONFIG_FILES` array → config files table (immediate)
|
||||
2. Embedded `ENV_DATA` object → environment grid (immediate)
|
||||
3. Embedded `VERSIONS_EMBEDDED` object → version cards (immediate)
|
||||
4. API fetch → overrides version cards with live data (async)
|
||||
5. API failure → embedded versions remain, non-fatal
|
||||
|
||||
## Embedded data categories for this page
|
||||
|
||||
### CONFIG_FILES (static, from file system stats)
|
||||
- path, size, size_bytes, modified (ISO timestamp), backed_up (bool), backup_desc
|
||||
|
||||
### ENV_DATA (static, from live system)
|
||||
- hostname, os, kernel, uptime, cpu, memory, disk, docker, hermes_path
|
||||
|
||||
### VERSIONS_EMBEDDED (fallback from live system)
|
||||
- hermes, caddy, python, os, docker
|
||||
|
||||
## Design details
|
||||
|
||||
- File paths rendered in monospace (`<span class="mono">`)
|
||||
- Backup status badges: green ✓ Yes / red ✗ No
|
||||
- Version cards centered with uppercase labels, large value text
|
||||
- Environment grid: 1-col mobile → 2-col tablet → 3-col desktop
|
||||
- Shared stylesheet (`/css/ops.css`) referenced — no inline duplication
|
||||
- Shared nav injected from `/nav.html`
|
||||
- File mode fixed to 644 after creation
|
||||
|
||||
## Rebuilding
|
||||
|
||||
To regenerate this page with updated embedded data:
|
||||
1. SSH into core
|
||||
2. Gather current file stats (size, timestamps) via `stat`
|
||||
3. Gather current system info (uptime, versions, disk, memory) via `df -h`, `free -h`, `hostname`, `cat /proc/uptime`
|
||||
4. Update the `CONFIG_FILES`, `ENV_DATA`, and `VERSIONS_EMBEDDED` objects in the HTML
|
||||
5. `chmod 644 /var/www/ops/config.html`
|
||||
@@ -0,0 +1,33 @@
|
||||
# Debt Recovery Portal Mockups
|
||||
|
||||
Built Jul 7, 2026 for debtrecoveryexperts.com.
|
||||
|
||||
## Customer-facing: debt-recovery.html
|
||||
|
||||
- Clean intake form: Debtor Information (name, company, address, phone, email), Claim Details (amount, description, state, type), Document Upload (multi-file drag-and-drop)
|
||||
- No AI analysis visible to customers
|
||||
- Submit button + terms acknowledgment
|
||||
|
||||
## Internal DRE Dashboard: dre-dashboard.html (dark theme)
|
||||
|
||||
- Claims queue table with AI scores
|
||||
- AI Analysis panel (internal only): score gauge, factor breakdown bars, case comparison panel, AI summary, risk flags, model attribution
|
||||
- Action buttons: Approve & Continue / Request More Docs / Reject
|
||||
- Progress timeline
|
||||
|
||||
## Domain plan
|
||||
|
||||
- WordPress frontend at debtrecoveryexperts.com
|
||||
- Portal at portal.debtrecoveryexperts.com (A record pending on Cloudflare)
|
||||
- DocuSeal webhook: debtrecoveryexperts.com/api/webhooks/docuseal (or portal subdomain)
|
||||
|
||||
## Seamless Signing Integration
|
||||
|
||||
DocuSeal runs on Core (port 3000). Exposed at https://sign.itpropartner.com via Caddy.
|
||||
|
||||
Signatures needed:
|
||||
- Limited Power of Attorney (needs online notarization via Proof RON API)
|
||||
- Service agreements, disbursement authorizations, internal approvals (DocuSeal only)
|
||||
- Proof handles RON; DocuSeal handles standard e-signatures
|
||||
|
||||
DocuSeal API token: saved in Hermes config/environment.
|
||||
@@ -0,0 +1,51 @@
|
||||
# DNS & Domains Management Pages
|
||||
|
||||
Built as part of the IT Pro Partner operations portal concept. Two-tab page with DNS zone management and Cloudflare domain registration.
|
||||
|
||||
## Key mockup file
|
||||
|
||||
`/root/portal-mockup/dns.html` — URL: https://vaultwarden.tailc2f3b0.ts.net/portal/dns.html
|
||||
|
||||
## Tab switching implementation
|
||||
|
||||
Important: the tab panes use `class="tab-pane"` as their selector. The JS iterates `document.querySelectorAll('.tab-pane')` by index. Each pane must have this class. Tab headers use `class="tab"` and `class="tab active"`. The click handler:
|
||||
|
||||
```
|
||||
document.querySelectorAll('.tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
const idx = Array.from(document.querySelectorAll('.tab')).indexOf(this);
|
||||
document.querySelectorAll('.tab-pane').forEach((p, i) => {
|
||||
p.style.display = i === idx ? 'block' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Cloudflare API endpoints used
|
||||
|
||||
Verify token: GET /client/v4/user/tokens/verify
|
||||
List zones: GET /client/v4/zones
|
||||
DNS records: GET /client/v4/zones/{id}/dns_records
|
||||
List accounts: GET /client/v4/accounts
|
||||
Domain list: GET /client/v4/accounts/{id}/registrar/domains
|
||||
Domain check: GET /client/v4/accounts/{id}/registrar/domains/{domain}/check
|
||||
Returns: available, can_register, supported_tld, premium, fees { transfer_fee, redemption_fee, registration_fee, renewal_fee, currency }
|
||||
|
||||
## Domain pricing (verified)
|
||||
|
||||
.com registration: $10.46/year (Cloudflare at cost, no markup)
|
||||
.com renewal: $10.46/year
|
||||
.com transfer: $10.46
|
||||
Premium domains: flagged separately
|
||||
|
||||
## Token permissions required
|
||||
|
||||
- DNS read/write: Zone -> DNS -> Edit
|
||||
- Domain list + expiry: Account -> Registrar -> Read
|
||||
- Domain purchase/transfer: Account -> Registrar -> Admin
|
||||
|
||||
## Navigation
|
||||
|
||||
When the DNS page exists, add `href="dns.html"` to the nav bar of every other mockup page.
|
||||
@@ -0,0 +1,87 @@
|
||||
# DRE Portal Mockups — Session July 7, 2026
|
||||
|
||||
## Debt Recovery Intake Form (ScrapingAnt-inspired split-screen)
|
||||
|
||||
File: `debt-recovery.html`
|
||||
|
||||
Two-panel layout inspired by scrapingant.com/signup:
|
||||
|
||||
**Left panel (desktop only):**
|
||||
- Dark gradient background: `linear-gradient(135deg, #0f172a 0%, #1e3a5f 40%, #b45309 100%)`
|
||||
- Logo + tagline: "Recover what's yours. Without the headache."
|
||||
- Feature icons with descriptions (AI analysis, tiered recovery, security)
|
||||
- Footer note: "Trusted by contractors and owner-operators across Texas"
|
||||
- Abstract SVG circle pattern overlay at 10% opacity
|
||||
|
||||
**Right panel (white, amber accent #amber-500 / #f59e0b):**
|
||||
- Form sections in order: Your Information → Debtor Information → Claim Details → Document Upload → Terms → Turnstile → Submit
|
||||
- Your Information: Full name, Business name, Email, Phone
|
||||
- Debtor Information: Business name, Contact person, Email, Phone, Address
|
||||
- Claim Details: Amount, Description, State (default Texas), Debt type dropdown
|
||||
- Document upload: empty drop zone (no placeholder files), files appear after upload
|
||||
- Cloudflare Turnstile invisible anti-bot (script from challenges.cloudflare.com, site key 0x4AAAAAADxaB0Bik1bqx_CA)
|
||||
- Terms checkboxes: agree to TOS + confirm accuracy
|
||||
- Responsive: collapses to single column on mobile, logo shifts inline
|
||||
|
||||
**Color palette:** Deep navy left gradient, white right panel, amber accent throughout.
|
||||
|
||||
## AI Chatbot — Lead Capture + Logged-In Experience
|
||||
|
||||
File: `dre-client-dashboard.html`
|
||||
|
||||
The chat widget (bottom-right amber bubble, fixed position) has three states:
|
||||
|
||||
### State 1: New Visitor (Lead Capture)
|
||||
- 4-field form: Full name (req), Company, Email (req), Phone
|
||||
- "Start Chat" button validates name + email required
|
||||
- Data stored in window._lead object (in production: sends to CRM/email)
|
||||
- Chat unlock blocked until lead submitted
|
||||
|
||||
### State 2: Logged-In Client
|
||||
- Skips lead capture entirely
|
||||
- Personalized greeting: "Welcome back, {Company}! Your claim against {Debtor} is in {Tier N}. I'm Agent D.R.E."
|
||||
- Case-specific quick buttons: My Case Status, Next Steps, Upload Docs
|
||||
- Flagged by `window._isLoggedIn = true` (set by backend on auth)
|
||||
|
||||
### State 3: Chat Conversation
|
||||
- Shows after lead capture (new visitor) or immediately (logged in)
|
||||
- FAQ quick-buttons: Fee structure, Required docs, Timeline
|
||||
- Free-text input with Send button
|
||||
- Case-specific answers for logged-in users (tier, status, timeline, upload docs)
|
||||
- PII verification: asking for claim number triggers "please verify your identity" response
|
||||
- Footer: "Responses from public DRE information. Not legal advice."
|
||||
|
||||
### Answer routing logic (mockup getAnswer function):
|
||||
1. Case-specific keywords first (tier, status, next steps, upload): returns personalized answer with claim context
|
||||
2. Claim number / specific / case details: triggers PII verification
|
||||
3. General FAQ (fee, cost, timeline, LPOA, payment): returns public knowledge answer with engaging question
|
||||
4. Greeting: returns proactive engagement question
|
||||
5. Fallback: invites to submit claim or learn more
|
||||
|
||||
All responses should: acknowledge the user's concern, answer clearly (no promises), and ask a leading question to continue the conversation.
|
||||
|
||||
## Fee Calculator (dre-fee-calculator.html)
|
||||
|
||||
- Single input field for claim amount (default $15,000)
|
||||
- Four side-by-side cards (Tier 1-4) with color-coded borders (green/blue/amber/red)
|
||||
- Each card shows: tier name, you receive (bold amount), DRE fee, timeline
|
||||
- Live calculation on input change — no submit button
|
||||
- Responsive: 4-col desktop, stacked mobile
|
||||
|
||||
## Debtor Payment Page (dre-pay.html)
|
||||
|
||||
- Minimal, no navigation, centered on payment
|
||||
- Fields: Claim/Invoice Number, Full Name, Amount ($)
|
||||
- Cloudflare Turnstile protection
|
||||
- Continue to Payment button → Stripe checkout
|
||||
- Trust signals: Secure Payment · ACH & Card Accepted · Receipt Emailed
|
||||
- Phone support link for help
|
||||
|
||||
## Case Aging Dashboard (dre-case-aging.html)
|
||||
|
||||
Dark-themed internal dashboard monitoring stalled cases:
|
||||
- Summary: active cases, avg time in tier, on-track rate
|
||||
- Table: claim, debtor, amount, current tier, age in tier, target, status
|
||||
- Color-coded: green (on track), amber/⚠ (approaching limit), red/🔴 (stalled)
|
||||
- Stalled case alert banner: "DRE-YYYY-NNNN has been in Tier N for X days — Y days past target"
|
||||
- SMS notification indicator: "SMS Notifications Active — Twilio Connected"
|
||||
@@ -0,0 +1,67 @@
|
||||
# DRE Portal Pages
|
||||
|
||||
Last updated: Jul 8, 2026
|
||||
|
||||
## File locations
|
||||
|
||||
### Public portal — portal.debtrecoveryexperts.com
|
||||
Root: `/var/www/capabilities/`
|
||||
|
||||
| File | Purpose | Auth |
|
||||
|------|---------|------|
|
||||
| `debt-recovery.html` | Claim intake form (split-screen) | Turnstile only |
|
||||
| `login.html` | Login page with SSO button | None (points to internal) |
|
||||
| `dre-fee-calculator.html` | Fee calculator | None |
|
||||
| `dre-pay.html` | Payment page | None |
|
||||
|
||||
### Internal portal — internal.debtrecoveryexperts.com
|
||||
Root: `/var/www/internal/`
|
||||
Auth: Cloudflare Access SSO (`@germainebrown.com` and `@yahoo.com` domains)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `index.html` | Landing page with 4 card links |
|
||||
| `dre-dashboard.html` | Claims dashboard with AI analysis |
|
||||
| `dre-case-aging.html` | Days-in-stage tracking, overdue alerts |
|
||||
| `letter-queue.html` | Demand letters pending review/approval |
|
||||
| `dre-client-dashboard.html` | LIGHT theme — what clients see |
|
||||
|
||||
## Persistent nav (all internal pages)
|
||||
5 links: Home, Claims, Aging, Letters, Clients. Active link gets `class="nav-link active"`. CSS:
|
||||
|
||||
```css
|
||||
.nav-link { font-size:13px; color:#94a3b8; text-decoration:none; transition:color 0.15s; }
|
||||
.nav-link:hover { color:#f59e0b; }
|
||||
.nav-link.active { color:#f59e0b; font-weight:500; }
|
||||
```
|
||||
|
||||
Dark theme (most pages): `bg-[#0f172a] border-b border-[#1e293b]`
|
||||
Light theme (client view): `bg-white border-b border-gray-200`
|
||||
|
||||
## DRE Logo (Concept C)
|
||||
```
|
||||
D (amber-500) | (gray-400) R (slate-400) | (gray-400) E (amber-500)
|
||||
```
|
||||
Inside `w-8 h-8 rounded-lg` badge with `bg-[#1e293b]` (dark) or `bg-white` (light).
|
||||
Wordmark: "DEBT Recovery Experts" (uppercase DEBT, rest normal case).
|
||||
|
||||
## Caddy config pattern
|
||||
```
|
||||
portal.debtrecoveryexperts.com {
|
||||
root * /var/www/capabilities
|
||||
try_files {path} /debt-recovery.html
|
||||
file_server
|
||||
}
|
||||
internal.debtrecoveryexperts.com {
|
||||
header Access-Control-Allow-Origin "*"
|
||||
root * /var/www/internal
|
||||
try_files {path} {path}.html /index.html
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
## File permission gotcha
|
||||
`write_file` creates files at 600. Caddy (non-root) can't read → 403. Always chmod to 644 after creating HTML files.
|
||||
|
||||
## Agent DRE chatbot pattern
|
||||
Frontend-only JS with keyword matching in `getAnswer()`. Responses should be short elevator pitches (2 sentences + follow-up question), not detailed process dumps. Matches on: fee, document, process/work, time, texas/lien, what is DRE, hello, login.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Fleet Tracker 360 — Live Dashboard Concept
|
||||
|
||||
Created: 2026-07-12
|
||||
File: `/var/www/ops/tracker-mockup.html` (37.9 KB, 1088 lines, pure CSS, no Tailwind)
|
||||
|
||||
## Why pure CSS instead of Tailwind CDN
|
||||
|
||||
Complex CSS animations (speed donut gauge, breadcrumb trails, timeline shimmer, pulse dots) and the CSS-only mini-map with grid overlay required fine-grained control that Tailwind CDN can't express. The `@apply` directive is silently broken with CDN-based Tailwind. For animation-heavy dashboards like this, pure CSS is the right choice — Tailwind CDN for simpler admin tables and forms.
|
||||
|
||||
## Mock data scenario — Cartagena trip
|
||||
|
||||
| Person/Asset | Status | Location | Details |
|
||||
|---|---|---|---|
|
||||
| Germaine | Walking (live) | Carrera 1 & Calle 47, Marbella, Cartagena | iPhone 15 Pro, 72% battery, GPS ±3m, 2.8 mph, heading 315° |
|
||||
| Garrison | Offline (3h 12m) | Savannah, GA | iPhone 14, WiFi disconnected |
|
||||
| 2024 Toyota 4Runner | Parked (engine off) | Airbnb, Cartagena | OBD2: 0 mph, 0 RPM, 62% fuel, 12.4V battery, 31°C coolant, 6,842 mi |
|
||||
|
||||
## Layout grid (12-column CSS Grid)
|
||||
|
||||
```
|
||||
Row 1: Hero Location (7 cols) | Alert Feed (5 cols)
|
||||
Row 2: ETA Grid (8 cols) | Daily Stats (4 cols)
|
||||
Row 3: Vehicle Telemetry (8) | Mini Map (4 cols)
|
||||
Row 4: Device Status Row (12 cols, full width)
|
||||
```
|
||||
|
||||
Responsive: 12-col → 6-col at 1100px → single column at 768px with card reordering (hero first, then alerts on mobile).
|
||||
|
||||
## Section design
|
||||
|
||||
### Hero Location Card
|
||||
- Person avatar (initials in gradient circle), name, device info, battery
|
||||
- Location block with pin icon, street address, lat/lng in monospace
|
||||
- 4 metric chips: speed, activity icon, heading, GPS accuracy
|
||||
- Live badge (green pulsing dot animation)
|
||||
|
||||
### ETA Grid
|
||||
- 4-column grid: Airbnb (here), Old City (18 min walk), Getsemaní (12 min), Bocagrande (32 min)
|
||||
- Walking ETAs from HERE Maps Routing API v8
|
||||
- Traffic badges (light/moderate/heavy) with color coding
|
||||
- Active route shimmer animation
|
||||
|
||||
### Vehicle Telemetry
|
||||
- CSS donut chart for speed (SVG circle with stroke-dasharray)
|
||||
- Grid of 6 gauges: speed, RPM, fuel (with bar), battery voltage, coolant temp, odometer
|
||||
- Vehicle info strip: make/model, VIN (masked), status badge
|
||||
- Engine-off state: all values at zero/idle
|
||||
|
||||
### Daily Stats
|
||||
- 2-column grid: miles walked, vehicle miles, steps, active time, trips
|
||||
- Mini progress bars for walked/vehicle miles
|
||||
- All values mock for a travel day
|
||||
|
||||
### Alert Feed
|
||||
- 5 alerts with color-coded left border (warning/amber, info/blue, ok/green)
|
||||
- Includes: Garrison offline, 4Runner idle >24h, geofence approach, flight reminder, OBD2 clean scan
|
||||
- Scrollable container (max-height: 260px)
|
||||
|
||||
### Mini Map
|
||||
- CSS-simulated Cartagena with Caribbean Sea gradient, landmass shape
|
||||
- Grid-line overlay for map-tile feel
|
||||
- Breadcrumb trail: 5 dots showing walking path + live position dot (larger, green pulse)
|
||||
- Map markers: Germaine (green circle), Airbnb (house), 4Runner (car)
|
||||
- Neighborhood labels: Marbella, Old City, Getsemaní, Bocagrande
|
||||
- Compass rose
|
||||
|
||||
### Device Status Row
|
||||
- Horizontal chip row: iPhone 15 Pro (online green dot), iPhone 14 (offline gray dot), OBD2 dongle (idle amber dot)
|
||||
- Each chip shows device name, owner, last-seen timestamp, coordinates
|
||||
- Offline devices rendered at reduced opacity
|
||||
|
||||
## Animations used
|
||||
|
||||
| Animation | Element | Purpose |
|
||||
|---|---|---|
|
||||
| `pulse-dot` (1.8s infinite) | Live badges, status dots | Simulates real-time data feed |
|
||||
| `eta-shimmer` (2.5s infinite) | Active ETA value | Indicates actively updating route |
|
||||
| `breadcrumb-blink` (3s infinite) | Trail dots on mini-map | Shows path progression |
|
||||
| `update-flash` (2s ease-out) | Timestamp text | New data arrival indicator |
|
||||
|
||||
## Color scheme
|
||||
|
||||
Matches ops portal: `--bg-primary: #0f172a`, `--bg-card: #1e293b`, `--accent: #f59e0b`, `--success: #22c55e`, `--warning: #f59e0b`, `--danger: #ef4444`, `--info: #3b82f6`.
|
||||
|
||||
## Backend API mapping (for future implementation)
|
||||
|
||||
| API | Data | Dashboard Section |
|
||||
|---|---|---|
|
||||
| Traccar `GET /api/positions` | Lat/lng, speed, heading, accuracy | Hero card, mini-map markers |
|
||||
| Traccar `GET /api/devices` | Device list, online/offline status, battery | Device status row |
|
||||
| Traccar `GET /api/reports/route` | Position history | Breadcrumb trails |
|
||||
| Traccar OBD2 protocol parser | Speed, RPM, fuel, voltage, temp, odometer | Telemetry gauges |
|
||||
| HERE Maps Routing API v8 | Walking/driving ETAs with live traffic | ETA grid |
|
||||
| Traccar `GET /api/events` + custom pipeline | Geofence, idle, offline alerts | Alert feed |
|
||||
| Custom aggregation | Daily miles walked, vehicle miles, steps, trips, active time | Daily stats |
|
||||
|
||||
## File permission note
|
||||
|
||||
Like all files under `/var/www/ops/`, run `chmod 644 /var/www/ops/tracker-mockup.html` after creation — Caddy serves these and needs read access.
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
# Leaflet.js + OpenStreetMap Map Integration
|
||||
|
||||
Replace SVG world maps with real interactive Leaflet.js maps using OpenStreetMap tiles.
|
||||
|
||||
## Setup
|
||||
|
||||
```html
|
||||
<!-- In <head> -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
```
|
||||
|
||||
## Container div (replaces old SVG `viewBox`)
|
||||
|
||||
```html
|
||||
<div id="leafletMap"></div>
|
||||
```
|
||||
|
||||
```css
|
||||
#leafletMap {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(14, 165, 233, 0.25);
|
||||
}
|
||||
```
|
||||
|
||||
## Dark theme overrides
|
||||
|
||||
Apply these to match a dark mockup theme:
|
||||
|
||||
```css
|
||||
.leaflet-container {
|
||||
background: #0a1628;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
background: #0f2b4a;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid rgba(14, 165, 233, 0.25);
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
background: #0f2b4a;
|
||||
border: 1px solid rgba(14, 165, 233, 0.25);
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 12px 16px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
min-width: 200px;
|
||||
}
|
||||
.leaflet-popup-close-button {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
.leaflet-popup-close-button:hover {
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
.leaflet-control-zoom a {
|
||||
background: #0f2b4a !important;
|
||||
color: #e2e8f0 !important;
|
||||
border-color: rgba(14, 165, 233, 0.25) !important;
|
||||
}
|
||||
.leaflet-control-zoom a:hover {
|
||||
background: #1a3a5c !important;
|
||||
}
|
||||
.leaflet-control-attribution {
|
||||
background: rgba(10, 22, 40, 0.8) !important;
|
||||
color: #64748b !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
color: #0ea5e9 !important;
|
||||
}
|
||||
```
|
||||
|
||||
## Region data (real coordinates)
|
||||
|
||||
Old SVG pattern used pixel coords + display strings. New pattern uses real lat/lng:
|
||||
|
||||
```js
|
||||
const MAP_REGIONS = [
|
||||
{ id: 1, name: "Florida East Coast", emoji: "🏖️", lat: 29.0, lng: -80.5 },
|
||||
// ...
|
||||
];
|
||||
```
|
||||
|
||||
## Map initialization + markers
|
||||
|
||||
```js
|
||||
function renderMapView() {
|
||||
// Destroy previous instance (important for view-switching UIs)
|
||||
if (window._leafletMap) {
|
||||
window._leafletMap.remove();
|
||||
window._leafletMap = null;
|
||||
}
|
||||
|
||||
const map = L.map('leafletMap', {
|
||||
center: [20, 0],
|
||||
zoom: 2,
|
||||
maxZoom: 5,
|
||||
minZoom: 2,
|
||||
zoomControl: true,
|
||||
});
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
maxNativeZoom: 5,
|
||||
}).addTo(map);
|
||||
|
||||
window._leafletMap = map;
|
||||
|
||||
MAP_REGIONS.forEach(mr => {
|
||||
// Color by state (available / yours / taken / waiting)
|
||||
const marker = L.circleMarker([mr.lat, mr.lng], {
|
||||
radius: 10,
|
||||
color: '#0ea5e9',
|
||||
fillColor: '#0ea5e9',
|
||||
fillOpacity: 0.5,
|
||||
opacity: 0.8,
|
||||
weight: 2,
|
||||
}).addTo(map);
|
||||
|
||||
marker.bindPopup(`<div class="lp-region">Content</div>`, {
|
||||
closeButton: true,
|
||||
className: 'dark-popup',
|
||||
maxWidth: 280,
|
||||
});
|
||||
});
|
||||
|
||||
// Fix layout after container becomes visible
|
||||
setTimeout(() => map.invalidateSize(), 100);
|
||||
}
|
||||
```
|
||||
|
||||
### State-based marker styling
|
||||
|
||||
| State | Color | Radius | Opacity |
|
||||
|---|---|---|---|
|
||||
| Available | `#0ea5e9` (teal) | 10 | 0.8 |
|
||||
| Your pick | `#f59e0b` (amber) | 12 | 0.9 |
|
||||
| Taken/drafted | `#dc2626` (red) | 8 | 0.5 |
|
||||
| Waiting/draft-not-started | `#64748b` (gray) | 8 | 0.4 |
|
||||
|
||||
## View-switching lifecycle
|
||||
|
||||
When toggling between list and map views, **destroy the Leaflet instance** on leaving the map view:
|
||||
|
||||
```js
|
||||
function switchView(view) {
|
||||
if (view === 'list') {
|
||||
if (window._leafletMap) {
|
||||
window._leafletMap.remove();
|
||||
window._leafletMap = null;
|
||||
}
|
||||
} else {
|
||||
renderMapView();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without this, Leaflet doesn't re-render correctly when its container is shown again — `invalidateSize()` alone is unreliable after display toggles.
|
||||
|
||||
## Popup buttons with inline onclick
|
||||
|
||||
Buttons inside Leaflet popups can call global functions:
|
||||
|
||||
```js
|
||||
marker.bindPopup(`
|
||||
<div class="lp-region">${mr.emoji} ${escapeHtml(mr.name)}</div>
|
||||
<div class="lp-stats">👁️${sightings} Sightings</div>
|
||||
<button class="map-popup-btn draft" onclick="makePickFromMap(${mr.id})">🦈 Draft</button>
|
||||
`, { ... });
|
||||
```
|
||||
|
||||
## Programmatic popup open/close
|
||||
|
||||
```js
|
||||
function openMapPopup(regionId) {
|
||||
const mr = MAP_REGIONS.find(r => r.id === regionId);
|
||||
if (mr && window._leafletMap) {
|
||||
window._leafletMap.eachLayer(layer => {
|
||||
if (layer instanceof L.CircleMarker &&
|
||||
layer.getLatLng().lat === mr.lat &&
|
||||
layer.getLatLng().lng === mr.lng) {
|
||||
layer.openPopup();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function closeMapPopup() {
|
||||
if (window._leafletMap) {
|
||||
window._leafletMap.closePopup();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Removing custom popup overlays
|
||||
|
||||
When switching to Leaflet popups, remove the old custom overlay HTML and CSS:
|
||||
|
||||
- **HTML to remove:** custom overlay divs (`#mapPopupOverlay`, `#mapPopup`, `#mapPopupContent`)
|
||||
- **CSS to remove:** `.map-popup-overlay`, `.map-popup`, `.map-popup-handle`, `.map-popup-header`, `.map-popup-flag`, `.map-popup-title`, `.map-popup-stats`, `.map-popup-stat`, `.map-popup-footer`
|
||||
- **CSS to keep:** `.map-popup-btn` and its variants — these still style buttons inside Leaflet popups
|
||||
- **JS to replace:** `openMapPopup()` / `closeMapPopup()` — redirect to Leaflet API as shown above
|
||||
@@ -0,0 +1,57 @@
|
||||
# Login Page Two-Panel Pattern
|
||||
|
||||
Built for IT Pro Partner portal. Served at `/portal/login.html`.
|
||||
|
||||
## Layout
|
||||
|
||||
Two-column layout at desktop (`md:grid-cols-2`), stacks vertically on mobile.
|
||||
|
||||
### Left panel — Existing Customer Login
|
||||
|
||||
Blue accent (`#2563eb`). Elements:
|
||||
|
||||
- User icon in blue circle
|
||||
- "Customer Login" heading
|
||||
- "Access your dashboard..." subtitle
|
||||
- Email field + password field
|
||||
- Remember me checkbox + forgot password link
|
||||
- Sign In button (blue)
|
||||
- Divider with "or"
|
||||
- Google SSO button
|
||||
|
||||
### Right panel — New Customer Inquiry
|
||||
|
||||
Green accent (`#16a34a`). No sign-in — lead capture form. Elements:
|
||||
|
||||
- Plus icon in green circle
|
||||
- "New Customer" heading
|
||||
- "Tell us what you need..." subtitle
|
||||
- First name + last name (side by side, `grid grid-cols-2 gap-3`)
|
||||
- Email field
|
||||
- Company field
|
||||
- Service interest checkboxes (8 options in 2x4 grid):
|
||||
- Internet / WISP, Business VoIP, DNS / Domain, Email Hosting
|
||||
- Web Hosting, Network Setup, Backup / DR, Other
|
||||
- Cloudflare Turnstile badge (invisible, text: "Protected by Cloudflare Turnstile")
|
||||
- Submit Inquiry button (green)
|
||||
- "We'll review your request and contact you within 1 business day" note
|
||||
|
||||
## Mobile responsive
|
||||
|
||||
- Stacks right panel below left panel
|
||||
- Full-width inputs
|
||||
- Service checkboxes collapse from 2->1 columns on small screens
|
||||
|
||||
## Color signals
|
||||
|
||||
- Blue = existing customer flow (login, familiarity)
|
||||
- Green = new customer flow (growth, action)
|
||||
- No blue/green mixing within same panel
|
||||
|
||||
## Top nav
|
||||
|
||||
Minimal: logo + Services · About · Contact links. Drops to logo-only on mobile.
|
||||
|
||||
## Footer
|
||||
|
||||
"IT Pro Partner © 2026 · Privacy · Terms". Centered, border-top, small text.
|
||||
@@ -0,0 +1,170 @@
|
||||
# Ops Portal — Logs & Events Page Pattern
|
||||
|
||||
Created: 2026-07-08
|
||||
Page: `/var/www/ops/logs.html`
|
||||
Source data: `/data/ops-status.json`
|
||||
|
||||
## Page Sections
|
||||
|
||||
| Section | Data Source | Render Pattern |
|
||||
|---------|-------------|----------------|
|
||||
| **Recent API Health Events** | `data.api_checks` (array) | Vertical timeline with color-coded dots per endpoint. Falls back to `data.api_health` if `api_checks` absent. |
|
||||
| **Service Events** | `data.services` (array) | Card grid. Cards with non-`active` status get red border/background. |
|
||||
| **Cron Job Errors** | `data.scheduled_jobs` (array) | Prominent red card section (`card-red`), hidden when no errors. Shows job name, last run (with relative age), script path, link to `/cron.html`. Green empty-state when clean. |
|
||||
| **Recent Failures Summary** | Aggregated from API checks, services, cron jobs, S3 | Filter-bar table (All / Errors / Warnings / OK) with per-filter counts. JS-based client-side filtering. |
|
||||
| **Future / Notes** | Static | Amber card listing planned syslog/journald integration |
|
||||
|
||||
## Reusable Patterns
|
||||
|
||||
### Timeline component
|
||||
|
||||
A vertical timeline with a left-side dot-and-line track. Each item has a colored dot (green/red/amber/gray), endpoint name, status badge, and timestamp with relative age.
|
||||
|
||||
```html
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<span class="tl-dot tl-ok"></span>
|
||||
<span class="tl-endpoint">api.example.com/health</span>
|
||||
<span class="tl-status"><span class="badge badge-ok">OK</span></span>
|
||||
<span class="tl-time">Jul 8, 10:45 PM (2m ago)</span>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
CSS:
|
||||
```css
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 24px;
|
||||
}
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 2px;
|
||||
background: var(--border-card);
|
||||
}
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
padding: 8px 0 8px 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 12px;
|
||||
border-bottom: 1px solid rgba(51,65,85,0.3);
|
||||
}
|
||||
.timeline-item .tl-dot {
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
top: 12px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.tl-dot.tl-ok { background: var(--green); }
|
||||
.tl-dot.tl-error { background: var(--red); }
|
||||
.tl-dot.tl-amber { background: var(--amber); }
|
||||
.tl-dot.tl-gray { background: var(--gray); }
|
||||
```
|
||||
|
||||
### Filter bar with counts
|
||||
|
||||
Bootstrap-style filter pills that show item counts and toggle content visibility:
|
||||
|
||||
```html
|
||||
<div class="filter-bar">
|
||||
<button class="filter-btn active" data-filter="all" onclick="applyFilter('all')">All <span class="fb-count" id="fAll">0</span></button>
|
||||
<button class="filter-btn" data-filter="error" onclick="applyFilter('error')">Errors <span class="fb-count" id="fError">0</span></button>
|
||||
<button class="filter-btn" data-filter="ok" onclick="applyFilter('ok')">OK <span class="fb-count" id="fOk">0</span></button>
|
||||
</div>
|
||||
```
|
||||
|
||||
CSS:
|
||||
```css
|
||||
.filter-btn {
|
||||
background: rgba(15,23,42,0.4);
|
||||
border: 1px solid var(--border-card);
|
||||
color: var(--text-secondary);
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.78rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-btn.active {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
background: rgba(245,158,11,0.08);
|
||||
}
|
||||
.filter-btn .fb-count { margin-left: 4px; opacity: 0.7; }
|
||||
```
|
||||
|
||||
JS pattern: maintain `currentFilter` and `failureItems[]`. Re-filter on each `applyFilter()` call by iterating items and checking status against the active filter. Toggle active class on buttons by matching `data-filter` attribute.
|
||||
|
||||
### Multi-source failure aggregation
|
||||
|
||||
The `collectFailures(data)` function walks ALL data sections and collects non-OK items into a flat array tagged with `source`:
|
||||
|
||||
```javascript
|
||||
function collectFailures(data) {
|
||||
var items = [];
|
||||
// API checks
|
||||
var apiChecks = getNested(data, 'api_checks', []);
|
||||
if (Array.isArray(apiChecks)) {
|
||||
for (var i = 0; i < apiChecks.length; i++) {
|
||||
items.push({ source: 'API Check', item: c.endpoint, status: cleanStatus, statusLabel: originalStatus, detail: c.message });
|
||||
}
|
||||
}
|
||||
// Services — only non-active
|
||||
// Cron jobs — only error/failed
|
||||
// S3 backups — only error/failed
|
||||
// API health — only error/failed
|
||||
return items;
|
||||
}
|
||||
```
|
||||
|
||||
Then OK items are added separately. The combined list drives both the filter counts and the table rendering.
|
||||
|
||||
### Red/amber card variants
|
||||
|
||||
- **Cron errors** use `class="card card-red"` with `--red-border` and `--red-bg`
|
||||
- **Future notes** use `class="card card-amber"` with `rgba(245,158,11,0.3)` border and `--amber-bg`
|
||||
|
||||
```css
|
||||
.card-red {
|
||||
border-color: rgba(239,68,68,0.4);
|
||||
background: var(--red-bg);
|
||||
}
|
||||
.card-amber {
|
||||
border-color: rgba(245,158,11,0.3);
|
||||
background: var(--amber-bg);
|
||||
}
|
||||
```
|
||||
|
||||
## Data flow notes
|
||||
|
||||
- The page fetches `/data/ops-status.json` on load and auto-refreshes every 30s
|
||||
- Falls back gracefully: `api_checks` → `api_health` if primary field missing
|
||||
- Cron errors section is conditionally shown/hidden via `display: none` — the red card is gated on `errors.length > 0`
|
||||
- All sections have empty states that say "No X data available" when their source array is empty
|
||||
|
||||
## nav.html integration
|
||||
|
||||
The page uses the shared nav partial via XHR:
|
||||
```html
|
||||
<div id="navPlaceholder"></div>
|
||||
<script>
|
||||
function loadNav() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/nav.html', true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
document.getElementById('navPlaceholder').innerHTML = xhr.responseText;
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() { loadNav(); loadLogs(); });
|
||||
</script>
|
||||
```
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# MikroTik Router Onboarding via Portal
|
||||
|
||||
When the user wants an onboarding section for MikroTik routers in the portal, the flow follows a "generate script → run on router → auto-checks in" pattern.
|
||||
|
||||
## Mockup layout
|
||||
|
||||
The mockup page at `/root/portal-mockup/onboard-router.html` shows:
|
||||
|
||||
### Left column — form
|
||||
- **Customer selector** — dropdown of existing customers (or create new)
|
||||
- **Router name/model** — optional fields
|
||||
- **Generate button** — produces the registration script
|
||||
- **Generated script panel** — syntax-highlighted RouterOS script with Copy/Download buttons
|
||||
- Sets system identity to a standard format (e.g. RTR-CUSTOMER-01)
|
||||
- Creates a read-only monitoring user
|
||||
- POSTs registration data to the portal API
|
||||
- Sets up a 60-second heartbeat scheduler
|
||||
- Token scoped to customer + expires in 24 hours
|
||||
|
||||
### Right column — status + instructions
|
||||
|
||||
- **How It Works** — three-step explainer: Generate → Run on Router → Auto-Connects
|
||||
- **Status card** — shows "Waiting for first check-in" or "Online" with last heartbeat timestamp
|
||||
- **Recently Onboarded** — list of recently added routers with green dot status indicators
|
||||
|
||||
## Registration script structure
|
||||
|
||||
```routeros
|
||||
# IT Pro Partner - Router Registration
|
||||
/system identity
|
||||
set name=RTR-CUSTOMER-01
|
||||
|
||||
/user add name=itpp-monitor \
|
||||
group=read password="$(/tool fetch url=...)"
|
||||
|
||||
/tool fetch url="https://portal.itpropartner.com/api/register" \
|
||||
http-method=post \
|
||||
http-data="{\"identity\":\"RTR-CUSTOMER-01\",\"token\":\"TOKEN\"}"
|
||||
|
||||
/system scheduler add name=heartbeat interval=1m \
|
||||
on-event="/tool fetch url=.../heartbeat"
|
||||
```
|
||||
|
||||
## Portal-side requirements
|
||||
|
||||
- API endpoint: `/api/register` — receives registration POST, validates token, stores device identity
|
||||
- API endpoint: `/api/heartbeat` — receives 60-second keepalive, updates last_seen timestamp
|
||||
- API endpoint: `/api/register/token` — generates one-time token per customer, 24h expiry
|
||||
- Dashboard shows: device name, customer, last heartbeat time, firmware, public IP
|
||||
@@ -0,0 +1,110 @@
|
||||
# Ops Portal — Network Page Pattern
|
||||
|
||||
Created: 2026-07-08
|
||||
Session: Built `/var/www/ops/network.html` — network infrastructure page for the ops portal
|
||||
Domain: `ops.itpropartner.com`
|
||||
Root: `/var/www/ops/`
|
||||
|
||||
## Page structure
|
||||
|
||||
| Section | Content |
|
||||
|---|---|
|
||||
| **Nav** | Shared via `fetch('/nav.html')` — nav partial already had the `/network.html` link |
|
||||
| **Header** | Title "Network", status summary, last-updated timestamp, refresh button |
|
||||
| **Network Health Overview** | 5-card grid: DNS Zones tracked, API Checks (ok/total), Routers tracked, Domains active, Overall health score |
|
||||
| **MikroTik Routers** | Amber placeholder card + conditional backup history row from S3 data |
|
||||
| **Ubiquiti / UISP** | Teal placeholder card |
|
||||
| **DNS & Domains** | Live table from `data.cloudflare_zones` or `data.domains` with soft-coded fallback |
|
||||
| **Network Health — Port & Service Checks** | Grid of API/port check cards |
|
||||
|
||||
## Reusable patterns
|
||||
|
||||
### 1. Fallback data for when collector is offline
|
||||
|
||||
When a page depends on data from a live collector that may be offline, provide a hardcoded fallback so the page is useful from day one:
|
||||
|
||||
```javascript
|
||||
var KNOWN_ZONES = [
|
||||
{ name: 'itpropartner.com', zone: '' },
|
||||
{ name: 'nousresearch.com', zone: '' },
|
||||
// ...
|
||||
];
|
||||
```
|
||||
|
||||
Render logic: prefer live data → fall back to known list → show "Pending" badges → the page always has content even if the data source is down.
|
||||
|
||||
### 2. Placeholder card pattern
|
||||
|
||||
Sections that aren't yet connected to a live data source use a `placeholder-card` with status badges:
|
||||
|
||||
```html
|
||||
<div class="placeholder-card">
|
||||
<div class="p-icon">🔄</div>
|
||||
<div class="p-body">
|
||||
<div class="p-title">Title of feature</div>
|
||||
<div class="p-desc">Description of what will show when configured</div>
|
||||
<div class="p-meta">
|
||||
<span>Status: Not configured</span>
|
||||
<span>Requires: API Key</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Three color variants are defined:
|
||||
- `.placeholder-card` (amber) — not yet configured
|
||||
- `.placeholder-card.placeholder-teal` — setup required (teal accent)
|
||||
- `.placeholder-card.placeholder-purple` — reserved for future use
|
||||
|
||||
### 3. Data-source-agnostic health grid
|
||||
|
||||
The health overview at the top fetches from multiple possible data paths:
|
||||
|
||||
| Metric | Data path priority |
|
||||
|---|---|
|
||||
| DNS Zones | `data.cloudflare_zones` → `data.domains` → `KNOWN_ZONES.length` |
|
||||
| API Checks | `data.api_health` → `data.api_checks` → 0 |
|
||||
| Routers | `data.routers` → 0 |
|
||||
| Overall health | Weighted combination of zone status + API status |
|
||||
|
||||
This means the grid always renders something, even if only partial data is available.
|
||||
|
||||
### 4. S3 backup cross-section
|
||||
|
||||
When an S3 backup bucket name matches a section (e.g., "mikrotik-backups"), the section shows backup history inline:
|
||||
|
||||
```javascript
|
||||
var s3Backups = getNested(data, 's3_backups', []);
|
||||
for (var i = 0; i < s3Backups.length; i++) {
|
||||
var bName = (b.name || b.bucket_name || b.bucket || '').toLowerCase();
|
||||
if (bName.indexOf('mikrotik') !== -1) {
|
||||
// Show backup row with last_upload, age color, status dot
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Plain CSS (no Tailwind CDN)
|
||||
|
||||
This page uses plain CSS with CSS custom properties — same dark theme variables as the rest of the portal but no Tailwind CDN dependency. This is an alternative approach to the Tailwind-heavy mockups listed elsewhere in this skill. Useful when:
|
||||
- The page is deployed in production (not a mockup)
|
||||
- Caching and no-external-dep is preferred
|
||||
- The shared `ops.css` stylesheet pattern is in play
|
||||
|
||||
## Data flow
|
||||
|
||||
Fetches `/data/ops-status.json` on load + auto-refresh every 30s. Expects these fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"cloudflare_zones": [{ "name": "...", "status": "active" }],
|
||||
"api_health": [{ "name": "...", "status": "ok" }],
|
||||
"network_checks": { "hostname": { "status": "reachable", "latency_ms": 42 } },
|
||||
"routers": [{ "name": "...", "status": "..." }],
|
||||
"domains": [{ "name": "...", "status": "...", "last_checked": "..." }],
|
||||
"s3_backups": [{ "name|bucket_name|bucket": "...", "status": "ok", "last_upload": "..." }]
|
||||
}
|
||||
```
|
||||
|
||||
## File
|
||||
|
||||
- `/var/www/ops/network.html` — 920 lines, 30 KB, plain HTML+CSS+JS, no external dependencies
|
||||
@@ -0,0 +1,45 @@
|
||||
# Network Dashboard + Router Detail Patterns
|
||||
|
||||
## Network Dashboard Page
|
||||
|
||||
Summary cards row at top:
|
||||
- Total Routers (count)
|
||||
- Online (count + percentage badge)
|
||||
- Alerts (count + "Needs attention" badge)
|
||||
- Avg Heartbeat (ms)
|
||||
|
||||
Wide router table with 9 columns: Status | Router | Customer | Model | Uptime | Tunnel | Installed | Latest | Last Seen
|
||||
|
||||
Firmware columns use color badges:
|
||||
- Green badge = router is current
|
||||
- Amber badge = update available
|
||||
|
||||
Status dots: green=Online, amber=Alert (missed heartbeat), red=Offline (no heartbeat >60m), gray=Pending (script sent, not yet installed)
|
||||
|
||||
Right-rail panels:
|
||||
1. Recent Alerts — color-coded rows with amber (warning) and red (critical) backgrounds, timestamp relative
|
||||
2. Top Bandwidth — horizontal progress bars with Mbps labels
|
||||
3. Quick Actions — Run command, Backup config, Reboot router, Update firmware, Onboard new (blue link)
|
||||
|
||||
Header has filter buttons (All, Online, Offline, Alert) and search input.
|
||||
|
||||
## Router Detail Page
|
||||
|
||||
Summary cards row: Uptime, CPU (with mini progress bar), RAM (with mini progress bar), Firmware (with update badge if outdated), Temperature.
|
||||
|
||||
Six tabs across the top:
|
||||
|
||||
| Tab | Default? | Content |
|
||||
|---|---|---|
|
||||
| DNS | ✅ Yes | Summary cards (total queries, unique domains, active clients, blocked). Two-column top-domains and top-clients panels. Searchable query log table: Time, Client IP, Domain, Type (A/AAAA badge), Response IP, Client Hostname. Time range filter. Client filter dropdown. "View all" link. |
|
||||
| Network | No | DHCP leases (hostname, IP, MAC, reserved/dynamic badge, active/offline/expired status, lease time, Edit/Release/Reserve action buttons). Interfaces table (name, IP, up/down status, TX, RX). Add Reservation form at bottom (hostname, IP, MAC, comment). |
|
||||
| Bandwidth | No | Time period selector (hour, 24h, 7d, 30d, 12mo). Chart placeholder. Four period summary cards (hourly, daily, monthly, yearly with up/down breakdown). |
|
||||
| Firewall | No | NAT rules, filter rules, port forwards |
|
||||
| Logs | No | Real-time log feed |
|
||||
| Config | No | Backup, restore, firmware management |
|
||||
|
||||
DHCP table: Reserved = purple badge. Dynamic = blue badge. Status dots: green=active, gray=offline, gray=expired.
|
||||
|
||||
## Files
|
||||
- mockup: `/root/portal-mockup/network-dashboard.html`
|
||||
- mockup: `/root/portal-mockup/router-detail.html`
|
||||
@@ -0,0 +1,60 @@
|
||||
# Customer & Router Onboarding Mockups
|
||||
|
||||
Built as part of the IT Pro Partner operations portal concept. Covers two onboarding flows:
|
||||
|
||||
## Customer Onboarding
|
||||
|
||||
Three-step flow:
|
||||
1. **Search Domain** — enter domain or customer name; portal checks all connected platforms for matches
|
||||
2. **Review Matches** — auto-detected services shown with platform badge + detail. Not-found services shown with dashed border + "Link manually" action
|
||||
3. **Confirm & Create** — batch action to link selected services and create customer record
|
||||
|
||||
### Service badge colors (per platform)
|
||||
| Platform | Badge |
|
||||
|---|---|
|
||||
| Cloudflare (DNS) | bg-amber-100 text-amber-700 |
|
||||
| RingLogix (VoIP) | bg-purple-100 text-purple-700 |
|
||||
| UniFi (Network) | bg-green-100 text-green-700 |
|
||||
| UISP (WISP) | bg-cyan-100 text-cyan-700 |
|
||||
| RunCloud (Web) | bg-blue-100 text-blue-700 |
|
||||
| MXroute (Email) | bg-pink-100 text-pink-700 |
|
||||
|
||||
### Key UX elements
|
||||
- "Create blank customer" link for new clients with no existing services
|
||||
- Save as Draft option for partial onboarding
|
||||
- Search triggers auto-scan across Cloudflare, RingLogix, UniFi, UISP APIs
|
||||
- Checkboxes allow selective linking (not all services need to be linked)
|
||||
|
||||
## MikroTik Router Onboarding (onboard-router.html)
|
||||
|
||||
URL: https://vaultwarden.tailc2f3b0.ts.net/portal/onboard-router.html
|
||||
|
||||
Two-column layout:
|
||||
- **Left:** Customer selector dropdown, router name/model fields, Generate button, generated ROS7 script with Copy/Download actions
|
||||
- **Right:** 3-step explainer (Generate -> Run on Router -> Auto-Connects), status card, recently onboarded list
|
||||
|
||||
### Registration script content
|
||||
The generated script is displayed in a code block with:
|
||||
- WireGuard interface + peer creation
|
||||
- Tunnel IP assignment
|
||||
- Firewall rules (allow tunnel traffic, allow WG UDP port)
|
||||
- SSH lockdown to tunnel network
|
||||
- SSH key file write + import (ROS7 syntax: /user/ssh-keys/import public-key-file=...)
|
||||
- 60-second heartbeat scheduler via /system scheduler
|
||||
- One-time token tied to customer, expires in 24h
|
||||
|
||||
### Status indicators
|
||||
- **Waiting:** Script generated but no check-in received (amber, clock icon)
|
||||
- **Online:** Router has checked in (green dot)
|
||||
|
||||
### Previously onboarded list
|
||||
Shows recent router deployments with name, online status, and time since registration.
|
||||
|
||||
## Home Router WireGuard Tunnel (Live Test)
|
||||
|
||||
Germaine's CCR connected to netcup server as proof of concept.
|
||||
- Server: 10.77.0.1:51820
|
||||
- Router: 10.77.0.2 (WireGuard) / 10.10.10.1 (internal LAN)
|
||||
- SSH accessible through tunnel via wisp_rsa key
|
||||
- Latency: ~37ms, 0% packet loss
|
||||
- RouterOS 7.18.2 on CCR2004-16G-2S+
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# Ops Portal — Shared Asset Architecture
|
||||
|
||||
Created: 2026-07-08
|
||||
Updated: 2026-07-08 (added config.html, cron.html, static-data-fallback pattern)
|
||||
Domain: `ops.itpropartner.com`
|
||||
Root: `/var/www/ops/`
|
||||
|
||||
## Files produced this session
|
||||
|
||||
| File | Purpose | Size |
|
||||
|------|---------|------|
|
||||
| `/var/www/ops/nav.html` | Shared navigation partial (responsive, 8 links, hamburger mobile) | 5.8 KB |
|
||||
| `/var/www/ops/template.html` | Base page template (CSS ref + nav injection + utils + footer) | 1.9 KB |
|
||||
| `/var/www/ops/css/ops.css` | Full shared stylesheet extracting all inline styles from dashboard | 10 KB (355 lines) |
|
||||
| `/var/www/ops/js/utils.js` | Shared utility functions (fetch status, format, badges, colors) | 6.2 KB (196 lines) |
|
||||
| `/var/www/ops/services.html` | Demo page showing the pattern (Services + API health) | 4.7 KB |
|
||||
| `/var/www/ops/network.html` | Network infrastructure page: health overview, MikroTik placeholder, Ubiquiti placeholder, DNS/domains table with Cloudflare fallback, API/port health check grid | 30 KB (920 lines) |
|
||||
| `/var/www/ops/logs.html` | Logs & Events page: API health timeline, service events grid, cron error card, multi-source failure summary with filter bar | 30 KB (1005 lines) |
|
||||
| `/var/www/ops/config.html` | Config page: versions, config files table, environment grid, infra diagram link. Uses **static embedded data + live API fallback** pattern (see below) | 14.8 KB (480 lines) |
|
||||
| `/var/www/ops/cron.html` | Cron jobs detail page | added in same batch |
|
||||
| `/var/www/ops/backups.html` | Backup status page | added in same batch |
|
||||
| `/var/www/ops/tracker-mockup.html` | FleetTracker360 live GPS dashboard concept (Cartagena trip scenario, pure CSS, no Tailwind) | 37.9 KB (1088 lines) |
|
||||
|
||||
## Caddy config (from `/etc/caddy/Caddyfile`)
|
||||
|
||||
```
|
||||
ops.itpropartner.com {
|
||||
root * /var/www/ops
|
||||
encode gzip
|
||||
file_server
|
||||
header /data/* {
|
||||
-Access-Control-Allow-Origin
|
||||
Access-Control-Allow-Origin "*"
|
||||
}
|
||||
log {
|
||||
output file /var/log/caddy/ops.log
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data source
|
||||
|
||||
`/data/ops-status.json` — produced by the ops-data-collector cron job. Structure:
|
||||
|
||||
- `timestamp` — ISO 8601 generation time
|
||||
- `cron_jobs[]` — array with `name`, `schedule`, `lastRun`, `status`, `script`
|
||||
- `services` — object mapping service names to status strings (active/inactive)
|
||||
- `disk_memory` — `disk_used_pct`, `memory_used_pct`
|
||||
- `s3_backups` — object mapping bucket names to `{status, last_upload, age_hours}`
|
||||
- `api_checks` — object mapping API names to status strings
|
||||
- `versions` — `hermes`, `caddy`, `python`, `os`
|
||||
- `netcup_server` — object with `id`, `template`, `ip`
|
||||
- `hetzner_servers[]` — array with `name`, `status`, `type`, `ip`, `id`
|
||||
|
||||
## Key patterns
|
||||
|
||||
- **Nav injection**: `<div id="nav"></div>` + `fetch('/nav.html')`. Inline JS in nav.html handles active-page detection.
|
||||
- **Data loading**: `fetchStatus()` from utils.js, then render each section. 30-second auto-refresh via `setInterval`.
|
||||
- **File mode fix**: Always `chmod 644` after `write_file` — Caddy 403s on mode 600 files.
|
||||
|
||||
### Static data + live API fallback pattern (used by config.html)
|
||||
|
||||
Some ops portal pages combine **embedded static data** with **live API enrichment**. This is useful when:
|
||||
|
||||
1. The page needs data the collector doesn't provide (e.g. file sizes, script counts, hardware specs)
|
||||
2. The data changes infrequently enough that a page-rebuild is acceptable
|
||||
3. The page should work even when the API is down
|
||||
|
||||
**Pattern (from config.html):**
|
||||
|
||||
```javascript
|
||||
// 1. Define embedded data as top-level JS objects in the page
|
||||
var CONFIG_FILES = [
|
||||
{ path: '/etc/caddy/Caddyfile', size: '2.8 KB', size_bytes: 2870, ... },
|
||||
// ...
|
||||
];
|
||||
|
||||
var ENV_DATA = {
|
||||
hostname: 'core',
|
||||
os: 'Debian GNU/Linux 13 (trixie)',
|
||||
// ...
|
||||
};
|
||||
|
||||
// 2. Render embedded data immediately (no API dependency)
|
||||
renderConfigFiles();
|
||||
renderEnvironment();
|
||||
|
||||
// 3. Try API fetch for enrichment — versions overlay onto embedded cards
|
||||
fetchStatus()
|
||||
.then(function(data) {
|
||||
renderVersions(data); // overrides version cards with live data
|
||||
// API failure is non-fatal — embedded data is already shown
|
||||
})
|
||||
.catch(function(err) {
|
||||
// API failed — embedded data is already displayed, so not critical
|
||||
console.warn('API fetch failed:', err);
|
||||
});
|
||||
```
|
||||
|
||||
**When to use this pattern vs pure API-driven:**
|
||||
- Pure API-driven (index.html, services.html): All content comes from the JSON. Page is empty until fetch completes. Ideal when the collector already supplies everything needed.
|
||||
- Static + API fallback (config.html): Some sections rendered immediately from embedded data. API enriches versions/freshness if available. Use when the collector doesn't/can't know some data (file timestamps, hardware specs, script inventory).
|
||||
|
||||
## All existing pages
|
||||
|
||||
- `/var/www/ops/index.html` — main dashboard
|
||||
- `/var/www/ops/servers.html` — server inventory
|
||||
- `/var/www/ops/backups.html` — backup status
|
||||
- `/var/www/ops/services.html` — services demo
|
||||
- `/var/www/ops/network.html` — network infrastructure
|
||||
- `/var/www/ops/logs.html` — logs & events
|
||||
- `/var/www/ops/cron.html` — cron jobs
|
||||
- `/var/www/ops/config.html` — system configuration
|
||||
- `/var/www/ops/nav.html` — the nav partial itself
|
||||
- `/var/www/ops/tracker-mockup.html` — FleetTracker360 live GPS dashboard concept (see `references/fleet-tracker-dashboard.md`)
|
||||
@@ -0,0 +1,75 @@
|
||||
# Servers Inventory Page — Session Reference
|
||||
|
||||
Created: 2026-07-08
|
||||
Files: `/var/www/ops/servers.html`, `/var/www/ops/nav.html`
|
||||
|
||||
## Data source structure
|
||||
|
||||
Fetched from `/data/ops-status.json` (auto-refresh 30s):
|
||||
|
||||
### `netcup_server` (object)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 890903,
|
||||
"template": "RS 2000 G12",
|
||||
"ip": "152.53.192.33"
|
||||
}
|
||||
```
|
||||
|
||||
Field name mapping (for resilience): `name || hostname`, `template`, `ip || ip_address`.
|
||||
|
||||
### `hetzner_servers` (array)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "wphost02",
|
||||
"status": "running",
|
||||
"type": "cpx21",
|
||||
"ip": "5.161.62.38",
|
||||
"id": 51140464
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Field name mapping: `name || hostname`, `type || server_type`, `ip || ip_address || ipv4`, `status`, `id`.
|
||||
|
||||
No `location` field present in current data — shows `—` for all rows.
|
||||
|
||||
### Resource percentages
|
||||
|
||||
```json
|
||||
{
|
||||
"overall": { "disk_used_pct": 6, "memory_used_pct": 39 },
|
||||
"disk_memory": { "disk_used_pct": 6, "memory_used_pct": 39 }
|
||||
}
|
||||
```
|
||||
|
||||
Fallback chain: `overall.disk_used_pct` → `disk_memory.disk_used_pct` → null.
|
||||
|
||||
## netcup plan specs
|
||||
|
||||
| Plan | vCPUs | RAM (GB) |
|
||||
|------|-------|----------|
|
||||
| RS 2000 G12 | 4 | 16 |
|
||||
|
||||
## Hetzner plan specs (CPX line)
|
||||
|
||||
| Plan | vCPUs | RAM (GB) |
|
||||
|------|-------|----------|
|
||||
| CPX11 | 2 | 4 |
|
||||
| CPX21 | 3 | 8 |
|
||||
| CPX31 | 4 | 16 |
|
||||
| CPX41 | 4 | 16 |
|
||||
| CPX51 | 8 | 32 |
|
||||
|
||||
## Environment
|
||||
|
||||
- Domain: `app.itpropartner.com` (or whatever Caddy routes to `/var/www/ops/`)
|
||||
- Data endpoint: `/data/ops-status.json`
|
||||
- Nav partial: `/nav.html`
|
||||
|
||||
## File permission
|
||||
|
||||
Both `servers.html` and `nav.html` created with `600` mode by `write_file`. Must `chmod 644` before Caddy can serve them without 403.
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
# Vehicles Database Inventory
|
||||
|
||||
As of **July 7, 2026**, the `vehicles.json` database at `/var/www/static/vehicles.json` (served at `https://core.itpropartner.com/vehicles.json`) contains **47 makes, 373 models, and 1,433 year-HP entries**.
|
||||
|
||||
## Criteria
|
||||
|
||||
- **All 2015+ production cars** with **400+ HP**
|
||||
- Every model year the vehicle was in production with that HP rating
|
||||
- Sub-400hp trims and model years are excluded
|
||||
- Non-production / prototype / concept cars excluded
|
||||
- Tuned/shop-built vehicles (RENNtech, Hennessey-tuned Jeeps, etc.) excluded unless they're a factory production model
|
||||
|
||||
## Make Inventory
|
||||
|
||||
| # | Make | Models | Years Covered | HP Range |
|
||||
|---|------|--------|---------------|----------|
|
||||
| 1 | Acura | 2 | 2017-2023 | 573-600 |
|
||||
| 2 | Alfa Romeo | 2 | 2017-2024 | 505 |
|
||||
| 3 | Aston Martin | 14 | 2016-2026 | 503-1160 |
|
||||
| 4 | Audi | 16 | 2016-2025 | 401-637 |
|
||||
| 5 | Bentley | 10 | 2016-2025 | 542-740 |
|
||||
| 6 | BMW | 31 | 2015-2025 | 405-738 |
|
||||
| 7 | Bugatti | 8 | 2015-2025 | 1183-1578 |
|
||||
| 8 | BYD | 3 | 2022-2025 | 517-1306 |
|
||||
| 9 | Cadillac | 4 | 2020-2025 | 472-682 |
|
||||
| 10 | Chevrolet | 9 | 2016-2026 | 455-1064 |
|
||||
| 11 | Czinger | 1 | 2023-2024 | 1250 |
|
||||
| 12 | Dodge | 11 | 2015-2023 | 485-1025 |
|
||||
| 13 | Ferrari | 30 | 2015-2026 | 553-1183 |
|
||||
| 14 | Ford | 12 | 2015-2025 | 418-760 |
|
||||
| 15 | GMC | 2 | 2022-2024 | 830-1000 |
|
||||
| 16 | Genesis | 6 | 2021-2025 | 409-429 |
|
||||
| 17 | Hennessey | 2 | 2022-2024 | 1817 |
|
||||
| 18 | Honda | 1 | 2017-2022 | 573-600 |
|
||||
| 19 | Hyundai | 2 | 2024-2025 | 601 |
|
||||
| 20 | Jaguar | 7 | 2015-2024 | 550-592 |
|
||||
| 21 | Jeep | 2 | 2018-2024 | 470-707 |
|
||||
| 22 | Kia | 1 | 2023-2025 | 576 |
|
||||
| 23 | Koenigsegg | 7 | 2015-2025 | 1280-1700 |
|
||||
| 24 | Lamborghini | 21 | 2015-2026 | 572-1001 |
|
||||
| 25 | Land Rover | 6 | 2015-2024 | 518-606 |
|
||||
| 26 | Lexus | 6 | 2015-2024 | 416-472 |
|
||||
| 27 | Lotus | 5 | 2018-2024 | 400-1972 |
|
||||
| 28 | Lucid | 6 | 2022-2026 | 430-1234 |
|
||||
| 29 | Maserati | 9 | 2019-2025 | 523-751 |
|
||||
| 30 | McLaren | 21 | 2015-2026 | 592-1258 |
|
||||
| 31 | Mercedes-AMG | 32 | 2015-2025 | 416-1063 |
|
||||
| 32 | Nio | 3 | 2017-2024 | 536-1342 |
|
||||
| 33 | Nissan | 4 | 2015-2025 | 400-600 |
|
||||
| 34 | Pagani | 5 | 2015-2025 | 720-864 |
|
||||
| 35 | Pininfarina | 1 | 2022-2024 | 1877 |
|
||||
| 36 | Polestar | 1 | 2020-2021 | 619 |
|
||||
| 37 | Porsche | 35 | 2015-2025 | 400-1093 |
|
||||
| 38 | Ram | 2 | 2021-2025 | 540-702 |
|
||||
| 39 | Rimac | 2 | 2015-2024 | 1088-1914 |
|
||||
| 40 | Rivian | 2 | 2022-2024 | 835 |
|
||||
| 41 | Rolls-Royce | 11 | 2015-2025 | 563-624 |
|
||||
| 42 | SSC | 1 | 2020-2023 | 1750 |
|
||||
| 43 | Shelby | 2 | 2020-2022 | 810-825 |
|
||||
| 44 | Tesla | 12 | 2016-2025 | 417-1020 |
|
||||
| 45 | Volvo | 5 | 2020-2024 | 415-619 |
|
||||
| 46 | W Motors | 1 | 2021-2022 | 900 |
|
||||
| 47 | Xiaomi | 2 | 2024-2026 | 663-1138 |
|
||||
|
||||
## Largest Makes by Model Count
|
||||
|
||||
1. **Porsche** — 35 models (911 variants, Cayenne, Panamera, Taycan, Macan)
|
||||
2. **Mercedes-AMG** — 32 models (GT family, C/E/S/CLA/GLC/GLE/GLS/G-class, AMG ONE)
|
||||
3. **BMW** — 31 models (M2-M8, X3M-X6M, XM, Alpina, i4/i5/i7/iX)
|
||||
4. **Ferrari** — 30 models (296/SF90/F8/488/812/Roma/Purosangue/LaFerrari + halo cars)
|
||||
5. **Lamborghini** — 21 models (Huracan variants, Aventador variants, Revuelto, Temerario, Urus)
|
||||
6. **McLaren** — 21 models (720S/750S/765LT/Artura/Senna/Speedtail/GT/P1/W1)
|
||||
7. **Audi** — 16 models (RS3/5/6/7, R8, e-tron GT, SQ7/8, S8)
|
||||
8. **Aston Martin** — 14 models (Vantage, DB11/12, DBS, DBX, Valkyrie, Valhalla)
|
||||
|
||||
## JSON Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"Make": {
|
||||
"Model Name": {
|
||||
"YYYY": HP_Value,
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Updating
|
||||
|
||||
To add new vehicles:
|
||||
1. Add the new make/model/year/HP to the JSON
|
||||
2. Validate with `python3 -c "import json; json.load(open('/var/www/static/vehicles.json'))"`
|
||||
3. Verify no 2015+ entries drop below 400hp
|
||||
4. Copy to `/root/portal-mockup/vehicles.json`
|
||||
5. Update this reference document's counts
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# WPForms Vehicle Selector Integration
|
||||
|
||||
When embedding the vehicle selector inside a WPForms form, the vehicle data must be written to a WPForms-managed field for submission.
|
||||
|
||||
## Setup (user does this)
|
||||
|
||||
1. In WPForms builder, add a **Single Line Text** field
|
||||
2. Label it "Vehicle" or "Vehicle Specs"
|
||||
3. In **Advanced → CSS Classes**, enter `apex-vehicle-field`
|
||||
4. Keep the existing **HTML Content** field with the vehicle dropdown snippet
|
||||
|
||||
## JS mechanism
|
||||
|
||||
The snippet detects the WPForms field via:
|
||||
```javascript
|
||||
var wpf = document.querySelector('.apex-vehicle-field input, .apex-vehicle-field textarea');
|
||||
if(wpf) wpf.value = mk + ' ' + md + ' ' + yr + ' - ' + cl + ' - ' + hp + ' HP';
|
||||
```
|
||||
|
||||
The format string is: `year color make model hp`
|
||||
Example: `"2022 Red Ferrari F8 Tributo 710"`
|
||||
|
||||
## Critical pitfalls
|
||||
|
||||
- **Variable scope:** `hp` must be defined in the same scope as the WPForms population line. Store it as `var hp = d[mk][md][yr]` before the `wpf.value` line. If `hp` is not defined at that scope, the field stays blank.
|
||||
- **Event handler method:** Do NOT use inline `onchange="vrUpdateModels()"` — WordPress/Elementor loaders may strip them. Use `.onchange = function() {...}` or `addEventListener()` inside an IIFE.
|
||||
- **Vehicle data embedding:** Embed the JSON data as a JS object literal (`var d = {...}`), NOT fetched via `fetch()`. Elementor HTML widgets may not execute async fetches reliably.
|
||||
- **Selector debugging:** If the field doesn't populate, check if `wpf` is null in browser dev tools. The CSS class `apex-vehicle-field` must match exactly what was entered in WPForms.
|
||||
- **Notification gate:** WPForms notifications may be gated behind payment completion. If "Pay Offline" is an option and the notification never fires, check the notification settings for `paypal_commerce: "1"` or similar payment gate flags. Remove them from the notification JSON to send immediately on form submission.
|
||||
Reference in New Issue
Block a user