Files
admin.derez.ai/index.html
T
oliver 230129e05b .
2026-06-09 19:21:14 -03:00

1140 lines
53 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>derez.ai — Admin</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="layout">
<!-- ═══ SIDEBAR ════════════════════════════════════════════════ -->
<nav class="sidebar">
<div class="sidebar-brand">
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="2" y="3" width="20" height="14" rx="2" />
<path d="M8 21h8M12 17v4" />
</svg>
derez.ai
</div>
<div class="sidebar-section-label">Network</div>
<div class="nav-item active" data-section="dns">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
<path d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3" />
</svg>
DNS Records
</div>
<div class="nav-item" data-section="coming-soon-firewall">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
</svg>
Firewall
</div>
<div class="sidebar-section-label">Infrastructure</div>
<div class="nav-item" data-section="instances">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
</svg>
Instances
</div>
<div class="nav-item" data-section="backup">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
Backup
</div>
<div class="nav-item" data-section="coming-soon-ssl">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="3" y="11" width="18" height="11" rx="2" />
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
</svg>
SSL / TLS
</div>
<div
class="sidebar-section-label"
style="
margin-top: 28px;
padding-top: 12px;
border-top: 1px solid var(--border);
"
>
Dashboards
</div>
<div class="nav-item" data-section="sales">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="3" y="3" width="7" height="7" />
<rect x="14" y="3" width="7" height="7" />
<rect x="14" y="14" width="7" height="7" />
<rect x="3" y="14" width="7" height="7" />
</svg>
Sales
</div>
<div class="nav-item" data-section="servers">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="2" y="2" width="20" height="8" rx="2" />
<rect x="2" y="14" width="20" height="8" rx="2" />
<line x1="6" y1="6" x2="6.01" y2="6" />
<line x1="6" y1="18" x2="6.01" y2="18" />
</svg>
Servers
</div>
<div class="nav-item" data-section="keys">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"
/>
</svg>
Keys
</div>
<div class="sidebar-footer">
<div
class="nav-item"
id="sidebar-user"
style="
cursor: default;
gap: 8px;
padding: 9px 18px;
border-left: none;
color: var(--text-muted);
font-size: 12px;
"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"
/>
<circle cx="12" cy="7" r="4" />
</svg>
<span id="sidebar-username">Not signed in</span>
</div>
<div class="nav-item" data-section="settings">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="3" />
<path
d="M19.07 4.93a10 10 0 0 0-14.14 0M4.93 19.07a10 10 0 0 0 14.14 0M12 2v2M12 20v2M2 12h2M20 12h2"
/>
</svg>
Settings
</div>
<div
class="nav-item"
onclick="doSignOut()"
style="
color: var(--danger);
border-left: none;
padding: 9px 0;
"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
Sign Out
</div>
</div>
</nav>
<!-- ═══ MAIN ════════════════════════════════════════════════════ -->
<div class="main">
<!-- Top bar -->
<div class="topbar">
<span class="topbar-title" id="page-title"
>DNS Records</span
>
<div class="topbar-actions" id="topbar-actions">
<button
class="btn btn-ghost btn-sm"
id="btn-refresh"
onclick="dnsLoadRecords()"
>
<svg
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M23 4v6h-6" />
<path d="M1 20v-6h6" />
<path
d="M3.51 9a9 9 0 0 1 14.36-3.36L23 10M1 14l5.13 4.36A9 9 0 0 0 20.49 15"
/>
</svg>
Refresh
</button>
</div>
</div>
<!-- Content -->
<div class="content">
<!-- ─── DNS Section ───────────────────────────────────────── -->
<div class="section active" id="section-dns">
<!-- Filter / search bar -->
<div
class="card"
style="padding: 14px 20px; margin-bottom: 16px"
>
<div
class="form-row"
style="
flex-wrap: nowrap;
gap: 10px;
align-items: center;
"
>
<div
class="form-group"
style="flex: 2; min-width: 140px"
>
<input
type="text"
id="dns-search"
placeholder="Search by name or content…"
oninput="dnsRender()"
/>
</div>
<div
class="form-group"
style="flex: 1; min-width: 110px"
>
<select
id="dns-filter-type"
onchange="dnsRender()"
>
<option value="">All types</option>
<option selected>A</option>
<option>AAAA</option>
<option>CNAME</option>
<option>MX</option>
<option>TXT</option>
<option>NS</option>
<option>SRV</option>
</select>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path
d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"
/>
<path
d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3"
/>
</svg>
Records
</span>
<span
id="dns-record-count"
class="text-muted"
style="font-size: 12px"
></span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Content</th>
<th>TTL</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="dns-table-body">
<tr>
<td colspan="6">
<div class="empty-state">
<div class="spinner"></div>
<p style="margin-top: 12px">
Loading records…
</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- ─── Add New Record ───────────────────────────────────── -->
<div class="card" id="dns-add-card">
<div
class="card-header"
style="margin-bottom: 14px"
>
<span class="card-title">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="8" y1="12" x2="16" y2="12" />
</svg>
Add New Record
</span>
</div>
<div class="form-row">
<div
class="form-group"
style="flex: 2; min-width: 140px"
>
<label
>Name
<span style="color: var(--text-muted)"
>(@ for root)</span
></label
>
<input
type="text"
id="add-name"
placeholder="e.g. www"
/>
</div>
<div
class="form-group"
style="flex: 1; min-width: 100px"
>
<label>Type</label>
<select id="add-type">
<option>A</option>
<option>AAAA</option>
<option>CNAME</option>
<option>MX</option>
<option>TXT</option>
<option>NS</option>
<option>SRV</option>
<option>CAA</option>
</select>
</div>
<div
class="form-group"
style="flex: 3; min-width: 180px"
>
<label>Content</label>
<input
type="text"
id="add-content"
placeholder="e.g. 1.2.3.4 or target.domain.com."
/>
</div>
<div
class="form-group"
style="flex: 1; min-width: 100px"
>
<label>TTL (s)</label>
<input
type="number"
id="add-ttl"
value="3600"
min="60"
/>
</div>
<div
class="form-group"
style="
flex: 0 0 auto;
justify-content: flex-end;
"
>
<label style="visibility: hidden"
>Add</label
>
<button
class="btn btn-primary"
id="add-submit-btn"
onclick="dnsAddRecord()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
Add Record
</button>
</div>
</div>
</div>
</div>
<!-- ─── Instances Section ──────────────────────────────────── -->
<div class="section" id="section-instances">
<!-- Search bar -->
<div
class="card"
style="padding: 14px 20px; margin-bottom: 16px"
>
<div
class="form-row"
style="
flex-wrap: nowrap;
gap: 10px;
align-items: center;
"
>
<div
class="form-group"
style="flex: 1; min-width: 140px"
>
<input
type="text"
id="inst-search"
placeholder="Search by agent, domain, UUID or server…"
oninput="instancesRender()"
/>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
/>
<polyline
points="3.27 6.96 12 12.01 20.73 6.96"
/>
<line
x1="12"
y1="22.08"
x2="12"
y2="12"
/>
</svg>
Instances
</span>
<span
id="inst-record-count"
class="text-muted"
style="font-size: 12px"
></span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>UUID</th>
<th>Agent</th>
<th>Domain</th>
<th>SSH Port</th>
<th>RAM</th>
<th>CPU</th>
<th>Server</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="inst-table-body">
<tr>
<td colspan="9">
<div class="empty-state">
<div class="spinner"></div>
<p style="margin-top: 12px">
Loading instances…
</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ─── Backup Section ─────────────────────────────────────── -->
<div class="section" id="section-backup">
<!-- Search bar -->
<div
class="card"
style="padding: 14px 20px; margin-bottom: 16px"
>
<div
class="form-row"
style="
flex-wrap: nowrap;
gap: 10px;
align-items: center;
"
>
<div
class="form-group"
style="flex: 1; min-width: 140px"
>
<input
type="text"
id="backup-search"
placeholder="Search by name or ID…"
oninput="backupRender()"
/>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
/>
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
Repositories
</span>
<span
id="backup-record-count"
class="text-muted"
style="font-size: 12px"
></span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>ID</th>
<th>Region</th>
<th style="text-align: right">
Size
</th>
<th>Last Modified</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="backup-table-body">
<tr>
<td colspan="7">
<div class="empty-state">
<div class="spinner"></div>
<p style="margin-top: 12px">
Loading repositories…
</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ─── Settings Section ──────────────────────────────────── -->
<div class="section" id="section-settings">
<div class="card">
<div class="card-header">
<span class="card-title">API Connection</span>
</div>
<div class="settings-grid">
<div
class="form-group"
style="grid-column: 1/-1"
>
<label>API Base URL</label>
<input
type="text"
id="setting-url"
placeholder="https://n8n.derez.ai/webhook"
/>
</div>
</div>
<div style="margin-top: 16px">
<button
class="btn btn-primary"
onclick="settingsSave()"
>
Save URL
</button>
<button
class="btn btn-ghost"
onclick="settingsTest()"
style="margin-left: 8px"
>
Test Connection
</button>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">Current Session</span>
</div>
<p
style="
font-size: 13px;
color: var(--text-muted);
margin-bottom: 16px;
"
>
Signed in as
<strong id="settings-session-email"></strong>.
Sign out to switch accounts.
</p>
<div style="margin-top: 16px">
<button
class="btn btn-danger"
onclick="doSignOut()"
>
Sign Out
</button>
</div>
</div>
</div>
<!-- ─── Keys Section ───────────────────────────────────── -->
<div class="section" id="section-keys">
<div class="card">
<div class="card-header">
<span class="card-title">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"
/>
</svg>
API Keys
</span>
<span
id="keys-record-count"
class="text-muted"
style="font-size: 12px"
></span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Key</th>
<th>Status</th>
<th>Limit</th>
<th>Remaining</th>
<th>Reset</th>
<th>Usage (monthly)</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="keys-table-body">
<tr>
<td colspan="9">
<div class="empty-state">
<div class="spinner"></div>
<p style="margin-top: 12px">
Loading keys…
</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ─── Sales Dashboard ─────────────────────────── -->
<div class="section" id="section-sales">
<div class="card">
<div class="card-header">
<span class="card-title">Sales Dashboard</span>
</div>
<div style="padding: 16px 0 0">
<iframe
src="https://derez.ai/operations.html"
style="
width: 100%;
height: 80vh;
border: none;
border-radius: 6px;
background: #fff;
"
title="Sales Dashboard"
allow="clipboard-read; clipboard-write"
></iframe>
<p
style="
font-size: 12px;
color: var(--text-muted);
margin-top: 10px;
text-align: center;
"
>
<a
href="https://derez.ai/operations.html"
target="_blank"
style="color: var(--accent)"
>
Open in new tab ↗
</a>
</p>
</div>
</div>
</div>
<div class="section" id="section-servers">
<div id="server-tabs-strip" class="server-tabs"></div>
<div id="server-dashboard">
<div class="empty-state">
<div class="spinner"></div>
<p style="margin-top: 12px">Loading servers…</p>
</div>
</div>
</div>
<!-- ─── Coming-soon placeholders ─────────────────────────── -->
<div class="section" id="section-coming-soon-firewall">
<div class="card">
<div class="empty-state">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
/>
</svg>
<p>Firewall management — coming soon</p>
</div>
</div>
</div>
<div class="section" id="section-coming-soon-ssl">
<div class="card">
<div class="empty-state">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect
x="3"
y="11"
width="18"
height="11"
rx="2"
/>
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
</svg>
<p>SSL / TLS management — coming soon</p>
</div>
</div>
</div>
</div>
<!-- /content -->
</div>
<!-- /main -->
</div>
<!-- /layout -->
<!-- ═══ INSTANCES EDIT MODAL ═════════════════════════════════ -->
<div class="modal-backdrop" id="inst-edit-backdrop">
<div class="modal">
<div class="modal-header">
<span class="modal-title">Edit Instance</span>
<button
class="modal-close"
onclick="instancesCloseEdit()"
aria-label="Close"
>
&#x2715;
</button>
</div>
<div class="modal-body">
<div class="form-row" style="flex-wrap: wrap">
<div class="form-group" style="flex: 0 0 100%">
<label
>UUID
<span class="text-muted"
>(read-only)</span
></label
>
<input
type="text"
id="inst-edit-uuid"
disabled
style="opacity: 0.5; cursor: not-allowed"
/>
</div>
<div
class="form-group"
style="flex: 1; min-width: 120px"
>
<label
>Agent
<span class="text-muted"
>(read-only)</span
></label
>
<input
type="text"
id="inst-edit-agent"
disabled
style="opacity: 0.5; cursor: not-allowed"
/>
</div>
<div
class="form-group"
style="flex: 2; min-width: 200px"
>
<label
>Domain
<span class="text-muted"
>(read-only)</span
></label
>
<input
type="text"
id="inst-edit-domain"
disabled
style="opacity: 0.5; cursor: not-allowed"
/>
</div>
</div>
<hr class="sep" />
<div class="form-row" style="flex-wrap: wrap">
<div
class="form-group"
style="flex: 1; min-width: 110px"
>
<label>SSH Port</label>
<input
type="text"
id="inst-edit-ssh-port"
placeholder="e.g. 22"
/>
</div>
<div
class="form-group"
style="flex: 1; min-width: 110px"
>
<label>RAM</label>
<input
type="text"
id="inst-edit-ram"
placeholder="e.g. 512m"
/>
</div>
<div
class="form-group"
style="flex: 1; min-width: 110px"
>
<label>CPU</label>
<input
type="text"
id="inst-edit-cpu"
placeholder="e.g. 2"
/>
</div>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-ghost"
onclick="instancesCloseEdit()"
>
Cancel
</button>
<button
class="btn btn-primary"
id="inst-save-btn"
onclick="instancesSaveEdit()"
>
<svg
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
Save Changes
</button>
</div>
</div>
</div>
<!-- ═══ CONFIRM DIALOG ══════════════════════════════════════════════ -->
<div class="modal-backdrop" id="confirm-backdrop">
<div class="modal" id="confirm-modal" style="max-width: 360px">
<div class="modal-header">
<span class="modal-title" id="confirm-title">Confirm</span>
<button
class="modal-close"
onclick="confirmClose()"
aria-label="Close"
>
&#x2715;
</button>
</div>
<div class="modal-body">
<p id="confirm-message" style="line-height: 1.6"></p>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="confirmClose()">
Cancel
</button>
<button class="btn btn-danger" id="confirm-ok-btn">
Delete
</button>
</div>
</div>
</div>
<!-- ═══ LOGIN MODAL ═══════════════════════════════════════════════════ -->
<div id="login-backdrop">
<div id="login-modal">
<div class="login-logo">
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="2" y="3" width="20" height="14" rx="2" />
<path d="M8 21h8M12 17v4" />
</svg>
<h2>derez.ai Admin</h2>
<p>Sign in with your derez.ai account</p>
</div>
<div id="login-error"></div>
<div class="modal-body">
<div class="form-group">
<label>Email</label>
<input
type="email"
id="login-email"
autocomplete="email"
placeholder="you@example.com"
/>
</div>
<div class="form-group">
<label>Password</label>
<input
type="password"
id="login-password"
autocomplete="current-password"
placeholder="••••••••"
/>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-primary"
style="width: 100%"
id="login-btn"
onclick="doSignIn()"
>
Sign In
</button>
</div>
</div>
</div>
<!-- ═══ TOAST CONTAINER ════════════════════════════════════════════ -->
<div id="toast-container"></div>
<script src="app.js"></script>
</body>
</html>