refactor: generalize into a distributable template — remove operator infra

- delete internal infra docs: system-of-record.md, sitemap.html status board
  (exposed live attack surface: subdomains, up/down status, client names)
- parameterize OS desktop: derive DOMAIN from location.hostname, u() helper
  builds service URLs so a friend deploying at their domain just works
- scrub all live service links to service.{DOMAIN} placeholders
- remove client domains (bicos, hkintercare, etc.) everywhere
- collapse brand/client site tiles to one generic Website tile
- single public CTA -> atlascorporation.nl (Get a workspace)
- relative SSO sign-out; sanitized server scripts to example.com

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Atlas Developer 2026-06-22 22:28:51 +02:00
parent 19ad0057c8
commit e43664a83a
12 changed files with 87 additions and 495 deletions

View file

@ -47,8 +47,8 @@ redundancy for access even if Caddy is down.
## Service Layers
### Tier 0 — Identity & Security
- **Authentik** (`auth.atlascorporation.nl`) — OIDC/SSO, MFA, app proxies
- **Vaultwarden** (`vault.atlascorporation.nl`) — end-to-end encrypted password vault
- **Authentik** (`auth.{DOMAIN}`) — OIDC/SSO, MFA, app proxies
- **Vaultwarden** (`vault.{DOMAIN}`) — end-to-end encrypted password vault
- **Caddy** — automatic TLS, reverse proxy, the sole public entry point
### Tier 1 — Core Business
@ -91,12 +91,6 @@ All served via Caddy. WordPress sites run in isolated Docker stacks:
| `atlasagency.nl` | Docker: agency-wordpress | Agency services |
| `atlasacademy.nl` | TransIP nginx (static HTML) | Academy landing |
| `atlaspos.nl` | TransIP | AtlasPOS product site |
| `atlashub.nl` | Docker: atlashub | Hub platform |
| `bicosagency.nl` | Docker: bicos-wordpress | Client site |
| `budgetenergiecoach.nl` | Docker | Client site |
| `hkintercare.nl` | Docker: hkincare-wordpress | Client site |
| `jouwenergieadvies.nl` | Docker | Client site |
| `reclamemans.nl` | Docker | Client site |
---

View file

@ -4,7 +4,7 @@
# Atlas OS
[![Status](https://img.shields.io/badge/status-live-4ec98a?style=flat-square&logo=checkmarx&logoColor=white)](https://command.atlascorporation.nl/os/)
[![Status](https://img.shields.io/badge/status-live-4ec98a?style=flat-square&logo=checkmarx&logoColor=white)](https://atlascorporation.nl)
[![Containers](https://img.shields.io/badge/containers-55-4a9fe2?style=flat-square&logo=docker&logoColor=white)](./SERVICES.md)
[![Apps](https://img.shields.io/badge/apps-39-4a9fe2?style=flat-square&logo=windowsterminal&logoColor=white)](./SERVICES.md)
[![Stack](https://img.shields.io/badge/stack-Caddy_·_Authentik_·_Docker-1c4fae?style=flat-square&logo=cloudflare&logoColor=white)](./ARCHITECTURE.md)
@ -14,7 +14,7 @@
**Your entire business running on one hardened Linux server.**
**One SSO identity. One browser login. One OS.**
[🚀 Live Desktop](https://command.atlascorporation.nl/os/) · [📐 Architecture](./ARCHITECTURE.md) · [📦 Services](./SERVICES.md) · [🛠 Deploy](./deploy/) · [🤖 AI Stack](./docs/ai-stack.md) · [📖 Wiki](https://git.atlascorporation.nl/chaib/atlas-os/wiki)
[🚀 Get a workspace](https://atlascorporation.nl) · [📐 Architecture](./ARCHITECTURE.md) · [📦 Services](./SERVICES.md) · [🛠 Deploy](./deploy/) · [🤖 AI Stack](./docs/ai-stack.md) · [📖 Wiki](https://git.atlascorporation.nl/chaib/atlas-os/wiki)
</div>
@ -28,11 +28,13 @@ It's the operational spine of **Atlas Corporation** (Tilburg, NL) — built by a
> Think of it as a Citrix-style desktop, but it's your own server, your own identity, your own data.
> **Want your own?** Self-host the whole stack with the [one-command installer](#-quick-deploy) below — or get a managed Atlas workspace at **[atlascorporation.nl](https://atlascorporation.nl)**.
<div align="center">
![Atlas OS desktop](./assets/desktop-preview.svg)
<sub>The Atlas OS desktop — categorized launcher, live cockpit panels, drag-and-drop windows. Served at <code>command.atlascorporation.nl/os/</code> behind SSO.</sub>
<sub>The Atlas OS desktop — categorized launcher, live cockpit panels, drag-and-drop windows. Served at <code>command.{DOMAIN}/os/</code> behind SSO.</sub>
</div>
@ -51,11 +53,11 @@ It's the operational spine of **Atlas Corporation** (Tilburg, NL) — built by a
## 🖥️ The Desktop
Open `https://command.atlascorporation.nl/os/` on any device. You get a space-themed boot screen → Authentik SSO login → a live desktop with your real data: a categorized launcher (Cockpit · Identity · AI · Work · Files · Ops · Sites), drag-and-drop windows, a taskbar, and cockpit panels that auto-refresh.
Open ``command.{DOMAIN}/os/`` on any device. You get a space-themed boot screen → Authentik SSO login → a live desktop with your real data: a categorized launcher (Cockpit · Identity · AI · Work · Files · Ops · Sites), drag-and-drop windows, a taskbar, and cockpit panels that auto-refresh.
**Remote Linux desktop** (tailnet-only):
```
http://atlas-01.tailab40ed.ts.net:8497
`desktop.{DOMAIN}` (tailnet-only)
```
A real XFCE desktop in the browser. Claude Code and Chromium are pre-installed — OAuth login works because the browser is *inside* the desktop. Requires Tailscale to be active on your device.
@ -127,45 +129,45 @@ Full detail in [ARCHITECTURE.md](./ARCHITECTURE.md).
### 🛡️ Identity
| App | Description | URL |
|-----|-------------|-----|
| Authentik | SSO, OIDC, MFA — gates all services | [auth.atlascorporation.nl](https://auth.atlascorporation.nl) |
| Vaultwarden | Self-hosted Bitwarden (iOS app works) | [vault.atlascorporation.nl](https://vault.atlascorporation.nl) |
| Authentik | SSO, OIDC, MFA — gates all services | `auth.{DOMAIN}` |
| Vaultwarden | Self-hosted Bitwarden (iOS app works) | `vault.{DOMAIN}` |
### 🤖 AI
| App | Description | URL |
|-----|-------------|-----|
| Atlas Chat | Open-WebUI — ChatGPT-style on local + cloud models | [chat.atlascorporation.nl](https://chat.atlascorporation.nl) |
| AI Gateway | LiteLLM proxy — unified OpenAI-compat API | [llm.atlascorporation.nl](https://llm.atlascorporation.nl) |
| Mindmap | AI-powered visual thinking | [mindmap.atlascorporation.nl](https://mindmap.atlascorporation.nl) |
| Leeragent | Learning & knowledge agent | [leeragent.atlascorporation.nl](https://leeragent.atlascorporation.nl) |
| Atlas Chat | Open-WebUI — ChatGPT-style on local + cloud models | `chat.{DOMAIN}` |
| AI Gateway | LiteLLM proxy — unified OpenAI-compat API | `llm.{DOMAIN}` |
| Mindmap | AI-powered visual thinking | `mindmap.{DOMAIN}` |
| Leeragent | Learning & knowledge agent | `leeragent.{DOMAIN}` |
See the full AI routing stack (local → free cloud → paid) in [docs/ai-stack.md](./docs/ai-stack.md).
### 💼 Work
| App | Description | URL |
|-----|-------------|-----|
| Odoo | ERP + CRM (leads, invoices, customers) | [odoo.atlascorporation.nl](https://odoo.atlascorporation.nl) |
| CFO / Firefly III | Double-entry bookkeeping + budget intelligence | [cfo.atlascorporation.nl](https://cfo.atlascorporation.nl) |
| Bank / Enable Banking | PSD2 bank sync (ING, Knab, etc.) | [fidi.atlascorporation.nl](https://fidi.atlascorporation.nl) |
| Paperless-ngx | Document management + OCR | [paperless.atlascorporation.nl](https://paperless.atlascorporation.nl) |
| OnlyOffice | Full office suite (Writer, Sheets, Slides) | [office.atlascorporation.nl](https://office.atlascorporation.nl) |
| Calendar | Nextcloud Calendar (DAV sync to iOS) | [cloud.atlascorporation.nl](https://cloud.atlascorporation.nl) |
| Odoo | ERP + CRM (leads, invoices, customers) | `odoo.{DOMAIN}` |
| CFO / Firefly III | Double-entry bookkeeping + budget intelligence | `cfo.{DOMAIN}` |
| Bank / Enable Banking | PSD2 bank sync (ING, Knab, etc.) | `fidi.{DOMAIN}` |
| Paperless-ngx | Document management + OCR | `paperless.{DOMAIN}` |
| OnlyOffice | Full office suite (Writer, Sheets, Slides) | `office.{DOMAIN}` |
| Calendar | Nextcloud Calendar (DAV sync to iOS) | `cloud.{DOMAIN}` |
### 📁 Files & Comms
| App | Description | URL |
|-----|-------------|-----|
| Nextcloud | File storage, DAV, photos | [cloud.atlascorporation.nl](https://cloud.atlascorporation.nl) |
| Stalwart Mail | Self-hosted email (SMTP/IMAP/JMAP) | [stalwart.atlascorporation.nl](https://stalwart.atlascorporation.nl) |
| Relay | Internal message relay service | [relay.atlascorporation.nl](https://relay.atlascorporation.nl) |
| Nextcloud | File storage, DAV, photos | `cloud.{DOMAIN}` |
| Stalwart Mail | Self-hosted email (SMTP/IMAP/JMAP) | `stalwart.{DOMAIN}` |
| Relay | Internal message relay service | `relay.{DOMAIN}` |
### ⚙️ Ops
| App | Description | URL |
|-----|-------------|-----|
| Forgejo | Self-hosted Git — this very repo | [git.atlascorporation.nl](https://git.atlascorporation.nl) |
| n8n | Workflow automation | [n8n.atlascorporation.nl](https://n8n.atlascorporation.nl) |
| Uptime Kuma | Service health monitoring | [uptime.atlascorporation.nl](https://uptime.atlascorporation.nl) |
| MeshCentral | Fleet management + remote access | [mesh.atlascorporation.nl](https://mesh.atlascorporation.nl) |
| Command | Atlas command dashboard | [command.atlascorporation.nl](https://command.atlascorporation.nl) |
| Remote Desktop | Webtop full Linux desktop (tailnet-only) | `atlas-01.tailab40ed.ts.net:8497` |
| n8n | Workflow automation | `n8n.{DOMAIN}` |
| Uptime Kuma | Service health monitoring | `uptime.{DOMAIN}` |
| MeshCentral | Fleet management + remote access | `mesh.{DOMAIN}` |
| Command | Atlas command dashboard | `command.{DOMAIN}` |
| Remote Desktop | Webtop full Linux desktop (tailnet-only) | `desktop.{DOMAIN} (tailnet-only)` |
### 🎓 Atlas Academy
A full education arm running on the same box — courses, coaching, and an ESF/BiSL/AI curriculum at [atlasacademy.nl](https://atlasacademy.nl). Lead capture posts straight into Odoo CRM via the `atlas-academy-relay` service (creates `crm.lead` records), with an email fallback. Product feed is wired to WooCommerce. See [docs/academy.md](./docs/academy.md).
@ -174,7 +176,7 @@ A full education arm running on the same box — courses, coaching, and an ESF/B
A first-class part of the OS, not an add-on: family-safe DNS (AdGuard Family), Quran at logon, Adhan playback + screen lock at El Feth Tilburg salah times, and recurring sunnah reminders across the device fleet. See [docs/islamic-layer.md](./docs/islamic-layer.md).
### 🌐 Managed Sites
`atlasworks.nl` · `atlascorporation.nl` · `atlasagency.nl` · `atlasacademy.nl` · `atlaspos.nl` · `atlashub.nl` · `bicosagency.nl` · `budgetenergiecoach.nl` · `hkintercare.nl` · `jouwenergieadvies.nl` · `reclamemans.nl`
Your own brand sites and any client WordPress sites — each in an isolated Docker stack behind Caddy.
---

View file

@ -1,7 +1,7 @@
# Atlas OS — Full Service Catalog
55 containers, 39 browser-accessible apps, 20+ background systemd services.
Live status: https://uptime.atlascorporation.nl
Live status: `uptime.{DOMAIN}`
---
@ -20,53 +20,52 @@ Live status: https://uptime.atlascorporation.nl
| Service | URL |
|---------|-----|
| Authentik | auth.atlascorporation.nl |
| Vaultwarden | vault.atlascorporation.nl |
| Authentik | auth.{DOMAIN} |
| Vaultwarden | vault.{DOMAIN} |
## AI
| Service | URL |
|---------|-----|
| Atlas Chat (Open-WebUI) | chat.atlascorporation.nl |
| AI Gateway (LiteLLM) | llm.atlascorporation.nl |
| Mindmap | mindmap.atlascorporation.nl |
| Leeragent | leeragent.atlascorporation.nl |
| Atlas Chat (Open-WebUI) | chat.{DOMAIN} |
| AI Gateway (LiteLLM) | llm.{DOMAIN} |
| Mindmap | mindmap.{DOMAIN} |
| Leeragent | leeragent.{DOMAIN} |
## Work
| Service | URL |
|---------|-----|
| Odoo 19 ERP | odoo.atlascorporation.nl |
| Firefly III (CFO) | cfo.atlascorporation.nl |
| Enable Banking | fidi.atlascorporation.nl |
| Paperless-ngx | paperless.atlascorporation.nl |
| OnlyOffice | office.atlascorporation.nl |
| Nextcloud Calendar | cloud.atlascorporation.nl/apps/calendar |
| Odoo 19 ERP | odoo.{DOMAIN} |
| Firefly III (CFO) | cfo.{DOMAIN} |
| Enable Banking | fidi.{DOMAIN} |
| Paperless-ngx | paperless.{DOMAIN} |
| OnlyOffice | office.{DOMAIN} |
| Nextcloud Calendar | cloud.{DOMAIN}/apps/calendar |
## Files & Communications
| Service | URL |
|---------|-----|
| Nextcloud | cloud.atlascorporation.nl |
| Stalwart Mail | stalwart.atlascorporation.nl |
| Relay | relay.atlascorporation.nl |
| Nextcloud | cloud.{DOMAIN} |
| Stalwart Mail | stalwart.{DOMAIN} |
| Relay | relay.{DOMAIN} |
## Ops & Infrastructure
| Service | URL |
|---------|-----|
| Forgejo | git.atlascorporation.nl |
| n8n | n8n.atlascorporation.nl |
| Uptime Kuma | uptime.atlascorporation.nl |
| MeshCentral | mesh.atlascorporation.nl |
| Command | command.atlascorporation.nl |
| Remote Desktop (tailnet-only) | atlas-01.tailab40ed.ts.net:8497 |
| n8n | n8n.{DOMAIN} |
| Uptime Kuma | uptime.{DOMAIN} |
| MeshCentral | mesh.{DOMAIN} |
| Command | command.{DOMAIN} |
| Remote Desktop (tailnet-only) | desktop.{DOMAIN} (tailnet-only) |
## Managed Sites
atlasworks.nl, atlascorporation.nl, atlasagency.nl, atlasacademy.nl, atlaspos.nl,
atlashub.nl, bicosagency.nl, budgetenergiecoach.nl, hkintercare.nl,
jouwenergieadvies.nl, reclamemans.nl
your-site-1.example, your-site-2.example
## Background systemd Services
@ -97,8 +96,8 @@ jouwenergieadvies.nl, reclamemans.nl
| Service | URL |
|---------|-----|
| Academy site | atlasacademy.nl |
| Lead relay (Odoo) | /academy-lead -> 127.0.0.1:7793 (atlas-academy-relay) |
| Product feed | atlasworks.nl/academy-feed.php |
| Lead relay (Odoo) | /academy-lead -> 127.0.0.1:<relay-port> (atlas-academy-relay) |
| Product feed | <your-store>/academy-feed.php |
## Islamic Layer

View file

@ -86,8 +86,8 @@
<!-- managed sites -->
<text x="40" y="452" class="h">11 MANAGED SITES</text>
<rect x="40" y="466" width="1120" height="58" rx="10" fill="#0e1d3c" stroke="#4a9fe2" stroke-opacity=".25"/>
<text x="56" y="490" class="b">atlasworks.nl · atlascorporation.nl · atlasagency.nl · atlasacademy.nl · atlaspos.nl · atlashub.nl</text>
<text x="56" y="510" class="s">bicosagency.nl · budgetenergiecoach.nl · hkintercare.nl · jouwenergieadvies.nl · reclamemans.nl — all via Caddy</text>
<text x="56" y="490" class="b">Your own brand sites + any client WordPress sites you host</text>
<text x="56" y="510" class="s">each in an isolated Docker stack — all fronted by Caddy</text>
<!-- footer rule -->
<rect x="40" y="560" width="1120" height="44" rx="10" fill="#0c1830"/>

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

@ -22,14 +22,14 @@ atlas-academy-relay (systemd on atlas-01, port 7793)
└─► email fallback → info@atlascorporation.nl
```
The relay is exposed via Caddy at `/academy-lead` (proxied to `127.0.0.1:7793`). It caches the Odoo UID so it doesn't re-authenticate on every lead.
The relay is exposed via Caddy at `/academy-lead` (proxied to `127.0.0.1:<relay-port>`). It caches the Odoo UID so it doesn't re-authenticate on every lead.
## Product feed
Courses and hardware bundles are pulled from WooCommerce via a JSON feed, so the Academy shop stays in sync with the live store without duplicating product data.
```
GET https://www.atlasworks.nl/academy-feed.php
GET https://www.<your-store>/academy-feed.php
→ { ts, count, products[] } # each: { id, title, price, type, img, url, cats }
```

View file

@ -4,7 +4,7 @@ A **real Linux desktop in the browser** — not a web app. Full XFCE on Ubuntu 2
streamed via Selkies/WebRTC, where you can run any application: Claude Code, Chromium,
a terminal, editors, anything you apt-install.
**URL:** https://desktop.atlascorporation.nl (login at the Caddy edge)
**URL:** `desktop.{DOMAIN}` (login at the Caddy edge)
## Why a real desktop
Unlike a headless server, a desktop has a browser — so the Claude Code OAuth login

View file

@ -1,86 +0,0 @@
# Atlas — System of Record (verified)
> Auto-generated from a read-only infrastructure audit (9 dimensions, 324 items, 106 agents). Every URL below was live-checked directly from the box. This is the single source of truth for *what exists and whether it works*.
## Tools & services (atlas-01)
| Service | Status | Access |
|---|---|---|
| **ai.atlascorporation.nl** — AI gateway (litellm) — backend gone | 🔴 404 dead | |
| **auth.atlascorporation.nl** — Authentik SSO (identity provider) | 🟢 live | |
| **cfo.atlascorporation.nl** — CFO finance panel | 🔐 SSO-gated | |
| **chat.atlascorporation.nl** — Open WebUI (AI chat) | 🔐 SSO-gated | |
| **cloud.atlascorporation.nl** — Nextcloud (files, calendar) | 🟢 live | |
| **command.atlascorporation.nl** — Command center + Atlas OS | 🔐 SSO-gated | |
| **fidi.atlascorporation.nl** — Firefly III data importer / bank | 🟢 live | |
| **git.atlascorporation.nl** — Forgejo (git) | 🟢 live | |
| **leeragent.atlascorporation.nl** — Leeragent | 🔐 SSO-gated | |
| **llm.atlascorporation.nl** — AI gateway | 🟢 live | |
| **mail.atlascorporation.nl** — Webmail — DOWN (bad gateway) | 🔴 502 down | |
| **mesh.atlascorporation.nl** — MeshCentral (remote desktop) | 🟢 live | |
| **mindmap.atlascorporation.nl** — STEEPLE mindmap | 🔐 SSO-gated | |
| **n8n.atlascorporation.nl** — n8n automation | 🔐 SSO-gated | |
| **odoo.atlascorporation.nl** — Odoo 19 CRM/ERP | 🔐 SSO-gated | |
| **office.atlascorporation.nl** — Office suite | 🟢 live | |
| **paperless.atlascorporation.nl** — Paperless-ngx (documents) | 🟢 live | |
| **proxy.atlascorporation.nl** — Proxy gateway | 🟢 live | |
| **relay.atlascorporation.nl** — Relay service | 🟢 live | |
| **stalwart.atlascorporation.nl** — Stalwart mail admin | 🔐 SSO-gated | |
| **twenty.atlascorporation.nl** — Twenty CRM — DOWN (no container) | 🔴 502 down | |
| **uptime.atlascorporation.nl** — Uptime Kuma | 🔐 SSO-gated | |
| **vault.atlascorporation.nl** — Vaultwarden (passwords) | 🟢 live | |
## Websites (own + client)
| Site | Status |
|---|---|
| **atlasworks.nl** — WooCommerce store (LIVE revenue) | 🟢 live |
| **atlascorporation.nl** — Corporate site | 🟢 live |
| **atlasagency.nl** — Agency site | 🟢 live |
| **atlasacademy.nl** — Academy | 🟢 live |
| **atlaspos.nl** — AtlasPOS landing (primary product) | 🟢 live |
| **atlashub.nl** — Atlas Hub (login) | 🟢 live |
| **bicosagency.nl** — Client — Bicos | 🟢 live |
| **budgetenergiecoach.nl** — Client — Budget Energie Coach | 🟢 live |
| **hkintercare.nl** — Client — HKinterCare | 🟢 live |
| **jouwenergieadvies.nl** — Client — Jouw Energie Advies | 🟢 live |
| **reclamemans.nl** — Client — ReclameMans | 🟢 live |
| **hkinternationalcare.nl** — Client — DOWN/unreachable (DNS?) | 🔴 unreachable |
## Needs attention (found by audit)
- 🔴 **ai.atlascorporation.nl** → 404. The LiteLLM gateway container is gone; `litellm-db` runs orphaned. Either redeploy the gateway or remove the DNS + DB.
- 🔴 **mail.atlascorporation.nl** → 502 (webmail bad gateway). Stalwart core is up; the webmail front-end is down.
- 🔴 **twenty.atlascorporation.nl** → 502. Twenty CRM has a compose file + saved creds but **no running container**.
- 🔴 **hkinternationalcare.nl** → unreachable (000) — DNS may not point at the box; the sister domain hkintercare.nl is live.
- 🟠 **arena-trevor** — idle `python:3.11-slim` running `sleep infinity`, no mounts/source. Abandoned sandbox; safe to remove.
- 🟠 **atlas-dms-db** — orphaned Postgres with no DMS app container.
- 🟠 Several mariadb/wordpress images pin old tags (php8.1, :latest) — patch/staleness risk.
## AI chat history (consolidation target)
AI conversation history is heavily fragmented across two hosts and at least six stores. The dominant volume is local Claude Code session transcripts: 2,867 *.jsonl files under C:\Users\Gebruiker\.claude\projects (28+ distinct project folders). atlas-01 holds a small secondary set of 25 Claude *.jsonl files under /root/.claude/projects. The open-webui chat DB (webui.db, 7.7MB) exists but ALL its chat tables (chat, chat_message, message, memory, note) are empty (0 rows) - chats were either never persisted there or were purged; the file has not been written since Jun 6. The message spine is split
- **Claude Code session transcripts (local PC)** — 2867 *.jsonl files - by far the largest AI-chat corpus. No central archive; lives only on this PC. Project folders span Users\Gebruiker, MEGA, config\atlas, atlas-electron, atlas-works, plus stray Users-HP/Users-boaar trees (multi-account residue). Primary consolidation source.
- **Claude Code session transcripts (atlas-01)** — 25 *.jsonl files. Small secondary set; should be folded into the unified archive alongside the local 2867.
- **open-webui chat database** — 7.7MB file but ALL chat tables are EMPTY: chat=0, chat_message=0, message=0, memory=0, note=0. No conversation content retained. Last modified Jun 6 - effectively stale/unused for chat history. Schema is rich (folders, knowledge, tools) but no chats persisted.
- **Message-spine kernel (live)** — decisions=458, events=3415. ~1.99MB, written today (Jun 22 20:20). This is the authoritative kernel and the natural home for a unified archive.
- **Message-spine kernel (empty stub)** — 0 bytes, no tables, created/touched today (Jun 22 19:44). Conflicts with /opt/atlas/mind/kernel.sqlite. Likely a wrong-path init or broken service writing to the wrong location. Delete or repoint to eliminate dual-kernel ambiguity.
- **atlas-assistant triage log** — 271 lines. Records are {source:imap, uid, msgid, from, subject, category, score, reason} - email triage, not conversation history. Stale: last write Jun 8 (~97KB). Keep OUT of the AI-chat archive; it belongs to the mail pipeline.
- **'evolution' store (WhatsApp Baileys auth)** — Contains creds.json, integration.json ({instanceName:atlas}), ~28 pre-key-*.json and ~17 session-*.json libsignal files (last write today). This is messaging-channel auth material, not AI conversation history. Misnamed relative to the audit's 'evolution store' expectation. Do NOT archive as chat; treat as sensitive credentials. (No secret values inspected/printed.)
## Security posture
**Fixed in this pass (read-only audit → applied):**
- ✅ PSD2 Enable Banking private key `0644 → 600` (was world-readable — CRITICAL).
- ✅ `odoo-selfhost-creds.env` `0644 → 600`.
- ✅ Enable Banking session/auth/app JSONs `0644 → 600`.
- ✅ Forgejo password stripped from the git remote URL; `.git/config` `→ 600`.
**Still requires the operator:**
- 🔴 **Authentik admin (`akadmin`) password is compromised** — it was pasted in plaintext into a chat transcript. Rotate it (and ideally enable MFA on the admin account). This is the master key to every SSO service.
- 🟠 Predictable/shared secrets to rotate next: Forgejo `chaib` password, the shared Evolution apikey, and the stale `chaib@atlasworks.nl` IMAP password (email ingestion has been failing).
---
*Generated by the Atlas system-of-record audit. Re-run anytime to refresh.*

View file

@ -43,14 +43,14 @@ CAL_CFG = ATLAS / "calendar-feeds.json"
CAL_CACHE = CACHE / "calendar-last.json"
LOG = ATLAS / "atlas-brain.log"
ATLAS01 = os.environ.get("ATLAS01_URL", "http://atlas-01.tailab40ed.ts.net")
ATLAS01 = os.environ.get("ATLAS01_URL", "http://localhost")
A01_USER = "atlas"
A01_PASS = os.environ.get("ATLAS_BASIC_PASS", "")
SERVICES = [
{"name": "Command Center", "url": "https://atlascorporation.nl/command"},
{"name": "Nextcloud", "url": "http://atlas-01:8443"},
{"name": "MeshCentral", "url": "https://mesh.atlascorporation.nl/"},
{"name": "MeshCentral", "url": "https://mesh.example.com/"},
{"name": "Odoo", "url": "http://<TAILSCALE_IP>:8069"},
{"name": "Atlas Portal", "url": "http://atlas-01/portal.html"},
{"name": "n8n", "url": "http://<TAILSCALE_IP>:5678"},

View file

@ -3,7 +3,7 @@
Atlas cockpit feed shim serves the command-ui SPA's /v1/* and /api/* feed
endpoints from existing on-disk data, so the Reach cockpit's "0/5 feeds" goes
live. Unknown paths proxy through to control-room (:5057) so nothing regresses.
Sits behind Caddy + Authentik on command.atlascorporation.nl; trusts upstream.
Sits behind Caddy + Authentik on command.example.com; trusts upstream.
"""
import json, os, subprocess, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

View file

@ -1,258 +0,0 @@
<!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:flex;flex-direction:column;gap:15px;z-index:10;max-height:calc(100vh - 120px);overflow-y:auto;overflow-x:hidden;width:188px}
.cathead{font-size:10px;font-weight:600;color:#6c7a9c;text-transform:uppercase;letter-spacing:.09em;padding-left:3px;margin-bottom:6px}
.catgrid{display:grid;grid-template-columns:repeat(2,80px);gap:12px}
.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;display:flex;flex-direction:column;gap:16px;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%;max-height:none;overflow:visible}
.ico{width:auto}.catgrid{grid-template-columns:repeat(4,1fr)}
.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"},
{id:"chat",name:"Atlas Chat",icon:"ti-message-chatbot",c:"#4ec98a",url:"https://chat.atlascorporation.nl"},
{id:"cfo",name:"CFO",icon:"ti-calculator",c:"#e0a234",url:"https://cfo.atlascorporation.nl"},
{id:"bank",name:"Bank",icon:"ti-building-bank",c:"#4ec98a",url:"https://fidi.atlascorporation.nl"},
{id:"paperless",name:"Paperless",icon:"ti-files",c:"#e0a234",url:"https://paperless.atlascorporation.nl"},
{id:"mesh",name:"MeshCentral",icon:"ti-screen-share",c:"#9b8cf0",url:"https://mesh.atlascorporation.nl"},
{id:"mindmap",name:"Mindmap",icon:"ti-bulb",c:"#e0a234",url:"https://mindmap.atlascorporation.nl"},
{id:"llm",name:"AI Gateway",icon:"ti-cpu",c:"#9b8cf0",url:"https://llm.atlascorporation.nl"},
{id:"leer",name:"Leeragent",icon:"ti-school",c:"#4a9fe2",url:"https://leeragent.atlascorporation.nl"},
{id:"relay",name:"Relay",icon:"ti-share",c:"#4ec98a",url:"https://relay.atlascorporation.nl"},
{id:"mailadmin",name:"Mail Admin",icon:"ti-server-cog",c:"#9b8cf0",url:"https://stalwart.atlascorporation.nl"},
{id:"arch",name:"Architecture",icon:"ti-sitemap",c:"#4a9fe2",url:"/os/sitemap.html"},
{id:"auth",name:"Identity",icon:"ti-shield-lock",c:"#e2655f",url:"https://auth.atlascorporation.nl"},
{id:"office",name:"Office",icon:"ti-file-text",c:"#4a9fe2",url:"https://office.atlascorporation.nl"},
{id:"s_works",name:"AtlasWorks",icon:"ti-shopping-cart",c:"#e0a234",url:"https://atlasworks.nl"},
{id:"s_corp",name:"Atlas Corp",icon:"ti-world",c:"#4a9fe2",url:"https://atlascorporation.nl"},
{id:"s_agency",name:"Atlas Agency",icon:"ti-briefcase",c:"#9b8cf0",url:"https://atlasagency.nl"},
{id:"s_academy",name:"Academy",icon:"ti-school",c:"#4ec98a",url:"https://atlasacademy.nl"},
{id:"s_pos",name:"AtlasPOS",icon:"ti-cash",c:"#e0a234",url:"https://atlaspos.nl"},
{id:"s_hub",name:"Atlas Hub",icon:"ti-layout-grid",c:"#4a9fe2",url:"https://atlashub.nl"},
{id:"s_bicos",name:"Bicos",icon:"ti-building",c:"#9b8cf0",url:"https://bicosagency.nl"},
{id:"s_bec",name:"Budget Energie",icon:"ti-bolt",c:"#e0a234",url:"https://budgetenergiecoach.nl"},
{id:"s_hki",name:"HKinterCare",icon:"ti-heart",c:"#e2655f",url:"https://hkintercare.nl"},
{id:"s_jea",name:"Jouw Energie",icon:"ti-sun",c:"#e0a234",url:"https://jouwenergieadvies.nl"},
{id:"s_rm",name:"ReclameMans",icon:"ti-speakerphone",c:"#4ec98a",url:"https://reclamemans.nl"},
{id:"desktop",name:"Remote Desktop",icon:"ti-device-desktop",c:"#9b8cf0",url:"https://desktop.atlascorporation.nl"}
];
var CATS={today:"Cockpit",money:"Cockpit",inbox:"Cockpit",pipeline:"Cockpit",fleet:"Cockpit",control:"Cockpit",auth:"Identity",vault:"Identity",chat:"AI",llm:"AI",mindmap:"AI",leer:"AI",odoo:"Work",cfo:"Work",bank:"Work",paperless:"Work",office:"Work",cal:"Work",files:"Files & Comms",mailadmin:"Files & Comms",relay:"Files & Comms",git:"Ops",n8n:"Ops",uptime:"Ops",mesh:"Ops",map:"Ops",arch:"Ops",s_works:"Sites",s_corp:"Sites",s_agency:"Sites",s_academy:"Sites",s_pos:"Sites",s_hub:"Sites",s_bicos:"Sites",s_bec:"Sites",s_hki:"Sites",s_jea:"Sites",s_rm:"Sites",desktop:"Ops"};
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{"&":"&amp;","<":"&lt;",">":"&gt;"}[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;var ORDER=["Cockpit","Identity","AI","Work","Files & Comms","Ops","Sites"];var groups={};apps.forEach(function(a){var c=(typeof CATS!=="undefined"&&CATS[a.id])||"More";(groups[c]=groups[c]||[]).push(a);});var cats=ORDER.slice();Object.keys(groups).forEach(function(k){if(cats.indexOf(k)<0)cats.push(k);});cats.forEach(function(cat){var list=groups[cat];if(!list||!list.length)return;var head=document.createElement("div");head.className="cathead";head.textContent=cat;g.appendChild(head);var grid=document.createElement("div");grid.className="catgrid";list.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>'+esc(a.name)+'</span>';d.onclick=function(){win(a);};d.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();win(a);}});grid.appendChild(d);});g.appendChild(grid);});}
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>

View file

@ -113,6 +113,7 @@
<script>
var COCK="/os/cockpit";
var DOMAIN=(location.hostname.split(".").filter(Boolean).slice(-2).join("."))||"example.com";function u(s){return "https://"+s+"."+DOMAIN;}
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);
@ -122,41 +123,30 @@ var apps=[
{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"},
{id:"chat",name:"Atlas Chat",icon:"ti-message-chatbot",c:"#4ec98a",url:"https://chat.atlascorporation.nl"},
{id:"cfo",name:"CFO",icon:"ti-calculator",c:"#e0a234",url:"https://cfo.atlascorporation.nl"},
{id:"bank",name:"Bank",icon:"ti-building-bank",c:"#4ec98a",url:"https://fidi.atlascorporation.nl"},
{id:"paperless",name:"Paperless",icon:"ti-files",c:"#e0a234",url:"https://paperless.atlascorporation.nl"},
{id:"mesh",name:"MeshCentral",icon:"ti-screen-share",c:"#9b8cf0",url:"https://mesh.atlascorporation.nl"},
{id:"mindmap",name:"Mindmap",icon:"ti-bulb",c:"#e0a234",url:"https://mindmap.atlascorporation.nl"},
{id:"llm",name:"AI Gateway",icon:"ti-cpu",c:"#9b8cf0",url:"https://llm.atlascorporation.nl"},
{id:"leer",name:"Leeragent",icon:"ti-school",c:"#4a9fe2",url:"https://leeragent.atlascorporation.nl"},
{id:"relay",name:"Relay",icon:"ti-share",c:"#4ec98a",url:"https://relay.atlascorporation.nl"},
{id:"mailadmin",name:"Mail Admin",icon:"ti-server-cog",c:"#9b8cf0",url:"https://stalwart.atlascorporation.nl"},
{id:"arch",name:"Architecture",icon:"ti-sitemap",c:"#4a9fe2",url:"/os/sitemap.html"},
{id:"auth",name:"Identity",icon:"ti-shield-lock",c:"#e2655f",url:"https://auth.atlascorporation.nl"},
{id:"office",name:"Office",icon:"ti-file-text",c:"#4a9fe2",url:"https://office.atlascorporation.nl"},
{id:"s_works",name:"AtlasWorks",icon:"ti-shopping-cart",c:"#e0a234",url:"https://atlasworks.nl"},
{id:"s_corp",name:"Atlas Corp",icon:"ti-world",c:"#4a9fe2",url:"https://atlascorporation.nl"},
{id:"s_agency",name:"Atlas Agency",icon:"ti-briefcase",c:"#9b8cf0",url:"https://atlasagency.nl"},
{id:"s_academy",name:"Academy",icon:"ti-school",c:"#4ec98a",url:"https://atlasacademy.nl"},
{id:"s_pos",name:"AtlasPOS",icon:"ti-cash",c:"#e0a234",url:"https://atlaspos.nl"},
{id:"s_hub",name:"Atlas Hub",icon:"ti-layout-grid",c:"#4a9fe2",url:"https://atlashub.nl"},
{id:"s_bicos",name:"Bicos",icon:"ti-building",c:"#9b8cf0",url:"https://bicosagency.nl"},
{id:"s_bec",name:"Budget Energie",icon:"ti-bolt",c:"#e0a234",url:"https://budgetenergiecoach.nl"},
{id:"s_hki",name:"HKinterCare",icon:"ti-heart",c:"#e2655f",url:"https://hkintercare.nl"},
{id:"s_jea",name:"Jouw Energie",icon:"ti-sun",c:"#e0a234",url:"https://jouwenergieadvies.nl"},
{id:"s_rm",name:"ReclameMans",icon:"ti-speakerphone",c:"#4ec98a",url:"https://reclamemans.nl"},
{id:"desktop",name:"Desktop (tailnet)",icon:"ti-device-desktop",c:"#9b8cf0",url:"http://atlas-01.tailab40ed.ts.net:8497"}
{id:"files",name:"Nextcloud",icon:"ti-folder",c:"#4a9fe2",url:u("cloud")},
{id:"cal",name:"Calendar",icon:"ti-calendar",c:"#4ec98a",url:u("cloud")+"/apps/calendar"},
{id:"odoo",name:"Odoo",icon:"ti-building-store",c:"#9b8cf0",url:u("odoo")},
{id:"git",name:"Git",icon:"ti-git-branch",c:"#e0a234",url:u("git")},
{id:"n8n",name:"n8n",icon:"ti-route",c:"#e2655f",url:u("n8n")},
{id:"vault",name:"Vault",icon:"ti-lock",c:"#4a9fe2",url:u("vault")},
{id:"uptime",name:"Uptime",icon:"ti-activity",c:"#4ec98a",url:u("uptime")},
{id:"map",name:"Command",icon:"ti-map-2",c:"#4a9fe2",url:u("command")},
{id:"chat",name:"Atlas Chat",icon:"ti-message-chatbot",c:"#4ec98a",url:u("chat")},
{id:"cfo",name:"CFO",icon:"ti-calculator",c:"#e0a234",url:u("cfo")},
{id:"bank",name:"Bank",icon:"ti-building-bank",c:"#4ec98a",url:u("fidi")},
{id:"paperless",name:"Paperless",icon:"ti-files",c:"#e0a234",url:u("paperless")},
{id:"mesh",name:"MeshCentral",icon:"ti-screen-share",c:"#9b8cf0",url:u("mesh")},
{id:"mindmap",name:"Mindmap",icon:"ti-bulb",c:"#e0a234",url:u("mindmap")},
{id:"llm",name:"AI Gateway",icon:"ti-cpu",c:"#9b8cf0",url:u("llm")},
{id:"leer",name:"Leeragent",icon:"ti-school",c:"#4a9fe2",url:u("leeragent")},
{id:"relay",name:"Relay",icon:"ti-share",c:"#4ec98a",url:u("relay")},
{id:"mailadmin",name:"Mail Admin",icon:"ti-server-cog",c:"#9b8cf0",url:u("stalwart")},
{id:"auth",name:"Identity",icon:"ti-shield-lock",c:"#e2655f",url:u("auth")},
{id:"office",name:"Office",icon:"ti-file-text",c:"#4a9fe2",url:u("office")},
{id:"s_main",name:"Website",icon:"ti-world",c:"#4a9fe2",url:"https://"+DOMAIN},
{id:"desktop",name:"Desktop (tailnet)",icon:"ti-device-desktop",c:"#9b8cf0",url:u("desktop")}
];
var CATS={today:"Cockpit",money:"Cockpit",inbox:"Cockpit",pipeline:"Cockpit",fleet:"Cockpit",control:"Cockpit",auth:"Identity",vault:"Identity",chat:"AI",llm:"AI",mindmap:"AI",leer:"AI",odoo:"Work",cfo:"Work",bank:"Work",paperless:"Work",office:"Work",cal:"Work",files:"Files & Comms",mailadmin:"Files & Comms",relay:"Files & Comms",git:"Ops",n8n:"Ops",uptime:"Ops",mesh:"Ops",map:"Ops",arch:"Ops",s_works:"Sites",s_corp:"Sites",s_agency:"Sites",s_academy:"Sites",s_pos:"Sites",s_hub:"Sites",s_bicos:"Sites",s_bec:"Sites",s_hki:"Sites",s_jea:"Sites",s_rm:"Sites",desktop:"Ops"};
var CATS={today:"Cockpit",money:"Cockpit",inbox:"Cockpit",pipeline:"Cockpit",fleet:"Cockpit",control:"Cockpit",auth:"Identity",vault:"Identity",chat:"AI",llm:"AI",mindmap:"AI",leer:"AI",odoo:"Work",cfo:"Work",bank:"Work",paperless:"Work",office:"Work",cal:"Work",files:"Files & Comms",mailadmin:"Files & Comms",relay:"Files & Comms",git:"Ops",n8n:"Ops",uptime:"Ops",mesh:"Ops",map:"Ops",s_main:"Sites",desktop:"Ops"};
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{"&":"&amp;","<":"&lt;",">":"&gt;"}[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";}
@ -251,7 +241,7 @@ 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("acctout").onclick=function(){closeAcctMenu();window.location.href="/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>

View file

@ -1,49 +0,0 @@
<!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>