fix(ucepsa): improve Shop Floor health dashboard presentation
This commit is contained in:
parent
2afc19105b
commit
10e5582b2c
@ -0,0 +1,120 @@
|
|||||||
|
# UCEPSA — Presentación dashboard Shop Floor v0.3.6.2
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Corregir exclusivamente la presentación del dashboard:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MESAVAULT — UCEPSA · Salud y contexto Shop Floor
|
||||||
|
```
|
||||||
|
|
||||||
|
No modifica PostgreSQL, Odoo, sesiones, paros, sincronizadores ni el Ledger.
|
||||||
|
|
||||||
|
## Correcciones
|
||||||
|
|
||||||
|
### Indicadores superiores
|
||||||
|
|
||||||
|
```text
|
||||||
|
Salud general
|
||||||
|
HEALTHY → verde
|
||||||
|
WARNING → naranja
|
||||||
|
CRITICAL → rojo
|
||||||
|
|
||||||
|
Sincronizador
|
||||||
|
HEALTHY → verde
|
||||||
|
DELAYED → naranja
|
||||||
|
STALE/ERROR → rojo
|
||||||
|
|
||||||
|
Advertencias
|
||||||
|
0 → verde
|
||||||
|
>= 1 → naranja
|
||||||
|
|
||||||
|
Críticas
|
||||||
|
0 → verde
|
||||||
|
>= 1 → rojo
|
||||||
|
```
|
||||||
|
|
||||||
|
Los paneles de texto se alimentan mediante códigos numéricos para evitar el
|
||||||
|
`No data` que Grafana mostraba al reducir valores de texto.
|
||||||
|
|
||||||
|
### Estado por máquina
|
||||||
|
|
||||||
|
Columnas visibles:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Máquina
|
||||||
|
Marcha
|
||||||
|
Telemetría
|
||||||
|
Orden
|
||||||
|
Workorder
|
||||||
|
Operario
|
||||||
|
Estado
|
||||||
|
Severidad
|
||||||
|
```
|
||||||
|
|
||||||
|
Se eliminan de esta tabla `Sync` y `Acción`, ya disponibles en otros paneles.
|
||||||
|
|
||||||
|
Los estados técnicos se traducen a lenguaje operativo:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RUNNING_WITHOUT_SHOPFLOOR_SESSION
|
||||||
|
→ En marcha sin sesión Shop Floor
|
||||||
|
|
||||||
|
TEMPORARY_STOP_INSIDE_SESSION
|
||||||
|
→ Parada dentro de sesión
|
||||||
|
|
||||||
|
IDLE_NO_ACTIVE_CONTEXT
|
||||||
|
→ Inactiva sin contexto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incidencias activas
|
||||||
|
|
||||||
|
Columnas:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Hora
|
||||||
|
Severidad
|
||||||
|
Máquina
|
||||||
|
Situación
|
||||||
|
Orden
|
||||||
|
Operario
|
||||||
|
Acción
|
||||||
|
```
|
||||||
|
|
||||||
|
Los códigos técnicos quedan traducidos a frases operativas.
|
||||||
|
|
||||||
|
## Despliegue
|
||||||
|
|
||||||
|
```bash
|
||||||
|
read -rsp "Token temporal de Grafana: " GRAFANA_API_TOKEN
|
||||||
|
echo
|
||||||
|
export GRAFANA_API_TOKEN
|
||||||
|
|
||||||
|
SCRIPT=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/ops/deploy_shopfloor_context_health_dashboard_v0362.sh
|
||||||
|
|
||||||
|
"$SCRIPT" deploy
|
||||||
|
"$SCRIPT" validate
|
||||||
|
|
||||||
|
unset GRAFANA_API_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
El despliegue reutiliza el mismo UID:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ucepsa-shopfloor-context-health
|
||||||
|
```
|
||||||
|
|
||||||
|
Por tanto, actualiza el dashboard existente sin crear duplicados.
|
||||||
|
|
||||||
|
## Validación esperada
|
||||||
|
|
||||||
|
```text
|
||||||
|
status = ok
|
||||||
|
panel_count = 10
|
||||||
|
datasource_uids = [bfnbcasbm6hhca]
|
||||||
|
numeric_health_stat = true
|
||||||
|
numeric_sync_stat = true
|
||||||
|
compact_machine_table = true
|
||||||
|
friendly_incident_text = true
|
||||||
|
errors = []
|
||||||
|
```
|
||||||
@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"description": "Supervisión SHADOW de máquina física, orden Odoo, sesión Shop Floor, sincronizador y calidad temporal. No alimenta el Ledger oficial.",
|
"description": "Supervisión SHADOW de máquina física, orden Odoo, sesión Shop Floor, sincronizador y calidad temporal. Presentación operativa v0.3.6.2. No alimenta el Ledger oficial.",
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"fiscalYearStartMonth": 0,
|
"fiscalYearStartMonth": 0,
|
||||||
"graphTooltip": 1,
|
"graphTooltip": 1,
|
||||||
@ -35,24 +35,29 @@
|
|||||||
},
|
},
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{
|
{
|
||||||
|
"type": "value",
|
||||||
"options": {
|
"options": {
|
||||||
"HEALTHY": {
|
"0": {
|
||||||
|
"text": "HEALTHY",
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"index": 0,
|
"index": 0
|
||||||
"text": "HEALTHY"
|
|
||||||
},
|
},
|
||||||
"WARNING": {
|
"1": {
|
||||||
|
"text": "WARNING",
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 1,
|
"index": 1
|
||||||
"text": "WARNING"
|
|
||||||
},
|
},
|
||||||
"CRITICAL": {
|
"2": {
|
||||||
|
"text": "CRITICAL",
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 2,
|
"index": 2
|
||||||
"text": "CRITICAL"
|
},
|
||||||
|
"3": {
|
||||||
|
"text": "UNKNOWN",
|
||||||
|
"color": "red",
|
||||||
|
"index": 3
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"type": "value"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"thresholds": {
|
"thresholds": {
|
||||||
@ -61,6 +66,14 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"value": null
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -89,7 +102,7 @@
|
|||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showPercentChange": false,
|
"showPercentChange": false,
|
||||||
"textMode": "auto",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.0.0",
|
"pluginVersion": "11.0.0",
|
||||||
@ -102,7 +115,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT overall_health_status AS \"Estado\"\n FROM mv_reports_ucepsa_prod.li_shopfloor_context_health_summary_v1",
|
"rawSql": "SELECT\n CASE overall_health_status\n WHEN 'HEALTHY' THEN 0\n WHEN 'WARNING' THEN 1\n WHEN 'CRITICAL' THEN 2\n ELSE 3\n END AS \"Estado\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_context_health_summary_v1",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -121,34 +134,34 @@
|
|||||||
},
|
},
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{
|
{
|
||||||
|
"type": "value",
|
||||||
"options": {
|
"options": {
|
||||||
"HEALTHY": {
|
"0": {
|
||||||
|
"text": "HEALTHY",
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"index": 0,
|
"index": 0
|
||||||
"text": "HEALTHY"
|
|
||||||
},
|
},
|
||||||
"DELAYED": {
|
"1": {
|
||||||
|
"text": "DELAYED",
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 1,
|
"index": 1
|
||||||
"text": "DELAYED"
|
|
||||||
},
|
},
|
||||||
"STALE": {
|
"2": {
|
||||||
|
"text": "STALE",
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 2,
|
"index": 2
|
||||||
"text": "STALE"
|
|
||||||
},
|
},
|
||||||
"ERROR": {
|
"3": {
|
||||||
|
"text": "ERROR",
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 3,
|
"index": 3
|
||||||
"text": "ERROR"
|
|
||||||
},
|
},
|
||||||
"NOT_INITIALIZED": {
|
"4": {
|
||||||
|
"text": "NOT INITIALIZED",
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 4,
|
"index": 4
|
||||||
"text": "NOT INITIALIZED"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"type": "value"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"thresholds": {
|
"thresholds": {
|
||||||
@ -157,6 +170,14 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"value": null
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -185,7 +206,7 @@
|
|||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showPercentChange": false,
|
"showPercentChange": false,
|
||||||
"textMode": "auto",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.0.0",
|
"pluginVersion": "11.0.0",
|
||||||
@ -198,7 +219,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT sync_health_status AS \"Estado\"\n FROM mv_reports_ucepsa_prod.li_shopfloor_context_health_summary_v1",
|
"rawSql": "SELECT\n CASE sync_health_status\n WHEN 'HEALTHY' THEN 0\n WHEN 'DELAYED' THEN 1\n WHEN 'STALE' THEN 2\n WHEN 'ERROR' THEN 3\n WHEN 'NOT_INITIALIZED' THEN 4\n ELSE 4\n END AS \"Estado\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_context_health_summary_v1",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -222,6 +243,14 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"value": null
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 120
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -250,7 +279,7 @@
|
|||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showPercentChange": false,
|
"showPercentChange": false,
|
||||||
"textMode": "auto",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.0.0",
|
"pluginVersion": "11.0.0",
|
||||||
@ -263,7 +292,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT seconds_since_sync_success AS \"Segundos\"\n FROM mv_reports_ucepsa_prod.li_shopfloor_context_health_summary_v1",
|
"rawSql": "SELECT\n seconds_since_sync_success AS \"Segundos\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_context_health_summary_v1",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -315,7 +344,7 @@
|
|||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showPercentChange": false,
|
"showPercentChange": false,
|
||||||
"textMode": "auto",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.0.0",
|
"pluginVersion": "11.0.0",
|
||||||
@ -328,7 +357,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT open_session_count AS \"Sesiones\"\n FROM mv_reports_ucepsa_prod.li_shopfloor_context_health_summary_v1",
|
"rawSql": "SELECT\n open_session_count AS \"Sesiones\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_context_health_summary_v1",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -352,6 +381,10 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"value": null
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -380,7 +413,7 @@
|
|||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showPercentChange": false,
|
"showPercentChange": false,
|
||||||
"textMode": "auto",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.0.0",
|
"pluginVersion": "11.0.0",
|
||||||
@ -393,7 +426,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT warning_incident_count AS \"Incidencias\"\n FROM mv_reports_ucepsa_prod.li_shopfloor_context_health_summary_v1",
|
"rawSql": "SELECT\n warning_incident_count AS \"Advertencias\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_context_health_summary_v1",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -417,6 +450,10 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"value": null
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -445,7 +482,7 @@
|
|||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showPercentChange": false,
|
"showPercentChange": false,
|
||||||
"textMode": "auto",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.0.0",
|
"pluginVersion": "11.0.0",
|
||||||
@ -458,7 +495,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT critical_incident_count AS \"Incidencias\"\n FROM mv_reports_ucepsa_prod.li_shopfloor_context_health_summary_v1",
|
"rawSql": "SELECT\n critical_incident_count AS \"Críticas\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_context_health_summary_v1",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -498,24 +535,60 @@
|
|||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
"options": "Estado"
|
"options": "Máquina"
|
||||||
},
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "custom.width",
|
"id": "custom.width",
|
||||||
"value": 260
|
"value": 95
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
"options": "Acción"
|
"options": "Marcha"
|
||||||
},
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "custom.width",
|
"id": "custom.width",
|
||||||
"value": 420
|
"value": 75
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Telemetría"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Orden"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 145
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Workorder"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 95
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -527,7 +600,19 @@
|
|||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "custom.width",
|
"id": "custom.width",
|
||||||
"value": 220
|
"value": 235
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Estado"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 300
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -541,29 +626,29 @@
|
|||||||
"id": "mappings",
|
"id": "mappings",
|
||||||
"value": [
|
"value": [
|
||||||
{
|
{
|
||||||
|
"type": "value",
|
||||||
"options": {
|
"options": {
|
||||||
"OK": {
|
"OK": {
|
||||||
|
"text": "OK",
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"index": 0,
|
"index": 0
|
||||||
"text": "OK"
|
|
||||||
},
|
},
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
"text": "INFO",
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
"index": 1,
|
"index": 1
|
||||||
"text": "INFO"
|
|
||||||
},
|
},
|
||||||
"WARNING": {
|
"WARNING": {
|
||||||
|
"text": "WARNING",
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 2,
|
"index": 2
|
||||||
"text": "WARNING"
|
|
||||||
},
|
},
|
||||||
"CRITICAL": {
|
"CRITICAL": {
|
||||||
|
"text": "CRITICAL",
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 3,
|
"index": 3
|
||||||
"text": "CRITICAL"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"type": "value"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -616,7 +701,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n machine_running AS \"Marcha\",\n telemetry_fresh AS \"Telemetría fresca\",\n COALESCE(published_production_order, '—') AS \"Orden publicada\",\n COALESCE(published_workorder_id, '—') AS \"WO publicado\",\n COALESCE(odoo_workorder_id::text, '—') AS \"WO sesión\",\n COALESCE(operator_name, operator_candidate_name, '—') AS \"Operario\",\n sync_health_status AS \"Sync\",\n live_context_status AS \"Estado\",\n live_severity AS \"Severidad\",\n recommended_action AS \"Acción\"\nFROM mv_reports_ucepsa_prod.li_shopfloor_machine_live_context_v1\nORDER BY machine_id",
|
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n CASE\n WHEN machine_running THEN 'Sí'\n ELSE 'No'\n END AS \"Marcha\",\n CASE\n WHEN telemetry_fresh THEN 'Sí'\n ELSE 'No'\n END AS \"Telemetría\",\n COALESCE(\n published_production_order,\n '—'\n ) AS \"Orden\",\n COALESCE(\n odoo_workorder_id::text,\n NULLIF(published_workorder_id, ''),\n '—'\n ) AS \"Workorder\",\n COALESCE(\n operator_name,\n operator_candidate_name,\n '—'\n ) AS \"Operario\",\n CASE live_context_status\n WHEN 'HEALTHY_OPEN_SESSION'\n THEN 'Produciendo con sesión Shop Floor'\n WHEN 'TEMPORARY_STOP_INSIDE_SESSION'\n THEN 'Parada dentro de sesión'\n WHEN 'RUNNING_WITHOUT_SHOPFLOOR_SESSION'\n THEN 'En marcha sin sesión Shop Floor'\n WHEN 'RUNNING_WITH_ORDER_NO_SHOPFLOOR_SESSION'\n THEN 'En marcha con orden, sin sesión'\n WHEN 'ORDER_WITHOUT_SHOPFLOOR_SESSION'\n THEN 'Orden preparada, sesión no iniciada'\n WHEN 'RUNNING_SESSION_WITHOUT_PUBLISHED_ORDER'\n THEN 'Sesión activa sin orden publicada'\n WHEN 'OPEN_SESSION_WITHOUT_PUBLISHED_ORDER'\n THEN 'Sesión abierta sin orden publicada'\n WHEN 'OPEN_SESSION_ORDER_MISMATCH'\n THEN 'Orden y sesión no coinciden'\n WHEN 'MULTIPLE_OPEN_SESSIONS'\n THEN 'Varias sesiones abiertas'\n WHEN 'OPEN_SESSION_DATA_QUALITY_ERROR'\n THEN 'Error de calidad en sesión'\n WHEN 'IDLE_NO_ACTIVE_CONTEXT'\n THEN 'Inactiva sin contexto'\n ELSE live_context_status\n END AS \"Estado\",\n CASE live_severity\n WHEN 'OK' THEN 'OK'\n WHEN 'INFO' THEN 'INFO'\n WHEN 'WARNING' THEN 'WARNING'\n WHEN 'CRITICAL' THEN 'CRITICAL'\n ELSE live_severity\n END AS \"Severidad\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_machine_live_context_v1\nORDER BY machine_id",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -650,6 +735,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Hora"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 175
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
@ -660,19 +757,19 @@
|
|||||||
"id": "mappings",
|
"id": "mappings",
|
||||||
"value": [
|
"value": [
|
||||||
{
|
{
|
||||||
|
"type": "value",
|
||||||
"options": {
|
"options": {
|
||||||
"WARNING": {
|
"WARNING": {
|
||||||
|
"text": "WARNING",
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 0,
|
"index": 0
|
||||||
"text": "WARNING"
|
|
||||||
},
|
},
|
||||||
"CRITICAL": {
|
"CRITICAL": {
|
||||||
|
"text": "CRITICAL",
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 1,
|
"index": 1
|
||||||
"text": "CRITICAL"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"type": "value"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -688,6 +785,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Máquina"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
@ -700,6 +809,30 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Orden"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 145
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Operario"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 220
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
@ -708,7 +841,7 @@
|
|||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "custom.width",
|
"id": "custom.width",
|
||||||
"value": 460
|
"value": 520
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -744,7 +877,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT\n observed_at AS \"Hora\",\n severity AS \"Severidad\",\n COALESCE(machine_id, '—') AS \"Máquina\",\n incident_scope AS \"Ámbito\",\n incident_code AS \"Código\",\n COALESCE(production_order, '—') AS \"Orden\",\n COALESCE(odoo_workorder_id::text, '—') AS \"Workorder\",\n COALESCE(operator_name, '—') AS \"Operario\",\n detail AS \"Situación\",\n recommended_action AS \"Acción\"\nFROM mv_reports_ucepsa_prod.li_shopfloor_context_incidents_v1\nORDER BY\n CASE severity\n WHEN 'CRITICAL' THEN 1\n WHEN 'WARNING' THEN 2\n ELSE 3\n END,\n machine_id NULLS FIRST,\n incident_code",
|
"rawSql": "SELECT\n observed_at AS \"Hora\",\n severity AS \"Severidad\",\n COALESCE(machine_id, '—') AS \"Máquina\",\n CASE incident_code\n WHEN 'RUNNING_WITHOUT_SHOPFLOOR_SESSION'\n THEN 'Máquina en marcha sin orden ni sesión Shop Floor'\n WHEN 'RUNNING_WITH_ORDER_NO_SHOPFLOOR_SESSION'\n THEN 'Máquina en marcha con orden, pero sin sesión Shop Floor'\n WHEN 'ORDER_WITHOUT_SHOPFLOOR_SESSION'\n THEN 'Orden publicada sin sesión Shop Floor'\n WHEN 'RUNNING_SESSION_WITHOUT_PUBLISHED_ORDER'\n THEN 'Sesión abierta y máquina en marcha, pero sin orden publicada'\n WHEN 'OPEN_SESSION_WITHOUT_PUBLISHED_ORDER'\n THEN 'Sesión abierta sin orden publicada'\n WHEN 'OPEN_SESSION_ORDER_MISMATCH'\n THEN 'La orden publicada no coincide con la sesión abierta'\n WHEN 'MULTIPLE_OPEN_SESSIONS'\n THEN 'Existen varias sesiones abiertas en la misma máquina'\n WHEN 'OPEN_SESSION_DATA_QUALITY_ERROR'\n THEN 'La sesión abierta presenta un error de calidad'\n WHEN 'SHOPFLOOR_SYNC_DELAYED'\n THEN 'La sincronización Shop Floor está retrasada'\n WHEN 'SHOPFLOOR_SYNC_STALE'\n THEN 'La sincronización Shop Floor está obsoleta'\n WHEN 'SHOPFLOOR_SYNC_ERROR'\n THEN 'El sincronizador Shop Floor ha registrado un error'\n WHEN 'SHOPFLOOR_SYNC_NOT_INITIALIZED'\n THEN 'El sincronizador Shop Floor no está inicializado'\n ELSE incident_code\n END AS \"Situación\",\n COALESCE(production_order, '—') AS \"Orden\",\n COALESCE(operator_name, '—') AS \"Operario\",\n recommended_action AS \"Acción\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_context_incidents_v1\nORDER BY\n CASE severity\n WHEN 'CRITICAL' THEN 1\n WHEN 'WARNING' THEN 2\n ELSE 3\n END,\n machine_id NULLS FIRST,\n incident_code",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -778,6 +911,54 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Máquina"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Orden"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 145
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Workorder"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Operario"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 235
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
@ -787,6 +968,10 @@
|
|||||||
{
|
{
|
||||||
"id": "unit",
|
"id": "unit",
|
||||||
"value": "m"
|
"value": "m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 125
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -799,18 +984,70 @@
|
|||||||
{
|
{
|
||||||
"id": "unit",
|
"id": "unit",
|
||||||
"value": "m"
|
"value": "m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 125
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
"options": "Acción"
|
"options": "Estado"
|
||||||
},
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "custom.width",
|
"id": "custom.width",
|
||||||
"value": 420
|
"value": 300
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Severidad"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "mappings",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"options": {
|
||||||
|
"OK": {
|
||||||
|
"text": "OK",
|
||||||
|
"color": "green",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"INFO": {
|
||||||
|
"text": "INFO",
|
||||||
|
"color": "blue",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"WARNING": {
|
||||||
|
"text": "WARNING",
|
||||||
|
"color": "orange",
|
||||||
|
"index": 2
|
||||||
|
},
|
||||||
|
"CRITICAL": {
|
||||||
|
"text": "CRITICAL",
|
||||||
|
"color": "red",
|
||||||
|
"index": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.cellOptions",
|
||||||
|
"value": {
|
||||||
|
"type": "color-text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 110
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -846,7 +1083,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n production_order AS \"Orden\",\n odoo_workorder_id AS \"Workorder\",\n COALESCE(operator_name, operator_candidate_name, '—') AS \"Operario\",\n session_started_at AS \"Inicio\",\n session_age_min AS \"Minutos abierta\",\n machine_running AS \"Marcha\",\n telemetry_fresh AS \"Telemetría fresca\",\n open_stop_duration_min AS \"Paro abierto min\",\n watch_status AS \"Estado\",\n watch_severity AS \"Severidad\",\n recommended_action AS \"Acción\"\nFROM mv_reports_ucepsa_prod.li_shopfloor_open_session_watch_v1\nORDER BY machine_id, session_started_at",
|
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n production_order AS \"Orden\",\n odoo_workorder_id AS \"Workorder\",\n COALESCE(\n operator_name,\n operator_candidate_name,\n '—'\n ) AS \"Operario\",\n session_started_at AS \"Inicio\",\n session_age_min AS \"Minutos abierta\",\n CASE\n WHEN machine_running THEN 'Sí'\n ELSE 'No'\n END AS \"Marcha\",\n COALESCE(\n open_stop_duration_min,\n 0\n ) AS \"Paro abierto min\",\n CASE watch_status\n WHEN 'HEALTHY_OPEN_SESSION'\n THEN 'Produciendo con sesión Shop Floor'\n WHEN 'TEMPORARY_STOP_INSIDE_SESSION'\n THEN 'Parada dentro de sesión'\n WHEN 'RUNNING_SESSION_WITHOUT_PUBLISHED_ORDER'\n THEN 'Sesión activa sin orden publicada'\n WHEN 'OPEN_SESSION_WITHOUT_PUBLISHED_ORDER'\n THEN 'Sesión abierta sin orden publicada'\n WHEN 'OPEN_SESSION_ORDER_MISMATCH'\n THEN 'Orden y sesión no coinciden'\n WHEN 'MULTIPLE_OPEN_SESSIONS'\n THEN 'Varias sesiones abiertas'\n WHEN 'OPEN_SESSION_DATA_QUALITY_ERROR'\n THEN 'Error de calidad en sesión'\n ELSE watch_status\n END AS \"Estado\",\n watch_severity AS \"Severidad\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_open_session_watch_v1\nORDER BY machine_id, session_started_at",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -883,31 +1120,79 @@
|
|||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"id": "byName",
|
"id": "byName",
|
||||||
"options": "DQ"
|
"options": "Máquina"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Orden"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 145
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Workorder"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Operario"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 235
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Calidad"
|
||||||
},
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "mappings",
|
"id": "mappings",
|
||||||
"value": [
|
"value": [
|
||||||
{
|
{
|
||||||
|
"type": "value",
|
||||||
"options": {
|
"options": {
|
||||||
"OK": {
|
"OK": {
|
||||||
|
"text": "OK",
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"index": 0,
|
"index": 0
|
||||||
"text": "OK"
|
|
||||||
},
|
},
|
||||||
"WARNING": {
|
"WARNING": {
|
||||||
|
"text": "WARNING",
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 1,
|
"index": 1
|
||||||
"text": "WARNING"
|
|
||||||
},
|
},
|
||||||
"CRITICAL": {
|
"CRITICAL": {
|
||||||
|
"text": "CRITICAL",
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 2,
|
"index": 2
|
||||||
"text": "CRITICAL"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"type": "value"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -916,6 +1201,22 @@
|
|||||||
"value": {
|
"value": {
|
||||||
"type": "color-text"
|
"type": "color-text"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Incidencias"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 280
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -951,7 +1252,7 @@
|
|||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n production_order AS \"Orden\",\n odoo_workorder_id AS \"Workorder\",\n session_status AS \"Sesión\",\n workorder_state AS \"Estado Odoo\",\n COALESCE(operator_name, operator_candidate_name, '—') AS \"Operario\",\n operator_confirmation_status AS \"Operario\",\n quantity_confirmation_status AS \"Cantidad\",\n confidence AS \"Confianza\",\n dq_status AS \"DQ\",\n array_to_string(issue_codes, ', ') AS \"Incidencias DQ\",\n started_at AS \"Inicio\",\n ended_at AS \"Fin\"\nFROM mv_reports_ucepsa_prod.li_shopfloor_session_data_quality_v1\nORDER BY\n CASE dq_status\n WHEN 'CRITICAL' THEN 1\n WHEN 'WARNING' THEN 2\n ELSE 3\n END,\n COALESCE(ended_at, started_at) DESC NULLS LAST,\n odoo_workorder_id DESC\nLIMIT 50",
|
"rawSql": "SELECT\n machine_id AS \"Máquina\",\n production_order AS \"Orden\",\n odoo_workorder_id AS \"Workorder\",\n CASE session_status\n WHEN 'OPEN' THEN 'Abierta'\n WHEN 'CLOSED' THEN 'Cerrada'\n WHEN 'PENDING' THEN 'Pendiente'\n ELSE session_status\n END AS \"Sesión\",\n workorder_state AS \"Estado Odoo\",\n COALESCE(\n operator_name,\n operator_candidate_name,\n '—'\n ) AS \"Operario\",\n operator_confirmation_status\n AS \"Confirmación operario\",\n quantity_confirmation_status\n AS \"Confirmación cantidad\",\n confidence AS \"Confianza\",\n dq_status AS \"Calidad\",\n COALESCE(\n NULLIF(\n array_to_string(\n issue_codes,\n ', '\n ),\n ''\n ),\n 'Sin incidencias'\n ) AS \"Incidencias\"\nFROM\n mv_reports_ucepsa_prod\n .li_shopfloor_session_data_quality_v1\nORDER BY\n CASE dq_status\n WHEN 'CRITICAL' THEN 1\n WHEN 'WARNING' THEN 2\n ELSE 3\n END,\n COALESCE(ended_at, started_at) DESC NULLS LAST,\n odoo_workorder_id DESC\nLIMIT 50",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -966,7 +1267,8 @@
|
|||||||
"ucepsa",
|
"ucepsa",
|
||||||
"shop-floor",
|
"shop-floor",
|
||||||
"context-health",
|
"context-health",
|
||||||
"shadow"
|
"shadow",
|
||||||
|
"presentation-v0362"
|
||||||
],
|
],
|
||||||
"templating": {
|
"templating": {
|
||||||
"list": []
|
"list": []
|
||||||
|
|||||||
335
ucepsa/edge-oee-demo/ops/deploy_shopfloor_context_health_dashboard_v0362.sh
Executable file
335
ucepsa/edge-oee-demo/ops/deploy_shopfloor_context_health_dashboard_v0362.sh
Executable file
@ -0,0 +1,335 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ACTION="${1:-deploy}"
|
||||||
|
|
||||||
|
GRAFANA_CONTAINER="${GRAFANA_CONTAINER:-mv_ucepsa_grafana}"
|
||||||
|
REPO_ROOT="${SHOPFLOOR_DASHBOARD_REPO_ROOT:-/srv/mesavault/40-clients/ucepsa/edge-oee-demo}"
|
||||||
|
DASHBOARD_FILE="${SHOPFLOOR_DASHBOARD_FILE:-$REPO_ROOT/grafana/dashboards/ucepsa-shopfloor-context-health.dashboard.json}"
|
||||||
|
FOLDER_UID="${SHOPFLOOR_DASHBOARD_FOLDER_UID:-ucepsa-mesavault}"
|
||||||
|
FOLDER_TITLE="${SHOPFLOOR_DASHBOARD_FOLDER_TITLE:-MESAVAULT — UCEPSA}"
|
||||||
|
DASHBOARD_UID="ucepsa-shopfloor-context-health"
|
||||||
|
DATASOURCE_UID="bfnbcasbm6hhca"
|
||||||
|
|
||||||
|
resolve_url() {
|
||||||
|
if [[ -n "${GRAFANA_URL:-}" ]]; then
|
||||||
|
printf '%s' "$GRAFANA_URL"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local port_line host_port container_ip
|
||||||
|
|
||||||
|
port_line="$(
|
||||||
|
docker port "$GRAFANA_CONTAINER" 3000/tcp 2>/dev/null \
|
||||||
|
| head -n 1 || true
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -n "$port_line" ]]; then
|
||||||
|
host_port="${port_line##*:}"
|
||||||
|
printf 'http://127.0.0.1:%s' "$host_port"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
container_ip="$(
|
||||||
|
docker inspect "$GRAFANA_CONTAINER" \
|
||||||
|
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "$container_ip" ]]; then
|
||||||
|
echo "ERROR: no se pudo resolver la URL de Grafana." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'http://%s:3000' "$container_ip"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "${GRAFANA_API_TOKEN:-}" ]]; then
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
ERROR: falta GRAFANA_API_TOKEN.
|
||||||
|
|
||||||
|
Cárguelo sin mostrarlo:
|
||||||
|
|
||||||
|
read -rsp "Token temporal de Grafana: " GRAFANA_API_TOKEN
|
||||||
|
echo
|
||||||
|
export GRAFANA_API_TOKEN
|
||||||
|
MSG
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export GRAFANA_URL_RESOLVED
|
||||||
|
GRAFANA_URL_RESOLVED="$(resolve_url)"
|
||||||
|
|
||||||
|
python3 - \
|
||||||
|
"$ACTION" \
|
||||||
|
"$DASHBOARD_FILE" \
|
||||||
|
"$FOLDER_UID" \
|
||||||
|
"$FOLDER_TITLE" \
|
||||||
|
"$DASHBOARD_UID" \
|
||||||
|
"$DATASOURCE_UID" <<'PY'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
(
|
||||||
|
action,
|
||||||
|
dashboard_file,
|
||||||
|
folder_uid,
|
||||||
|
folder_title,
|
||||||
|
dashboard_uid,
|
||||||
|
datasource_uid,
|
||||||
|
) = sys.argv[1:]
|
||||||
|
|
||||||
|
base_url = os.environ["GRAFANA_URL_RESOLVED"].rstrip("/")
|
||||||
|
token = os.environ["GRAFANA_API_TOKEN"]
|
||||||
|
|
||||||
|
|
||||||
|
def request(method, path, payload=None, allow_404=False):
|
||||||
|
url = base_url + path
|
||||||
|
body = None
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload is not None:
|
||||||
|
body = json.dumps(
|
||||||
|
payload,
|
||||||
|
ensure_ascii=False,
|
||||||
|
).encode("utf-8")
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=body,
|
||||||
|
headers=headers,
|
||||||
|
method=method,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(
|
||||||
|
req,
|
||||||
|
timeout=20,
|
||||||
|
) as response:
|
||||||
|
raw = response.read()
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
return json.loads(raw.decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
detail = exc.read().decode(
|
||||||
|
"utf-8",
|
||||||
|
errors="replace",
|
||||||
|
)
|
||||||
|
if allow_404 and exc.code == 404:
|
||||||
|
return None
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Grafana API {method} {path}: "
|
||||||
|
f"HTTP {exc.code}: {detail}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
|
health = request("GET", "/api/health")
|
||||||
|
if not health or health.get("database") != "ok":
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Grafana no está saludable: {health!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
folder = request(
|
||||||
|
"GET",
|
||||||
|
f"/api/folders/{urllib.parse.quote(folder_uid)}",
|
||||||
|
allow_404=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if action == "deploy" and folder is None:
|
||||||
|
request(
|
||||||
|
"POST",
|
||||||
|
"/api/folders",
|
||||||
|
{
|
||||||
|
"uid": folder_uid,
|
||||||
|
"title": folder_title,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if action == "deploy":
|
||||||
|
with open(
|
||||||
|
dashboard_file,
|
||||||
|
encoding="utf-8",
|
||||||
|
) as handle:
|
||||||
|
dashboard = json.load(handle)
|
||||||
|
|
||||||
|
result = request(
|
||||||
|
"POST",
|
||||||
|
"/api/dashboards/db",
|
||||||
|
{
|
||||||
|
"dashboard": dashboard,
|
||||||
|
"folderUid": folder_uid,
|
||||||
|
"overwrite": True,
|
||||||
|
"message": (
|
||||||
|
"MESAVAULT Shop Floor Context Health "
|
||||||
|
"presentation fix v0.3.6.2"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
print(json.dumps(
|
||||||
|
{
|
||||||
|
"status": "deployed",
|
||||||
|
"grafana_url": base_url,
|
||||||
|
"dashboard_uid": dashboard_uid,
|
||||||
|
"folder_uid": folder_uid,
|
||||||
|
"response": result,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
))
|
||||||
|
|
||||||
|
elif action == "validate":
|
||||||
|
result = request(
|
||||||
|
"GET",
|
||||||
|
(
|
||||||
|
"/api/dashboards/uid/"
|
||||||
|
+ urllib.parse.quote(dashboard_uid)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
dashboard = result["dashboard"]
|
||||||
|
panels = {
|
||||||
|
panel.get("id"): panel
|
||||||
|
for panel in dashboard.get("panels", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource_uids = sorted({
|
||||||
|
panel.get("datasource", {}).get("uid")
|
||||||
|
for panel in dashboard.get("panels", [])
|
||||||
|
if isinstance(
|
||||||
|
panel.get("datasource"),
|
||||||
|
dict,
|
||||||
|
)
|
||||||
|
and panel.get("datasource", {}).get("uid")
|
||||||
|
})
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if dashboard.get("uid") != dashboard_uid:
|
||||||
|
errors.append("UID inesperado")
|
||||||
|
|
||||||
|
if result.get("meta", {}).get("folderUid") != folder_uid:
|
||||||
|
errors.append("Carpeta inesperada")
|
||||||
|
|
||||||
|
if len(dashboard.get("panels", [])) != 10:
|
||||||
|
errors.append("El dashboard no tiene 10 paneles")
|
||||||
|
|
||||||
|
if datasource_uids != [datasource_uid]:
|
||||||
|
errors.append(
|
||||||
|
f"Datasource inesperado: {datasource_uids!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
health_sql = (
|
||||||
|
panels.get(1, {})
|
||||||
|
.get("targets", [{}])[0]
|
||||||
|
.get("rawSql", "")
|
||||||
|
)
|
||||||
|
sync_sql = (
|
||||||
|
panels.get(2, {})
|
||||||
|
.get("targets", [{}])[0]
|
||||||
|
.get("rawSql", "")
|
||||||
|
)
|
||||||
|
machine_sql = (
|
||||||
|
panels.get(7, {})
|
||||||
|
.get("targets", [{}])[0]
|
||||||
|
.get("rawSql", "")
|
||||||
|
)
|
||||||
|
incident_sql = (
|
||||||
|
panels.get(8, {})
|
||||||
|
.get("targets", [{}])[0]
|
||||||
|
.get("rawSql", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
if "CASE overall_health_status" not in health_sql:
|
||||||
|
errors.append(
|
||||||
|
"Salud general no usa código numérico"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "CASE sync_health_status" not in sync_sql:
|
||||||
|
errors.append(
|
||||||
|
"Sincronizador no usa código numérico"
|
||||||
|
)
|
||||||
|
|
||||||
|
for forbidden in ('AS "Sync"', 'AS "Acción"'):
|
||||||
|
if forbidden in machine_sql:
|
||||||
|
errors.append(
|
||||||
|
f"Tabla de máquinas conserva columna {forbidden}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'AS "Situación"' not in incident_sql:
|
||||||
|
errors.append(
|
||||||
|
"Incidencias no usa situación operativa"
|
||||||
|
)
|
||||||
|
|
||||||
|
output = {
|
||||||
|
"status": "ok" if not errors else "error",
|
||||||
|
"grafana_url": base_url,
|
||||||
|
"dashboard_uid": dashboard.get("uid"),
|
||||||
|
"title": dashboard.get("title"),
|
||||||
|
"folder_uid": result.get("meta", {}).get(
|
||||||
|
"folderUid"
|
||||||
|
),
|
||||||
|
"panel_count": len(
|
||||||
|
dashboard.get("panels", [])
|
||||||
|
),
|
||||||
|
"datasource_uids": datasource_uids,
|
||||||
|
"version": dashboard.get("version"),
|
||||||
|
"url": result.get("meta", {}).get("url"),
|
||||||
|
"presentation_checks": {
|
||||||
|
"numeric_health_stat": (
|
||||||
|
"CASE overall_health_status"
|
||||||
|
in health_sql
|
||||||
|
),
|
||||||
|
"numeric_sync_stat": (
|
||||||
|
"CASE sync_health_status"
|
||||||
|
in sync_sql
|
||||||
|
),
|
||||||
|
"compact_machine_table": (
|
||||||
|
'AS "Sync"' not in machine_sql
|
||||||
|
and 'AS "Acción"' not in machine_sql
|
||||||
|
),
|
||||||
|
"friendly_incident_text": (
|
||||||
|
'AS "Situación"' in incident_sql
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"errors": errors,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(
|
||||||
|
output,
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
elif action == "export":
|
||||||
|
result = request(
|
||||||
|
"GET",
|
||||||
|
(
|
||||||
|
"/api/dashboards/uid/"
|
||||||
|
+ urllib.parse.quote(dashboard_uid)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
print(json.dumps(
|
||||||
|
result["dashboard"],
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
))
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise SystemExit(
|
||||||
|
"Uso: "
|
||||||
|
"deploy_shopfloor_context_health_dashboard_v0362.sh "
|
||||||
|
"{deploy|validate|export}"
|
||||||
|
)
|
||||||
|
PY
|
||||||
Loading…
x
Reference in New Issue
Block a user