825 lines
42 KiB
HTML
825 lines
42 KiB
HTML
<!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">
|
||
<!-- JSONEditor (tree view) CSS -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.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">
|
||
<div class="info-section" style="margin: 1rem 0;">
|
||
<p><strong>Notice:</strong> Esta UI ha sido reemplazada por la SPA React. Accede a <a href="/app">/app</a>.
|
||
Esta página no se usa en producción.</p>
|
||
</div>
|
||
<!-- 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="config-editor">🧩 Config Editor</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>
|
||
</div>
|
||
|
||
<!-- 🧩 CONFIG EDITOR TAB -->
|
||
<div class="tab-content" id="config-editor-tab">
|
||
<article>
|
||
<header>🧩 Dynamic JSON Config Editor</header>
|
||
<div class="info-section">
|
||
<p><strong>Esquemas:</strong> Selecciona un esquema y edita PLC, Datasets o Plot Sessions con
|
||
formularios dinámicos.</p>
|
||
<p><strong>Import/Export:</strong> Puedes importar desde un archivo JSON o exportar el actual.</p>
|
||
</div>
|
||
|
||
<div class="grid">
|
||
<div>
|
||
<label>Schema</label>
|
||
<select id="schema-selector"></select>
|
||
</div>
|
||
<div class="controls" style="align-self: end; display: flex; gap: .5rem;">
|
||
<button id="btn-export-config" class="outline">⬇️ Export</button>
|
||
<label class="outline" style="margin: 0;">
|
||
⬆️ Import <input type="file" id="import-file" accept="application/json"
|
||
style="display:none;">
|
||
</label>
|
||
<button id="btn-save-config">💾 Save</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="config-form-container" style="margin-top: 1rem;"></div>
|
||
</article>
|
||
</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="toggle-plot-form-btn" class="outline">➕ New Plot</button>
|
||
</div>
|
||
</header>
|
||
|
||
|
||
|
||
<!-- Plot Creation/Edit Form (Collapsible) -->
|
||
<div id="plot-form-container" class="collapsible-section" style="display: none;">
|
||
<article class="plot-form-article">
|
||
<header>
|
||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||
<span id="plot-form-title">🆕 Create New Plot</span>
|
||
<button type="button" id="close-plot-form-btn" class="close-btn">×</button>
|
||
</div>
|
||
</header>
|
||
|
||
<form id="plot-form">
|
||
<div class="form-group">
|
||
<label for="plot-form-name">Plot Name:</label>
|
||
<input type="text" id="plot-form-name" placeholder="Temperature Monitoring" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Variables to Plot:</label>
|
||
<div class="variables-selection">
|
||
<div id="selected-variables-display" class="selected-variables">
|
||
<p class="no-variables">No variables selected</p>
|
||
</div>
|
||
<button type="button" id="select-variables-btn" class="outline">
|
||
🎨 Select Variables & Colors
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="plot-form-time-window">Time Window (seconds):</label>
|
||
<input type="number" id="plot-form-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-form-y-min" placeholder="Auto Min" step="any">
|
||
<span>to</span>
|
||
<input type="number" id="plot-form-y-max" placeholder="Auto Max" step="any">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" id="plot-form-trigger-enabled"
|
||
onchange="togglePlotFormTriggerConfig()">
|
||
Enable Trigger System
|
||
</label>
|
||
</div>
|
||
|
||
<div id="plot-form-trigger-config" style="display: none;">
|
||
<div class="form-group">
|
||
<label for="plot-form-trigger-variable">Trigger Variable:</label>
|
||
<select id="plot-form-trigger-variable">
|
||
<option value="">No trigger</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" id="plot-form-trigger-on-true" checked>
|
||
Trigger on True (uncheck for False)
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary" id="plot-form-submit">Create Plot</button>
|
||
<button type="button" class="btn btn-secondary" id="cancel-plot-form">Cancel</button>
|
||
</div>
|
||
</form>
|
||
</article>
|
||
</div>
|
||
|
||
<!-- Sub-tabs para plots -->
|
||
<nav class="sub-tabs" id="plot-sub-tabs" style="display: none;">
|
||
<!-- Los sub-tabs se crearán dinámicamente aquí -->
|
||
</nav>
|
||
|
||
<!-- Contenido de sub-tabs -->
|
||
<div id="plot-sub-content">
|
||
<!-- Contenido de plots se mostrará aquí -->
|
||
</div>
|
||
|
||
<!-- Plot Sessions Container (vista inicial) -->
|
||
<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>
|
||
|
||
</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">×</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>
|
||
|
||
<!-- Variable Selection Modal (NEW) -->
|
||
<div id="variable-selection-modal" class="modal" style="display: none;">
|
||
<div class="modal-content variable-modal">
|
||
<header class="modal-header">
|
||
<h3>🎨 Select Variables & Colors</h3>
|
||
<button type="button" class="close-btn" id="close-variable-modal">×</button>
|
||
</header>
|
||
|
||
<div class="modal-body">
|
||
<div class="variable-selection-container">
|
||
<div class="datasets-sidebar">
|
||
<h4>📊 Datasets</h4>
|
||
<div id="datasets-list" class="datasets-list">
|
||
<p>Loading datasets...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="variables-main">
|
||
<div class="variables-header">
|
||
<h4>🔧 Variables</h4>
|
||
<div class="selection-controls">
|
||
<button type="button" id="select-all-variables" class="btn btn-sm outline">Select
|
||
All</button>
|
||
<button type="button" id="deselect-all-variables" class="btn btn-sm outline">Deselect
|
||
All</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="variables-list" class="variables-list">
|
||
<p class="no-dataset-message">Select a dataset to see its variables</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="selected-summary">
|
||
<h4>📝 Selected Variables Summary</h4>
|
||
<div id="selected-variables-summary" class="selected-summary-list">
|
||
<p>No variables selected</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" id="cancel-variable-selection">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="confirm-variable-selection">Confirm Selection</button>
|
||
</div>
|
||
</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">×</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 - Cargar en orden estricto (versiones compatibles) -->
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/luxon@2"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.3.1"></script>
|
||
<script src="https://unpkg.com/chartjs-plugin-zoom@1.2.1/dist/chartjs-plugin-zoom.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@2.0.0"></script>
|
||
<!-- JSONEditor (tree view) -->
|
||
<script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.js"></script>
|
||
<!-- JSONForm deps (jQuery + Underscore) and JSONForm -->
|
||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/gh/jsonform/jsonform@2.5.1/lib/deps/opt/jsv.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/gh/jsonform/jsonform@2.5.1/lib/jsonform.js"></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/config_editor.js"></script>
|
||
<script src="/static/js/main.js"></script>
|
||
|
||
|
||
</body>
|
||
|
||
</html> |