1496 lines
72 KiB
HTML
1496 lines
72 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="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="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-section-label"
|
|
style="
|
|
margin-top: 28px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--border);
|
|
"
|
|
>
|
|
Dashboards
|
|
</div>
|
|
<div class="nav-item" data-section="crm">
|
|
<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="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
|
<circle cx="9" cy="7" r="4" />
|
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
</svg>
|
|
CRM
|
|
</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="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"
|
|
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>Product</th>
|
|
<th>Server</th>
|
|
<th>Email</th>
|
|
<th>Period</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="inst-table-body">
|
|
<tr>
|
|
<td colspan="8">
|
|
<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>
|
|
|
|
<!-- ─── 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>
|
|
|
|
<!-- ─── CRM Section ─────────────────────────── -->
|
|
<div class="section" id="section-crm">
|
|
<!-- Action bar -->
|
|
<div
|
|
class="card"
|
|
style="padding: 10px 20px; margin-bottom: 16px"
|
|
>
|
|
<div
|
|
class="form-row"
|
|
style="
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
align-items: center;
|
|
"
|
|
>
|
|
<div class="form-group" style="flex: 0 0 auto">
|
|
<button
|
|
class="btn btn-sm"
|
|
id="crm-hold-btn"
|
|
onclick="crmSetStageSelected('Hold')"
|
|
disabled
|
|
style="
|
|
white-space: nowrap;
|
|
background: var(--accent-dim);
|
|
color: var(--accent);
|
|
border: 1px solid var(--border-hi);
|
|
"
|
|
>
|
|
Set Hold
|
|
</button>
|
|
</div>
|
|
<div class="form-group" style="flex: 0 0 auto">
|
|
<button
|
|
class="btn btn-sm"
|
|
id="crm-active-btn"
|
|
onclick="crmSetStageSelected('Active')"
|
|
disabled
|
|
style="
|
|
white-space: nowrap;
|
|
background: var(--accent-dim);
|
|
color: var(--accent);
|
|
border: 1px solid var(--border-hi);
|
|
"
|
|
>
|
|
Set Active
|
|
</button>
|
|
</div>
|
|
<div class="form-group" style="flex: 0 0 auto">
|
|
<button
|
|
class="btn btn-danger btn-sm"
|
|
id="crm-delete-selected-btn"
|
|
onclick="crmDeleteSelected()"
|
|
disabled
|
|
style="white-space: nowrap"
|
|
>
|
|
<svg
|
|
width="12"
|
|
height="12"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2.5"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<polyline points="3 6 5 6 21 6" />
|
|
<path
|
|
d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"
|
|
/>
|
|
<path d="M10 11v6M14 11v6" />
|
|
<path
|
|
d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"
|
|
/>
|
|
</svg>
|
|
Delete Checked
|
|
</button>
|
|
</div>
|
|
<div
|
|
class="form-group"
|
|
style="
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
"
|
|
id="crm-category-counts"
|
|
></div>
|
|
<span style="flex: 1; min-width: 0"></span>
|
|
</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="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"
|
|
/>
|
|
<circle cx="9" cy="7" r="4" />
|
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
</svg>
|
|
CRM Contacts
|
|
</span>
|
|
<span
|
|
id="crm-record-count"
|
|
class="text-muted"
|
|
style="font-size: 12px"
|
|
></span>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 36px">
|
|
<input
|
|
type="checkbox"
|
|
id="crm-check-all"
|
|
onchange="crmToggleAll()"
|
|
title="Select / deselect all"
|
|
/>
|
|
</th>
|
|
<th
|
|
onclick="crmToggleSort('Name')"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Name
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="Name"
|
|
></span>
|
|
</th>
|
|
<th
|
|
onclick="
|
|
crmToggleSort('Company')
|
|
"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Company
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="Company"
|
|
></span>
|
|
</th>
|
|
<th
|
|
onclick="crmToggleSort('email')"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Email
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="email"
|
|
></span>
|
|
</th>
|
|
<th
|
|
onclick="crmToggleSort('Stage')"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Stage
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="Stage"
|
|
></span>
|
|
</th>
|
|
<th
|
|
onclick="
|
|
crmToggleSort('strategy')
|
|
"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Strategy
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="strategy"
|
|
></span>
|
|
</th>
|
|
<th
|
|
onclick="
|
|
crmToggleSort('category')
|
|
"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Category
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="category"
|
|
></span>
|
|
</th>
|
|
<th
|
|
onclick="
|
|
crmToggleSort('affiliate')
|
|
"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Referral
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="affiliate"
|
|
></span>
|
|
</th>
|
|
<th
|
|
onclick="
|
|
crmToggleSort(
|
|
'date_next_contact',
|
|
)
|
|
"
|
|
style="
|
|
cursor: pointer;
|
|
user-select: none;
|
|
"
|
|
>
|
|
Date Next Contact
|
|
<span
|
|
class="sort-arrow"
|
|
data-sort="date_next_contact"
|
|
></span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tr class="crm-search-row">
|
|
<th></th>
|
|
<th>
|
|
<input
|
|
type="text"
|
|
id="crm-search-name"
|
|
placeholder="Filter Name…"
|
|
oninput="crmRender()"
|
|
class="crm-filter-input"
|
|
/>
|
|
</th>
|
|
<th>
|
|
<input
|
|
type="text"
|
|
id="crm-search-company"
|
|
placeholder="Filter Company…"
|
|
oninput="crmRender()"
|
|
class="crm-filter-input"
|
|
/>
|
|
</th>
|
|
<th>
|
|
<input
|
|
type="text"
|
|
id="crm-search-email"
|
|
placeholder="Filter Email…"
|
|
oninput="crmRender()"
|
|
class="crm-filter-input"
|
|
/>
|
|
</th>
|
|
<th>
|
|
<input
|
|
type="text"
|
|
id="crm-search-stage"
|
|
placeholder="Filter Stage…"
|
|
oninput="crmRender()"
|
|
class="crm-filter-input"
|
|
/>
|
|
</th>
|
|
<th>
|
|
<input
|
|
type="text"
|
|
id="crm-search-strategy"
|
|
placeholder="Filter Strategy…"
|
|
oninput="crmRender()"
|
|
class="crm-filter-input"
|
|
/>
|
|
</th>
|
|
<th>
|
|
<input
|
|
type="text"
|
|
id="crm-search-category"
|
|
placeholder="Filter Category…"
|
|
oninput="crmRender()"
|
|
class="crm-filter-input"
|
|
/>
|
|
</th>
|
|
<th style="white-space: nowrap">
|
|
<input
|
|
type="text"
|
|
id="crm-search-affiliate"
|
|
placeholder="Filter Referral…"
|
|
oninput="crmRender()"
|
|
class="crm-filter-input"
|
|
/>
|
|
<button
|
|
class="btn btn-ghost btn-sm"
|
|
onclick="crmClearFilters()"
|
|
title="Clear all filters"
|
|
>
|
|
✕
|
|
</button>
|
|
</th>
|
|
</tr>
|
|
<tbody id="crm-table-body">
|
|
<tr>
|
|
<td colspan="9">
|
|
<div class="empty-state">
|
|
<div class="spinner"></div>
|
|
<p style="margin-top: 12px">
|
|
Loading contacts…
|
|
</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>
|
|
</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"
|
|
>
|
|
✕
|
|
</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>
|
|
|
|
<!-- ═══ CRM EDIT MODAL ════════════════════════════════════ -->
|
|
<div class="modal-backdrop" id="crm-edit-backdrop">
|
|
<div class="modal" style="max-width: 640px">
|
|
<div class="modal-header">
|
|
<span class="modal-title" id="crm-edit-title"
|
|
>Edit Contact</span
|
|
>
|
|
<button
|
|
class="modal-close"
|
|
onclick="crmCloseEdit()"
|
|
aria-label="Close"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="hidden" id="crm-edit-id" />
|
|
<div class="form-row" style="flex-wrap: wrap">
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 160px"
|
|
>
|
|
<label>Name</label>
|
|
<input
|
|
type="text"
|
|
id="crm-edit-name"
|
|
placeholder="Contact name"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 160px"
|
|
>
|
|
<label>Company</label>
|
|
<input
|
|
type="text"
|
|
id="crm-edit-company"
|
|
placeholder="Company"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 200px"
|
|
>
|
|
<label>Email</label>
|
|
<input
|
|
type="email"
|
|
id="crm-edit-email"
|
|
placeholder="email@example.com"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="form-row" style="flex-wrap: wrap">
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 120px"
|
|
>
|
|
<label>Stage</label>
|
|
<select id="crm-edit-stage">
|
|
<option value="">— Select —</option>
|
|
<option>Active</option>
|
|
<option>Hold</option>
|
|
<option>Blacklist</option>
|
|
<option>Won</option>
|
|
<option>Lost</option>
|
|
<option>Contacted</option>
|
|
<option>Cold</option>
|
|
<option>Warm</option>
|
|
<option>Bounced</option>
|
|
</select>
|
|
</div>
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 160px"
|
|
>
|
|
<label>Strategy</label>
|
|
<input
|
|
type="text"
|
|
id="crm-edit-strategy"
|
|
placeholder="Strategy"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 120px"
|
|
>
|
|
<label>Category</label>
|
|
<input
|
|
type="text"
|
|
id="crm-edit-category"
|
|
placeholder="Category"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="form-row" style="flex-wrap: wrap">
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 120px"
|
|
>
|
|
<label>Affiliate</label>
|
|
<input
|
|
type="text"
|
|
id="crm-edit-affiliate"
|
|
placeholder="Affiliate"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 120px"
|
|
>
|
|
<label>Number</label>
|
|
<input
|
|
type="text"
|
|
id="crm-edit-number"
|
|
placeholder="Phone number"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="form-group"
|
|
style="flex: 1; min-width: 160px"
|
|
>
|
|
<label>Date Next Contact</label>
|
|
<input type="date" id="crm-edit-date-next" />
|
|
</div>
|
|
</div>
|
|
<div class="form-group" style="margin-top: 10px">
|
|
<label>History / Notes</label>
|
|
<textarea
|
|
id="crm-edit-history"
|
|
rows="4"
|
|
placeholder="Contact history, notes…"
|
|
style="width: 100%; resize: vertical"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-ghost" onclick="crmCloseEdit()">
|
|
Cancel
|
|
</button>
|
|
<button
|
|
class="btn btn-primary"
|
|
id="crm-save-btn"
|
|
onclick="crmSaveEdit()"
|
|
>
|
|
<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"
|
|
>
|
|
✕
|
|
</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>
|