S7_snap7_Stremer_n_Recorder/templates/index.html

555 lines
19 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="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PLC S7-315 Streamer & Logger</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.status-bar {
background: white;
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.status-item {
padding: 15px;
border-radius: 10px;
text-align: center;
font-weight: bold;
}
.status-connected {
background: linear-gradient(135deg, #4CAF50, #45a049);
color: white;
}
.status-disconnected {
background: linear-gradient(135deg, #f44336, #d32f2f);
color: white;
}
.status-streaming {
background: linear-gradient(135deg, #2196F3, #1976D2);
color: white;
}
.status-idle {
background: linear-gradient(135deg, #9E9E9E, #757575);
color: white;
}
.card {
background: white;
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
}
.card h2 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.5rem;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: #667eea;
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
}
.btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
padding: 12px 25px;
border-radius: 25px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s;
margin: 5px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.btn-success {
background: linear-gradient(135deg, #4CAF50, #45a049);
}
.btn-danger {
background: linear-gradient(135deg, #f44336, #d32f2f);
}
.btn-warning {
background: linear-gradient(135deg, #ff9800, #f57c00);
}
.variables-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.variables-table th,
.variables-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.variables-table th {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
font-weight: bold;
}
.variables-table tr:hover {
background-color: #f5f5f5;
}
.alert {
padding: 15px;
border-radius: 8px;
margin: 10px 0;
font-weight: bold;
}
.alert-success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.alert-error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.form-row {
grid-template-columns: 1fr;
}
.controls {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🏭 PLC S7-315 Streamer & Logger</h1>
<p>Sistema de monitoreo y streaming en tiempo real</p>
</div>
<!-- Barra de Estado -->
<div class="status-bar">
<div class="status-grid">
<div class="status-item" id="plc-status">
🔌 PLC: Desconectado
</div>
<div class="status-item" id="stream-status">
📡 Streaming: Inactivo
</div>
<div class="status-item status-idle">
📊 Variables: {{ status.variables_count }}
</div>
<div class="status-item status-idle">
⏱️ Intervalo: {{ status.sampling_interval }}s
</div>
</div>
</div>
<!-- Mensajes de estado -->
<div id="messages"></div>
<!-- Configuración PLC -->
<div class="card">
<h2>⚙️ Configuración PLC S7-315</h2>
<form id="plc-config-form">
<div class="form-row">
<div class="form-group">
<label>IP del PLC:</label>
<input type="text" id="plc-ip" value="{{ status.plc_config.ip }}" placeholder="192.168.1.100">
</div>
<div class="form-group">
<label>Rack:</label>
<input type="number" id="plc-rack" value="{{ status.plc_config.rack }}" min="0" max="7">
</div>
<div class="form-group">
<label>Slot:</label>
<input type="number" id="plc-slot" value="{{ status.plc_config.slot }}" min="0" max="31">
</div>
</div>
<div class="controls">
<button type="submit" class="btn">💾 Guardar Configuración</button>
<button type="button" class="btn btn-success" id="connect-btn">🔗 Conectar PLC</button>
<button type="button" class="btn btn-danger" id="disconnect-btn">❌ Desconectar PLC</button>
</div>
</form>
</div>
<!-- Configuración UDP -->
<div class="card">
<h2>🌐 Configuración Gateway UDP (PlotJuggler)</h2>
<form id="udp-config-form">
<div class="form-row">
<div class="form-group">
<label>Host UDP:</label>
<input type="text" id="udp-host" value="{{ status.udp_config.host }}" placeholder="127.0.0.1">
</div>
<div class="form-group">
<label>Puerto UDP:</label>
<input type="number" id="udp-port" value="{{ status.udp_config.port }}" min="1" max="65535">
</div>
<div class="form-group">
<label>Intervalo de Muestreo (s):</label>
<input type="number" id="sampling-interval" value="{{ status.sampling_interval }}" min="0.01"
max="10" step="0.01">
</div>
</div>
<div class="controls">
<button type="submit" class="btn">💾 Guardar Configuración</button>
<button type="button" class="btn btn-warning" id="update-sampling-btn">⏱️ Actualizar
Intervalo</button>
</div>
</form>
</div>
<!-- Variables del PLC -->
<div class="card">
<h2>📋 Variables del PLC</h2>
<!-- Formulario para añadir variables -->
<form id="variable-form">
<div class="form-row">
<div class="form-group">
<label>Nombre Variable:</label>
<input type="text" id="var-name" placeholder="temperatura" required>
</div>
<div class="form-group">
<label>Data Block (DB):</label>
<input type="number" id="var-db" min="1" max="9999" value="1" required>
</div>
<div class="form-group">
<label>Offset:</label>
<input type="number" id="var-offset" min="0" max="8192" value="0" required>
</div>
<div class="form-group">
<label>Tipo de Dato:</label>
<select id="var-type" required>
<option value="real">REAL (Float 32-bit)</option>
<option value="int">INT (16-bit)</option>
<option value="dint">DINT (32-bit)</option>
<option value="bool">BOOL</option>
</select>
</div>
</div>
<button type="submit" class="btn"> Añadir Variable</button>
</form>
<!-- Tabla de variables -->
<table class="variables-table">
<thead>
<tr>
<th>Nombre</th>
<th>Data Block</th>
<th>Offset</th>
<th>Tipo</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="variables-tbody">
{% for name, var in variables.items() %}
<tr>
<td>{{ name }}</td>
<td>DB{{ var.db }}</td>
<td>{{ var.offset }}</td>
<td>{{ var.type.upper() }}</td>
<td>
<button class="btn btn-danger" onclick="removeVariable('{{ name }}')">🗑️ Eliminar</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Control de Streaming -->
<div class="card">
<h2>🚀 Control de Streaming</h2>
<div class="controls">
<button class="btn btn-success" id="start-streaming-btn">▶️ Iniciar Streaming</button>
<button class="btn btn-danger" id="stop-streaming-btn">⏹️ Detener Streaming</button>
<button class="btn" onclick="location.reload()">🔄 Actualizar Estado</button>
</div>
</div>
</div>
<script>
// Función para mostrar mensajes
function showMessage(message, type = 'success') {
const messagesDiv = document.getElementById('messages');
const alertClass = type === 'success' ? 'alert-success' : 'alert-error';
messagesDiv.innerHTML = `<div class="alert ${alertClass}">${message}</div>`;
setTimeout(() => {
messagesDiv.innerHTML = '';
}, 5000);
}
// Función para actualizar estado visual
function updateStatus() {
fetch('/api/status')
.then(response => response.json())
.then(data => {
const plcStatus = document.getElementById('plc-status');
const streamStatus = document.getElementById('stream-status');
if (data.plc_connected) {
plcStatus.textContent = '🔌 PLC: Conectado';
plcStatus.className = 'status-item status-connected';
} else {
plcStatus.textContent = '🔌 PLC: Desconectado';
plcStatus.className = 'status-item status-disconnected';
}
if (data.streaming) {
streamStatus.textContent = '📡 Streaming: Activo';
streamStatus.className = 'status-item status-streaming';
} else {
streamStatus.textContent = '📡 Streaming: Inactivo';
streamStatus.className = 'status-item status-idle';
}
})
.catch(error => console.error('Error actualizando estado:', error));
}
// Configuración PLC
document.getElementById('plc-config-form').addEventListener('submit', function (e) {
e.preventDefault();
const data = {
ip: document.getElementById('plc-ip').value,
rack: parseInt(document.getElementById('plc-rack').value),
slot: parseInt(document.getElementById('plc-slot').value)
};
fetch('/api/plc/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
});
});
// Configuración UDP
document.getElementById('udp-config-form').addEventListener('submit', function (e) {
e.preventDefault();
const data = {
host: document.getElementById('udp-host').value,
port: parseInt(document.getElementById('udp-port').value)
};
fetch('/api/udp/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
});
});
// Conectar PLC
document.getElementById('connect-btn').addEventListener('click', function () {
fetch('/api/plc/connect', { method: 'POST' })
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
updateStatus();
});
});
// Desconectar PLC
document.getElementById('disconnect-btn').addEventListener('click', function () {
fetch('/api/plc/disconnect', { method: 'POST' })
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
updateStatus();
});
});
// Añadir variable
document.getElementById('variable-form').addEventListener('submit', function (e) {
e.preventDefault();
const data = {
name: document.getElementById('var-name').value,
db: parseInt(document.getElementById('var-db').value),
offset: parseInt(document.getElementById('var-offset').value),
type: document.getElementById('var-type').value
};
fetch('/api/variables', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
if (data.success) {
location.reload();
}
});
});
// Eliminar variable
function removeVariable(name) {
if (confirm(`¿Está seguro de eliminar la variable "${name}"?`)) {
fetch(`/api/variables/${name}`, { method: 'DELETE' })
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
if (data.success) {
location.reload();
}
});
}
}
// Iniciar streaming
document.getElementById('start-streaming-btn').addEventListener('click', function () {
fetch('/api/streaming/start', { method: 'POST' })
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
updateStatus();
});
});
// Detener streaming
document.getElementById('stop-streaming-btn').addEventListener('click', function () {
fetch('/api/streaming/stop', { method: 'POST' })
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
updateStatus();
});
});
// Actualizar intervalo
document.getElementById('update-sampling-btn').addEventListener('click', function () {
const interval = parseFloat(document.getElementById('sampling-interval').value);
fetch('/api/sampling', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ interval: interval })
})
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success ? 'success' : 'error');
});
});
// Actualizar estado cada 5 segundos
setInterval(updateStatus, 5000);
// Actualización inicial
updateStatus();
</script>
</body>
</html>