Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11898faa3d | ||
|
|
896f3b01bb |
+265
@@ -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 (L175–239 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">` (L84–100)
|
||||
- `<div class="nav-item" data-section="crm">` (L112–129)
|
||||
- `<div class="nav-item" data-section="sales">` (L130–147)
|
||||
|
||||
Also remove the corresponding `.section` elements:
|
||||
|
||||
- `section-keys` (the div near L1060)
|
||||
- `section-crm` (the div near L1098)
|
||||
- `section-sales` (the div near L1098–1106)
|
||||
|
||||
### `app.js` — Remove these functions & state
|
||||
|
||||
- State: `keysData` (L22), `crmRecords` (L23), `crmSortKey` (L24), `crmSortDir` (L25)
|
||||
- Labels: `keys`, `sales`, `crm` entries from the label map (L192–194)
|
||||
- Lazy-loads: the `if (key === "keys")` and `if (key === "crm")` blocks (L233–237)
|
||||
- Functions: `keysLoadRecords`, `keysRender`, `keysDelete` (L1303–1398)
|
||||
- Functions: `crmLoadRecords`, `crmRender`, `crmClearFilters`, `crmToggleSort`, `crmUpdateDeleteBtn`, `crmToggleAll`, `crmDeleteContact`, `crmDeleteSelected`, `crmSetStageSelected`, `crmOpenEdit`, `crmCloseEdit`, `crmSaveEdit` (L1401–1823)
|
||||
|
||||
### `styles.css` — Remove CRM-specific styles
|
||||
|
||||
- `.crm-filter-input` and related rules (L824–838+)
|
||||
- `.crm-search-row th` (L839–842)
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user