#!/usr/bin/env python3 import base64 import json import sys import urllib.error import urllib.request from pathlib import Path BASE_URL = "http://127.0.0.1:3000" DASHBOARD_UID = "ucepsa-edge-oee-machine" def read_env(path=".env"): env = {} p = Path(path) if not p.exists(): return env for raw in p.read_text().splitlines(): line = raw.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) env[key.strip()] = value.strip().strip('"').strip("'") return env env = read_env() user = env.get("GRAFANA_ADMIN_USER", "admin") password = env.get("GRAFANA_ADMIN_PASSWORD") if not password: print("ERROR: no encuentro GRAFANA_ADMIN_PASSWORD en .env", file=sys.stderr) sys.exit(1) token = base64.b64encode(f"{user}:{password}".encode()).decode() def api(method, path, payload=None): data = None headers = { "Authorization": f"Basic {token}", "Content-Type": "application/json", "Accept": "application/json", } if payload is not None: data = json.dumps(payload).encode() req = urllib.request.Request( BASE_URL + path, data=data, headers=headers, method=method, ) try: with urllib.request.urlopen(req, timeout=20) as resp: body = resp.read().decode() return json.loads(body) if body else {} except urllib.error.HTTPError as e: print("HTTP ERROR:", e.code, e.read().decode(), file=sys.stderr) raise datasources = api("GET", "/api/datasources") print("Datasources encontrados:") for d in datasources: print(f"- name={d.get('name')} type={d.get('type')} uid={d.get('uid')} url={d.get('url')} database={d.get('database')}") pg = next( ( d for d in datasources if d.get("type") in ("postgres", "grafana-postgresql-datasource") or "postgres" in (d.get("type") or "").lower() or "postgres" in (d.get("name") or "").lower() or "mesavault" in (d.get("name") or "").lower() ), None, ) if not pg: print("ERROR: no encuentro datasource PostgreSQL/MESAVAULT en Grafana", file=sys.stderr) sys.exit(1) DS = { "type": "postgres", "uid": pg["uid"], } def target(sql, ref="A", fmt="table"): return { "refId": ref, "datasource": DS, "format": fmt, "rawQuery": True, "rawSql": sql, "editorMode": "code", } def stat_panel(panel_id, title, x, y, w, h, sql, unit="none", mappings=None): field_defaults = { "unit": unit, "thresholds": { "mode": "absolute", "steps": [ {"color": "green", "value": None}, ], }, } if mappings: field_defaults["mappings"] = mappings return { "id": panel_id, "type": "stat", "title": title, "datasource": DS, "gridPos": {"x": x, "y": y, "w": w, "h": h}, "targets": [target(sql)], "options": { "reduceOptions": { "values": False, "calcs": ["lastNotNull"], "fields": "", }, "orientation": "auto", "textMode": "auto", "colorMode": "value", "graphMode": "none", "justifyMode": "auto", }, "fieldConfig": { "defaults": field_defaults, "overrides": [], }, } def timeseries_panel(panel_id, title, x, y, w, h, sql, unit="none"): return { "id": panel_id, "type": "timeseries", "title": title, "datasource": DS, "gridPos": {"x": x, "y": y, "w": w, "h": h}, "targets": [target(sql, fmt="time_series")], "fieldConfig": { "defaults": { "unit": unit, "custom": { "drawStyle": "line", "lineInterpolation": "linear", "lineWidth": 1, "fillOpacity": 5, "showPoints": "never", }, }, "overrides": [], }, "options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": True, }, "tooltip": { "mode": "multi", "sort": "none", }, }, } def table_panel(panel_id, title, x, y, w, h, sql): return { "id": panel_id, "type": "table", "title": title, "datasource": DS, "gridPos": {"x": x, "y": y, "w": w, "h": h}, "targets": [target(sql)], "options": { "showHeader": True, "cellHeight": "sm", }, "fieldConfig": { "defaults": {}, "overrides": [], }, } comm_mapping = [ { "type": "value", "options": { "1": {"text": "OK", "color": "green"}, "0": {"text": "NO OK", "color": "red"}, }, } ] run_mapping = [ { "type": "value", "options": { "1": {"text": "EN MARCHA", "color": "green"}, "0": {"text": "PARADA", "color": "yellow"}, }, } ] dashboard = { "id": None, "uid": DASHBOARD_UID, "title": "MESAVAULT Edge-OEE — Máquina", "tags": ["mesavault", "ucepsa", "edge-oee"], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh": "5s", "time": { "from": "now-30m", "to": "now", }, "templating": { "list": [ { "name": "machine_id", "type": "query", "label": "Máquina", "datasource": DS, "refresh": 1, "sort": 1, "multi": False, "includeAll": False, "query": "SELECT machine_id FROM mv_reports_ucepsa_demo.machine_state_latest_multi ORDER BY machine_id", "current": { "text": "CORT-00", "value": "CORT-00", "selected": True, }, } ] }, "panels": [ stat_panel( 1, "Comunicación", 0, 0, 6, 4, """ SELECT CASE WHEN age_s < 30 AND comm_ok = true AND COALESCE(sample_valid, true) = true THEN 1 ELSE 0 END AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, mappings=comm_mapping, ), stat_panel( 2, "Edad último dato", 6, 0, 6, 4, """ SELECT age_s AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, unit="s", ), stat_panel( 3, "Estado máquina", 12, 0, 6, 4, """ SELECT CASE WHEN machine_running = true THEN 1 ELSE 0 END AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, mappings=run_mapping, ), stat_panel( 4, "Ciclos/min", 18, 0, 6, 4, """ SELECT cycle_rate_ppm AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, unit="cpm", ), stat_panel( 5, "Potencia total", 0, 4, 6, 4, """ SELECT power_total_kw AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, unit="kwatt", ), stat_panel( 6, "Energía importada", 6, 4, 6, 4, """ SELECT import_kwh AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, unit="kwatth", ), stat_panel( 7, "Frecuencia", 12, 4, 6, 4, """ SELECT frequency_hz AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, unit="hertz", ), stat_panel( 8, "Pulsos acumulados", 18, 4, 6, 4, """ SELECT cycle_pulse_count AS value FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, unit="none", ), timeseries_panel( 9, "Potencia por fase", 0, 8, 12, 8, """ SELECT ts AS "time", active_power_l1_w AS "L1 W", active_power_l2_w AS "L2 W", active_power_l3_w AS "L3 W", active_power_w AS "Total W" FROM mv_reports_ucepsa_demo.energy_3phase_timeseries WHERE $__timeFilter(ts) AND site = 'ucepsa_onpremise' AND machine_id = '$machine_id' ORDER BY ts """, unit="watt", ), timeseries_panel( 10, "Tensiones L1/L2/L3", 12, 8, 12, 8, """ SELECT ts AS "time", voltage_l1_v AS "L1 V", voltage_l2_v AS "L2 V", voltage_l3_v AS "L3 V" FROM mv_reports_ucepsa_demo.energy_3phase_timeseries WHERE $__timeFilter(ts) AND site = 'ucepsa_onpremise' AND machine_id = '$machine_id' ORDER BY ts """, unit="volt", ), timeseries_panel( 11, "Corrientes L1/L2/L3", 0, 16, 12, 8, """ SELECT ts AS "time", current_l1_a AS "L1 A", current_l2_a AS "L2 A", current_l3_a AS "L3 A" FROM mv_reports_ucepsa_demo.energy_3phase_timeseries WHERE $__timeFilter(ts) AND site = 'ucepsa_onpremise' AND machine_id = '$machine_id' ORDER BY ts """, unit="amp", ), table_panel( 12, "Última lectura trifásica", 12, 16, 12, 8, """ SELECT machine_id, round(age_s::numeric, 1) AS age_s, comm_ok, sample_valid, energy_map, machine_running, cycle_input_state, cycle_pulse_count, wise_cycle_counter_raw, round(voltage_l1_v::numeric, 2) AS v_l1, round(voltage_l2_v::numeric, 2) AS v_l2, round(voltage_l3_v::numeric, 2) AS v_l3, round(current_l1_a::numeric, 3) AS i_l1, round(current_l2_a::numeric, 3) AS i_l2, round(current_l3_a::numeric, 3) AS i_l3, round(power_total_kw::numeric, 4) AS kw_total, digital_inputs FROM mv_reports_ucepsa_demo.machine_state_latest_multi WHERE machine_id = '$machine_id' """, ), table_panel( 13, "Odoo vs máquina", 0, 24, 12, 7, """ SELECT machine_id, order_ref, product_name, odoo_state, machine_running, telemetry_age_s, cycle_rate_ppm, status_check, diagnostic_message FROM mv_reports_ucepsa_demo.odoo_vs_machine_demo WHERE machine_id = '$machine_id' """, ), table_panel( 14, "Loss Intelligence", 12, 24, 12, 7, """ SELECT priority_rank, priority_score, priority_level, machine_id, order_ref, loss_type, severity, duration_min, cost_eur, status, description, recommended_action FROM mv_reports_ucepsa_demo.loss_priority_board WHERE machine_id = '$machine_id' ORDER BY priority_rank """, ), ], } payload = { "dashboard": dashboard, "overwrite": True, "message": "Create MESAVAULT Edge-OEE machine dashboard with machine selector", } result = api("POST", "/api/dashboards/db", payload) print("Dashboard creado/actualizado:") print(BASE_URL + result.get("url", f"/d/{DASHBOARD_UID}"))