Files
chat/ws_sos_tester.html
Andrew K. Choi cfc93cb99a
All checks were successful
continuous-integration/drone/push Build is passing
feat: Fix nutrition service and add location-based alerts
Changes:
- Fix nutrition service: add is_active column and Pydantic validation for UUID/datetime
- Add location-based alerts feature: users can now see alerts within 1km radius
- Fix CORS and response serialization in nutrition service
- Add getCurrentLocation() and loadAlertsNearby() functions
- Improve UI for nearby alerts display with distance and response count
2025-12-13 16:34:50 +09:00

1207 lines
42 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚨 WebSocket SOS Service Tester</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
color: white;
margin-bottom: 30px;
padding: 20px;
background: rgba(0,0,0,0.2);
border-radius: 10px;
border-left: 4px solid #ff4444;
}
header h1 {
font-size: 2.5em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
header p {
font-size: 1em;
opacity: 0.95;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.tab-btn {
padding: 12px 20px;
border: none;
background: rgba(255,255,255,0.2);
color: white;
cursor: pointer;
border-radius: 5px;
font-weight: bold;
transition: all 0.3s;
}
.tab-btn:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
}
.tab-btn.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.panel {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
border-left: 4px solid #667eea;
}
.panel.warning {
border-left-color: #ff9800;
}
.panel.success {
border-left-color: #4caf50;
}
.panel.danger {
border-left-color: #f44336;
}
.panel h2 {
color: #667eea;
margin-bottom: 15px;
font-size: 1.5em;
}
.panel h3 {
color: #555;
margin: 15px 0 10px 0;
font-size: 1.1em;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: bold;
font-size: 0.95em;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
textarea,
select {
width: 100%;
padding: 10px;
border: 2px solid #e0e0e0;
border-radius: 5px;
font-size: 0.95em;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="number"]:focus,
textarea:focus,
select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 10px rgba(102, 126, 234, 0.2);
}
textarea {
resize: vertical;
min-height: 80px;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 15px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
font-size: 0.95em;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-success {
background: #4caf50;
color: white;
}
.btn-success:hover {
background: #45a049;
transform: translateY(-2px);
}
.btn-danger {
background: #f44336;
color: white;
}
.btn-danger:hover {
background: #da190b;
transform: translateY(-2px);
}
.btn-warning {
background: #ff9800;
color: white;
}
.btn-warning:hover {
background: #e68900;
transform: translateY(-2px);
}
.status {
padding: 15px;
border-radius: 5px;
margin-top: 10px;
font-weight: bold;
display: none;
animation: slideIn 0.3s ease;
}
.status.show {
display: block;
}
.status.success {
background: #d4edda;
color: #155724;
border: 2px solid #4caf50;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 2px solid #f44336;
}
.status.info {
background: #d1ecf1;
color: #0c5460;
border: 2px solid #667eea;
}
.status.warning {
background: #fff3cd;
color: #856404;
border: 2px solid #ff9800;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message-log {
background: #f5f5f5;
border: 2px solid #e0e0e0;
border-radius: 5px;
padding: 15px;
height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.9em;
margin-top: 15px;
}
.log-entry {
padding: 8px;
margin: 5px 0;
border-radius: 3px;
word-break: break-all;
animation: slideIn 0.2s ease;
}
.log-entry.info {
background: #e3f2fd;
color: #1565c0;
border-left: 3px solid #667eea;
}
.log-entry.success {
background: #e8f5e9;
color: #2e7d32;
border-left: 3px solid #4caf50;
}
.log-entry.error {
background: #ffebee;
color: #c62828;
border-left: 3px solid #f44336;
}
.log-entry.warning {
background: #fff8e1;
color: #f57f17;
border-left: 3px solid #ff9800;
}
.log-entry.timestamp {
font-size: 0.85em;
opacity: 0.7;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin: 20px 0;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.stat-value {
font-size: 2em;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.9em;
opacity: 0.9;
}
.connection-indicator {
display: inline-block;
width: 15px;
height: 15px;
border-radius: 50%;
margin-right: 10px;
animation: pulse 2s infinite;
}
.connection-indicator.connected {
background: #4caf50;
}
.connection-indicator.disconnected {
background: #f44336;
animation: none;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.alert-box {
padding: 15px;
border-radius: 5px;
margin: 15px 0;
display: flex;
align-items: center;
gap: 10px;
}
.alert-box.info {
background: #e3f2fd;
border-left: 4px solid #667eea;
color: #1565c0;
}
.alert-box.success {
background: #e8f5e9;
border-left: 4px solid #4caf50;
color: #2e7d32;
}
.alert-box.warning {
background: #fff3cd;
border-left: 4px solid #ff9800;
color: #856404;
}
.alert-box.error {
background: #f8d7da;
border-left: 4px solid #f44336;
color: #721c24;
}
.code-block {
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 0.9em;
margin: 10px 0;
}
.highlight {
background: #fffacd;
padding: 2px 4px;
border-radius: 3px;
}
footer {
text-align: center;
color: white;
margin-top: 40px;
padding: 20px;
opacity: 0.8;
}
@media (max-width: 768px) {
.grid-2, .grid-3 {
grid-template-columns: 1fr;
}
header h1 {
font-size: 1.8em;
}
.tabs {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🚨 WebSocket SOS Service Tester</h1>
<p>Real-time Emergency Signal Testing Platform</p>
</header>
<div class="tabs">
<button class="tab-btn active" onclick="switchTab('auth')">🔐 Authentication</button>
<button class="tab-btn" onclick="switchTab('websocket')">📡 WebSocket</button>
<button class="tab-btn" onclick="switchTab('sos')">🚨 SOS Alert</button>
<button class="tab-btn" onclick="switchTab('monitor')">📊 Monitor</button>
<button class="tab-btn" onclick="switchTab('guide')">📖 Guide</button>
</div>
<!-- 🔐 Authentication Tab -->
<div id="auth" class="tab-content active">
<div class="grid-2">
<div class="panel">
<h2>🔐 Login</h2>
<div class="form-group">
<label>Email:</label>
<input type="email" id="login-email" placeholder="wstester@test.com" value="wstester@test.com">
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" id="login-password" placeholder="WsTest1234!" value="WsTest1234!">
</div>
<div class="button-group">
<button class="btn-primary" onclick="login()">🔓 Login</button>
<button class="btn-warning" onclick="clearAuth()">🗑️ Clear</button>
</div>
<div id="auth-status" class="status"></div>
</div>
<div class="panel">
<h2>📋 Token Info</h2>
<div id="token-info" style="font-family: monospace; font-size: 0.9em; word-break: break-all;">
<p style="color: #999;">No token yet. Login first.</p>
</div>
<button class="btn-warning" onclick="copyToken()" style="margin-top: 15px;">📋 Copy Token</button>
</div>
</div>
</div>
<!-- 📡 WebSocket Tab -->
<div id="websocket" class="tab-content">
<div class="grid-2">
<div class="panel">
<h2>📡 WebSocket Connection</h2>
<p style="margin-bottom: 15px;">
<span class="connection-indicator disconnected" id="ws-indicator"></span>
<span id="ws-status">Disconnected</span>
</p>
<div class="form-group">
<label>WebSocket URL:</label>
<input type="text" id="ws-url" placeholder="ws://localhost:8002/api/v1/emergency/ws/{user_id}?token={jwt_token}" readonly>
</div>
<div class="button-group">
<button class="btn-success" onclick="connectWebSocket()">🔌 Connect</button>
<button class="btn-danger" onclick="disconnectWebSocket()">❌ Disconnect</button>
</div>
<div id="ws-status-msg" class="status"></div>
</div>
<div class="panel">
<h2>🏓 Ping/Pong Test</h2>
<p>Test connection heartbeat:</p>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="ping-count">0</div>
<div class="stat-label">Pings Sent</div>
</div>
<div class="stat-card">
<div class="stat-value" id="pong-count">0</div>
<div class="stat-label">Pongs Received</div>
</div>
<div class="stat-card">
<div class="stat-value" id="ping-latency">--</div>
<div class="stat-label">Latency (ms)</div>
</div>
</div>
<button class="btn-primary" onclick="sendPing()">📤 Send Ping</button>
</div>
</div>
<div class="panel">
<h2>📨 WebSocket Messages Log</h2>
<button class="btn-warning" onclick="clearLog()">🗑️ Clear Log</button>
<div id="ws-log" class="message-log"></div>
</div>
</div>
<!-- 🚨 SOS Alert Tab -->
<div id="sos" class="tab-content">
<div class="grid-2">
<div class="panel">
<h2>🚨 Create SOS Alert</h2>
<div class="form-group">
<label>Alert Type:</label>
<select id="alert-type">
<option value="violence">🚨 Violence</option>
<option value="medical">🏥 Medical</option>
<option value="harassment">😠 Harassment</option>
<option value="unsafe_area">⚠️ Unsafe Area</option>
<option value="accident">🚗 Accident</option>
<option value="fire">🔥 Fire</option>
<option value="general">📍 General Emergency</option>
</select>
</div>
<div class="form-group">
<label>Latitude:</label>
<input type="number" id="alert-lat" placeholder="55.7558" value="55.7558" step="0.0001">
</div>
<div class="form-group">
<label>Longitude:</label>
<input type="number" id="alert-lon" placeholder="37.6173" value="37.6173" step="0.0001">
</div>
<div class="form-group">
<label>Address:</label>
<input type="text" id="alert-address" placeholder="Current location" value="Test Location">
</div>
<div class="form-group">
<label>Message:</label>
<textarea id="alert-message" placeholder="Describe the emergency...">🚨 WebSocket Test SOS Alert</textarea>
</div>
<div class="button-group">
<button class="btn-danger" onclick="createSOSAlert()">🚨 Create SOS Alert</button>
<button class="btn-warning" onclick="clearSOSForm()">🗑️ Clear</button>
</div>
<div id="sos-status" class="status"></div>
</div>
<div class="panel">
<h2>📍 Location Info</h2>
<button class="btn-primary" onclick="getLocation()" style="margin-bottom: 15px;">📍 Get Current Location</button>
<div id="location-info" style="background: #f5f5f5; padding: 15px; border-radius: 5px; min-height: 100px;">
<p style="color: #999;">Location will be shown here</p>
</div>
</div>
</div>
<div class="panel">
<h2>📊 Recent Alerts</h2>
<div id="alerts-list" style="max-height: 300px; overflow-y: auto;">
<p style="color: #999;">No alerts created yet</p>
</div>
</div>
</div>
<!-- 📊 Monitor Tab -->
<div id="monitor" class="tab-content">
<div class="grid-3">
<div class="panel">
<h3>🟢 Connection Status</h3>
<div class="stat-card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); margin-top: 10px;">
<div id="conn-status" class="stat-value">OFF</div>
</div>
</div>
<div class="panel">
<h3>📨 Messages Received</h3>
<div class="stat-card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); margin-top: 10px;">
<div id="msg-count" class="stat-value">0</div>
</div>
</div>
<div class="panel">
<h3>⏱️ Uptime</h3>
<div class="stat-card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); margin-top: 10px;">
<div id="uptime" class="stat-value">0s</div>
</div>
</div>
</div>
<div class="grid-2">
<div class="panel">
<h3>📈 Performance Metrics</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 8px;">Avg Latency:</td>
<td style="text-align: right; font-weight: bold;" id="avg-latency">--</td>
</tr>
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 8px;">Max Latency:</td>
<td style="text-align: right; font-weight: bold;" id="max-latency">--</td>
</tr>
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 8px;">Min Latency:</td>
<td style="text-align: right; font-weight: bold;" id="min-latency">--</td>
</tr>
<tr>
<td style="padding: 8px;">Success Rate:</td>
<td style="text-align: right; font-weight: bold;" id="success-rate">--</td>
</tr>
</table>
</div>
<div class="panel">
<h3>🧪 Auto Test</h3>
<p style="margin-bottom: 15px; font-size: 0.95em;">Run automated tests every 30 seconds:</p>
<div class="button-group">
<button class="btn-primary" onclick="startAutoTest()">▶️ Start</button>
<button class="btn-danger" onclick="stopAutoTest()">⏹️ Stop</button>
</div>
<div id="auto-test-status" class="status" style="display: none;"></div>
</div>
</div>
<div class="panel">
<h2>📊 System Statistics</h2>
<div id="system-stats" style="font-family: monospace; font-size: 0.9em; white-space: pre-wrap; word-break: break-word; max-height: 200px; overflow-y: auto; background: #f5f5f5; padding: 15px; border-radius: 5px;">
Waiting for data...
</div>
</div>
</div>
<!-- 📖 Guide Tab -->
<div id="guide" class="tab-content">
<div class="panel">
<h2>📖 How to Test WebSocket SOS Service</h2>
<h3>1. 🔐 Step 1: Authenticate</h3>
<ol style="margin: 10px 0 20px 20px;">
<li>Go to <strong>Authentication</strong> tab</li>
<li>Enter credentials (default: wstester@test.com / WsTest1234!)</li>
<li>Click <strong>Login</strong></li>
<li>You should see a JWT token in the Token Info panel</li>
</ol>
<h3>2. 📡 Step 2: Connect WebSocket</h3>
<ol style="margin: 10px 0 20px 20px;">
<li>Go to <strong>WebSocket</strong> tab</li>
<li>Click <strong>Connect</strong> button</li>
<li>You should see connection status change to "Connected"</li>
<li>Green indicator means WebSocket is active</li>
</ol>
<h3>3. 🏓 Step 3: Test Ping/Pong</h3>
<ol style="margin: 10px 0 20px 20px;">
<li>Click <strong>Send Ping</strong> button</li>
<li>Watch for pong response in the message log</li>
<li>Latency should be < 100ms</li>
</ol>
<h3>4. 🚨 Step 4: Create SOS Alert</h3>
<ol style="margin: 10px 0 20px 20px;">
<li>Go to <strong>SOS Alert</strong> tab</li>
<li>Fill in location (latitude/longitude)</li>
<li>Enter message</li>
<li>Click <strong>Create SOS Alert</strong></li>
<li>Check WebSocket Messages Log for alert notification</li>
</ol>
<h3>5. 📊 Step 5: Monitor Results</h3>
<ol style="margin: 10px 0 20px 20px;">
<li>Go to <strong>Monitor</strong> tab</li>
<li>View connection status, message count, and uptime</li>
<li>Check performance metrics (latency, success rate)</li>
</ol>
<div class="alert-box info" style="margin-top: 20px;">
<span style="font-size: 1.2em;"></span>
<div>
<strong>Default Test Credentials:</strong><br>
Email: <code style="background: #f5f5f5; padding: 2px 5px;">wstester@test.com</code><br>
Password: <code style="background: #f5f5f5; padding: 2px 5px;">WsTest1234!</code>
</div>
</div>
<h3 style="margin-top: 30px;">🔧 API Endpoints</h3>
<div class="code-block">
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "wstester@test.com",
"password": "WsTest1234!"
}
</div>
<div class="code-block">
WebSocket: ws://localhost:8002/api/v1/emergency/ws/{user_id}?token={jwt_token}
</div>
<div class="code-block">
POST /api/v1/alert
Authorization: Bearer {jwt_token}
Content-Type: application/json
{
"latitude": 55.7558,
"longitude": 37.6173,
"address": "Location",
"alert_type": "SOS",
"message": "Emergency message"
}
</div>
<h3 style="margin-top: 30px;">💡 Tips</h3>
<ul style="margin: 10px 0 20px 20px;">
<li>Keep WebSocket connected while testing SOS alerts</li>
<li>Messages appear in real-time in the WebSocket Messages Log</li>
<li>Green indicator = Connection is active</li>
<li>Red indicator = Connection is lost</li>
<li>Latency < 100ms is excellent</li>
<li>Use Monitor tab to track performance</li>
</ul>
</div>
</div>
</div>
<footer>
<p>🚨 WebSocket SOS Service Tester | v1.0 | Test Date: 13 Dec 2025</p>
</footer>
<script>
// Global state
let state = {
token: localStorage.getItem('jwt_token') || null,
userId: localStorage.getItem('user_id') || null,
ws: null,
connected: false,
pingCount: 0,
pongCount: 0,
messageCount: 0,
messages: [],
alerts: [],
startTime: null,
latencies: [],
autoTestRunning: false,
autoTestInterval: null
};
// API Configuration
const CONFIG = {
userServiceUrl: 'http://localhost:8001',
emergencyServiceUrl: 'http://localhost:8002',
wsUrl: 'ws://localhost:8002'
};
// Tab switching
function switchTab(tabName) {
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(tabName).classList.add('active');
event.target.classList.add('active');
}
// ===== Authentication Functions =====
async function login() {
const email = document.getElementById('login-email').value;
const password = document.getElementById('login-password').value;
if (!email || !password) {
showStatus('auth-status', 'Please fill in all fields', 'error');
return;
}
showStatus('auth-status', 'Logging in...', 'info');
try {
const response = await fetch(`${CONFIG.userServiceUrl}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error(`Login failed: ${response.status}`);
}
const data = await response.json();
state.token = data.access_token;
// Decode JWT to get user_id
const parts = state.token.split('.');
if (parts.length === 3) {
const payload = JSON.parse(atob(parts[1]));
state.userId = payload.sub;
}
localStorage.setItem('jwt_token', state.token);
localStorage.setItem('user_id', state.userId);
showStatus('auth-status', '✅ Login successful!', 'success');
displayTokenInfo();
updateWSUrl();
} catch (error) {
showStatus('auth-status', `${error.message}`, 'error');
}
}
function displayTokenInfo() {
const info = document.getElementById('token-info');
if (state.token) {
info.innerHTML = `
<p><strong>Token:</strong></p>
<p style="word-break: break-all; background: #f5f5f5; padding: 10px; border-radius: 5px; font-size: 0.85em;">${state.token.substring(0, 100)}...</p>
<p><strong>User ID:</strong> ${state.userId}</p>
<p><strong>Status:</strong> <span style="color: #4caf50;">✅ Authenticated</span></p>
`;
}
}
function copyToken() {
if (state.token) {
navigator.clipboard.writeText(state.token);
showStatus('auth-status', 'Token copied to clipboard!', 'success');
}
}
function clearAuth() {
state.token = null;
state.userId = null;
localStorage.removeItem('jwt_token');
localStorage.removeItem('user_id');
document.getElementById('token-info').innerHTML = '<p style="color: #999;">No token yet. Login first.</p>';
showStatus('auth-status', 'Authentication cleared', 'info');
}
// ===== WebSocket Functions =====
function updateWSUrl() {
if (state.token && state.userId) {
const url = `${CONFIG.wsUrl}/api/v1/emergency/ws/${state.userId}?token=${state.token}`;
document.getElementById('ws-url').value = url;
}
}
function connectWebSocket() {
if (!state.token) {
showStatus('ws-status-msg', 'Please login first', 'error');
return;
}
if (state.ws && state.connected) {
showStatus('ws-status-msg', 'Already connected', 'warning');
return;
}
const url = `${CONFIG.wsUrl}/api/v1/emergency/ws/${state.userId}?token=${state.token}`;
try {
state.ws = new WebSocket(url);
state.ws.onopen = () => {
state.connected = true;
state.startTime = new Date();
updateConnectionIndicator();
showStatus('ws-status-msg', '✅ WebSocket connected!', 'success');
addLog('Connection established', 'success');
};
state.ws.onmessage = (event) => {
state.messageCount++;
addLog(`Message: ${event.data}`, 'info');
try {
const data = JSON.parse(event.data);
if (data.type === 'pong') {
state.pongCount++;
const latency = Date.now() - (state.lastPingTime || 0);
state.latencies.push(latency);
document.getElementById('pong-count').textContent = state.pongCount;
document.getElementById('ping-latency').textContent = latency + 'ms';
} else if (data.type === 'emergency_alert') {
addLog(`🚨 SOS Alert: ${JSON.stringify(data)}`, 'error');
}
} catch (e) {
// Invalid JSON, just log as-is
}
document.getElementById('msg-count').textContent = state.messageCount;
};
state.ws.onerror = (error) => {
addLog(`Error: ${error.message}`, 'error');
showStatus('ws-status-msg', `❌ WebSocket error: ${error}`, 'error');
};
state.ws.onclose = () => {
state.connected = false;
updateConnectionIndicator();
showStatus('ws-status-msg', '⚠️ WebSocket disconnected', 'warning');
addLog('Disconnected', 'warning');
};
} catch (error) {
showStatus('ws-status-msg', `❌ Failed to connect: ${error.message}`, 'error');
}
}
function disconnectWebSocket() {
if (state.ws) {
state.ws.close();
state.connected = false;
updateConnectionIndicator();
showStatus('ws-status-msg', 'Disconnected', 'info');
}
}
function sendPing() {
if (!state.connected) {
showStatus('ws-status-msg', 'WebSocket not connected', 'error');
return;
}
state.lastPingTime = Date.now();
state.pingCount++;
state.ws.send(JSON.stringify({ type: 'ping' }));
document.getElementById('ping-count').textContent = state.pingCount;
addLog('Ping sent', 'info');
}
// ===== SOS Alert Functions =====
async function createSOSAlert() {
if (!state.token) {
showStatus('sos-status', 'Please login first', 'error');
return;
}
const alertType = document.getElementById('alert-type').value;
const lat = parseFloat(document.getElementById('alert-lat').value);
const lon = parseFloat(document.getElementById('alert-lon').value);
const address = document.getElementById('alert-address').value;
const message = document.getElementById('alert-message').value;
if (!lat || !lon || !address || !message) {
showStatus('sos-status', 'Please fill in all fields', 'error');
return;
}
showStatus('sos-status', 'Creating SOS alert...', 'info');
try {
const response = await fetch(`${CONFIG.emergencyServiceUrl}/api/v1/alert`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${state.token}`
},
body: JSON.stringify({
latitude: lat,
longitude: lon,
address: address,
alert_type: alertType,
message: message,
contact_emergency_services: true,
notify_emergency_contacts: true
})
});
if (!response.ok) {
throw new Error(`Failed: ${response.status}`);
}
const alert = await response.json();
state.alerts.unshift(alert);
showStatus('sos-status', '✅ SOS Alert created!', 'success');
addLog(`🚨 SOS Alert created: ID=${alert.id}`, 'success');
updateAlertsList();
clearSOSForm();
} catch (error) {
showStatus('sos-status', `${error.message}`, 'error');
addLog(`Error creating alert: ${error.message}`, 'error');
}
}
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
document.getElementById('alert-lat').value = lat.toFixed(4);
document.getElementById('alert-lon').value = lon.toFixed(4);
const info = document.getElementById('location-info');
info.innerHTML = `
<p><strong>📍 Current Location:</strong></p>
<p>Latitude: <strong>${lat.toFixed(6)}</strong></p>
<p>Longitude: <strong>${lon.toFixed(6)}</strong></p>
<p>Accuracy: <strong>${position.coords.accuracy.toFixed(0)}m</strong></p>
`;
}, () => {
document.getElementById('location-info').innerHTML = '<p style="color: #f44336;">❌ Location access denied</p>';
});
} else {
document.getElementById('location-info').innerHTML = '<p style="color: #f44336;">❌ Geolocation not supported</p>';
}
}
function updateAlertsList() {
const list = document.getElementById('alerts-list');
if (state.alerts.length === 0) {
list.innerHTML = '<p style="color: #999;">No alerts created yet</p>';
return;
}
list.innerHTML = state.alerts.map(alert => `
<div style="background: #f5f5f5; padding: 10px; margin: 10px 0; border-radius: 5px; border-left: 4px solid #f44336;">
<p><strong>ID:</strong> ${alert.id}</p>
<p><strong>Type:</strong> ${alert.alert_type}</p>
<p><strong>Location:</strong> ${alert.address}</p>
<p><strong>Message:</strong> ${alert.message}</p>
</div>
`).join('');
}
function clearSOSForm() {
document.getElementById('alert-type').value = 'SOS';
document.getElementById('alert-lat').value = '55.7558';
document.getElementById('alert-lon').value = '37.6173';
document.getElementById('alert-address').value = 'Test Location';
document.getElementById('alert-message').value = '🚨 WebSocket Test SOS Alert';
}
// ===== Monitoring Functions =====
function startAutoTest() {
if (state.autoTestRunning) return;
state.autoTestRunning = true;
showStatus('auto-test-status', '▶️ Auto test started - running every 30 seconds', 'success');
state.autoTestInterval = setInterval(() => {
if (state.connected) {
sendPing();
}
}, 30000);
// Run initial test
sendPing();
}
function stopAutoTest() {
if (state.autoTestInterval) {
clearInterval(state.autoTestInterval);
}
state.autoTestRunning = false;
showStatus('auto-test-status', '⏹️ Auto test stopped', 'info');
}
function updateStats() {
// Update uptime
if (state.startTime) {
const uptime = Math.floor((new Date() - state.startTime) / 1000);
document.getElementById('uptime').textContent = uptime + 's';
}
// Update connection status
document.getElementById('conn-status').textContent = state.connected ? 'ON' : 'OFF';
// Update performance metrics
if (state.latencies.length > 0) {
const avg = Math.round(state.latencies.reduce((a, b) => a + b) / state.latencies.length);
const max = Math.max(...state.latencies);
const min = Math.min(...state.latencies);
const rate = ((state.pongCount / state.pingCount) * 100).toFixed(1);
document.getElementById('avg-latency').textContent = avg + 'ms';
document.getElementById('max-latency').textContent = max + 'ms';
document.getElementById('min-latency').textContent = min + 'ms';
document.getElementById('success-rate').textContent = rate + '%';
}
// Update system stats
const stats = `
Connected: ${state.connected ? 'YES ✅' : 'NO ❌'}
Messages Received: ${state.messageCount}
Pings Sent: ${state.pingCount}
Pongs Received: ${state.pongCount}
Token Valid: ${state.token ? 'YES ✅' : 'NO ❌'}
Uptime: ${state.startTime ? Math.floor((new Date() - state.startTime) / 1000) + 's' : '0s'}
`.trim();
document.getElementById('system-stats').textContent = stats;
}
// ===== UI Helper Functions =====
function updateConnectionIndicator() {
const indicator = document.getElementById('ws-indicator');
const status = document.getElementById('ws-status');
if (state.connected) {
indicator.classList.remove('disconnected');
indicator.classList.add('connected');
status.textContent = '🟢 Connected';
} else {
indicator.classList.add('disconnected');
indicator.classList.remove('connected');
status.textContent = '🔴 Disconnected';
}
}
function showStatus(elementId, message, type) {
const element = document.getElementById(elementId);
element.textContent = message;
element.className = 'status show ' + type;
}
function addLog(message, type) {
const log = document.getElementById('ws-log');
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = 'log-entry ' + type;
entry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
function clearLog() {
document.getElementById('ws-log').innerHTML = '';
}
// ===== Initialization =====
window.addEventListener('DOMContentLoaded', () => {
// Restore session
if (state.token && state.userId) {
displayTokenInfo();
updateWSUrl();
}
// Update stats every second
setInterval(updateStats, 1000);
// Show initial info
addLog('Tester loaded. Ready to test!', 'success');
});
// Cleanup on unload
window.addEventListener('beforeunload', () => {
if (state.ws) {
state.ws.close();
}
});
</script>
</body>
</html>