Fix sensor_list_report for standalone Piston execution

Das Script wurde so angepasst, dass es außerhalb der bisherigen Host-Umgebung zuverlässig in Piston läuft. Dazu wurde die HTML-Ausgabe explizit an stdout gebunden, die Generierung robuster gegenüber fehlenden Daten gemacht und potenziell problematische dynamische Inhalte HTML-escaped.
main
mathias.sillhengst 2 weeks ago
parent 6a9e7beeed
commit 277c2126e2

@ -1,9 +1,10 @@
import httpx import html
import json
from datetime import datetime from datetime import datetime
# GraphQL Query für alle Sensoren import httpx
query = '''
QUERY = """
query GetAllSensors { query GetAllSensors {
sensors { sensors {
id id
@ -17,47 +18,65 @@ query GetAllSensors {
} }
} }
} }
''' """.strip()
# API Aufruf
with httpx.Client() as client: def escape_text(value, default=""):
response = client.post( text = default if value is None else str(value)
f"{EXTERNAL_BASE_URL}/graphql", return html.escape(text, quote=True)
json={"query": query},
headers=AUTH_HEADERS,
timeout=30.0 def build_error(message):
return (
"<div style='color: var(--color-danger, #dc3545); "
"font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif;'>"
f"{escape_text(message)}"
"</div>"
) )
def build_report():
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: if response.status_code != 200:
result = f"<div style='color: var(--color-danger);'>Fehler beim Abrufen der Sensoren: {response.status_code}</div>" return build_error(f"Fehler beim Abrufen der Sensoren: {response.status_code}")
else:
data = response.json() data = response.json()
sensors = data.get('data', {}).get('sensors', []) sensors = data.get("data", {}).get("sensors", [])
# Sortiere Sensoren nach MeasureConcept Name sensors.sort(
sensors.sort(key=lambda s: (s['measureConcept']['name'].strip() if s['measureConcept']['name'] else '', s['name'].strip())) key=lambda s: (
(s.get("measureConcept") or {}).get("name", "").strip(),
# Gruppiere nach MeasureConcept (s.get("name") or "").strip(),
measure_concepts = {} )
for sensor in sensors: )
mc_id = sensor['measureConcept']['id']
mc_name = sensor['measureConcept']['name'].strip() if sensor['measureConcept']['name'] else 'Unbekannt' measure_concepts = {}
mc_desc = sensor['measureConcept']['description'] or '' for sensor in sensors:
measure_concept = sensor.get("measureConcept") or {}
if mc_id not in measure_concepts: mc_id = str(measure_concept.get("id") or "unknown")
measure_concepts[mc_id] = { mc_name = (measure_concept.get("name") or "Unbekannt").strip()
'name': mc_name, mc_desc = measure_concept.get("description") or ""
'description': mc_desc,
'sensors': [] if mc_id not in measure_concepts:
} measure_concepts[mc_id] = {
measure_concepts[mc_id]['sensors'].append(sensor) "name": mc_name,
"description": mc_desc,
# Statistiken "sensors": [],
total_sensors = len(sensors) }
total_concepts = len(measure_concepts)
measure_concepts[mc_id]["sensors"].append(sensor)
# HTML generieren
html = f''' total_sensors = len(sensors)
total_concepts = len(measure_concepts)
html_output = f"""
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -257,7 +276,7 @@ with httpx.Client() as client:
<body> <body>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<h1>🔧 Sensor Übersicht</h1> <h1>Sensor Uebersicht</h1>
<div class="stats"> <div class="stats">
<div class="stat-card"> <div class="stat-card">
<div class="number">{total_sensors}</div> <div class="number">{total_sensors}</div>
@ -269,127 +288,127 @@ with httpx.Client() as client:
</div> </div>
</div> </div>
</div> </div>
<div class="filters"> <div class="filters">
<div class="filter-group"> <div class="filter-group">
<label for="searchInput">🔍 Sensor suchen:</label> <label for="searchInput">Sensor suchen:</label>
<input type="text" id="searchInput" placeholder="Sensor-Name, ID oder Beschreibung..." onkeyup="filterSensors()"> <input type="text" id="searchInput" placeholder="Sensor-Name, ID oder Beschreibung..." onkeyup="filterSensors()">
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label for="conceptFilter">📊 MeasureConcept filtern:</label> <label for="conceptFilter">MeasureConcept filtern:</label>
<select id="conceptFilter" onchange="filterSensors()"> <select id="conceptFilter" onchange="filterSensors()">
<option value="">Alle MeasureConcepts anzeigen</option> <option value="">Alle MeasureConcepts anzeigen</option>
''' """
# MeasureConcept Options für Filter for mc_id, mc_data in sorted(measure_concepts.items(), key=lambda item: item[1]["name"]):
for mc_id, mc_data in sorted(measure_concepts.items(), key=lambda x: x[1]['name']): mc_name = escape_text(mc_data["name"], "Unbekannt")
mc_name = mc_data['name'] sensor_count = len(mc_data["sensors"])
sensor_count = len(mc_data['sensors']) html_output += (
html += f' <option value="{mc_id}">{mc_name} ({sensor_count} Sensoren)</option>\n' f' <option value="{escape_text(mc_id)}">'
f"{mc_name} ({sensor_count} Sensoren)</option>\n"
html += ''' )
html_output += """
</select> </select>
</div> </div>
</div> </div>
<div id="sensorsContent"> <div id="sensorsContent">
''' """
# MeasureConcepts und Sensoren ausgeben for mc_id, mc_data in sorted(measure_concepts.items(), key=lambda item: item[1]["name"]):
for mc_id, mc_data in sorted(measure_concepts.items(), key=lambda x: x[1]['name']): safe_mc_id = escape_text(mc_id)
mc_name = mc_data['name'] mc_name = escape_text(mc_data["name"], "Unbekannt")
mc_desc = mc_data['description'] mc_desc = escape_text(mc_data["description"])
sensors_in_concept = mc_data['sensors'] sensors_in_concept = mc_data["sensors"]
html += f''' html_output += f"""
<div class="measure-concept" data-concept-id="{mc_id}"> <div class="measure-concept" data-concept-id="{safe_mc_id}">
<div class="concept-header" onclick="toggleConcept('{mc_id}')"> <div class="concept-header" onclick="toggleConcept('{safe_mc_id}')">
<div> <div>
<h3> <h3>
📊 {mc_name} {mc_name}
<span class="sensor-count">{len(sensors_in_concept)} Sensoren</span> <span class="sensor-count">{len(sensors_in_concept)} Sensoren</span>
</h3> </h3>
''' """
if mc_desc: if mc_desc:
html += f' <div class="concept-description">{mc_desc}</div>\n' html_output += f' <div class="concept-description">{mc_desc}</div>\n'
html += ''' html_output += f"""
</div> </div>
</div> </div>
<div class="sensors-container" id="sensors-{mc_id}"> <div class="sensors-container" id="sensors-{safe_mc_id}">
<div class="sensor-grid"> <div class="sensor-grid">
''' """
for sensor in sensors_in_concept: for sensor in sensors_in_concept:
sensor_id = sensor['id'] sensor_id = escape_text(sensor.get("id"), "Unbekannt")
sensor_name = sensor['name'].strip() if sensor['name'] else 'Unbekannter Name' sensor_name = escape_text((sensor.get("name") or "").strip(), "Unbekannter Name")
sensor_extern = sensor.get('nameExtern', '') or '' sensor_extern = escape_text(sensor.get("nameExtern") or "")
sensor_desc = sensor.get('description', '') or '' sensor_desc = escape_text(sensor.get("description") or "")
html += f''' html_output += f"""
<div class="sensor-card" data-sensor-name="{sensor_name}" data-sensor-id="{sensor_id}" data-sensor-desc="{sensor_desc}"> <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-id">ID: {sensor_id}</div>
<div class="sensor-name">{sensor_name}</div> <div class="sensor-name">{sensor_name}</div>
''' """
if sensor_extern and sensor_extern.strip() != '-': if sensor_extern and sensor_extern != "-":
html += f' <div class="sensor-extern">Extern: {sensor_extern}</div>\n' html_output += f' <div class="sensor-extern">Extern: {sensor_extern}</div>\n'
if sensor_desc: if sensor_desc:
html += f' <div class="sensor-description">{sensor_desc}</div>\n' html_output += f' <div class="sensor-description">{sensor_desc}</div>\n'
else: else:
html += ' <div class="sensor-description no-data">Keine Beschreibung verfügbar</div>\n' html_output += ' <div class="sensor-description no-data">Keine Beschreibung verfuegbar</div>\n'
html += ' </div>\n' html_output += " </div>\n"
html += ''' html_output += """
</div> </div>
</div> </div>
</div> </div>
''' """
# Timestamp und JavaScript current_time = datetime.now().strftime("%d.%m.%Y um %H:%M:%S")
current_time = datetime.now().strftime('%d.%m.%Y um %H:%M:%S') html_output += f"""
html += f'''
</div> </div>
<div class="timestamp"> <div class="timestamp">
📅 Erstellt am {current_time} Erstellt am {current_time}
</div> </div>
</div> </div>
<script> <script>
function toggleConcept(conceptId) {{ function toggleConcept(conceptId) {{
const container = document.getElementById('sensors-' + conceptId); const container = document.getElementById('sensors-' + conceptId);
container.classList.toggle('active'); if (container) {{
container.classList.toggle('active');
}}
}} }}
function filterSensors() {{ function filterSensors() {{
const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const conceptFilter = document.getElementById('conceptFilter').value; const conceptFilter = document.getElementById('conceptFilter').value;
const concepts = document.querySelectorAll('.measure-concept'); const concepts = document.querySelectorAll('.measure-concept');
concepts.forEach(concept => {{ concepts.forEach(concept => {{
const conceptId = concept.getAttribute('data-concept-id'); const conceptId = concept.getAttribute('data-concept-id');
let showConcept = false; let showConcept = false;
// Concept Filter prüfen
if (conceptFilter && conceptFilter !== conceptId) {{ if (conceptFilter && conceptFilter !== conceptId) {{
concept.style.display = 'none'; concept.style.display = 'none';
return; return;
}} }}
// Sensor Cards filtern
const sensorCards = concept.querySelectorAll('.sensor-card'); const sensorCards = concept.querySelectorAll('.sensor-card');
sensorCards.forEach(card => {{ sensorCards.forEach(card => {{
const sensorName = card.getAttribute('data-sensor-name').toLowerCase(); const sensorName = card.getAttribute('data-sensor-name').toLowerCase();
const sensorId = card.getAttribute('data-sensor-id').toLowerCase(); const sensorId = card.getAttribute('data-sensor-id').toLowerCase();
const sensorDesc = card.getAttribute('data-sensor-desc').toLowerCase(); const sensorDesc = card.getAttribute('data-sensor-desc').toLowerCase();
if (!searchTerm || if (!searchTerm ||
sensorName.includes(searchTerm) || sensorName.includes(searchTerm) ||
sensorId.includes(searchTerm) || sensorId.includes(searchTerm) ||
sensorDesc.includes(searchTerm)) {{ sensorDesc.includes(searchTerm)) {{
card.style.display = 'block'; card.style.display = 'block';
showConcept = true; showConcept = true;
@ -397,18 +416,21 @@ with httpx.Client() as client:
card.style.display = 'none'; card.style.display = 'none';
}} }}
}}); }});
concept.style.display = showConcept ? 'block' : 'none'; concept.style.display = showConcept ? 'block' : 'none';
}}); }});
}} }}
// Alle Concepts initial zuklappen
document.querySelectorAll('.sensors-container').forEach(container => {{ document.querySelectorAll('.sensors-container').forEach(container => {{
container.classList.remove('active'); container.classList.remove('active');
}}); }});
</script> </script>
</body> </body>
</html> </html>
''' """
result = html return html_output
result = build_report()
print(result)

Loading…
Cancel
Save