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

456 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>Forward SMS events to external services</p>
</div>
<button class="btn btn-primary" onclick="App.openWebhookModal()">+ Add Webhook</button>
</div>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Phone filter</th>
<th>URL</th>
<th>Events</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="webhook-list">
<tr><td colspan="6"><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="My webhook" />
</div>
<div class="form-group">
<label>URL</label>
<input id="wh-url" placeholder="https://example.com/webhook" />
</div>
<div class="form-row">
<div class="form-group">
<label>HTTP Method</label>
<select id="wh-method">
<option value="POST">POST</option>
<option value="GET">GET</option>
<option value="PUT">PUT</option>
</select>
</div>
<div class="form-group">
<label>Events</label>
<select id="wh-events">
<option value="received">Received only</option>
<option value="sent">Sent only</option>
<option value="both">Both</option>
</select>
</div>
</div>
<div class="form-group">
<label>Phone number filter (leave empty for all)</label>
<input id="wh-phone" placeholder="+46701234567" />
</div>
<div class="form-group">
<label>Extra headers (JSON object, optional)</label>
<textarea id="wh-headers" rows="3" placeholder='{"X-Api-Key": "secret"}'></textarea>
</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>