Compare commits

...
2 Commits
Author SHA1 Message Date
oliver 11898faa3d cloudflare fix 2026-08-10 08:16:28 -03:00
oliver 896f3b01bb Create REUSE_GUIDE.md 2026-08-10 08:12:03 -03:00
2 changed files with 271 additions and 5 deletions
+265
View File
@@ -0,0 +1,265 @@
# admin.derez.ai — UI Reuse Guide
Stripped-down version: **Servers page only**. No Sales, no CRM, no API Keys.
---
## 1. UI Architecture (Single HTML File)
The entire front-end is a **single-page app** in a single `index.html` with:
| File | Role |
|---|---|
| `index.html` | Full DOM layout: sidebar, sections, modals (login, confirm) |
| `styles.css` | All styles: layout, sidebar, cards, tables, badges, modal, toast, server dashboard |
| `app.js` | State, auth, API helpers, routing, all CRUD for each section |
The pattern is straightforward — you can keep one file per section or keep it monolithic. The CSS and JS are written in vanilla ES6+ with no build step.
---
## 2. Login & Session (Cookie-Based)
### How it works
1. User fills email + password in `#login-modal` (inside `#login-backdrop`).
2. `doSignIn()` POSTs to the auth endpoint with `{ email, password }`.
3. The backend returns an object with a `sessionid` field.
4. The session ID and email are saved in cookies:
- `al_session` — the session token
- `al_email` — the signed-in email
### On page load
```js
const _boot = getCookie("al_session");
if (_boot) {
currentSession = _boot;
currentEmail = getCookie("al_email") || "";
showApp(); // hides login modal, shows sidebar
loadData(); // loads your default section data
} else {
showAuth(); // shows login modal
}
```
### Cookie helpers (already in `app.js`)
```js
setCookie(name, value, days) // SameSite=Strict
getCookie(name)
deleteCookie(name)
```
### API fetch wrapper
```js
async function apiFetch(url, options = {})
```
- Automatically attaches `X-Session-Id` header from `currentSession`.
- On HTTP 401, deletes cookies, clears local state, and reloads the page (back to login).
### Sign out
```js
function doSignOut()
```
- Sends a `DELETE` to the auth endpoint with the session ID.
- Deletes cookies (`al_session`, `al_email`).
- Clears `localStorage`.
- Shows the login screen again.
### What you need to change for a new project
| Constant / Variable | Change to |
|---|---|
| `const API_BASE` (L2) | Your backend base URL, e.g. `"https://api.yourproject.com/webhook"` |
| `ROUTES.auth` (L6) | Your login endpoint path, e.g. `"/auth/login"` |
| Cookie names `al_session` / `al_email` | Rename to avoid conflicts, e.g. `"myapp_session"` |
| `apiFetch` 401 handler | Customize or keep (auto-redirects to login) |
---
## 3. How Sections Work (Routing)
### Sidebar navigation
Each sidebar item in `index.html`:
```html
<div class="nav-item" data-section="servers"> ... Servers</div>
```
The click handler (L175239 in `app.js`):
1. Reads `el.dataset.section` → e.g. `"servers"`
2. Toggles `.active` class on the nav item
3. Toggles `.active` class on `#section-servers`
4. Sets the page title from a label map
5. Shows the refresh button
6. Lazy-loads data if empty
### Corresponding section element
```html
<div class="section" id="section-servers"> ... </div>
```
Only one `.section` has `.active` at a time.
---
## 4. Servers Page (Dashboard)
### Data flow
```
serversLoadData()
├── GET https://n8n.derez.ai/webhook/server ← seat list (fetched directly, no auth)
│ Returns: [{ server: "hostname-01" }, ...]
├── For each server:
│ GET apiUrl(ROUTES.servers) + "?server=<name>" ← full dashboard (authenticated)
│ Parses item.text (JSON string) or item directly
│ Stores in serversData[]
└── serversRenderTabs() + serversRenderDashboard(0)
```
### Dashboard DOM structure
```
.section#section-servers
├── .card-header
│ ├── Tab bar: .server-tabs / #server-tabs-strip
│ └── Buttons (Update All, Harden All — optional)
└── .card#server-dashboard
├── Summary card (name, OS, kernel, uptime, status badge)
├── KPI row (CPU, RAM, Disk, Security Score)
├── Performance bars (CPU, RAM, Disk usage)
├── Security checks (SSH, Seccomp, AppArmor, SELinux)
├── Exposed ports
├── Action advice (priority items, upgrades)
└── Details: containers, packages, storage, Podman
```
### Key data shape (`serversData[i]`)
```js
{
server: {
name: "hostname-01",
os: "Ubuntu 22.04",
kernel: "5.15.0-xxx",
uptime: "42 days"
},
summary: {
overall_status: "green" | "yellow" | "red"
},
performance: {
cpu_idle_percent: 85.3,
ram_percent: 62,
ram_gb: 7.8,
total_ram_gb: 16
},
storage: {
total_gb: 256,
used_gb: 120,
percent: 47
},
kpis: {
kpi_19_security_score: 72
},
security: {
ssh_password_auth: false,
ssh_root_login: false,
seccomp_enabled: true,
apparmor_enabled: true,
selinux_enabled: false,
firewall_exposed_services: ["22", "80", "443"]
},
action_advice: {
priority_actions: [],
upgrade_needed: true,
ram_upgrade_recommended: false,
container_cleanup_needed: false
},
containers: [ /* … */ ],
packages: [ /* … */ ],
podman: { /* … */ },
seats: { server: "hostname-01", seat: 3 }
}
```
### What you need to change for the servers page
| Item | Change to |
|---|---|
| Seat list URL (hardcoded `https://n8n.derez.ai/webhook/server` in `serversLoadData`) | Your server list endpoint |
| `ROUTES.servers` (L10) | Your server dashboard endpoint path |
| The `item.text` JSON parsing (L918) | Adjust if your API shape differs |
| Dashboard widget content (render function L983+) | Keep, expand, or trim as needed |
---
## 5. What's Stripped: No Sales, No CRM, No Keys
The following have been **removed** from this consolidated doc — you should delete them from the HTML/CSS/JS when reusing:
### `index.html` — Remove these sidebar nav items
- `<div class="nav-item" data-section="keys">` (L84100)
- `<div class="nav-item" data-section="crm">` (L112129)
- `<div class="nav-item" data-section="sales">` (L130147)
Also remove the corresponding `.section` elements:
- `section-keys` (the div near L1060)
- `section-crm` (the div near L1098)
- `section-sales` (the div near L10981106)
### `app.js` — Remove these functions & state
- State: `keysData` (L22), `crmRecords` (L23), `crmSortKey` (L24), `crmSortDir` (L25)
- Labels: `keys`, `sales`, `crm` entries from the label map (L192194)
- Lazy-loads: the `if (key === "keys")` and `if (key === "crm")` blocks (L233237)
- Functions: `keysLoadRecords`, `keysRender`, `keysDelete` (L13031398)
- Functions: `crmLoadRecords`, `crmRender`, `crmClearFilters`, `crmToggleSort`, `crmUpdateDeleteBtn`, `crmToggleAll`, `crmDeleteContact`, `crmDeleteSelected`, `crmSetStageSelected`, `crmOpenEdit`, `crmCloseEdit`, `crmSaveEdit` (L14011823)
### `styles.css` — Remove CRM-specific styles
- `.crm-filter-input` and related rules (L824838+)
- `.crm-search-row th` (L839842)
---
## 6. What You Keep (Optional Sections)
If you also want **DNS**, **Instances**, or **Backup** in your new project, their patterns are the same:
| Section | Endpoint | Functions (in `app.js`) | Section ID |
|---|---|---|---|
| **DNS** | `ROUTES.dns` | `dnsLoadRecords`, `dnsRender`, `dnsDeleteRecord`, `dnsAddRecord` | `#section-dns` |
| **Instances** | `ROUTES.instances` | `instancesLoadRecords`, `instancesRender`, `instancesCopySSH`, `instancesDelete`, `instancesOpenEdit`, `instancesCloseEdit`, `instancesSaveEdit` | `#section-instances` |
| **Backup** | `ROUTES.backup` | `backupLoadRecords`, `backupRender`, `backupDelete` | `#section-backup` |
Each follows the same lazy-load, render, CRUD pattern.
---
## 7. Quick Start for a New Project
1. Copy `index.html`, `styles.css`, `app.js` to your project.
2. In `app.js`:
- Change `API_BASE` to your backend URL.
- Change `ROUTES` entries to match your API paths.
- Update the cookie names if desired.
- Remove `keys`, `crm`, `sales` code (see §5).
3. In `index.html`:
- Remove keys, CRM, and Sales nav items and section elements.
- Update the page title in `<head>`.
- Update the brand text "derez.ai" in the sidebar.
4. In `styles.css`:
- Remove CRM-specific CSS.
5. Update the server seat-list URL in `serversLoadData()` to your own endpoint.
6. Serve from any static server — no build step required.
+6 -5
View File
@@ -317,8 +317,8 @@ function dnsRender() {
const name = r.name.toLowerCase();
if (HIDDEN_NAMES.includes(name)) return false;
const matchType = !typeFilter || r.type === typeFilter;
const content = (r.records || [])
.map((x) => x.content)
const content = (r.records && r.records.length ? r.records : [r])
.map((x) => x.content || "")
.join(" ")
.toLowerCase();
const matchSearch =
@@ -347,7 +347,7 @@ function dnsRender() {
tbody.innerHTML = filtered
.map((r) => {
const records = r.records || [];
const records = r.records && r.records.length ? r.records : [r];
const content = records
.map((x) => `<span class="mono">${escHtml(x.content)}</span>`)
.join("<br>");
@@ -364,6 +364,7 @@ function dnsRender() {
<td style="white-space:nowrap;">${statusHtml}</td>
<td>
<button class="btn btn-danger btn-sm"
data-id="${escHtml(r.id)}"
data-name="${escHtml(r.name)}"
data-type="${escHtml(r.type)}"
onclick="dnsDeleteRecord(this)">
@@ -382,6 +383,7 @@ function dnsRender() {
/* ─── DNS — Delete ─────────────────────────────────────────────────── */
async function dnsDeleteRecord(btn) {
const record = {
id: btn.dataset.id,
name: btn.dataset.name,
type: btn.dataset.type,
};
@@ -396,8 +398,7 @@ async function dnsDeleteRecord(btn) {
method: "DELETE",
headers: apiHeaders(),
body: JSON.stringify({
name: record.name,
type: record.type,
id: record.id,
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);