signalk-parquet
Version:
SignalK plugin and webapp that archives SK data to Parquet files with a regimen control system, advanced querying, Claude integrated AI analysis, spatial capabilities, and REST API.
1,014 lines (879 loc) • 71.4 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SignalK Parquet Data Interface</title>
<link rel="icon" type="image/png" href="parquet.png">
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #2BB5D6 0%, #013542 100%);
color: white;
padding: 20px 0;
margin-bottom: 20px;
border-radius: 10px;
text-align: center;
position: relative;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
margin: 0 0 10px 0;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.section {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.section h2 {
color: #667eea;
margin-bottom: 15px;
font-size: 1.5rem;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #555;
}
input, select, textarea {
width: 100%;
padding: 10px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 14px;
transition: border-color 0.3s;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #667eea;
}
textarea {
resize: vertical;
min-height: 100px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
button {
background: #667eea;
color: white;
padding: 12px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
margin-right: 10px;
margin-bottom: 10px;
}
button:hover {
background: #5a6fd8;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-secondary {
background: #6c757d;
}
.btn-secondary:hover {
background: #5a6268;
}
.results {
margin-top: 20px;
}
.table-container {
overflow-x: auto;
margin-top: 15px;
}
table {
width: 100%;
border-collapse: collapse;
min-width: 600px;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
word-wrap: break-word;
}
th:nth-child(1), td:nth-child(1) { min-width: 180px; } /* Path */
th:nth-child(2), td:nth-child(2) { width: 60px; } /* Enabled */
th:nth-child(3), td:nth-child(3) { min-width: 100px; } /* Regimen */
th:nth-child(4), td:nth-child(4) { min-width: 120px; } /* Source */
th:nth-child(5), td:nth-child(5) { width: 90px; } /* Context */
th:nth-child(6), td:nth-child(6) { min-width: 120px; } /* Exclude MMSI */
th:nth-child(7), td:nth-child(7) { width: 140px; } /* Actions */
th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
}
tr:hover {
background-color: #f8f9fa;
}
.edit-command-form-row td {
padding: 0;
background: transparent;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
padding: 10px;
border-radius: 5px;
margin-top: 10px;
}
.success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
padding: 10px;
border-radius: 5px;
margin-top: 10px;
}
.path-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 15px;
margin-top: 15px;
}
.path-item {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
border-left: 4px solid #667eea;
}
.path-item h4 {
margin-bottom: 5px;
color: #495057;
}
.path-item p {
font-size: 0.9rem;
color: #666;
}
.path-item button {
margin-top: 10px;
padding: 5px 10px;
font-size: 12px;
}
.query-examples {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-top: 15px;
}
.query-examples h4 {
margin-bottom: 10px;
color: #495057;
}
.query-examples ul {
list-style: none;
padding: 0;
}
.query-examples li {
margin-bottom: 10px;
padding: 10px;
background: white;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.3s;
}
.query-examples li:hover {
background: #e9ecef;
}
.query-examples code {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9rem;
}
.stats {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
padding: 10px;
background: #f8f9fa;
border-radius: 5px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: #667eea;
}
.stat-label {
font-size: 0.9rem;
color: #666;
}
.collapsible-header {
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
margin: -20px -20px 15px -20px;
background: #f8f9fa;
border-radius: 10px 10px 0 0;
transition: background-color 0.3s;
}
.collapsible-header:hover {
background: #e9ecef;
}
.collapsible-header h2 {
margin: 0;
}
.collapse-icon {
font-size: 1.2rem;
transition: transform 0.3s;
}
.collapse-icon.collapsed {
transform: rotate(-90deg);
}
.collapsible-content {
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.collapsible-content.collapsed {
max-height: 0;
}
.collapsible-content.expanded {
max-height: 2000px;
}
/* Tab Navigation Styles */
.tab-navigation {
display: flex;
background: white;
border-radius: 10px 10px 0 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 0;
overflow-x: auto;
}
.tab-button {
background: #f8f9fa;
border: none;
padding: 15px 20px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: #666;
border-radius: 0;
transition: all 0.3s;
white-space: nowrap;
border-bottom: 3px solid transparent;
margin: 0;
}
.tab-button:first-child {
border-radius: 10px 0 0 0;
}
.tab-button:last-child {
border-radius: 0 10px 0 0;
}
.tab-button:hover {
background: #e9ecef;
color: #333;
}
.tab-button.active {
background: white;
color: #667eea;
border-bottom-color: #667eea;
}
.tab-content {
background: white;
border-radius: 0 0 10px 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.tab-panel {
display: none;
}
.tab-panel.active {
display: block;
}
.tab-panel .section {
margin-bottom: 0;
box-shadow: none;
border-radius: 0 0 10px 10px;
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 2rem;
}
.stats {
flex-direction: column;
gap: 10px;
}
.tab-navigation {
flex-wrap: wrap;
}
.tab-button {
flex: 1;
min-width: 120px;
padding: 12px 15px;
font-size: 13px;
}
.tab-button:first-child,
.tab-button:last-child {
border-radius: 0;
}
}
/* Hierarchical Path Tree Styles */
.path-tree-container {
border: 1px solid #ddd;
border-radius: 4px;
background: white;
max-height: 400px;
overflow-y: auto;
padding: 8px;
}
.path-tree-item {
padding: 4px 8px;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
gap: 4px;
}
.path-tree-item:hover {
background: #f0f8ff;
}
.path-tree-item.selected {
background: #d0e8ff;
font-weight: 500;
}
.path-tree-item.has-value {
color: #0066cc;
}
.path-tree-toggle {
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
flex-shrink: 0;
}
.path-tree-children {
margin-left: 20px;
display: none;
}
.path-tree-children.expanded {
display: block;
}
.path-tree-search {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 8px;
}
.path-tree-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="parquet.png" alt="Parquet" style="height: 80px; position: absolute; left: 20px; top: 20px; border-radius: 5px;">
<h1 style="margin: 0 0 10px 0;">
SignalK Parquet Data Store
</h1>
<p>Export, query, and explore your SignalK data archived as Parquet files</p>
</div>
<!-- Tab Navigation -->
<div class="tab-navigation">
<button class="tab-button active" onclick="showTab('pathConfig')">⚙️ Path Configuration</button>
<button class="tab-button" onclick="showTab('commandManager')">🎮 Regimen/Commands Manager</button>
<button class="tab-button" onclick="showTab('liveConnections')" style="display: none;">🔴 Live Connections</button>
<button class="tab-button" onclick="showTab('dataPaths')">🗄️ Query Database</button>
<button class="tab-button" onclick="showTab('aiAnalysis')">🧠 AI Analysis <small>(EXPERIMENTAL)</small></button>
<button class="tab-button" onclick="showTab('dataValidation')">🔍 Data Validation</button>
<button class="tab-button" onclick="showTab('s3Config')">☁️ Cloud Status</button>
</div>
<!-- Tab Content Panels -->
<div class="tab-content">
<!-- Path Configuration Tab -->
<div id="pathConfig" class="tab-panel active">
<div class="section">
<h2>⚙️ Path Configuration</h2>
<p>Manage which SignalK paths to collect data from:</p>
<!-- Home Port Configuration -->
<div id="homePortConfig" style="margin: 15px 0 20px 0; padding: 15px; background: #e8f5e9; border-radius: 8px; border: 2px solid #4caf50;">
<h3 style="margin: 0 0 10px 0; color: #2e7d32; display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none;" onclick="toggleHomePortCollapse()">
<span id="homePortToggle">▼</span>
🏠 Home Port Location
<span style="font-size: 0.8em; font-weight: normal; color: #666;">(Used for proximity-based thresholds)</span>
</h3>
<div id="homePortDetails">
<div style="display: grid; grid-template-columns: 1fr 1fr auto; gap: 12px; align-items: end;">
<div>
<label for="homePortLat" style="display: block; margin-bottom: 5px; font-weight: 500; color: #333;">Latitude:</label>
<input type="number" id="homePortLat" step="0.000001" placeholder="e.g., 40.712800" style="width: 100%; padding: 8px; border: 1px solid #4caf50; border-radius: 4px;">
</div>
<div>
<label for="homePortLon" style="display: block; margin-bottom: 5px; font-weight: 500; color: #333;">Longitude:</label>
<input type="number" id="homePortLon" step="0.000001" placeholder="e.g., -74.006000" style="width: 100%; padding: 8px; border: 1px solid #4caf50; border-radius: 4px;">
</div>
<div>
<button onclick="setCurrentLocationAsHomePort()" style="background: #2196f3; padding: 8px 12px; white-space: nowrap;">📍 Use Current Position</button>
</div>
</div>
<div style="display: flex; gap: 10px; align-items: center; margin-top: 12px;">
<button onclick="saveHomePort()" style="background: #4caf50; padding: 8px 16px;">💾 Save Home Port</button>
<div id="homePortStatus" style="font-size: 0.9em;"></div>
</div>
</div>
</div>
<button onclick="loadPathConfigurations()">🔄 Refresh Paths</button>
<button onclick="showAddPathForm()">➕ Add New Path</button>
<button id="toggleCommandsBtn" onclick="toggleCommandPaths()">👁️ Show Commands</button>
<!-- Add Path Form (hidden by default) -->
<div id="addPathForm" style="display: none; margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 5px; min-height: 400px; overflow: visible;">
<h4>Add New Path Configuration</h4>
<div class="form-group">
<label for="pathSignalK">SignalK Path:</label>
<div style="margin-bottom: 8px; display: flex; align-items: center; gap: 15px;">
<label style="display: flex; align-items: center; font-weight: normal; cursor: pointer; white-space: nowrap;">
<span style="margin-right: 5px;">🏠 Self paths</span>
<input type="radio" id="pathFilterSelf" name="pathFilter" value="self" checked onchange="updatePathFilter()">
</label>
<label style="display: flex; align-items: center; font-weight: normal; cursor: pointer; white-space: nowrap;">
<span style="margin-right: 5px;">🚢 Other vessel paths</span>
<input type="radio" id="pathFilterOthers" name="pathFilter" value="others" onchange="updatePathFilter()">
</label>
</div>
<input type="text" id="pathSignalKSearch" class="path-tree-search" placeholder="🔍 Search paths...">
<div id="pathSignalKTree" class="path-tree-container"></div>
<input type="hidden" id="pathSignalK">
<button type="button" onclick="showCustomPathInput('pathSignalK')" style="margin-top: 5px; padding: 6px 12px; background: #666; color: white; border: none; border-radius: 4px; font-size: 0.9em;">🖊️ Enter Custom Path</button>
<input type="text" id="pathSignalKCustom" placeholder="Enter custom SignalK path (e.g., navigation.position)" style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ddd; border-radius: 4px; display: none;">
</div>
<div class="form-group">
<label for="pathEnabled" style="display: inline !important; margin-bottom: 0 !important;">Always Enabled:</label>
<input type="checkbox" id="pathEnabled" style="display: inline !important; width: auto !important; margin-left: 10px;">
</div>
<div class="form-group">
<label for="pathRegimen">Regimen Control:</label>
<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-height: 150px; overflow-y: auto; background: white;">
<div id="regimenCheckboxes">
<!-- Regimen checkboxes will be populated here -->
</div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
<label for="customRegimen" style="font-size: 0.9em; color: #666;">Custom Regimen:</label>
<input type="text" id="customRegimen" placeholder="Enter custom regimen name" style="width: 100%; padding: 6px; margin-top: 5px; border: 1px solid #ddd; border-radius: 3px;">
<button type="button" onclick="addCustomRegimen()" style="margin-top: 5px; padding: 4px 8px; background: #28a745; color: white; border: none; border-radius: 3px; font-size: 0.8em;">➕ Add</button>
</div>
</div>
</div>
<div class="form-group">
<label for="pathContext">Context:</label>
<input type="text" id="pathContext" placeholder="e.g., vessels.self or vessels.*" value="vessels.self">
</div>
<div class="form-group">
<label for="pathSource">Source Filter (optional):</label>
<input type="text" id="pathSource" placeholder="e.g., mqtt-weatherflow-udp">
<small style="color: #666; display: block; margin-top: 5px;">
Filter by specific data source. Leave empty to accept all sources.
</small>
</div>
<div class="form-group">
<label for="pathExcludeMMSI">Exclude MMSI (for vessels.* context):</label>
<input type="text" id="pathExcludeMMSI" placeholder="e.g., 123456789, 987654321 (comma-separated)">
<small style="color: #666; display: block; margin-top: 5px;">
Only applicable when context is "vessels.*". Leave empty to include all vessels.
</small>
</div>
<div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd; min-height: 60px; display: flex; gap: 10px; align-items: center;">
<button onclick="addPathConfiguration()">✅ Add Path</button>
<button class="btn-secondary" onclick="hideAddPathForm()">❌ Cancel</button>
</div>
</div>
<div id="pathConfigContainer">
<div class="loading">Loading path configurations...</div>
</div>
</div>
</div>
<!-- Command Manager Tab -->
<div id="commandManager" class="tab-panel">
<div class="section">
<h2>🎮 Regimen/Commands Manager</h2>
<p>Register and manage SignalK commands for remote control:</p>
<button onclick="showAddCommandForm()">➕ Add New Command</button>
<!-- Add Command Form (hidden by default) -->
<div id="addCommandForm" style="display: none; margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 5px;">
<h4>Register New Command</h4>
<div class="form-group">
<label for="commandName">Command Name:</label>
<input type="text" id="commandName" placeholder="e.g., captureWeather, startLogging">
</div>
<div class="form-group">
<label for="commandDescription">Description (optional):</label>
<input type="text" id="commandDescription" placeholder="e.g., Start weather data capture">
</div>
<div class="form-group">
<label for="commandKeywords">Keywords (for Claude context matching):</label>
<input type="text" id="commandKeywords" placeholder="e.g., anchor, anchoring, drag, swing">
<small style="color: #666; font-size: 11px;">Separate multiple keywords with commas</small>
</div>
<!-- Default State is now hardcoded to OFF on server side -->
<!-- Threshold Configuration -->
<div style="margin-top: 20px; padding: 15px; background: #f0f8ff; border: 1px solid #b3d9ff; border-radius: 5px;">
<h5 style="margin-bottom: 15px; color: #0066cc;">🎯 Threshold-Based Activation</h5>
<div id="addCommandThresholdsList">
<!-- New thresholds will be populated here -->
</div>
<button type="button" id="addCommandThresholdButton" onclick="addNewCommandThreshold()" style="background: #4caf50; color: white; border: none; padding: 8px 12px; border-radius: 4px; margin-top: 10px;">➕ Add Threshold</button>
<!-- Threshold form replaced by unified modal -->
</div>
<div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
<button onclick="registerCommand()">✅ Register Command</button>
<button class="btn-secondary" onclick="hideAddCommandForm()">❌ Cancel</button>
</div>
</div>
<!-- Edit Command Form (hidden by default) -->
<div id="editCommandForm" style="display: none; margin: 20px 0; padding: 20px; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 5px; width: 100%; box-sizing: border-box;">
<h4>Edit Command</h4>
<div class="form-group">
<label for="editCommandName">Command Name:</label>
<input type="text" id="editCommandName" readonly style="background: #f8f9fa;">
</div>
<div class="form-group">
<label for="editCommandDescription">Description:</label>
<input type="text" id="editCommandDescription" placeholder="e.g., Start weather data capture">
</div>
<div class="form-group">
<label for="editCommandKeywords">Keywords (for Claude context matching):</label>
<input type="text" id="editCommandKeywords" placeholder="e.g., anchor, anchoring, drag, swing">
<small style="color: #666; font-size: 11px;">Separate multiple keywords with commas</small>
</div>
<!-- Default State is now hardcoded to OFF on server side -->
<!-- Threshold Configuration -->
<div style="margin-top: 20px; padding: 15px; background: #f0f8ff; border: 1px solid #b3d9ff; border-radius: 5px;">
<h5 style="margin-bottom: 15px; color: #0066cc;">🎯 Threshold-Based Activation</h5>
<div id="thresholdsList">
<!-- Existing thresholds will be populated here -->
</div>
<button type="button" id="editCommandAddThresholdButton" onclick="addNewThreshold()" style="background: #4caf50; color: white; border: none; padding: 8px 12px; border-radius: 4px; margin-top: 10px;">➕ Add Threshold</button>
<!-- Threshold form replaced by unified modal -->
</div>
<div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd; display: flex; flex-direction: column; gap: 10px;">
<div style="display: flex; gap: 10px; align-items: center;">
<button id="updateCommandButton" onclick="updateCommand()" disabled style="opacity: 0.7;">✅ Update Command</button>
<button class="btn-secondary" onclick="hideEditCommandForm()">❌ Cancel</button>
</div>
<div id="editCommandStatus" style="font-size: 0.85em; color: #0066cc; display: none;"></div>
</div>
</div>
<div id="commandContainer">
<div class="loading">Loading commands...</div>
</div>
<!-- Command History -->
<div style="margin-top: 30px;">
<h3>📋 Command History</h3>
<button onclick="loadCommandHistory()">🔄 Refresh History</button>
<div id="commandHistoryContainer" style="margin-top: 15px;">
<div class="loading">Loading command history...</div>
</div>
</div>
</div>
</div>
<!-- Live Connections Tab -->
<div id="liveConnections" class="tab-panel">
<div class="section" style="display: none;">
<h2>🔴 Live Connections - DISABLED</h2>
<p>Streaming functionality has been disabled.</p>
<div style="margin-bottom: 20px;">
<button onclick="loadStreams()">🔄 Refresh Streams</button>
<button onclick="showAddStreamForm()">➕ Create New Stream</button>
</div>
<!-- Add Stream Form (hidden by default) -->
<div id="addStreamForm" style="display: none; margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 5px;">
<h4>Create New Historical Stream</h4>
<div class="form-group">
<label for="streamName">Stream Name:</label>
<input type="text" id="streamName" placeholder="e.g., Navigation History">
</div>
<div class="form-group">
<label for="streamPath">SignalK Path:</label>
<div style="display: flex; gap: 10px; align-items: center;">
<select id="streamPath" style="flex: 1;">
<option value="">Loading available paths...</option>
</select>
<button type="button" onclick="refreshSignalKPaths()" style="padding: 8px; background: #007bff; color: white; border: none; border-radius: 3px;" title="Refresh path list">🔄</button>
</div>
<small style="color: #666; display: block; margin-top: 5px;">
Available paths from historical data. Click refresh if you don't see your path.
</small>
</div>
<div class="form-group">
<label for="streamTimeRange">Time Range:</label>
<select id="streamTimeRange">
<option value="1h">Last 1 Hour</option>
<option value="6h">Last 6 Hours</option>
<option value="24h">Last 24 Hours</option>
<option value="7d">Last 7 Days</option>
<option value="30d">Last 30 Days</option>
<option value="custom">Custom Range</option>
</select>
</div>
<div class="form-group" id="customTimeRange" style="display: none;">
<label>Custom Time Range:</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="datetime-local" id="streamStartTime" placeholder="Start time">
<input type="datetime-local" id="streamEndTime" placeholder="End time">
</div>
</div>
<div class="form-group">
<label for="streamResolution">Data Resolution:</label>
<select id="streamResolution">
<option value="1000">1 second</option>
<option value="5000">5 seconds</option>
<option value="15000">15 seconds</option>
<option value="30000" selected>30 seconds</option>
<option value="60000">1 minute</option>
<option value="300000">5 minutes</option>
</select>
</div>
<div class="form-group">
<label for="streamRate">Streaming Rate:</label>
<select id="streamRate">
<option value="100">Fast (100ms intervals)</option>
<option value="500" selected>Normal (500ms intervals)</option>
<option value="1000">Slow (1 second intervals)</option>
<option value="5000">Very Slow (5 second intervals)</option>
</select>
</div>
<div class="form-group">
<label for="streamAggregateMethod">Statistical Function:</label>
<select id="streamAggregateMethod">
<option value="average" selected>Average - arithmetic mean of values</option>
<option value="min">Minimum - lowest value in time window</option>
<option value="max">Maximum - highest value in time window</option>
<option value="first">First - earliest value in time window</option>
<option value="last">Last - most recent value in time window</option>
<option value="mid">Median - middle value when sorted</option>
<option value="middle_index">Middle Index - middle value by position</option>
</select>
<small style="color: #666; display: block; margin-top: 5px;">
Statistical function applied to data within each time window
</small>
</div>
<div class="form-group">
<label for="streamWindowSize">Rolling Window Size:</label>
<select id="streamWindowSize">
<option value="5">5 data points</option>
<option value="10" selected>10 data points</option>
<option value="20">20 data points</option>
<option value="50">50 data points</option>
<option value="100">100 data points</option>
</select>
<small style="color: #666; display: block; margin-top: 5px;">
Number of recent values used for real-time statistical calculations
</small>
</div>
<div class="form-group">
<label for="streamAutoStart">Auto-start stream:</label>
<input type="checkbox" id="streamAutoStart" checked>
<small style="color: #666; display: block; margin-top: 5px;">
Start streaming immediately after creation
</small>
</div>
<div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
<button onclick="createStream()">✅ Create Stream</button>
<button class="btn-secondary" onclick="hideAddStreamForm()">❌ Cancel</button>
</div>
</div>
<!-- Active Streams Display -->
<div id="streamsContainer">
<div class="loading">Loading streams...</div>
</div>
<!-- Stream Statistics -->
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;">
<h3>📊 Stream Statistics</h3>
<div id="streamStats" style="margin-top: 15px;">
<div class="loading">Loading statistics...</div>
</div>
</div>
<!-- Live Data Display -->
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;">
<h3>📡 Live Data Stream</h3>
<p style="margin-bottom: 15px;">Real-time time-bucketed statistical streaming data (newest first, max 100 entries):</p>
<!-- Data Interpretation Guide -->
<div style="background: #f0f8ff; padding: 15px; border-radius: 5px; margin-bottom: 15px; font-size: 13px;">
<strong>📊 How to Read This Data:</strong><br/>
• <strong>📊 INITIAL</strong>: Full time window data (first load with many buckets)<br/>
• <strong>📈 INCREMENTAL</strong>: New data points since last update (sliding window - fewer buckets)<br/>
• <strong>Statistical Method</strong>: MAX, AVG, MIN, FIRST, LAST, MID applied to each time bucket<br/>
• <strong>Buckets</strong>: Number of time intervals processed (e.g., 30-second time buckets)<br/>
• <strong>Value</strong>: Result of statistical calculation for that time bucket<br/>
• <strong>EMA/SMA</strong>: Moving averages (10-period) calculated for numeric values - EMA responds faster to changes<br/>
• <strong>Time</strong>: Timestamp of the time bucket (not delivery time)
</div>
<div style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; border-radius: 5px;">
<table id="liveDataTable" style="width: 100%; border-collapse: collapse; font-size: 12px;">
<thead style="position: sticky; top: 0; background: #f8f9fa; z-index: 1;">
<tr>
<th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Bucket Time</th>
<th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Stream Type & Statistical Method</th>
<th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Path</th>
<th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Statistical Value</th>
<th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">EMA (10)</th>
<th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">SMA (10)</th>
</tr>
</thead>
<tbody id="liveDataBody">
<tr>
<td colspan="6" style="padding: 20px; text-align: center; color: #666;">
No data streaming yet. Start a stream to see live data.
</td>
</tr>
</tbody>
</table>
</div>
<div style="margin-top: 10px; font-size: 11px; color: #666;">
<span id="liveDataCount">0</span> time-bucketed entries •
<button onclick="clearLiveData()" style="font-size: 11px; padding: 2px 8px;">🗑️ Clear</button>
<button onclick="toggleLiveDataPause()" id="pauseDataBtn" style="font-size: 11px; padding: 2px 8px; margin-left: 5px;">⏸️ Pause</button>
<button onclick="showDataSummary()" style="font-size: 11px; padding: 2px 8px; margin-left: 5px;">📊 Summary</button>
</div>
<!-- Data Summary Panel -->
<div id="dataSummaryPanel" style="display: none; margin-top: 15px; padding: 15px; background: #f8f9fa; border-radius: 5px;">
<h4>📊 Data Summary & Trends</h4>
<div id="dataSummaryContent"></div>
</div>
</div>
</div>
</div>
<!-- AI Analysis Tab -->
<div id="aiAnalysis" class="tab-panel">
<div class="section">
<h2>🧠 AI Analysis with Claude</h2><small>EXPERIMENTAL</small>
<p>Analyze your maritime data using Claude AI for insights, patterns, and recommendations.</p>
<!-- Connection Test -->
<div style="margin-bottom: 20px;">
<button onclick="testClaudeConnection()" id="testConnectionBtn">🔗 Test Claude Connection</button>
<div id="claudeConnectionResult" style="margin-top: 10px;"></div>
</div>
<!-- Vessel Context Section -->
<div style="margin-bottom: 30px; border: 2px solid #e3f2fd; border-radius: 8px; background: #fafbfc;">
<div onclick="toggleVesselContext()" style="padding: 15px 20px; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;">
<div>
<h3 style="margin: 0;">⚓ Vessel Context Document</h3>
<p style="margin: 5px 0 0 0; color: #555; font-size: 0.9em;">Click to expand - context automatically included with every AI analysis</p>
</div>
<span id="vesselContextToggleIcon" style="font-size: 1.2em; color: #667eea;">▶</span>
</div>
<div id="vesselContextContent" style="display: none; padding: 0 20px 20px 20px; border-top: 1px solid #e3f2fd;">
<!-- Auto-extracted vessel info -->
<div style="margin-bottom: 20px;">
<h4 style="margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between;">
<span>📊 Auto-Extracted Vessel Information</span>
<button onclick="refreshVesselInfo()" style="font-size: 0.9em; padding: 6px 12px;">🔄 Refresh from SignalK</button>
</h4>
<div id="autoVesselInfo" style="background: white; padding: 15px; border-radius: 5px; border: 1px solid #ddd;">
<div style="color: #666; font-style: italic;">Loading vessel information...</div>
</div>
</div>
<!-- Custom context -->
<div style="margin-bottom: 20px;">
<h4 style="margin-bottom: 10px;">✏️ Custom Context & Notes</h4>
<p style="font-size: 0.9em; color: #666; margin-bottom: 10px;">Add any additional context about your vessel, operations, or specific information you want Claude to consider during analysis:</p>
<textarea id="customVesselContext"
placeholder="e.g., 'This is a racing sailboat primarily used for coastal sailing. The vessel has recently had engine maintenance. We're particularly interested in wind performance analysis...'"
style="width: 100%; min-height: 120px; padding: 12px; border: 1px solid #ddd; border-radius: 5px; resize: vertical; font-family: inherit;"></textarea>
</div>
<!-- Context preview and actions -->
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<button onclick="saveVesselContext()" style="background: #4CAF50; padding: 8px 16px;">💾 Save Context</button>
<button onclick="previewClaudeContext()" style="background: #2196F3; padding: 8px 16px;">👁️ Preview Claude Context</button>
<div id="vesselContextStatus" style="margin-left: 10px; font-size: 0.9em;"></div>
</div>
<!-- Context preview modal -->
<div id="contextPreviewModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; overflow-y: auto;">
<div style="max-width: 800px; margin: 50px auto; background: white; border-radius: 10px; padding: 20px; max-height: 80vh; overflow-y: auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3>👁️ Claude Context Preview</h3>
<button onclick="closeContextPreview()" style="background: #f44336; color: white; padding: 5px 10px; border-radius: 3px;">×</button>
</div>
<p style="color: #666; margin-bottom: 15px;">This is the context that will be sent to Claude with every analysis:</p>
<pre id="contextPreviewContent" style="background: #f8f9fa; padding: 15px; border-radius: 5px; border: 1px solid #ddd; white-space: pre-wrap; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.9em; line-height: 1.4; max-height: 400px; overflow-y: auto;"></pre>
<div style="margin-top: 15px; padding: 10px; background: #e8f5e8; border-radius: 5px; border-left: 4px solid #4CAF50;">
<small><strong>💡 Tip:</strong> A good vessel context helps Claude provide more accurate and relevant insights for your specific vessel and operations.</small>
</div>
</div>
</div>
</div>
</div>
<!-- Custom Analysis -->
<div style="margin-bottom: 30px;">
<h3>🎯 Custom Analysis</h3>
<div style="margin-top: 15px;">
<!-- Analysis Mode Toggle with Model Selector -->
<div style="margin-bottom: 20px; padding: 15px; border: 2px solid #e3f2fd; border-radius: 8px; background: #f8f9ff;">
<div style="margin-bottom: 10px;">
<label style="display: flex; align-items: center; justify-content: space-between; cursor: pointer; font-weight: bold;">
<span>🚀 Direct Database Access</span>
<input type="checkbox" id="enableDatabaseAccess" checked onchange="toggleAnalysisMode()" style="transform: scale(1.2);">
</label>
</div>
<div style="font-size: 0.9em; color: #555;">
<div id="legacyModeDesc" style="color: #666; display: none;">
<strong>Legacy:</strong> Pre-load data samples for analysis (faster, limited scope)
</div>
<div id="databaseModeDesc" style="color: #1976d2;">
<strong>Enhanced:</strong> Claude explores your database interactively (more powerful, complete historical access)
</div>
</div>
<!-- Claude Model Selection - moved here -->
<div style="margin-top: 15px;">
<label for="claudeModelMain">Claude Model:</label>
<select id="claudeModelMain" style="width: 100%; padding: 8px; margin-top: 5px;">
<option value="claude-sonnet-4-20250514" selected>Claude Sonnet 4 - Latest Sonnet model (recommended)</option>
<option value="claude-opus-4-1-20250805">Claude Opus 4.1 - Most powerful model (premium)</option>
<option value="claude-opus-4-20250514">Claude Opus 4 - High performance model (premium)</option>
</select>
<p style="font-size: 0.9em; color: #666; margin: 5px 0 0 0;">Choose the Claude model for analysis. Sonnet provides the most detailed insights.</p>
</div>
</div>
<!-- Collapsible Selection Options -->
<div id="selectionOptionsSection" style="margin-bottom: 20px;">
<div onclick="toggleSelectionOptions()" style="cursor: pointer; padding: 10px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 5px; user-select: none;">
<span id="selectionToggleIcon" style="margin-right: 8px;">▶</span>
<strong>Selection Options</strong>