S7_snap7_Stremer_n_Recorder/templates/index.html

746 lines
38 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" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PLC S7-315 Streamer & Logger</title>
<!-- Pico.css offline -->
<link rel="stylesheet" href="/static/css/pico.min.css">
<!-- Custom styles -->
<link rel="stylesheet" href="/static/css/styles.css">
</head>
<body>
<!-- Theme Selector -->
<div class="theme-selector">
<button id="theme-light" class="active" onclick="setTheme('light')">☀️ Light</button>
<button id="theme-dark" onclick="setTheme('dark')">🌙 Dark</button>
<button id="theme-auto" onclick="setTheme('auto')">🔄 Auto</button>
</div>
<main class="container">
<!-- Header -->
<header class="header">
<h1>
<img src="/images/SIDEL.png" alt="SIDEL Logo" class="header-logo">
-- PLC S7-31x Streamer & Logger
</h1>
<p>Real-time monitoring and streaming system</p>
</header>
<!-- Status Bar -->
<section class="status-grid">
<div class="status-item" id="plc-status">
🔌 PLC: Disconnected
<div style="margin-top: 8px;">
<button type="button" id="status-connect-btn">🔗 Connect</button>
</div>
</div>
<div class="status-item" id="stream-status">
📡 UDP Streaming: Inactive
<div style="margin-top: 8px;">
<button type="button" id="status-streaming-btn">⏹️ Stop</button>
</div>
</div>
<div class="status-item status-idle">
📊 Datasets: {{ status.datasets_count }} ({{ status.active_datasets_count }} active)
</div>
<div class="status-item status-idle">
📋 Variables: {{ status.total_variables }}
</div>
<div class="status-item status-idle">
⏱️ Interval: {{ status.sampling_interval }}s
</div>
<div class="status-item" id="csv-status">
💾 CSV: Inactive
</div>
<div class="status-item" id="disk-space">
💽 Disk Space: Calculating...
</div>
</section>
<!-- Status messages -->
<div id="messages"></div>
<!-- Configuration Grid -->
<div class="config-grid">
<!-- PLC Configuration -->
<article>
<header>⚙️ PLC S7-315 Configuration</header>
<form id="plc-config-form">
<div class="form-row">
<label>
PLC IP Address:
<input type="text" id="plc-ip" value="{{ status.plc_config.ip }}"
placeholder="192.168.1.100">
</label>
<label>
Rack:
<input type="number" id="plc-rack" value="{{ status.plc_config.rack }}" min="0" max="7">
</label>
<label>
Slot:
<input type="number" id="plc-slot" value="{{ status.plc_config.slot }}" min="0" max="31">
</label>
</div>
<div class="controls">
<button type="submit">💾 Save Configuration</button>
<button type="button" id="connect-btn">🔗 Connect PLC</button>
<button type="button" id="disconnect-btn" class="secondary">❌ Disconnect PLC</button>
</div>
</form>
</article>
<!-- UDP Configuration -->
<article>
<header>🌐 UDP Gateway Configuration (PlotJuggler)</header>
<form id="udp-config-form">
<div class="form-row">
<label>
UDP Host:
<input type="text" id="udp-host" value="{{ status.udp_config.host }}"
placeholder="127.0.0.1">
</label>
<label>
UDP Port:
<input type="number" id="udp-port" value="{{ status.udp_config.port }}" min="1" max="65535">
</label>
<label>
Sampling Interval (s):
<input type="number" id="sampling-interval" value="{{ status.sampling_interval }}"
min="0.01" max="10" step="0.01">
</label>
</div>
<div class="controls">
<button type="submit">💾 Save Configuration</button>
<button type="button" id="update-sampling-btn" class="secondary">⏱️ Update Interval</button>
</div>
</form>
</article>
</div>
<!-- Tab Navigation -->
<nav class="tabs">
<button class="tab-btn active" data-tab="datasets">📊 Datasets & Variables</button>
<button class="tab-btn" data-tab="plotting">📈 Real-Time Plotting</button>
<button class="tab-btn" data-tab="events">📋 Events & Logs</button>
</nav>
<!-- Tab Content -->
<div class="tab-content active" id="datasets-tab">
<!-- Integrated Dataset & Variables Management -->
<article>
<header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>📊 Dataset & Variables Management</span>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<select id="dataset-selector" style="min-width: 200px;">
<option value="">Select a dataset...</option>
</select>
<button type="button" id="new-dataset-btn" class="outline"> New</button>
<button type="button" id="delete-dataset-btn" class="secondary">🗑️ Delete</button>
</div>
</div>
</header>
<!-- Dataset Status Bar -->
<div id="dataset-status-bar" style="display: none; margin-bottom: 1rem;">
<div class="info-section" style="margin-bottom: 0;">
<div
style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
<div style="display: flex; gap: 2rem; flex-wrap: wrap;">
<span><strong>📂 Name:</strong> <span id="dataset-name"></span></span>
<span><strong>🏷️ Prefix:</strong> <span id="dataset-prefix"></span></span>
<span><strong>⏱️ Sampling:</strong> <span id="dataset-sampling"></span></span>
<span><strong>📊 Variables:</strong> <span id="dataset-var-count"></span></span>
<span><strong>📡 Streaming:</strong> <span id="dataset-stream-count"></span></span>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<span id="dataset-status" class="status-item"></span>
<button type="button" id="activate-dataset-btn" class="outline">▶️ Activate</button>
<button type="button" id="deactivate-dataset-btn" class="secondary">⏹️
Deactivate</button>
</div>
</div>
</div>
</div>
<!-- Variables Management Section -->
<div id="variables-management" style="display: none;">
<!-- Real-time Variable Monitoring Info -->
<div
style="margin-bottom: 1rem; padding: 1rem; background: var(--pico-card-background-color); border-radius: var(--pico-border-radius); border: var(--pico-border-width) solid var(--pico-border-color);">
<div
style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
<div>
<strong>📊 Automatic Variable Monitoring</strong>
<br>
<small style="color: var(--pico-muted-color);">
Variables are automatically monitored and recorded when PLC is connected and dataset
is
active
</small>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<button type="button" id="toggle-streaming-btn" class="outline"
onclick="toggleRealTimeStreaming()">
▶️ Start Live Display
</button>
</div>
</div>
<div id="last-refresh-time"
style="margin-top: 0.5rem; font-size: 0.9em; color: var(--pico-muted-color);">
Live display shows cached values from automatic monitoring
</div>
</div>
<!-- Add Variable Form -->
<form id="variable-form" style="margin-bottom: 1.5rem;">
<div class="form-row">
<label>
Variable Name:
<input type="text" id="var-name" placeholder="temperature" required>
</label>
<label>
Memory Area:
<select id="var-area" required onchange="toggleFields()">
<option value="db">DB (Data Block)</option>
<option value="mw">MW (Memory Words)</option>
<option value="pew">PEW (Process Input Words)</option>
<option value="paw">PAW (Process Output Words)</option>
<option value="e">E (Input Bits)</option>
<option value="a">A (Output Bits)</option>
<option value="mb">MB (Memory Bits)</option>
</select>
</label>
<label id="db-field">
Data Block (DB):
<input type="number" id="var-db" min="1" max="9999" value="1" required>
</label>
<label>
Offset:
<input type="number" id="var-offset" min="0" max="8192" value="0" required>
</label>
<label id="bit-field" style="display: none;">
Bit Position:
<select id="var-bit">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</label>
<label>
Data Type:
<select id="var-type" required>
<option value="real">REAL (Float 32-bit)</option>
<option value="int">INT (16-bit Signed)</option>
<option value="uint">UINT (16-bit Unsigned)</option>
<option value="dint">DINT (32-bit Signed)</option>
<option value="udint">UDINT (32-bit Unsigned)</option>
<option value="word">WORD (16-bit)</option>
<option value="byte">BYTE (8-bit)</option>
<option value="sint">SINT (8-bit Signed)</option>
<option value="usint">USINT (8-bit Unsigned)</option>
<option value="bool">BOOL</option>
</select>
</label>
</div>
<button type="submit"> Add Variable</button>
</form>
<!-- Variables Table -->
<div
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h4 style="margin: 0;">📊 Variables in Dataset</h4>
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
<div style="margin-left: 1rem;">
<span id="last-refresh-time"
style="color: var(--pico-muted-color); font-size: 0.9rem;"></span>
</div>
</div>
</div>
<table class="variables-table">
<thead>
<tr>
<th>Name</th>
<th>Memory Area</th>
<th>Offset</th>
<th>Type</th>
<th>Current Value</th>
<th>Stream to PlotJuggler</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="variables-tbody">
{% for name, var in variables.items() %}
<tr>
<td>{{ name }}</td>
<td>
{% if var.area == 'db' or var.get('db') %}
DB{{ var.get('db', 'N/A') }}.{{ var.offset }}
{% elif var.area == 'mw' or var.area == 'm' %}
MW{{ var.offset }}
{% elif var.area == 'pew' or var.area == 'pe' %}
PEW{{ var.offset }}
{% elif var.area == 'paw' or var.area == 'pa' %}
PAW{{ var.offset }}
{% elif var.area == 'e' %}
E{{ var.offset }}.{{ var.bit }}
{% elif var.area == 'a' %}
A{{ var.offset }}.{{ var.bit }}
{% elif var.area == 'mb' %}
M{{ var.offset }}.{{ var.bit }}
{% else %}
DB{{ var.get('db', 'N/A') }}.{{ var.offset }}
{% endif %}
</td>
<td>{{ var.offset }}</td>
<td>{{ var.type.upper() }}</td>
<td id="value-{{ name }}"
style="font-family: monospace; font-weight: bold; color: var(--pico-muted-color);">
--
</td>
<td>
<label>
<input type="checkbox" id="stream-{{ name }}" role="switch"
onchange="toggleStreaming('{{ name }}', this.checked)">
Enable
</label>
</td>
<td>
<button class="outline" onclick="editVariable('{{ name }}')">✏️ Edit</button>
<button class="secondary" onclick="removeVariable('{{ name }}')">🗑️ Remove</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- No Dataset Selected Message -->
<div id="no-dataset-message" style="text-align: center; padding: 2rem; color: var(--pico-muted-color);">
<p>📊 Please select a dataset to manage its variables</p>
<p>Or create a new dataset to get started</p>
</div>
</article>
<!-- PlotJuggler Streaming Control -->
<article>
<header>📡 PlotJuggler UDP Streaming Control</header>
<div class="info-section">
<p><strong>📡 UDP Streaming:</strong> Sends only variables marked for streaming to PlotJuggler via
UDP
</p>
<p><strong>💾 Automatic Recording:</strong> When PLC is connected, all datasets with variables
automatically record to CSV files</p>
<p><strong>📁 File Organization:</strong> records/[dd-mm-yyyy]/[prefix]_[hour].csv (e.g.,
temp_14.csv,
pressure_14.csv)</p>
<p><strong>⏱️ Independent Operation:</strong> CSV recording works independently of UDP streaming -
always active when PLC is connected</p>
</div>
<div class="controls">
<button id="start-streaming-btn">📡 Start UDP Streaming</button>
<button class="secondary" id="stop-streaming-btn">⏹️ Stop UDP Streaming</button>
<button class="outline" onclick="location.reload()">🔄 Refresh Status</button>
</div>
</article>
<!-- CSV Recording Configuration -->
<article>
<header>📁 CSV Recording Configuration</header>
<div class="info-section">
<p><strong>📂 Directory Management:</strong> Configure where CSV files are saved and manage file
rotation</p>
<p><strong>🔄 Automatic Cleanup:</strong> Set limits by size, days, or hours to automatically delete
old
files</p>
<p><strong>💿 Disk Space:</strong> Monitor available space and estimated recording time remaining
</p>
</div>
<!-- Current Configuration Display -->
<div class="csv-config-display" id="csv-config-display">
<div class="config-grid">
<div class="config-item">
<strong>📁 Records Directory:</strong>
<span id="csv-directory-path">Loading...</span>
</div>
<div class="config-item">
<strong>🔄 Rotation Enabled:</strong>
<span id="csv-rotation-enabled">Loading...</span>
</div>
<div class="config-item">
<strong>📊 Max Size:</strong>
<span id="csv-max-size">Loading...</span>
</div>
<div class="config-item">
<strong>📅 Max Days:</strong>
<span id="csv-max-days">Loading...</span>
</div>
<div class="config-item">
<strong>⏰ Max Hours:</strong>
<span id="csv-max-hours">Loading...</span>
</div>
<div class="config-item">
<strong>🧹 Cleanup Interval:</strong>
<span id="csv-cleanup-interval">Loading...</span>
</div>
</div>
</div>
<!-- Directory Information -->
<div class="csv-directory-info" id="csv-directory-info" style="margin-top: 1rem;">
<details>
<summary><strong>📊 Directory Information</strong></summary>
<div class="directory-stats" id="directory-stats">
<p>Loading directory information...</p>
</div>
</details>
</div>
<!-- Configuration Form -->
<details style="margin-top: 1rem;">
<summary><strong>⚙️ Modify Configuration</strong></summary>
<form id="csv-config-form" style="margin-top: 1rem;">
<div class="grid">
<div>
<label for="records-directory">📁 Records Directory:</label>
<input type="text" id="records-directory" name="records_directory" placeholder="records"
required>
<small>Base directory where CSV files will be saved</small>
</div>
<div>
<label>
<input type="checkbox" id="rotation-enabled" name="rotation_enabled">
🔄 Enable File Rotation
</label>
<small>Automatically delete old files based on limits below</small>
</div>
</div>
<div class="grid">
<div>
<label for="max-size-mb">📊 Max Total Size (MB):</label>
<input type="number" id="max-size-mb" name="max_size_mb" min="1" step="1"
placeholder="1000">
<small>Maximum total size of all CSV files in MB (leave empty for no limit)</small>
</div>
<div>
<label for="max-days">📅 Max Days to Keep:</label>
<input type="number" id="max-days" name="max_days" min="1" step="1" placeholder="30">
<small>Delete files older than this many days (leave empty for no limit)</small>
</div>
</div>
<div class="grid">
<div>
<label for="max-hours">⏰ Max Hours to Keep:</label>
<input type="number" id="max-hours" name="max_hours" min="1" step="1" placeholder="">
<small>Delete files older than this many hours (overrides days setting)</small>
</div>
<div>
<label for="cleanup-interval">🧹 Cleanup Interval (hours):</label>
<input type="number" id="cleanup-interval" name="cleanup_interval_hours" min="1"
step="1" value="24" required>
<small>How often to run automatic cleanup</small>
</div>
</div>
<div class="controls">
<button type="submit">💾 Save Configuration</button>
<button type="button" class="secondary" onclick="loadCsvConfig()">🔄 Reload</button>
<button type="button" class="outline" onclick="triggerManualCleanup()">🧹 Manual
Cleanup</button>
</div>
</form>
</details>
</article>
<!-- Application Events Log -->
<article>
<header>📋 Application Events Log</header>
<div class="info-section">
<p><strong>📝 Event Tracking:</strong> Connection events, configuration changes, errors and system
status</p>
<p><strong>💾 Persistent Storage:</strong> Events are saved to disk and persist between application
restarts</p>
</div>
<div class="log-controls">
<button class="outline" onclick="refreshEventLog()">🔄 Refresh Log</button>
<button class="outline" onclick="clearLogView()">🧹 Clear View</button>
<select id="log-limit" onchange="refreshEventLog()">
<option value="25">Last 25 events</option>
<option value="50" selected>Last 50 events</option>
<option value="100">Last 100 events</option>
<option value="200">Last 200 events</option>
</select>
<div class="log-stats" id="log-stats">
Loading log statistics...
</div>
</div>
<div class="log-container" id="events-log">
<div class="log-entry log-info">
<div class="log-header">
<span>📡 System</span>
<span class="log-timestamp">Loading...</span>
</div>
<div class="log-message">Loading application events...</div>
</div>
</div>
</article>
</main>
<!-- Edit Variable Modal -->
<div id="edit-modal" class="modal" style="display: none;">
<article>
<header>
<h3>✏️ Edit Variable</h3>
<button class="close" onclick="closeEditModal()" aria-label="Close">&times;</button>
</header>
<form id="edit-variable-form">
<div class="form-row">
<label>
Variable Name:
<input type="text" id="edit-var-name" required>
</label>
<label>
Memory Area:
<select id="edit-var-area" required onchange="toggleEditFields()">
<option value="db">DB (Data Block)</option>
<option value="mw">MW (Memory Words)</option>
<option value="pew">PEW (Process Input Words)</option>
<option value="paw">PAW (Process Output Words)</option>
<option value="e">E (Input Bits)</option>
<option value="a">A (Output Bits)</option>
<option value="mb">MB (Memory Bits)</option>
</select>
</label>
<label id="edit-db-field">
Data Block (DB):
<input type="number" id="edit-var-db" min="1" max="9999" value="1" required>
</label>
<label>
Offset:
<input type="number" id="edit-var-offset" min="0" max="8192" value="0" required>
</label>
<label id="edit-bit-field" style="display: none;">
Bit Position:
<select id="edit-var-bit">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</label>
<label>
Data Type:
<select id="edit-var-type" required>
<option value="real">REAL (Float 32-bit)</option>
<option value="int">INT (16-bit Signed)</option>
<option value="uint">UINT (16-bit Unsigned)</option>
<option value="dint">DINT (32-bit Signed)</option>
<option value="udint">UDINT (32-bit Unsigned)</option>
<option value="word">WORD (16-bit)</option>
<option value="byte">BYTE (8-bit)</option>
<option value="sint">SINT (8-bit Signed)</option>
<option value="usint">USINT (8-bit Unsigned)</option>
<option value="bool">BOOL (Boolean)</option>
</select>
</label>
</div>
<footer>
<button type="button" class="secondary" onclick="closeEditModal()">❌ Cancel</button>
<button type="submit">💾 Update Variable</button>
</footer>
</form>
</article>
</div>
</div>
<!-- 📈 PLOTTING TAB -->
<div class="tab-content" id="plotting-tab">
<article>
<header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>📈 Real-Time Plotting</span>
<button type="button" id="new-plot-btn" class="outline"> New Plot</button>
</div>
</header>
<div class="info-section">
<p><strong>📈 Real-Time Plotting:</strong> Create interactive charts using cached data from active
datasets</p>
<p><strong>🎯 Trigger System:</strong> Use boolean variables to automatically restart traces when
conditions are met</p>
<p><strong>⚡ Performance:</strong> Uses recording cache - no additional PLC load</p>
<p><strong>📊 Multiple Sessions:</strong> Create multiple independent plot sessions with different
variables and settings</p>
</div>
<!-- Plot Sessions Container -->
<div id="plot-sessions-container">
<div style="text-align: center; padding: 2rem; color: var(--pico-muted-color);">
<p>📈 No plot sessions created yet</p>
<p>Click "New Plot" to create your first real-time chart</p>
</div>
</div>
</article>
</div>
<!-- 📋 EVENTS TAB -->
<div class="tab-content" id="events-tab">
<article>
<header>📋 Events & System Logs</header>
<div class="info-section">
<p><strong>📋 Event Logging:</strong> Monitor system events, errors, and operational status</p>
<p><strong>🔍 Real-time Updates:</strong> Events are automatically updated as they occur</p>
<p><strong>📊 Filtering:</strong> Filter events by type and time range</p>
</div>
<div class="log-controls">
<button id="refresh-events-btn">🔄 Refresh Events</button>
<button id="clear-events-btn" class="secondary">🗑️ Clear Events</button>
<div class="log-stats">
<span id="events-count">Loading...</span> events
</div>
</div>
<div class="log-container" id="events-container">
<p>Loading events...</p>
</div>
</article>
</div>
<!-- New Plot Modal -->
<div id="new-plot-modal" class="modal" style="display: none;">
<div class="modal-content">
<h4>Create New Plot Session</h4>
<form id="new-plot-form">
<div class="form-group">
<label for="plot-name">Plot Name:</label>
<input type="text" id="plot-name" placeholder="Temperature Monitoring" required>
</div>
<div class="form-group">
<label>Variables to Plot:</label>
<div id="plot-variables-selector">
<p>Loading available variables...</p>
</div>
</div>
<div class="form-group">
<label for="plot-time-window">Time Window (seconds):</label>
<input type="number" id="plot-time-window" value="60" min="10" max="3600" required>
</div>
<div class="form-group">
<label>Y-Axis Range (optional):</label>
<div class="range-inputs">
<input type="number" id="plot-y-min" placeholder="Auto Min" step="any">
<span>to</span>
<input type="number" id="plot-y-max" placeholder="Auto Max" step="any">
</div>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="plot-trigger-enabled" onchange="toggleTriggerConfig()">
Enable Trigger System
</label>
</div>
<div id="trigger-config" style="display: none;">
<div class="form-group">
<label for="plot-trigger-variable">Trigger Variable:</label>
<select id="plot-trigger-variable">
<option value="">No trigger</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="plot-trigger-on-true" checked>
Trigger on True (uncheck for False)
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Create Plot</button>
<button type="button" class="btn btn-secondary" onclick="closeNewPlotModal()">Cancel</button>
</div>
</form>
</div>
</div>
<!-- New Dataset Modal -->
<div id="dataset-modal" class="modal" style="display: none;">
<article>
<header>
<h3>Create New Dataset</h3>
<button class="close" id="close-dataset-modal" aria-label="Close">&times;</button>
</header>
<form id="dataset-form">
<label>
Dataset ID:
<input type="text" id="dataset-id" placeholder="e.g., temperature_sensors" required>
<small>Used internally, no spaces or special characters</small>
</label>
<label>
Dataset Name:
<input type="text" id="dataset-name-input" placeholder="e.g., Temperature Sensors" required>
</label>
<label>
CSV Prefix:
<input type="text" id="dataset-prefix-input" placeholder="e.g., temp" required>
<small>Files will be named: prefix_hour.csv (e.g., temp_14.csv)</small>
</label>
<label>
Sampling Interval (seconds):
<input type="number" id="dataset-sampling-input" placeholder="Leave empty to use global interval"
min="0.01" step="0.01">
<small>Leave empty to use global sampling interval</small>
</label>
<footer>
<button type="button" class="secondary" id="cancel-dataset-btn">Cancel</button>
<button type="submit">Create Dataset</button>
</footer>
</form>
</article>
</div>
<!-- Chart.js Libraries -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
<!-- JavaScript Modules -->
<script src="/static/js/utils.js"></script>
<script src="/static/js/theme.js"></script>
<script src="/static/js/status.js"></script>
<script src="/static/js/plc.js"></script>
<script src="/static/js/variables.js"></script>
<script src="/static/js/datasets.js"></script>
<script src="/static/js/streaming.js"></script>
<script src="/static/js/csv.js"></script>
<script src="/static/js/events.js"></script>
<script src="/static/js/tabs.js"></script>
<script src="/static/js/plotting.js"></script>
<script src="/static/js/main.js"></script>
</body>
</html>