Zaroz
Notificaciones y soporte

Notificaciones, flujos en vivo y tickets de soporte

Las tres formas en que Zaroz te cuenta lo que pasa, y la única por la que respondes. Los ejemplos usan el ayudante de Usar la API de Zaroz. Para los eventos de las unidades de un Workload (y sus webhooks) consulta Eventos y Webhooks.

Notificaciones

Mensajes de la plataforma para tu cuenta: una factura, un servicio que falló, una invitación. Van de más reciente a más antigua y paginadas. unread_count es el total de todas las páginas, que es lo que muestra una insignia.

curl

TerminalCode
curl -sS "$ZAROZ_API/me/notifications?page=1&page_size=20" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/me/notifications/$NOTIFICATION/read" -H "$AUTH" # idempotente curl -sS -X POST "$ZAROZ_API/me/notifications/read-all" -H "$AUTH"

TypeScript

TypeScriptCode
import { call } from "./zaroz"; const feed = await call("GET", "/me/notifications?page_size=20"); console.log(`${feed.body.unread_count} sin leer`); for (const n of feed.body.data) console.log(n.kind, n.title, n.href, n.read); await call("POST", "/me/notifications/read-all");

Python

Code
from zaroz import call _, feed = call("GET", "/me/notifications?page_size=20") print(feed["unread_count"], "sin leer") for n in feed["data"]: print(n["kind"], n["title"], n["href"], n["read"]) call("POST", "/me/notifications/read-all")

Marcar una notificación como leída es idempotente. Una notificación que no es tuya responde 404, igual que una que no existe.

Flujos en vivo (eventos enviados por el servidor)

Tres rutas mantienen abierto un text/event-stream para que no tengas que consultar periódicamente.

FlujoRutaTramas
Tus nuevas notificacionesGET /me/notifications/streamnotification_created
El estado en vivo de un servicioGET /service-instances/{id}/status/streamstatus, network_metrics, resource_metrics, vm_progress
La conversación de un ticketGET /support/tickets/{id}/eventsmessage_sent, message_edited, ...

El servidor cierra cada conexión a los quince minutos y la primera trama fija el intervalo de reconexión, así que reconecta cuando termine (un EventSource del navegador lo hace solo). La trama status del flujo del servicio trae el cuadro completo (estado agregado, las acciones del ciclo de vida que tienen sentido ahora, la operación en curso, cada recurso visible con su uso en vivo) al conectar, cuando cambia un recurso y cada cinco segundos. Responde 404 en un servicio cuya suscripción está suspendida, terminada, cancelada o fallida.

curl

TerminalCode
curl -sS -N "$ZAROZ_API/service-instances/$SERVICE/status/stream" -H "$AUTH" -H "Accept: text/event-stream"

TypeScript

TypeScriptCode
// Node 20+: un fetch normal puede leer el flujo; EventSource no puede enviar la cabecera Authorization. const res = await fetch(`${process.env.ZAROZ_API}/service-instances/${SERVICE}/status/stream`, { headers: { Authorization: `Bearer ${process.env.ZAROZ_TOKEN}`, Accept: "text/event-stream" }, }); const decoder = new TextDecoder(); let buffer = ""; for await (const chunk of res.body as any) { buffer += decoder.decode(chunk, { stream: true }); let i; while ((i = buffer.indexOf("\n\n")) >= 0) { const frame = buffer.slice(0, i); buffer = buffer.slice(i + 2); const event = /^event: (.*)$/m.exec(frame)?.[1]; const data = /^data: (.*)$/m.exec(frame)?.[1]; if (event === "status" && data) console.log(JSON.parse(data)); } }

Python

Code
import json, os, requests url = f"{os.environ['ZAROZ_API']}/service-instances/{SERVICE}/status/stream" headers = {"Authorization": "Bearer " + os.environ["ZAROZ_TOKEN"], "Accept": "text/event-stream"} with requests.get(url, headers=headers, stream=True, timeout=(10, 60)) as r: event = None for line in r.iter_lines(decode_unicode=True): if line.startswith("event: "): event = line[7:] elif line.startswith("data: ") and event == "status": print(json.loads(line[6:]))

Usa un cliente HTTP normal como arriba para scripts. Un EventSource del navegador no puede poner la cabecera Authorization, así que las apps web hacen el flujo a través de su propio backend.

Tickets de soporte

Los tickets pertenecen a una organización; todos los miembros ven los de la organización y pueden responder a los de un compañero. Las identidades del personal nunca se exponen: las respuestas aparecen como "Support Team".

curl

TerminalCode
# abrir un ticket en una organización curl -sS -X POST "$ZAROZ_API/organizations/$ORG/tickets" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"subject": "El servidor no arranca", "body": "Se reinicia en bucle desde el último despliegue. Id del servicio: …"}' # o en tu organización personal curl -sS -X POST "$ZAROZ_API/me/tickets" -H "$AUTH" -H "Content-Type: application/json" -d '{"subject": "…", "body": "…"}' curl -sS "$ZAROZ_API/support/tickets?page_size=20" -H "$AUTH" curl -sS "$ZAROZ_API/support/tickets/$TICKET" -H "$AUTH" # con sus mensajes curl -sS -X POST "$ZAROZ_API/support/tickets/$TICKET/messages" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"body": "Aquí van los logs que pedisteis."}'

TypeScript

TypeScriptCode
const opened = await call("POST", `/organizations/${ORG}/tickets`, { subject: "El servidor no arranca", body: "Se reinicia en bucle desde el último despliegue.", }); const TICKET = opened.body.ticket.id; await call("POST", `/support/tickets/${TICKET}/messages`, { body: "Aquí van los logs que pedisteis." }); const detail = await call("GET", `/support/tickets/${TICKET}`); for (const m of detail.body.messages) console.log(m.author_name, m.body);

Python

Code
_, opened = call("POST", f"/organizations/{ORG}/tickets", { "subject": "El servidor no arranca", "body": "Se reinicia en bucle desde el último despliegue.", }) TICKET = opened["ticket"]["id"] call("POST", f"/support/tickets/{TICKET}/messages", {"body": "Aquí van los logs que pedisteis."}) _, detail = call("GET", f"/support/tickets/{TICKET}") for m in detail["messages"]: print(m["author_name"], m["body"])

Abrir un ticket en /me/tickets usa siempre tu organización personal; no hay organización que equivocar. La lista muestra una vista previa del último mensaje, el recuento de no leídos y el estado de la valoración. Cuando un ticket se cierra puedes enviar una valoración de satisfacción una vez: POST /support/tickets/{id}/review con problem_solved (booleano), resolution_speed y response_quality (enteros pequeños) y un comment opcional; solo puede hacerlo el propietario del ticket.

Dar acceso de soporte a un servicio

Soporte puede pedir, dentro de un ticket, acceso a uno de tus servicios. Tú decides:

curl

TerminalCode
curl -sS "$ZAROZ_API/support/tickets/$TICKET/access-requests" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/support/access-requests/$REQUEST/approve" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/support/access-requests/$REQUEST/deny" -H "$AUTH" curl -sS "$ZAROZ_API/support/access-grants" -H "$AUTH" # quién está dentro ahora mismo curl -sS -X DELETE "$ZAROZ_API/support/access-requests/$REQUEST" -H "$AUTH" # retirar una aprobación

TypeScript

TypeScriptCode
const grants = await call("GET", "/support/access-grants"); for (const g of grants.body) console.log(g.service_instance_id, g.expires_at, g.state); await call("DELETE", `/support/access-requests/${grants.body[0].id}`); // retirar en cualquier momento

Python

Code
_, grants = call("GET", "/support/access-grants") for g in grants: print(g["service_instance_id"], g["expires_at"], g["state"]) call("DELETE", f"/support/access-requests/{grants[0]['id']}") # retirar en cualquier momento

Una concesión aprobada es para el único servicio que nombra la petición y nada más de lo que tienes. Empieza ahora y termina sola: tras max_duration_hours, en el momento en que se cierra el ticket o cuando la retiras. GET /support/access-grants responde solo las concesiones vivas, así que es la comprobación de "¿hay alguien dentro de mi infraestructura ahora mismo?".

Last modified on