sms-gateway/public/index.html
2026-03-04 19:54:03 +01:00

448 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SMS Gateway</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/css/style.css" />
</head>
<body>
<div id="app">
<!-- ── Login ───────────────────────────────────────────────────────────── -->
<div id="login-page">
<div class="login-card">
<h1>📡 SMS Gateway</h1>
<p>Sign in to access the dashboard</p>
<div id="login-error" class="alert alert-danger" style="display:none;margin-bottom:16px;"></div>
<form id="login-form" style="display:flex;flex-direction:column;gap:16px;">
<div class="form-group">
<label>Username</label>
<input id="login-username" type="text" placeholder="admin" autocomplete="username" />
</div>
<div class="form-group">
<label>Password</label>
<input id="login-password" type="password" placeholder="••••••••" autocomplete="current-password" />
</div>
<button type="submit" class="btn btn-primary" style="width:100%;justify-content:center;">Sign in</button>
</form>
</div>
</div>
<!-- ── Sidebar ─────────────────────────────────────────────────────────── -->
<nav id="sidebar">
<div class="sidebar-logo">
<span>📡</span> SMS Gateway
</div>
<div class="nav-item active" data-page="dashboard">
<span class="icon">🏠</span> Dashboard
</div>
<div class="nav-item" data-page="send">
<span class="icon">✉️</span> Send SMS
</div>
<div class="nav-item" data-page="received">
<span class="icon">📥</span> Received
</div>
<div class="nav-item" data-page="sent">
<span class="icon">📤</span> Sent
</div>
<div class="nav-item" data-page="webhooks">
<span class="icon">🔗</span> Webhooks
</div>
<div class="nav-item" data-page="tokens">
<span class="icon">🔑</span> API Tokens
</div>
<div class="nav-item" data-page="settings">
<span class="icon">⚙️</span> Settings
</div>
<div class="sidebar-spacer"></div>
<div class="sidebar-user">
<div class="avatar" id="user-avatar">A</div>
<div class="user-info">
<div class="user-name" id="user-name">Admin</div>
</div>
<button class="logout-btn" id="logout-btn" title="Sign out"></button>
</div>
</nav>
<!-- ── Main ────────────────────────────────────────────────────────────── -->
<main id="main">
<!-- Dashboard -->
<div class="page active" id="page-dashboard">
<div class="page-header">
<div>
<h2>Dashboard</h2>
<p>Overview of your SMS gateway</p>
</div>
<button class="btn btn-outline" onclick="App.refreshDashboard()">↻ Refresh</button>
</div>
<div class="stat-grid">
<div class="stat-card">
<div class="stat-label">Messages Sent</div>
<div class="stat-value blue" id="stat-sent"></div>
</div>
<div class="stat-card">
<div class="stat-label">Messages Received</div>
<div class="stat-value green" id="stat-received"></div>
</div>
<div class="stat-card">
<div class="stat-label">Failed</div>
<div class="stat-value red" id="stat-failed"></div>
</div>
</div>
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<strong>Recent Messages</strong>
<button class="btn btn-sm btn-outline" onclick="App.loadDashboard()"></button>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th></th>
<th>Phone</th>
<th>Message</th>
<th>Status</th>
<th>Time</th>
</tr>
</thead>
<tbody id="recent-messages">
<tr><td colspan="5"><div class="empty-state"><div class="spinner"></div></div></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Send SMS -->
<div class="page" id="page-send">
<div class="page-header">
<div>
<h2>Send SMS</h2>
<p>Send a message directly from this dashboard</p>
</div>
</div>
<div class="card" style="max-width:600px;">
<form class="send-form" id="send-form">
<div id="send-alert" style="display:none;"></div>
<div class="form-group">
<label>Recipient phone number</label>
<input id="send-number" type="tel" placeholder="+46701234567" />
</div>
<div class="form-group">
<label>Message</label>
<textarea id="send-message" placeholder="Type your message…" rows="4"></textarea>
<small style="color:var(--muted);"><span id="char-count">0</span> / 160 characters (SMS segment 1)</small>
</div>
<div style="display:flex;gap:10px;">
<button type="submit" class="btn btn-primary" id="send-btn">
✉️ Send Message
</button>
</div>
</form>
</div>
</div>
<!-- Received Messages -->
<div class="page" id="page-received">
<div class="page-header">
<div>
<h2>Received Messages</h2>
<p>Incoming SMS messages from your device</p>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-outline" onclick="App.triggerPoll()">↻ Poll device</button>
</div>
</div>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>From</th>
<th>Message</th>
<th>Received at</th>
</tr>
</thead>
<tbody id="received-list">
<tr><td colspan="3"><div class="empty-state"><div class="spinner"></div></div></td></tr>
</tbody>
</table>
</div>
<div class="pagination" id="received-pagination"></div>
</div>
</div>
<!-- Sent Messages -->
<div class="page" id="page-sent">
<div class="page-header">
<div>
<h2>Sent Messages</h2>
<p>Outgoing SMS messages</p>
</div>
</div>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>To</th>
<th>Message</th>
<th>Status</th>
<th>Sent at</th>
</tr>
</thead>
<tbody id="sent-list">
<tr><td colspan="4"><div class="empty-state"><div class="spinner"></div></div></td></tr>
</tbody>
</table>
</div>
<div class="pagination" id="sent-pagination"></div>
</div>
</div>
<!-- Webhooks -->
<div class="page" id="page-webhooks">
<div class="page-header">
<div>
<h2>Webhooks</h2>
<p>Receive alerts from external apps (uptime-kuma, etc.) and send SMS</p>
</div>
<button class="btn btn-primary" onclick="App.openWebhookModal()">+ Add Webhook</button>
</div>
<div class="card" style="margin-bottom:16px;">
<strong style="display:block;margin-bottom:8px;">How it works</strong>
<p style="color:var(--muted);font-size:13px;margin-bottom:8px;">
Each webhook has a unique URL. Configure your external app to POST JSON to that URL and this gateway will send an SMS.
</p>
<code style="font-size:12px;color:var(--primary);">POST /api/hooks/&lt;token&gt; — no auth required, the token is the secret</code>
<p style="color:var(--muted);font-size:13px;margin-top:8px;">
The <strong>message template</strong> supports <code style="color:var(--primary);">{{key}}</code> placeholders resolved from the JSON body.
For uptime-kuma the top-level <code style="color:var(--primary);">msg</code> field contains the full alert text.
</p>
</div>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Send SMS to</th>
<th>Webhook URL</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="webhook-list">
<tr><td colspan="5"><div class="empty-state"><div class="spinner"></div></div></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- API Tokens -->
<div class="page" id="page-tokens">
<div class="page-header">
<div>
<h2>API Tokens</h2>
<p>Bearer tokens for external API access</p>
</div>
<button class="btn btn-primary" onclick="App.openTokenModal()">+ Create Token</button>
</div>
<div class="card" style="margin-bottom:16px;">
<strong style="display:block;margin-bottom:8px;">API Usage</strong>
<p style="color:var(--muted);font-size:13px;margin-bottom:12px;">
Use your token in the <code style="color:var(--primary);">Authorization</code> header:
</p>
<div class="token-value">Authorization: Bearer &lt;your-token&gt;</div>
<div style="margin-top:12px;display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--muted);">
<code>POST /api/sms/send { "number": "+46…", "message": "…" }</code>
<code>GET /api/sms/messages</code>
<code>GET /api/sms/messages/received</code>
<code>GET /api/sms/messages/sent</code>
</div>
</div>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Token</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="token-list">
<tr><td colspan="5"><div class="empty-state"><div class="spinner"></div></div></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Settings -->
<div class="page" id="page-settings">
<div class="page-header">
<div>
<h2>Settings</h2>
<p>Configure your Teltonika device connection</p>
</div>
</div>
<div class="card" style="max-width:600px;">
<div class="config-section">
<div>
<h3>Device Connection</h3>
<div style="display:flex;flex-direction:column;gap:16px;">
<div id="settings-alert" style="display:none;"></div>
<div class="form-group">
<label>Device IP address</label>
<input id="cfg-ip" placeholder="192.168.1.1" />
</div>
<div class="form-row">
<div class="form-group">
<label>Username</label>
<input id="cfg-username" placeholder="admin" />
</div>
<div class="form-group">
<label>Password</label>
<input id="cfg-password" type="password" placeholder="••••••••" />
</div>
</div>
<div class="form-group">
<label>Modem ID</label>
<input id="cfg-modem" placeholder="1-1" />
<small style="color:var(--muted);">Modem identifier (e.g. 1-1, 3-1.4)</small>
</div>
<div class="form-group">
<label>Poll interval (seconds)</label>
<input id="cfg-poll" type="number" min="10" max="3600" placeholder="30" />
</div>
<div style="display:flex;gap:10px;">
<button class="btn btn-primary" onclick="App.saveConfig()">Save settings</button>
<button class="btn btn-outline" onclick="App.testConnection()">Test connection</button>
</div>
</div>
</div>
<div>
<h3>Logging</h3>
<div style="display:flex;flex-direction:column;gap:16px;">
<div id="log-alert" style="display:none;"></div>
<div class="form-group">
<label>Log level</label>
<select id="cfg-log-level">
<option value="ERROR">ERROR — errors only</option>
<option value="WARNING">WARNING — errors + warnings</option>
<option value="INFO">INFO — normal operation (default)</option>
<option value="DEBUG">DEBUG — verbose, includes HTTP requests</option>
</select>
<small style="color:var(--muted);">Also configurable via <code>LOG_LEVEL</code> env var at startup</small>
</div>
<button class="btn btn-primary" onclick="App.saveLogLevel()" style="align-self:flex-start;">
Apply log level
</button>
</div>
</div>
<div>
<h3>Change Password</h3>
<div style="display:flex;flex-direction:column;gap:16px;">
<div id="pw-alert" style="display:none;"></div>
<div class="form-group">
<label>Current password</label>
<input id="pw-current" type="password" />
</div>
<div class="form-group">
<label>New password</label>
<input id="pw-new" type="password" />
</div>
<div class="form-group">
<label>Confirm new password</label>
<input id="pw-confirm" type="password" />
</div>
<button class="btn btn-primary" onclick="App.changePassword()" style="align-self:flex-start;">
Update password
</button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- ── Toast container ───────────────────────────────────────────────────── -->
<div id="toast-container"></div>
<!-- ── Webhook Modal ─────────────────────────────────────────────────────── -->
<div class="modal-backdrop" id="webhook-modal">
<div class="modal">
<div class="modal-header">
<h3 id="webhook-modal-title">Add Webhook</h3>
<button class="modal-close" onclick="App.closeModal('webhook-modal')">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Name</label>
<input id="wh-name" placeholder="Uptime-kuma alerts" />
</div>
<div class="form-group">
<label>Send SMS to (phone number)</label>
<input id="wh-phone" placeholder="+46701234567" />
</div>
<div class="form-group">
<label>Message template</label>
<input id="wh-template" placeholder="{{msg}}" />
<small style="color:var(--muted);font-size:11px;">
Use <code>{{key}}</code> or <code>{{key.subkey}}</code> — resolved from the incoming JSON body.
Default: <code>{{msg}}</code> (works for uptime-kuma).
</small>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="App.closeModal('webhook-modal')">Cancel</button>
<button class="btn btn-primary" onclick="App.saveWebhook()">Save</button>
</div>
</div>
</div>
<!-- ── Token Modal ───────────────────────────────────────────────────────── -->
<div class="modal-backdrop" id="token-modal">
<div class="modal">
<div class="modal-header">
<h3>Create API Token</h3>
<button class="modal-close" onclick="App.closeModal('token-modal')">×</button>
</div>
<div class="modal-body">
<div id="token-created" style="display:none;">
<div class="alert alert-success">Token created — copy it now, it will not be shown again.</div>
<div style="margin-top:12px;" class="form-group">
<label>Token value</label>
<div class="token-value" id="new-token-value"></div>
</div>
<button class="btn btn-outline btn-sm" style="margin-top:8px;" onclick="App.copyToken()">Copy</button>
</div>
<div id="token-form">
<div class="form-group">
<label>Token name</label>
<input id="tk-name" placeholder="e.g. My Application" />
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="App.closeModal('token-modal')">Close</button>
<button class="btn btn-primary" id="token-create-btn" onclick="App.createToken()">Create</button>
</div>
</div>
</div>
<script src="/js/app.js"></script>
</body>
</html>