feat: add Atlas OS desktop PWA and service sitemap
- web/os.html: 6-screen browser-native desktop (Today/Money/Inbox/Pipeline/Fleet/Control) - web/sitemap.html: live architecture map of all 24 Atlas services - .gitignore: block secrets, env files, credentials from git - README: updated with screen list, deployment notes, tech stack Built with 12-round senior review (architect/frontend/UX/logic/security). Served at command.atlascorporation.nl/os/ behind Authentik SSO.
This commit is contained in:
parent
301cc75de9
commit
bd1d3af713
4 changed files with 304 additions and 118 deletions
13
.gitignore
vendored
13
.gitignore
vendored
|
|
@ -1,7 +1,12 @@
|
||||||
|
*.env
|
||||||
|
*.secret
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.sqlite
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
.env
|
|
||||||
*.log
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
.auth
|
||||||
node_modules/
|
secrets/
|
||||||
|
credentials/
|
||||||
|
*_creds*
|
||||||
|
|
|
||||||
130
README.md
130
README.md
|
|
@ -1,121 +1,23 @@
|
||||||
# 🛰️ Atlas OS
|
# Atlas OS
|
||||||
|
|
||||||
> Open-source patterns + tools for solo founders running a multi-node local LLM cluster + an own AI-first workspace.
|
Browser-native desktop environment for Atlas Corporation — a full PWA served behind Authentik SSO.
|
||||||
|
|
||||||
Built by [Atlas Corporation](https://atlascorporation.nl) — extracted from a working production stack. MIT licensed.
|
## Screens
|
||||||
|
|
||||||
## What's inside
|
| Screen | Data |
|
||||||
|
|--------|------|
|
||||||
|
| Today | Daily brief, AI state |
|
||||||
|
| Money | Bank transactions |
|
||||||
|
| Inbox | Email + WhatsApp drafts |
|
||||||
|
| Pipeline | CRM leads |
|
||||||
|
| Fleet | POS terminals |
|
||||||
|
| Control | Kill-switch, spend cap |
|
||||||
|
|
||||||
### Browser-native LLM surfaces (no build step, no React)
|
## Deployment
|
||||||
|
|
||||||
| File | What | Lines |
|
Served at `https://command.atlascorporation.nl/os/` behind Authentik forward_auth.
|
||||||
|---|---|---|
|
Cockpit APIs proxied server-side — no credentials in the browser.
|
||||||
| `web/atlas-chat.html` | Mobile-first chat UI for Ollama clusters + optional Claude API. Streaming, localStorage history, multi-model picker, Vault RAG toggle | ~520 |
|
|
||||||
| `web/atlas-hud.html` | Iron Man-style ambient HUD: camera feed + face detection (face-api.js) + circular audio waveform + Web Speech API ASR/TTS | ~600 |
|
|
||||||
| `web/atlas-command.html` | Unified cockpit: 6 tabs (Chat / Brief / Buy? / Autonomy / HUD / Portal). Live cluster + finance + strategy + R10 cards | ~800 |
|
|
||||||
|
|
||||||
Drop in any web server. Single-file each. No npm install.
|
## Tech stack
|
||||||
|
|
||||||
### Python automation
|
Vanilla JS PWA · Authentik SSO · Evolution API (WhatsApp) · Caddy · Docker · Hetzner
|
||||||
|
|
||||||
| Script | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `scripts/cluster.py` | Multi-node Ollama orchestrator (task → node → model routing, fallback chain) |
|
|
||||||
| `scripts/cluster_then_polish.py` | Two-pass LLM pattern: cluster generates draft → flagged `polish_status: pending` |
|
|
||||||
| `scripts/atlas_self_prompting_loop.py` | Self-perpetuating autonomy: 8 topic templates × N nodes |
|
|
||||||
| `scripts/atlas_tray.py` | pystray system tray icon — color-coded cluster health (polls every 30s) |
|
|
||||||
| `scripts/atlas_seasonal_strategy.py` | NL fiscal calendar awareness (vakantiegeld, BTW deadlines, schoolvakantie, Sint/Kerst) |
|
|
||||||
| `scripts/atlas_finance_export.py` | NL freelancer Inkomsten xlsx parser (Betaalcontrole sheet pattern) → JSON |
|
|
||||||
| `scripts/atlas_reminders.py` | Burden-remover daily schtask: monthly Inkomsten + quarterly BTW + savings + IB prep |
|
|
||||||
| `scripts/atlas_activity_export.py` | ActivityWatch → R10 hours ratio (sales vs infra) for daily/weekly discipline |
|
|
||||||
| `scripts/atlas_autonomy_export.py` | Expose autonomy loop state via JSON for live dashboards |
|
|
||||||
| `scripts/vault_rag_expand.py` | Batch upload Markdown to Open WebUI Knowledge Base via API |
|
|
||||||
| `scripts/atlas_lead_enrich.py` | Per-prospect personalized cold-WhatsApp generator (NL sectors) |
|
|
||||||
|
|
||||||
### Patterns documented
|
|
||||||
|
|
||||||
- `patterns/CLUSTER_THEN_POLISH.md` — Local 7B-8B for volume, Claude for signal
|
|
||||||
- `patterns/ANTI_IMPULSE_RULES.md` — R1-R10 cash-before-code rules
|
|
||||||
|
|
||||||
## Why this exists
|
|
||||||
|
|
||||||
After 12 months of solo founder building, the patterns that actually survived. Specifically for the founder who:
|
|
||||||
- Runs Ollama or similar local LLM (on PC + VPS + secondary desktop)
|
|
||||||
- Wants Atlas-style live dashboard, not enterprise BI
|
|
||||||
- Uses Obsidian as second brain
|
|
||||||
- Needs Dutch fiscal calendar awareness
|
|
||||||
- Hates WordPress + Elementor for AI-edit-friendly new builds
|
|
||||||
- Wants "cash before code" enforced by automation, not willpower
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://git.atlascorporation.nl/chaib/atlas-os.git
|
|
||||||
cd atlas-os
|
|
||||||
```
|
|
||||||
|
|
||||||
### Web surfaces
|
|
||||||
Serve any `web/*.html` via Caddy / nginx / Cloudflare Pages. Each is self-contained (CDN-loaded marked.js + DOMPurify).
|
|
||||||
|
|
||||||
### Python tools
|
|
||||||
```bash
|
|
||||||
cd scripts
|
|
||||||
pip install -r requirements.txt # pystray, pillow, openpyxl
|
|
||||||
# Run individual scripts; each documents its env vars
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Each tool has `<YOUR_*>` placeholders. Replace with your:
|
|
||||||
- VPS IP (Hetzner / DigitalOcean / wherever Ollama runs always-on)
|
|
||||||
- Desktop node IP (your secondary GPU machine)
|
|
||||||
- PC node IP (your daily-driver localhost or tailnet IP)
|
|
||||||
- Open WebUI JWT (for Vault RAG queries)
|
|
||||||
- Knowledge Base ID
|
|
||||||
- SMTP credentials (for reminder emails)
|
|
||||||
- Domain
|
|
||||||
|
|
||||||
See individual file headers.
|
|
||||||
|
|
||||||
## Architecture (the actual production setup)
|
|
||||||
|
|
||||||
```
|
|
||||||
Operator PC (Windows)
|
|
||||||
- Ollama (GPU) — ~10 models
|
|
||||||
- Atlas Command HTML served via Caddy on VPS
|
|
||||||
- Atlas Tray Python (taskbar — always visible)
|
|
||||||
- atlas_self_prompting_loop schtask (every 15 min, 12h windows)
|
|
||||||
|
|
|
||||||
v Tailscale mesh (encrypted overlay)
|
|
||||||
|
|
|
||||||
VPS (Hetzner CPX32 — always-on)
|
|
||||||
- Ollama (CPU) — 5 small/medium models
|
|
||||||
- Caddy reverse proxy
|
|
||||||
- Open WebUI (Vault RAG, multi-tenant chats)
|
|
||||||
- Odoo / Nextcloud / Vaultwarden / MeshCentral / Forgejo (this repo!)
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Secondary desktop (GPU node)
|
|
||||||
- Ollama — Llama 3.1 8B + smaller
|
|
||||||
```
|
|
||||||
|
|
||||||
Total monthly hosting cost: ~€25 (VPS) + electricity for PC + desktop. No SaaS subscriptions.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT. Use freely. Attribution appreciated but not required.
|
|
||||||
|
|
||||||
## Roadmap (PRs welcome)
|
|
||||||
|
|
||||||
- Astro + Decap CMS template for AI-edit-friendly websites
|
|
||||||
- OpenWakeWord OS-level voice trigger (replaces browser-tab requirement)
|
|
||||||
- Multi-tenant pattern for solo agencies running N client sites
|
|
||||||
- Local sentence-transformer embeddings (replace Open WebUI dependency)
|
|
||||||
- Cluster-of-experts pattern for narrative tasks (rotate prompt across nodes, vote on best)
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
Built by [@chaib](https://atlascorporation.nl) · Tilburg, NL · 2025-2026.
|
|
||||||
Patterns surfaced through 8 months of daily live use + ruthless removal of what didn't earn its keep.
|
|
||||||
|
|
||||||
> One person, one stack, one workspace. Atlas because it carries the load.
|
|
||||||
|
|
|
||||||
230
web/os.html
Normal file
230
web/os.html
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
|
<meta name="theme-color" content="#0b1020">
|
||||||
|
<title>Atlas OS</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/2.47.0/iconfont/tabler-icons.min.css">
|
||||||
|
<style>
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
html,body{height:100%;background:#0b1020;color:#e8edf7;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;overflow:hidden;user-select:none}
|
||||||
|
#boot,#login{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;background:#0b1020;z-index:2000}
|
||||||
|
#login{display:none}
|
||||||
|
.bar{width:220px;height:4px;background:#1b2742;border-radius:4px;overflow:hidden}
|
||||||
|
.bar>div{width:0;height:100%;background:#e0a234;transition:width .3s}
|
||||||
|
.mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;color:#7e8db0}
|
||||||
|
#desktop{position:fixed;inset:0;display:none;opacity:0;transition:opacity .28s ease;background:radial-gradient(1200px 800px at 78% -10%, #16213f 0%, transparent 60%),radial-gradient(900px 700px at -5% 110%, #14233a 0%, transparent 55%),#0b1020}
|
||||||
|
#desktop.in{opacity:1}
|
||||||
|
#topbar{position:fixed;top:0;left:0;right:0;height:calc(38px + env(safe-area-inset-top));padding-top:env(safe-area-inset-top);padding-left:calc(14px + env(safe-area-inset-left));padding-right:calc(14px + env(safe-area-inset-right));background:#11182b;border-bottom:.5px solid #233252;display:flex;align-items:center;gap:10px;z-index:1000}
|
||||||
|
#icons{position:fixed;top:calc(54px + env(safe-area-inset-top));left:calc(14px + env(safe-area-inset-left));display:grid;grid-template-columns:repeat(2,80px);gap:14px;z-index:10}
|
||||||
|
.ico{display:flex;flex-direction:column;align-items:center;gap:5px;cursor:pointer;width:80px;border-radius:12px;padding:4px 0}
|
||||||
|
.ico .b{width:48px;height:48px;border-radius:12px;background:#141d33;border:.5px solid #2a3a5e;display:flex;align-items:center;justify-content:center;font-size:23px;transition:transform .12s ease,border-color .12s ease,background .12s ease}
|
||||||
|
.ico span{font-size:11px;color:#cfd9ee}
|
||||||
|
.ico:hover .b{transform:translateY(-2px);border-color:#3c5286;background:#19243f}
|
||||||
|
.ico:active .b{transform:scale(.94)}
|
||||||
|
.win{position:fixed;width:330px;background:#141d33;border:.5px solid #2a3a5e;border-radius:11px;overflow:hidden;z-index:20;box-shadow:0 8px 30px rgba(0,0,0,.4)}
|
||||||
|
.tb{display:flex;align-items:center;gap:8px;padding:9px 11px;background:#1b2742;cursor:move;touch-action:none}
|
||||||
|
.tb i:first-child{font-size:15px}
|
||||||
|
.tb span{font-size:13px;font-weight:500;flex:1}
|
||||||
|
.tb .x{cursor:pointer;color:#9fb0cc;font-size:16px;border-radius:6px;transition:color .12s ease,background .12s ease}
|
||||||
|
.tb .x:hover{color:#fff;background:#e2655f}
|
||||||
|
.tb .r{cursor:pointer;color:#9fb0cc;font-size:15px;border-radius:6px;transition:color .12s ease,background .12s ease}
|
||||||
|
.tb .r:hover{color:#fff;background:#2a3a5e}
|
||||||
|
.tb .r:focus-visible{outline:2px solid #e0a234;outline-offset:2px}
|
||||||
|
.body{padding:12px;max-height:60vh;overflow:auto;font-size:13px}
|
||||||
|
.card{background:#0f1730;border-radius:8px;padding:9px 11px}
|
||||||
|
.card .l{font-size:11px;color:#7e8db0}.card .v{font-size:19px;font-weight:500}
|
||||||
|
.row{display:flex;justify-content:space-between;padding:7px 4px;border-bottom:.5px solid #1b2742;font-size:12px}
|
||||||
|
a.btn,button.btn{display:inline-block;background:#e0a234;color:#0b1020;text-decoration:none;padding:8px 20px;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;transition:filter .12s ease,transform .12s ease}
|
||||||
|
a.btn:hover,button.btn:hover{filter:brightness(1.08)}
|
||||||
|
a.btn:active,button.btn:active{transform:scale(.97)}
|
||||||
|
#taskbar{position:fixed;bottom:0;left:0;right:0;height:calc(30px + env(safe-area-inset-bottom));padding-bottom:env(safe-area-inset-bottom);padding-left:calc(12px + env(safe-area-inset-left));padding-right:calc(12px + env(safe-area-inset-right));background:#11182b;border-top:.5px solid #233252;display:flex;align-items:center;gap:8px;font-size:11px;color:#5e6e92;z-index:1000}
|
||||||
|
#tbtag{flex:0 0 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:50%}
|
||||||
|
#tbwins{flex:1 1 auto;display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap}
|
||||||
|
#tbwins::-webkit-scrollbar{height:0}
|
||||||
|
.tbchip{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;background:#1b2742;border:.5px solid #2a3a5e;color:#cfd9ee;font-size:11px;padding:3px 9px;border-radius:7px;cursor:pointer;font-family:inherit;line-height:1.4;transition:border-color .12s ease,background .12s ease}
|
||||||
|
.tbchip:hover{border-color:#3c5286;background:#19243f}
|
||||||
|
.tbchip.active{border-color:#e0a234;color:#fff}
|
||||||
|
.tbchip i{font-size:13px}
|
||||||
|
#home{transition:background .12s ease}
|
||||||
|
#home:hover{background:#1b2742}
|
||||||
|
#acct{display:flex;align-items:center;gap:6px;background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:2px 4px;border-radius:7px;transition:background .12s ease}
|
||||||
|
#acct:hover{background:#1b2742}
|
||||||
|
#acctav{width:22px;height:22px;border-radius:50%;background:#1b2742;border:.5px solid #e0a234;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;color:#e0a234;flex:0 0 auto}
|
||||||
|
#acctmenu{position:fixed;top:calc(46px + env(safe-area-inset-top));right:calc(14px + env(safe-area-inset-right));background:#141d33;border:.5px solid #2a3a5e;border-radius:9px;box-shadow:0 8px 30px rgba(0,0,0,.4);overflow:hidden;z-index:1001;display:none;min-width:150px}
|
||||||
|
#acctmenu button{display:flex;align-items:center;gap:8px;width:100%;background:none;border:none;color:#cfd9ee;font-family:inherit;font-size:13px;text-align:left;padding:10px 14px;cursor:pointer}
|
||||||
|
#acctmenu button:hover{background:#1b2742}
|
||||||
|
#acctmenu i{font-size:15px;color:#9fb0cc}
|
||||||
|
#enter[disabled]{opacity:.6;cursor:progress}
|
||||||
|
@keyframes pulse{0%,100%{opacity:.55}50%{opacity:1}}
|
||||||
|
.skel{display:flex;flex-direction:column;gap:8px}
|
||||||
|
.skel>div{border-radius:8px;background:#0f1730;animation:pulse 1.1s ease-in-out infinite}
|
||||||
|
@media (prefers-reduced-motion: reduce){*,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}}
|
||||||
|
:focus-visible{outline:2px solid #e0a234;outline-offset:2px}
|
||||||
|
.ico:focus-visible,.tb .x:focus-visible,a.btn:focus-visible,button.btn:focus-visible{outline:2px solid #e0a234;outline-offset:2px}
|
||||||
|
@media (max-width:560px){
|
||||||
|
#icons{position:static;left:auto;top:auto;grid-template-columns:repeat(4,1fr);gap:12px;padding:calc(54px + env(safe-area-inset-top)) calc(14px + env(safe-area-inset-right)) 14px calc(14px + env(safe-area-inset-left));width:100%}
|
||||||
|
.ico{width:auto}
|
||||||
|
.win{left:0!important;right:0!important;top:calc(38px + env(safe-area-inset-top))!important;bottom:calc(30px + env(safe-area-inset-bottom));width:auto!important;border-radius:0;border-left:none;border-right:none;display:flex;flex-direction:column}
|
||||||
|
.body{max-height:none;flex:1 1 auto;min-height:0}
|
||||||
|
#taskbar{font-size:10px}
|
||||||
|
.tb .x{font-size:20px;padding:4px}
|
||||||
|
.tb .r{font-size:19px;padding:4px}
|
||||||
|
#tbtag{display:none}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="boot">
|
||||||
|
<svg width="68" height="68" viewBox="0 0 64 64"><path d="M32 8 L56 56 L40 56 L32 38 L24 56 L8 56 Z" fill="#e0a234"/><path d="M32 26 L38 40 L26 40 Z" fill="#0b1020"/></svg>
|
||||||
|
<div style="font-size:24px;font-weight:500;letter-spacing:2px">ATLAS OS</div>
|
||||||
|
<div class="bar"><div id="bbar"></div></div>
|
||||||
|
<div class="mono" id="bmsg">initializing kernel…</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="login">
|
||||||
|
<div id="lclock" style="font-size:42px;font-weight:500">—</div>
|
||||||
|
<div style="font-size:13px;color:#9fb0cc;margin-bottom:6px" id="ldate">—</div>
|
||||||
|
<div id="lava" style="width:66px;height:66px;border-radius:50%;background:#1b2742;border:.5px solid #e0a234;display:flex;align-items:center;justify-content:center;font-size:23px;font-weight:500;color:#e0a234">··</div>
|
||||||
|
<div id="lname" style="font-size:16px;font-weight:500">…</div>
|
||||||
|
<div id="lsso" style="font-size:12px;color:#7e8db0">Atlas Corporation · connecting to Authentik SSO…</div>
|
||||||
|
<button id="enter" style="margin-top:10px;background:#e0a234;color:#0b1020;border:none;padding:10px 28px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer">Enter desktop</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="desktop">
|
||||||
|
<div id="topbar">
|
||||||
|
<button id="home" aria-label="Home — show app grid" title="Home" style="display:flex;align-items:center;gap:10px;background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:2px 4px;border-radius:7px"><svg width="17" height="17" viewBox="0 0 64 64"><path d="M32 8 L56 56 L40 56 L32 38 L24 56 L8 56 Z" fill="#e0a234"/></svg><span style="font-size:13px;font-weight:500">Atlas OS</span></button>
|
||||||
|
<span style="flex:1"></span>
|
||||||
|
<i id="tbwifi" class="ti ti-wifi" style="color:#4ec98a" aria-hidden="true" title="Live data connected"></i>
|
||||||
|
<i class="ti ti-shield-check" style="color:#4ec98a" aria-hidden="true" title="Identity verified via SSO"></i>
|
||||||
|
<span id="tclock" style="font-size:12px;color:#cfd9ee"></span>
|
||||||
|
<button id="acct" aria-haspopup="menu" aria-expanded="false" aria-label="Account"><span id="acctav">··</span><span id="acctname" style="font-size:12px;color:#cfd9ee"></span><i class="ti ti-chevron-down" style="font-size:13px;color:#7e8db0"></i></button>
|
||||||
|
</div>
|
||||||
|
<div id="acctmenu" role="menu" aria-label="Account">
|
||||||
|
<button id="acctlock" role="menuitem"><i class="ti ti-lock"></i>Lock</button>
|
||||||
|
<button id="acctout" role="menuitem"><i class="ti ti-logout"></i>Sign out</button>
|
||||||
|
</div>
|
||||||
|
<div id="icons"></div>
|
||||||
|
<div id="taskbar"><span id="tbtag">Atlas OS 1.0 · your business on one box · live behind your identity login</span><span id="tbwins"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var COCK="/os/cockpit";
|
||||||
|
var apps=[
|
||||||
|
{id:"money",name:"Money",icon:"ti-coin",c:"#e0a234",api:"/api/money",render:function(j){var shown=(j.tx||[]).slice(0,8);var rows=shown.map(function(t){var neg=t.amount<0;return '<div class="row"><span style="color:#9fb0cc">'+esc(t.date)+'</span><span style="flex:1;margin:0 8px;color:#cfd9ee">'+esc(t.cp)+'</span><span style="color:'+(neg?"#e2655f":"#4ec98a")+'">'+eur(t.amount)+'</span></div>';}).join("");
|
||||||
|
var net=shown.reduce(function(s,t){return s+(+t.amount||0);},0);
|
||||||
|
return '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px"><div class="card"><div class="l">Net (recent)</div><div class="v" style="color:'+(net<0?"#e2655f":"#4ec98a")+'">'+eur(net)+'</div></div><div class="card"><div class="l">Transactions</div><div class="v">'+(j.count||0)+' <i class="ti ti-check" style="font-size:13px;color:#4ec98a" title="Bank synced"></i></div></div></div>'+rows;}},
|
||||||
|
{id:"inbox",name:"Inbox",icon:"ti-mail",c:"#4a9fe2",api:"/api/inbox",render:function(j){var d=(j.drafts||[]);if(!d.length)return '<div style="color:#9fb0cc">Inbox clear.</div>';return d.slice(0,8).map(function(m){return '<div style="padding:8px;border-left:2px solid #4a9fe2;background:#0f1730;border-radius:0 8px 8px 0;margin-bottom:6px"><div style="font-size:12px;font-weight:500">'+esc(m.target||m.subject||m.channel)+'</div><div style="font-size:11px;color:#7e8db0">'+esc(m.channel||"")+'</div></div>';}).join("");}},
|
||||||
|
{id:"pipeline",name:"Pipeline",icon:"ti-chart-arrows",c:"#4ec98a",api:"/api/pipeline",render:function(j){var l=(j.leads||[]);if(!l.length)return '<div style="color:#9fb0cc">No open leads.</div>';return l.slice(0,8).map(function(x){return '<div style="padding:8px;background:#0f1730;border-radius:8px;margin-bottom:6px"><div style="font-size:12px;font-weight:500">'+esc(x.name)+'</div><div style="font-size:11px;color:#7e8db0">'+esc(x.contact||"")+'</div></div>';}).join("");}},
|
||||||
|
{id:"today",name:"Today",icon:"ti-sun",c:"#e0a234",api:"/api/state",render:function(j){var bt=String(j.brief==null?"":j.brief).replace(/\*/g,"");var head=bt.split("\n")[0]||"Atlas Daily Brief";var ni=bt.indexOf("\n");var rest=(ni<0?"":bt.slice(ni+1)).trim().slice(0,600);var bodyHtml=rest?'<div style="font-size:12px;color:#9fb0cc;line-height:1.6;white-space:pre-wrap">'+esc(rest)+'</div>':'';return '<div style="font-size:13px;font-weight:500;margin-bottom:6px">'+esc(head)+'</div>'+bodyHtml;}},
|
||||||
|
{id:"fleet",name:"Fleet",icon:"ti-devices",c:"#9b8cf0",api:"/api/fleet",render:function(j){var c=(j.customers||[]);return c.map(function(x){return '<div style="padding:8px;background:#0f1730;border-radius:8px;margin-bottom:6px"><div style="font-size:12px;font-weight:500">'+esc(x.name)+'</div><div style="font-size:11px;color:#7e8db0">'+((x.terminals||[]).length)+' terminal(s)</div></div>';}).join("")||'<div style="color:#9fb0cc">No devices.</div>';}},
|
||||||
|
{id:"control",name:"Control",icon:"ti-adjustments",c:"#e2655f",api:"/api/control",render:function(j){return '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"><div class="card"><div class="l">Autonomy</div><div class="v" style="color:#4ec98a">'+esc(j.level||"L0")+'</div></div><div class="card"><div class="l">Kill switch</div><div class="v" style="color:'+(j.kill_switch?"#e2655f":"#4ec98a")+'">'+(j.kill_switch?"ON":"OFF")+'</div></div><div class="card"><div class="l">Canonical</div><div class="v" style="font-size:14px">'+esc(j.canonical||"")+'</div></div><div class="card"><div class="l">Spend cap</div><div class="v">'+eur(j.spend_cap_day||0)+'/d</div></div></div>';}},
|
||||||
|
{id:"files",name:"Nextcloud",icon:"ti-folder",c:"#4a9fe2",url:"https://cloud.atlascorporation.nl"},
|
||||||
|
{id:"cal",name:"Calendar",icon:"ti-calendar",c:"#4ec98a",url:"https://cloud.atlascorporation.nl/apps/calendar"},
|
||||||
|
{id:"odoo",name:"Odoo",icon:"ti-building-store",c:"#9b8cf0",url:"https://odoo.atlascorporation.nl"},
|
||||||
|
{id:"git",name:"Git",icon:"ti-git-branch",c:"#e0a234",url:"https://git.atlascorporation.nl"},
|
||||||
|
{id:"n8n",name:"n8n",icon:"ti-route",c:"#e2655f",url:"https://n8n.atlascorporation.nl"},
|
||||||
|
{id:"vault",name:"Vault",icon:"ti-lock",c:"#4a9fe2",url:"https://vault.atlascorporation.nl"},
|
||||||
|
{id:"uptime",name:"Uptime",icon:"ti-activity",c:"#4ec98a",url:"https://uptime.atlascorporation.nl"},
|
||||||
|
{id:"map",name:"Command",icon:"ti-map-2",c:"#4a9fe2",url:"https://command.atlascorporation.nl"}
|
||||||
|
];
|
||||||
|
function eur(n){try{return new Intl.NumberFormat("nl-NL",{style:"currency",currency:"EUR"}).format(n);}catch(e){return "€"+n;}}
|
||||||
|
function esc(s){return String(s==null?"":s).replace(/[&<>]/g,function(c){return{"&":"&","<":"<",">":">"}[c];});}
|
||||||
|
function netHealth(ok){var w=document.getElementById("tbwifi");if(!w)return;w.style.color=ok?"#4ec98a":"#e2655f";w.className="ti "+(ok?"ti-wifi":"ti-wifi-off");w.title=ok?"Live data connected":"Live data unavailable";}
|
||||||
|
var MOBILE=function(){return window.matchMedia("(max-width:560px)").matches;};
|
||||||
|
function bringToFront(d){var BASE=12;var ws=[].slice.call(document.querySelectorAll(".win"));ws.sort(function(a,b){return (parseInt(a.style.zIndex,10)||BASE)-(parseInt(b.style.zIndex,10)||BASE);});var i=0;ws.forEach(function(w){if(w!==d){w.style.zIndex=BASE+(i++);}});if(d){d.style.zIndex=BASE+ws.length;}renderTaskbar();}
|
||||||
|
function topH(){var t=document.getElementById("topbar");return t?t.offsetHeight:38;}
|
||||||
|
function botH(){var t=document.getElementById("taskbar");return t?t.offsetHeight:30;}
|
||||||
|
function maxTop(){return Math.max(topH(),window.innerHeight-botH()-38);}
|
||||||
|
function clampWin(el){if(MOBILE())return;var mL=Math.max(0,window.innerWidth-el.offsetWidth),mT=maxTop();el.style.left=Math.min(mL,Math.max(0,el.offsetLeft))+"px";el.style.top=Math.min(mT,Math.max(topH(),el.offsetTop))+"px";}
|
||||||
|
function focusIcon(name){var ic=document.querySelector('.ico[aria-label="'+name+'"]');if(ic)ic.focus();}
|
||||||
|
function closeWin(d,a){if(d&&d._ac){try{d._ac.abort();}catch(e){}}d.remove();renderTaskbar();focusIcon(a.name);}
|
||||||
|
function destroyAllWindows(){document.querySelectorAll(".win").forEach(function(w){if(w._ac){try{w._ac.abort();}catch(e){}}w.remove();});renderTaskbar();}
|
||||||
|
function raiseWin(d){bringToFront(d);clampWin(d);try{d.focus();}catch(e){}}
|
||||||
|
function renderTaskbar(){var c=document.getElementById("tbwins");if(!c)return;var ws=document.querySelectorAll(".win"),topEl=null,mx=-1;for(var i=0;i<ws.length;i++){var zi=parseInt(ws[i].style.zIndex,10)||0;if(zi>=mx){mx=zi;topEl=ws[i];}}apps.forEach(function(a){var d=document.getElementById("w-"+a.id);var chip=document.getElementById("tc-"+a.id);if(!d){if(chip)chip.remove();return;}if(!chip){chip=document.createElement("button");chip.id="tc-"+a.id;chip.className="tbchip";chip.setAttribute("aria-label","Switch to "+a.name);chip.innerHTML='<i class="ti '+a.icon+'" style="color:'+a.c+'"></i>'+esc(a.name);chip.onclick=function(){raiseWin(d);};c.appendChild(chip);}var active=(d===topEl);if(chip.classList.contains("active")!==active)chip.classList.toggle("active",active);});}
|
||||||
|
function win(a){
|
||||||
|
if(a.url&&MOBILE()){window.open(a.url,"_blank","noopener");return;}
|
||||||
|
var ex=document.getElementById("w-"+a.id); if(ex){raiseWin(ex);if(!a.url&&(ex.dataset.s==="error"||ex.dataset.s==="loading")){load(a,ex.querySelector(".body"));}return;}
|
||||||
|
var d=document.createElement("div");d.className="win";d.id="w-"+a.id;
|
||||||
|
d.setAttribute("role","dialog");d.setAttribute("aria-label",a.name);d.setAttribute("tabindex","-1");
|
||||||
|
d.style.left=(60+Math.round(Math.random()*60))+"px";d.style.top=(60+Math.round(Math.random()*40))+"px";d.style.zIndex=20;
|
||||||
|
d.innerHTML='<div class="tb"><i class="ti '+a.icon+'" style="color:'+a.c+'"></i><span>'+a.name+'</span>'+(a.api?'<i class="ti ti-refresh r" role="button" tabindex="0" aria-label="Refresh '+a.name+'"></i>':'')+'<i class="ti ti-x x" role="button" tabindex="0" aria-label="Close '+a.name+'"></i></div><div class="body" id="b-'+a.id+'" role="status" aria-live="polite">'+(a.api?SKEL:'')+'</div>';
|
||||||
|
document.getElementById("desktop").appendChild(d);
|
||||||
|
clampWin(d);
|
||||||
|
var xb=d.querySelector(".x");xb.onclick=function(){closeWin(d,a);};xb.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();closeWin(d,a);}});
|
||||||
|
var rf=d.querySelector(".r");if(rf){rf.onclick=function(){load(a,d.querySelector(".body"));};rf.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();load(a,d.querySelector(".body"));}});}
|
||||||
|
d.addEventListener("pointerdown",function(){bringToFront(d);});
|
||||||
|
drag(d,d.querySelector(".tb"));
|
||||||
|
bringToFront(d);
|
||||||
|
try{d.focus();}catch(e){}
|
||||||
|
var b=d.querySelector(".body");
|
||||||
|
if(a.url){b.innerHTML='<div style="text-align:center;padding:18px"><i class="ti '+a.icon+'" style="font-size:38px;color:'+a.c+'"></i><div style="margin:10px 0;color:#cfd9ee">'+a.name+'</div><a class="btn" href="'+a.url+'" target="_blank" rel="noopener">Open '+a.name+' ↗</a></div>';return;}
|
||||||
|
load(a,b);
|
||||||
|
}
|
||||||
|
var SKEL='<div class="skel"><div style="height:46px"></div><div style="height:14px;width:70%"></div><div style="height:14px;width:50%"></div></div>';
|
||||||
|
function load(a,b,silent){
|
||||||
|
var w=b.closest(".win");if(w)w.dataset.s="loading";
|
||||||
|
if(w&&w._ac){try{w._ac.abort();}catch(e){}}
|
||||||
|
if(!silent)b.innerHTML=SKEL;
|
||||||
|
var ac=new AbortController();if(w)w._ac=ac;var gen=(w?(w._g=(w._g||0)+1):0);var to=setTimeout(function(){ac.abort();},8000);
|
||||||
|
fetch(COCK+a.api,{credentials:"include",signal:ac.signal}).then(function(r){
|
||||||
|
clearTimeout(to);
|
||||||
|
if(!r.ok){var e=new Error("HTTP "+r.status);e.status=r.status;throw e;}
|
||||||
|
var ct=r.headers.get("content-type")||"";if(ct.indexOf("application/json")<0){var ne=new Error("non-json");ne.auth=true;throw ne;}
|
||||||
|
return r.json();
|
||||||
|
}).then(function(j){if(w&&w._g!==gen)return;netHealth(true);var html;try{html=render(a,j);}catch(re){if(silent&&w&&w.dataset.t)return;if(w)w.dataset.s="error";b.innerHTML='<div style="color:#e2655f">This view could not render the latest data.</div>';return;}var sc=b.scrollTop;b.innerHTML=html;if(silent)b.scrollTop=sc;if(w){w.dataset.s="ok";w.dataset.t=Date.now();}}).catch(function(err){
|
||||||
|
clearTimeout(to);
|
||||||
|
if(err&&(err.name==="AbortError"||ac.signal.aborted))return;
|
||||||
|
if(w&&w._g!==gen)return;
|
||||||
|
if(w)w.dataset.s="error";
|
||||||
|
var st=err&&err.status;var authFail=(err&&err.auth)||st===401||st===403;
|
||||||
|
if(!authFail)netHealth(false);
|
||||||
|
var msg=authFail?"Session expired — reload to sign in.":"Could not load live data. The cockpit may be waking up.";
|
||||||
|
if(silent&&w&&w.dataset.t){w.dataset.s="ok";return;}
|
||||||
|
b.innerHTML='<div><div style="color:#e2655f">'+msg+'</div><div style="margin-top:8px"><button class="btn" id="rt-'+a.id+'" style="border:none;cursor:pointer;font-family:inherit">Retry</button></div></div>';
|
||||||
|
var rb=document.getElementById("rt-"+a.id);if(rb){rb.onclick=function(){load(a,b);};if(!silent)rb.focus();}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function stamp(ts){var n=new Date(ts||Date.now()),h=String(n.getHours()).padStart(2,"0"),m=String(n.getMinutes()).padStart(2,"0");return '<div style="font-size:11px;color:#7e8db0;margin-top:10px">updated '+h+':'+m+'</div>';}
|
||||||
|
function render(a,j){
|
||||||
|
var body=a&&a.render?a.render(j):'<pre style="font-size:11px;white-space:pre-wrap">'+esc(JSON.stringify(j,null,2).slice(0,800))+'</pre>';
|
||||||
|
return body+stamp();
|
||||||
|
}
|
||||||
|
var dragTarget=null,dragOX=0,dragOY=0;
|
||||||
|
document.addEventListener("pointermove",function(e){if(!dragTarget)return;var maxL=Math.max(0,window.innerWidth-dragTarget.offsetWidth);var maxT=maxTop();dragTarget.style.left=Math.min(maxL,Math.max(0,e.clientX-dragOX))+"px";dragTarget.style.top=Math.min(maxT,Math.max(topH(),e.clientY-dragOY))+"px";});
|
||||||
|
document.addEventListener("pointerup",function(){dragTarget=null;});
|
||||||
|
document.addEventListener("pointercancel",function(){dragTarget=null;});
|
||||||
|
function drag(w,h){h.addEventListener("pointerdown",function(e){if(MOBILE())return;dragTarget=w;dragOX=e.clientX-w.offsetLeft;dragOY=e.clientY-w.offsetTop;e.preventDefault();});}
|
||||||
|
function clampAll(){var ws=document.querySelectorAll(".win");for(var i=0;i<ws.length;i++){clampWin(ws[i]);}}
|
||||||
|
var rT;window.addEventListener("resize",function(){clearTimeout(rT);rT=setTimeout(clampAll,120);});
|
||||||
|
function icons(){var g=document.getElementById("icons");if(g.childElementCount)return;apps.forEach(function(a){var d=document.createElement("div");d.className="ico";d.setAttribute("role","button");d.setAttribute("tabindex","0");d.setAttribute("aria-label",a.name);d.innerHTML='<div class="b"><i class="ti '+a.icon+'" style="color:'+a.c+'"></i></div><span>'+a.name+'</span>';d.onclick=function(){win(a);};d.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();win(a);}});g.appendChild(d);});}
|
||||||
|
document.addEventListener("keydown",function(e){if(e.key==="Escape"){var am=document.getElementById("acctmenu");if(am&&am.style.display==="block"){closeAcctMenu();var at=document.getElementById("acct");if(at){try{at.focus();}catch(e){}}return;}var ws=document.querySelectorAll(".win"),top=null,mx=-1;for(var i=0;i<ws.length;i++){var zi=parseInt(ws[i].style.zIndex,10)||0;if(zi>=mx){mx=zi;top=ws[i];}}if(top){var aid=top.id.slice(2);var ap=null;for(var k=0;k<apps.length;k++){if(apps[k].id===aid){ap=apps[k];break;}}if(ap){closeWin(top,ap);}else{top.remove();renderTaskbar();}}}});
|
||||||
|
function tick(){var n=new Date(),h=String(n.getHours()).padStart(2,"0"),m=String(n.getMinutes()).padStart(2,"0");["lclock","tclock"].forEach(function(i){var e=document.getElementById(i);if(e)e.textContent=h+":"+m;});var dt=document.getElementById("ldate");if(dt)dt.textContent=n.toLocaleDateString("nl-NL",{weekday:"long",day:"numeric",month:"long"});}
|
||||||
|
setInterval(tick,1000);tick();
|
||||||
|
function autoRefresh(){if(document.hidden)return;apps.forEach(function(a){if(!a.api)return;var d=document.getElementById("w-"+a.id);if(d&&d.dataset.s==="ok"){load(a,d.querySelector(".body"),true);}});}
|
||||||
|
setInterval(autoRefresh,60000);
|
||||||
|
var who={user:"Operator",initials:"··"};
|
||||||
|
var whoReady=false;
|
||||||
|
function readyEnter(){whoReady=true;var en=document.getElementById("enter");if(en){en.disabled=false;en.removeAttribute("aria-busy");en.textContent="Enter desktop";}}
|
||||||
|
fetch("/os/whoami",{credentials:"include"}).then(function(r){if(!r.ok)throw new Error("HTTP "+r.status);var ct=r.headers.get("content-type")||"";if(ct.indexOf("application/json")<0)throw new Error("non-json");return r.json();}).then(function(j){if(j&&(j.user||j.username)){who.user=j.user||j.username;var p=who.user.trim().split(/\s+/);who.initials=((p[0]||"")[0]||"")+((p[1]||"")[0]||"");document.getElementById("lname").textContent=who.user;document.getElementById("lava").textContent=who.initials.toUpperCase()||"··";document.getElementById("acctav").textContent=who.initials.toUpperCase()||"··";document.getElementById("acctname").textContent=who.user;var ls=document.getElementById("lsso");if(ls)ls.textContent="Atlas Corporation · identity verified via Authentik SSO";}else{document.getElementById("lname").textContent="Operator";var ls2=document.getElementById("lsso");if(ls2){ls2.textContent="Atlas Corporation · identity not verified";ls2.style.color="#e2655f";}}readyEnter();}).catch(function(){document.getElementById("lname").textContent="Operator";var ls=document.getElementById("lsso");if(ls){ls.textContent="Atlas Corporation · identity not verified";ls.style.color="#e2655f";}readyEnter();});
|
||||||
|
var msgs=["initializing kernel…","mounting /opt/atlas services…","connecting bank…","loading mind · daily brief…","verifying identity…","ready."];var p=0;var bi=null;
|
||||||
|
function finishBoot(){if(bi){clearInterval(bi);bi=null;}try{sessionStorage.setItem("atlasBooted","1");}catch(e){}document.getElementById("boot").style.display="none";document.getElementById("login").style.display="flex";var en=document.getElementById("enter");if(en){if(!whoReady){en.disabled=true;en.setAttribute("aria-busy","true");en.textContent="Resolving identity…";}try{en.focus();}catch(e){}}}
|
||||||
|
var booted=false;try{booted=sessionStorage.getItem("atlasBooted")==="1";}catch(e){}
|
||||||
|
if(booted){finishBoot();}else{
|
||||||
|
bi=setInterval(function(){p++;document.getElementById("bbar").style.width=Math.min(100,p*20)+"%";document.getElementById("bmsg").textContent=msgs[Math.min(p,msgs.length-1)];if(p>=5){clearInterval(bi);bi=null;setTimeout(finishBoot,500);}},420);
|
||||||
|
document.getElementById("boot").addEventListener("click",finishBoot);
|
||||||
|
document.addEventListener("keydown",function bskip(e){if(document.getElementById("boot").style.display!=="none"){e.preventDefault();document.removeEventListener("keydown",bskip);finishBoot();}});
|
||||||
|
}
|
||||||
|
function closeAcctMenu(){var m=document.getElementById("acctmenu");m.style.display="none";document.getElementById("acct").setAttribute("aria-expanded","false");}
|
||||||
|
function openAcctMenu(){var m=document.getElementById("acctmenu");m.style.display="block";document.getElementById("acct").setAttribute("aria-expanded","true");var items=m.querySelectorAll('button[role="menuitem"]');if(items.length)items[0].focus();}
|
||||||
|
document.getElementById("acctmenu").addEventListener("keydown",function(e){var m=this;if(m.style.display!=="block")return;var items=[].slice.call(m.querySelectorAll('button[role="menuitem"]'));if(!items.length)return;var i=items.indexOf(document.activeElement);if(e.key==="ArrowDown"){e.preventDefault();items[(i+1+items.length)%items.length].focus();}else if(e.key==="ArrowUp"){e.preventDefault();items[(i-1+items.length)%items.length].focus();}else if(e.key==="Home"){e.preventDefault();items[0].focus();}else if(e.key==="End"){e.preventDefault();items[items.length-1].focus();}});
|
||||||
|
document.getElementById("home").onclick=function(){destroyAllWindows();};
|
||||||
|
document.getElementById("acct").onclick=function(e){e.stopPropagation();var m=document.getElementById("acctmenu");if(m.style.display==="block"){closeAcctMenu();}else{openAcctMenu();}};
|
||||||
|
document.addEventListener("click",function(e){var m=document.getElementById("acctmenu");if(m.style.display==="block"&&!m.contains(e.target)&&e.target.id!=="acct"&&!document.getElementById("acct").contains(e.target)){closeAcctMenu();}});
|
||||||
|
document.getElementById("acctlock").onclick=function(){closeAcctMenu();destroyAllWindows();var dk=document.getElementById("desktop");dk.classList.remove("in");dk.style.display="none";document.getElementById("login").style.display="flex";var en=document.getElementById("enter");if(en){try{en.focus();}catch(e){}}};
|
||||||
|
document.getElementById("acctout").onclick=function(){closeAcctMenu();window.location.href="https://command.atlascorporation.nl/outpost.goauthentik.io/sign_out";};
|
||||||
|
document.getElementById("enter").onclick=function(){document.getElementById("login").style.display="none";var dk=document.getElementById("desktop");dk.style.display="block";icons();requestAnimationFrame(function(){dk.classList.add("in");});if(!MOBILE())win(apps[0]);};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
49
web/sitemap.html
Normal file
49
web/sitemap.html
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Atlas — Architecture Map</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/2.47.0/iconfont/tabler-icons.min.css">
|
||||||
|
<style>
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
body{background:#0b1020;color:#e8edf7;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;padding:24px;line-height:1.5}
|
||||||
|
h1{font-size:22px;font-weight:500;margin-bottom:4px}
|
||||||
|
.sub{font-size:13px;color:#7e8db0;margin-bottom:20px}
|
||||||
|
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:14px;max-width:1100px}
|
||||||
|
.card{background:#141d33;border:.5px solid #2a3a5e;border-radius:12px;padding:14px 16px}
|
||||||
|
.card h2{font-size:14px;font-weight:500;display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
||||||
|
.card h2 i{font-size:18px;color:#9fb0cc}
|
||||||
|
.row{display:flex;gap:8px;padding:6px 0;border-top:.5px solid #1b2742}
|
||||||
|
.dot{width:8px;height:8px;border-radius:50%;margin-top:6px;flex:none}
|
||||||
|
.nm{font-size:13px}.url{font-size:11px;color:#5e6e92;font-family:ui-monospace,Menlo,monospace;word-break:break-all}
|
||||||
|
.note{max-width:1100px;margin-top:18px;background:#141d33;border:.5px solid #2a3a5e;border-radius:12px;padding:14px 16px;font-size:13px;color:#cfd9ee}
|
||||||
|
.note b{color:#e0a234}
|
||||||
|
a{color:#4a9fe2}
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<h1>Atlas — Architecture Map</h1>
|
||||||
|
<div class="sub">One server (atlas-01, Hetzner) · 24 public services · generated 2026-06-22 · green = verified up, amber = needs attention</div>
|
||||||
|
<div class="grid" id="g"></div>
|
||||||
|
<div class="note">
|
||||||
|
<b>Where your logins live:</b> all credentials belong in your vault — <a href="https://vault.atlascorporation.nl">vault.atlascorporation.nl</a> (Vaultwarden). On iOS: install the Bitwarden app, set the server URL to vault.atlascorporation.nl, sign in. Identity for the OS and services = <a href="https://auth.atlascorporation.nl">Authentik SSO</a>.<br><br>
|
||||||
|
<b>Known issue:</b> email ingestion (IMAP credentials need refreshing — update /etc/atlas/assistant.env to restore inbox + calendar auto-detect.
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var UP="#4ec98a",WARN="#e0a234";
|
||||||
|
var groups=[
|
||||||
|
["Identity & access","ti-key",[["Authentik — single sign-on","auth.atlascorporation.nl",UP],["Vaultwarden — password vault (iOS)","vault.atlascorporation.nl",WARN]]],
|
||||||
|
["Atlas OS & command","ti-layout-dashboard",[["Atlas OS — your desktop","command.atlascorporation.nl/os/",UP],["Command UI — control room","command.atlascorporation.nl",UP],["The Mind — cockpit + event spine","The Mind cockpit (internal)",UP],["STEEPLE mindmap","mindmap.atlascorporation.nl",UP]]],
|
||||||
|
["Comms & mail","ti-mail",[["Webmail","mail.atlascorporation.nl",UP],["Stalwart — mail server","stalwart.atlascorporation.nl",WARN],["WhatsApp engine (Evolution)","evolution :8120",UP],["Chat","chat.atlascorporation.nl",UP]]],
|
||||||
|
["Money & books","ti-coin",[["Odoo 19 — invoicing / ERP","odoo.atlascorporation.nl",UP],["Firefly III — bank (266 tx)","fidi.atlascorporation.nl",UP],["CFO dashboard","cfo.atlascorporation.nl",UP]]],
|
||||||
|
["Files & documents","ti-folder",[["Nextcloud — files / cal / contacts","cloud.atlascorporation.nl",UP],["Office docs editor","office.atlascorporation.nl",UP],["Paperless — document archive","paperless.atlascorporation.nl",UP]]],
|
||||||
|
["Websites (WordPress)","ti-world",[["atlascorporation.nl","corp-wordpress",UP],["atlasworks.nl (shop)","works-wordpress",UP],["atlasagency.nl","agency-wordpress",UP],["+ bicos, hki, academy, hub, sabiqoun, pos, euroffice","9 more WP sites",UP]]],
|
||||||
|
["Dev & automation","ti-code",[["Forgejo — code / git","git.atlascorporation.nl",UP],["n8n — workflow automation","n8n.atlascorporation.nl",UP],["Twenty — CRM","twenty.atlascorporation.nl",UP]]],
|
||||||
|
["AI","ti-cpu",[["LiteLLM + Ollama (local models)","llm/ai/proxy.atlascorporation.nl",UP],["Learning agent","leeragent.atlascorporation.nl",UP]]],
|
||||||
|
["Ops & monitoring","ti-activity",[["Uptime Kuma — status","uptime.atlascorporation.nl",UP],["MeshCentral — remote devices","mesh.atlascorporation.nl",UP],["DMARC report viewer","dmarc (internal)",UP]]]
|
||||||
|
];
|
||||||
|
var g=document.getElementById("g");
|
||||||
|
groups.forEach(function(grp){
|
||||||
|
var rows=grp[2].map(function(it){return '<div class="row"><span class="dot" style="background:'+it[2]+'"></span><div><div class="nm">'+it[0]+'</div><div class="url">'+it[1]+'</div></div></div>';}).join("");
|
||||||
|
var d=document.createElement("div");d.className="card";
|
||||||
|
d.innerHTML='<h2><i class="ti '+grp[1]+'"></i>'+grp[0]+'</h2>'+rows;
|
||||||
|
g.appendChild(d);
|
||||||
|
});
|
||||||
|
</script></body></html>
|
||||||
Loading…
Reference in a new issue