deploy script: sensor_list_report

main
martin.schweitzer 3 weeks ago
parent ca243a875f
commit 98889c7276

@ -0,0 +1,414 @@
import httpx
import json
from datetime import datetime
# GraphQL Query für alle Sensoren
query = '''
query GetAllSensors {
sensors {
id
name
nameExtern
description
measureConcept {
id
name
description
}
}
}
'''
# API Aufruf
with httpx.Client() as client:
response = client.post(
f"{EXTERNAL_BASE_URL}/graphql",
json={"query": query},
headers=AUTH_HEADERS,
timeout=30.0
)
if response.status_code != 200:
result = f"<div style='color: var(--color-danger);'>Fehler beim Abrufen der Sensoren: {response.status_code}</div>"
else:
data = response.json()
sensors = data.get('data', {}).get('sensors', [])
# Sortiere Sensoren nach MeasureConcept Name
sensors.sort(key=lambda s: (s['measureConcept']['name'].strip() if s['measureConcept']['name'] else '', s['name'].strip()))
# Gruppiere nach MeasureConcept
measure_concepts = {}
for sensor in sensors:
mc_id = sensor['measureConcept']['id']
mc_name = sensor['measureConcept']['name'].strip() if sensor['measureConcept']['name'] else 'Unbekannt'
mc_desc = sensor['measureConcept']['description'] or ''
if mc_id not in measure_concepts:
measure_concepts[mc_id] = {
'name': mc_name,
'description': mc_desc,
'sensors': []
}
measure_concepts[mc_id]['sensors'].append(sensor)
# Statistiken
total_sensors = len(sensors)
total_concepts = len(measure_concepts)
# HTML generieren
html = f'''
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sensor Liste</title>
<style>
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}}
.container {{
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}}
.header {{
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid var(--color-primary, #007bff);
}}
.header h1 {{
color: var(--color-primary, #007bff);
margin: 0;
font-size: 2.5em;
}}
.stats {{
display: flex;
justify-content: center;
gap: 30px;
margin: 20px 0;
flex-wrap: wrap;
}}
.stat-card {{
background: linear-gradient(135deg, var(--color-primary, #007bff), #0056b3);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
min-width: 150px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}}
.stat-card .number {{
font-size: 2.5em;
font-weight: bold;
margin: 0;
}}
.stat-card .label {{
font-size: 0.9em;
opacity: 0.9;
margin-top: 5px;
}}
.filters {{
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
border-left: 4px solid var(--color-primary, #007bff);
}}
.filter-group {{
margin-bottom: 15px;
}}
.filter-group label {{
display: block;
font-weight: bold;
margin-bottom: 5px;
color: #495057;
}}
.filter-group input, .filter-group select {{
width: 100%;
max-width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
}}
.measure-concept {{
margin-bottom: 30px;
border: 1px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}}
.concept-header {{
background: linear-gradient(135deg, #495057, #6c757d);
color: white;
padding: 15px 20px;
cursor: pointer;
transition: background-color 0.3s;
}}
.concept-header:hover {{
background: linear-gradient(135deg, #6c757d, #495057);
}}
.concept-header h3 {{
margin: 0;
font-size: 1.3em;
display: flex;
justify-content: space-between;
align-items: center;
}}
.concept-description {{
font-size: 0.9em;
opacity: 0.9;
margin-top: 5px;
}}
.sensor-count {{
background: rgba(255,255,255,0.2);
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8em;
font-weight: normal;
}}
.sensors-container {{
display: none;
background: white;
}}
.sensors-container.active {{
display: block;
}}
.sensor-grid {{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 15px;
padding: 20px;
}}
.sensor-card {{
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 15px;
transition: all 0.3s;
}}
.sensor-card:hover {{
background: #e9ecef;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}}
.sensor-id {{
background: var(--color-primary, #007bff);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8em;
font-weight: bold;
display: inline-block;
margin-bottom: 8px;
}}
.sensor-name {{
font-weight: bold;
color: #495057;
margin-bottom: 5px;
word-break: break-all;
}}
.sensor-extern {{
color: var(--color-success, #28a745);
font-size: 0.9em;
margin-bottom: 5px;
font-style: italic;
}}
.sensor-description {{
color: #6c757d;
font-size: 0.9em;
line-height: 1.4;
}}
.no-data {{
color: #999;
font-style: italic;
}}
.timestamp {{
text-align: center;
color: #6c757d;
font-size: 0.9em;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #dee2e6;
}}
@media (max-width: 768px) {{
.container {{
padding: 15px;
}}
.stats {{
flex-direction: column;
align-items: center;
}}
.sensor-grid {{
grid-template-columns: 1fr;
}}
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔧 Sensor Übersicht</h1>
<div class="stats">
<div class="stat-card">
<div class="number">{total_sensors}</div>
<div class="label">Sensoren gesamt</div>
</div>
<div class="stat-card">
<div class="number">{total_concepts}</div>
<div class="label">MeasureConcepts</div>
</div>
</div>
</div>
<div class="filters">
<div class="filter-group">
<label for="searchInput">🔍 Sensor suchen:</label>
<input type="text" id="searchInput" placeholder="Sensor-Name, ID oder Beschreibung..." onkeyup="filterSensors()">
</div>
<div class="filter-group">
<label for="conceptFilter">📊 MeasureConcept filtern:</label>
<select id="conceptFilter" onchange="filterSensors()">
<option value="">Alle MeasureConcepts anzeigen</option>
'''
# MeasureConcept Options für Filter
for mc_id, mc_data in sorted(measure_concepts.items(), key=lambda x: x[1]['name']):
mc_name = mc_data['name']
sensor_count = len(mc_data['sensors'])
html += f' <option value="{mc_id}">{mc_name} ({sensor_count} Sensoren)</option>\n'
html += '''
</select>
</div>
</div>
<div id="sensorsContent">
'''
# MeasureConcepts und Sensoren ausgeben
for mc_id, mc_data in sorted(measure_concepts.items(), key=lambda x: x[1]['name']):
mc_name = mc_data['name']
mc_desc = mc_data['description']
sensors_in_concept = mc_data['sensors']
html += f'''
<div class="measure-concept" data-concept-id="{mc_id}">
<div class="concept-header" onclick="toggleConcept('{mc_id}')">
<div>
<h3>
📊 {mc_name}
<span class="sensor-count">{len(sensors_in_concept)} Sensoren</span>
</h3>
'''
if mc_desc:
html += f' <div class="concept-description">{mc_desc}</div>\n'
html += '''
</div>
</div>
<div class="sensors-container" id="sensors-{mc_id}">
<div class="sensor-grid">
'''
for sensor in sensors_in_concept:
sensor_id = sensor['id']
sensor_name = sensor['name'].strip() if sensor['name'] else 'Unbekannter Name'
sensor_extern = sensor.get('nameExtern', '') or ''
sensor_desc = sensor.get('description', '') or ''
html += f'''
<div class="sensor-card" data-sensor-name="{sensor_name}" data-sensor-id="{sensor_id}" data-sensor-desc="{sensor_desc}">
<div class="sensor-id">ID: {sensor_id}</div>
<div class="sensor-name">{sensor_name}</div>
'''
if sensor_extern and sensor_extern.strip() != '-':
html += f' <div class="sensor-extern">Extern: {sensor_extern}</div>\n'
if sensor_desc:
html += f' <div class="sensor-description">{sensor_desc}</div>\n'
else:
html += ' <div class="sensor-description no-data">Keine Beschreibung verfügbar</div>\n'
html += ' </div>\n'
html += '''
</div>
</div>
</div>
'''
# Timestamp und JavaScript
current_time = datetime.now().strftime('%d.%m.%Y um %H:%M:%S')
html += f'''
</div>
<div class="timestamp">
📅 Erstellt am {current_time}
</div>
</div>
<script>
function toggleConcept(conceptId) {{
const container = document.getElementById('sensors-' + conceptId);
container.classList.toggle('active');
}}
function filterSensors() {{
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const conceptFilter = document.getElementById('conceptFilter').value;
const concepts = document.querySelectorAll('.measure-concept');
concepts.forEach(concept => {{
const conceptId = concept.getAttribute('data-concept-id');
let showConcept = false;
// Concept Filter prüfen
if (conceptFilter && conceptFilter !== conceptId) {{
concept.style.display = 'none';
return;
}}
// Sensor Cards filtern
const sensorCards = concept.querySelectorAll('.sensor-card');
sensorCards.forEach(card => {{
const sensorName = card.getAttribute('data-sensor-name').toLowerCase();
const sensorId = card.getAttribute('data-sensor-id').toLowerCase();
const sensorDesc = card.getAttribute('data-sensor-desc').toLowerCase();
if (!searchTerm ||
sensorName.includes(searchTerm) ||
sensorId.includes(searchTerm) ||
sensorDesc.includes(searchTerm)) {{
card.style.display = 'block';
showConcept = true;
}} else {{
card.style.display = 'none';
}}
}});
concept.style.display = showConcept ? 'block' : 'none';
}});
}}
// Alle Concepts initial zuklappen
document.querySelectorAll('.sensors-container').forEach(container => {{
container.classList.remove('active');
}});
</script>
</body>
</html>
'''
result = html
Loading…
Cancel
Save