Zum Hauptinhalt springen

Ein Dashboard einbetten

Einbetten ist der Punkt, an dem ein Dashboard aufhört, etwas zu sein, das Ihr Team öffnet, und Teil eines Produkts wird, das Ihre Kunden nutzen. Vibedasher bietet zwei Wege dafür, und der Unterschied ist nicht kosmetisch — er entscheidet, wer die Daten lesen kann.

Öffentliche SpurSpur mit signiertem Token
Dashboard-view_modepublicprotected
Wer sehen kannjeder mit der URLnur ein Betrachter, für den Ihr Backend ein Token ausgestellt hat
Filterung pro Betrachter auf Zeilenebeneneinja, serverseitig durchgesetzt
Was Sie einbetteneine URLeine URL plus ein kurzlebiges Token

Ein private-Dashboard ist über keine der beiden Spuren einbettbar. Beide Spuren erfordern, dass das Dashboard zuerst veröffentlicht ist — siehe Veröffentlichen und teilen.

Einbetten ist eine von zwei Antworten

Einbetten legt unser gerendertes Dashboard in ein Rechteck auf Ihrer Seite. Die andere Antwort ist Eject — Sie nehmen den React- und SQL-Quellcode und rendern ihn selbst, nativ, mit uns außerhalb des Render-Pfads und nur der Daten-Engine darunter. Welche der beiden Sie wollen, entscheiden vor allem zwei Fragen: Müssen verschiedene Betrachter verschiedene Zeilen sehen (Einbetten ist die einzige Spur, die das kann), und muss das Dashboard aussehen und sich verhalten wie der Rest Ihrer App (nur Auswerfen bringt Ihnen das). Die Abwägungen stehen nebeneinander in Einbetten oder auswerfen.

Die öffentliche Spur​

Wenn das Dashboard mit view_mode: public veröffentlicht ist, ist ein Iframe die gesamte Integration:

<iframe
src="https://cloud.vibedasher.com/v/<slug>"
width="100%"
height="800"
style="border: 0;"
title="Vibedasher dashboard"
sandbox="allow-scripts allow-same-origin allow-downloads allow-popups"
allowfullscreen
loading="lazy"
></iframe>

allow-same-origin wirkt neben allow-scripts alarmierend, ist hier aber gerade deshalb sicher, weil der Frame cross-origin zu Ihrer Seite ist: Es gewährt dem Renderer seine eigene Origin, nicht Ihre. allow-top-navigation fehlt bewusst, sodass der Frame Ihre Seite nicht wegnavigieren kann. allow-downloads ist das, was den PNG-Export von Diagrammen ermöglicht.

Das Snippet funktioniert von jeder Seite, die Sie kontrollieren — auf unserer Seite muss zuvor nichts konfiguriert werden. Die Zielgruppe, unter der Sie veröffentlichen, ist hier die entscheidende Kontrolle, und Die Grenze des Rahmens legt diese Wahl dar.

Der Freigabedialog in der App liefert Ihnen dieses Snippet vorausgefüllt. Holen Sie es von dort, statt die URL selbst zusammenzubauen.

Die Spur mit signiertem Token​

Das ist die, die es sich zu verstehen lohnt, weil sie der einzige Ort ist, an dem Vibedasher zeilenbasierte Sicherheit umsetzt.

Ihr Backend stellt das Token aus — Sie signieren nichts selbst​

Vibedasher signiert das Token selbst, ES256, mit seinem eigenen rotierenden Schlüssel. Ihr Backend fordert eines mit seinem API-Schlüssel an:

from vibedasher.models import EmbedTokenRequest, EmbedTokenRequestFilters

resp = client.embed_tokens.create_embed(
EmbedTokenRequest(
viz_id=1234,
filters=EmbedTokenRequestFilters.from_dict({"region": "EMEA"}),
ttl_seconds=600,
)
)
print(resp.token, resp.expires_in)
const resp = await client.embedTokens.createEmbed({
vizId: 1234,
filters: { region: 'EMEA' },
ttlSeconds: 600,
});
Es gibt kein „Embed-Secret“

Sie halten kein Signaturmaterial und brauchen keines — stellen Sie Tokens wie hier gezeigt aus.

Oder mit reinem HTTP, aus jeder Sprache:

curl -X POST https://api.vibedasher.com/api/v1/embed/token \
-H "X-Api-Key: $VIBEDASHER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vizId": 1234, "ttlSeconds": 600, "filters": {"region": "EMEA"}}'
# → {"token": "<ES256 JWT>", "token_type": "embed", "expires_in": 600}

Der Anfragekörper nimmt:

FeldErforderlichHinweise
vizIdjaGenau ein Dashboard. Ein Token kann keinen Ordner oder ein Konto abdecken.
filtersneinDie Zeilenfilter. Siehe unten.
viewerIdneinEine undurchsichtige Sitz-ID. Nur für Audit und Messung — kein Eingabewert für die Zugriffskontrolle. Verlassen Sie sich nicht darauf, um irgendetwas einzuschränken.
ttlSecondsneinStandard 600. Untergrenze 60, harte Obergrenze 86 400 (24 h). Werte außerhalb des Bereichs werden beim Ausstellen begrenzt und bei der Verifizierung erneut geprüft. Die Verifizierung erlaubt einen kleinen Zeitversatz-Spielraum über exp hinaus, begrenzt auf 120 s und auf die Hälfte der Lebensdauer des Tokens selbst — die tatsächlich durchgesetzte Lebensdauer beträgt damit nie mehr als das 1,5-Fache von expires_in, und ab einer TTL von 240 s ist der Spielraum die feste Obergrenze von 120 s.
apiKeyIdneinNur nötig, wenn aus einer Browser-Sitzung heraus ausgestellt wird, um das Token an die Domain-Zulassungsliste und den Widerruf eines Schlüssels zu binden.

Die account_id wird aus Ihrem Zugangsdatum entnommen und nie aus dem Körper gelesen.

Das Ausstellen schlägt mit einer Meldung fehl, die die gescheiterte Prüfung benennt — Sie stellen ein Token aus, Sie präsentieren keines, also sagt Ihnen der Fehler, was am Dashboard oder am Schlüssel zu ändern ist:

StatusDetailAbhilfe
403die Visualisierung gehört einem anderen Kontomit einem API-Schlüssel des besitzenden Kontos ausstellen
403die Visualisierung kann nicht per Token eingebettet werden: ihre Sichtbarkeit muss protected seindie Sichtbarkeit des Dashboards im Freigabedialog auf Geschützt stellen
404die Visualisierung ist nicht veröffentlichtzuerst veröffentlichen
403kein verwendbarer API-Schlüssel, an den das Token gebunden werden kannder Schlüssel fehlt, ist inaktiv, abgelaufen oder gehört einem anderen Konto
404Viz nicht gefundenvizId prüfen

Ein public-Dashboard wird von dieser Spur abgelehnt — es hat bereits die anonyme Spur, und beide zu mischen wäre ein Weg, Zeilenfilter zu vermuten, wo keine sind.

Zeilenfilter​

filters ist eine Zuordnung von Spaltenname zu einem Skalar oder einer Liste. Ein Skalar wird zu =, eine Liste zu IN, und mehrere Schlüssel werden mit UND verknüpft. Die Schlüssel sind der sqlName der Spalte, wie ihn GET /v1/datasets/{id} meldet — der Name, den die SQL-Sicht offenlegt, nicht der Anzeigename.

Die Plattform hängt keine WHERE-Klausel an Ihre Abfrage an; sie schreibt jede Datensatz-Referenz in eine gefilterte Relation um und behält den ursprünglichen Alias:

FROM orders
-> FROM (SELECT * FROM "orders" WHERE "tenant" = 'acme') AS orders

Das macht den Geltungsbereich nicht erweiterbar und nicht bloß vorhanden. Was die Seite anfordert, greift außerhalb der gefilterten Relation, kann sie also nur weiter einengen — WHERE tenant = 'globex' OR 1=1 sieht weiterhin genau einen Mandanten, eine umschließende Unterabfrage umschließt die gefilterte Relation, beide Zweige eines UNION werden umgeschrieben, und eine CTE, die den Alias überdeckt, eine schemaqualifizierte Referenz sowie jede dateilesende Funktion werden rundweg abgelehnt.

Drei Eigenschaften machen das prüfbar:

  • Die Filter stammen ausschließlich aus dem verifizierten Token. Sie werden nie aus dem Request-Body gelesen, also kann ein Betrachter, der im Netzwerk-Tab stöbert, seinen eigenen Geltungsbereich nicht erweitern.
  • Ein Filterschlüssel, der keine Spalte der Datensätze des Dashboards benennt, wird bei der Ausstellung abgelehnt — 403, unter Nennung des Schlüssels und Auflistung der tatsächlich vorhandenen Spaltennamen. Es wird kein Token signiert. Dasselbe gilt für einen Wert, der sich nicht in den Typ seiner Spalte überführen lässt. Ein Filter, der stillschweigend auf alles passt, wäre ein Datenleck im Gewand einer Sicherheitskontrolle — er wird abgelehnt, bevor er zu einem Link werden kann, den Sie einem Kunden aushändigen.
  • Die Ausstellung sagt Ihnen, was das Token erlaubt, im Feld scope ihrer Antwort. Lesen Sie es; nehmen Sie es nicht an.

Lesen Sie scope.fullyScoped — es heißt nicht „die Filter wurden akzeptiert"​

Ein Dashboard kann mehr als einen Datensatz lesen, und ein Filter kann nur auf einen Datensatz wirken, der diese Spalte besitzt — man kann nicht nach einer Spalte filtern, die eine Tabelle nicht hat. Ein Dashboard über orders und price_list mit dem Geltungsbereich {"tenant": "acme"} filtert also orders und liest price_list vollständig. Die Seite lädt, jede Kachel zeichnet, jede Zahl ist plausibel, und an der Anfrage wirkt nichts falsch.

Das ist richtig und beabsichtigt, wenn der zweite Datensatz eine gemeinsam genutzte Nachschlagetabelle ist — eine Preisliste, ein Tarifkatalog, ein Satz Bezeichnungen. Es ist nicht das, was Sie wollen, wenn der zweite Datensatz kundenbezogene Zeilen unter einer anders benannten Spalte führt (tenant_id statt tenant, customer, org). In der Antwort der Ausstellung erfahren Sie, in welchem der beiden Fälle Sie sind:

{
"token": "<ES256 JWT>", "token_type": "embed", "expires_in": 600,
"scope": {
"vizId": 1234,
"filtered": [{"datasetId": 7, "datasetName": "orders",
"predicate": "\"tenant\" = 'acme'", "columns": ["tenant"]}],
"unfiltered": [{"datasetId": 8, "datasetName": "price_list",
"predicate": null, "columns": []}],
"unresolvedDatasetIds": [],
"fullyScoped": false,
"meaning": "PARTIALLY SCOPED — read this before handing the link out. ..."
}
}
FeldWas damit zu tun ist
fullyScopedDas Feld, auf das man prüft. true nur, wenn jeder Datensatz, den das Dashboard liest, durch diese Filter eingegrenzt ist. Alles andere ist false.
filtered[].predicateDas exakte SQL, das auf diesen Datensatz angewendet wird. Das ist der Satz, den man einem Sicherheitsprüfer vorlegt.
unfiltered[]Datensätze, die dieses Token vollständig liest. Entscheiden Sie je Datensatz, ob das eine gemeinsame Nachschlagetabelle ist oder eine Lücke.
unresolvedDatasetIdsDatensätze, deren Schema nicht gelesen werden konnte. Nicht dasselbe wie ungefiltert: „liest alles" und „wir wissen nicht, was dies liest" sind verschiedene Antworten, und keine wird in die andere überführt.

Eine vernünftige Produktionsprüfung in Ihrer Ausstellungs-Route ist, das Token nicht zurückzugeben, wenn scope.fullyScoped false ist und Sie true erwartet haben — Sie wissen, welche Ihrer Dashboards kundenbezogen sein sollen, die Plattform nicht.

Das Token präsentieren​

Der Frame kann keinen Header setzen, also reist das Token in der URL mit:

<iframe
src="https://cloud.vibedasher.com/v/<slug>?embed_token=<token>"
sandbox="allow-scripts allow-same-origin allow-downloads allow-popups"
allowfullscreen
></iframe>

Embed-Antworten werden mit Referrer-Policy: no-referrer gesendet, damit das Token nicht über einen Referer-Header bei Unteranfragen durchsickert. Es bleibt dennoch ein Bearer-Zugangsdatum in einer URL, was der eigentliche Grund ist, warum die Standard-TTL zehn Minuten beträgt: pro Seitenaufruf ausstellen, kurz halten und aktualisieren, statt die TTL zu erhöhen.

Ihre Domain-Zulassungsliste einrichten​

Ein API-Schlüssel kann eine Liste von Domains tragen, von denen aus seine Embed-Tokens respektiert werden. Eine leere Liste bedeutet Alles-erlaubt. Das ist eine bewusste Entscheidung, damit die Funktion in dem Moment funktioniert, in dem Sie sie ausprobieren, und es bedeutet, dass ein nicht konfigurierter Schlüssel auf Origin-Ebene nichts schützt. Setzen Sie allowed_domains auf dem Schlüssel, bevor Sie in Produktion gehen.

Die Token-Aktualisierungsschleife​

Ein Zehn-Minuten-Token auf einem Dashboard, das jemand den ganzen Nachmittag offen lässt, muss aktualisiert werden, und der Renderer sagt Ihnen, wann:

  1. Der Frame sendet EMBED_TOKEN_EXPIRED (oder EMBED_TOKEN_INVALID) an Ihre Seite.
  2. Ihre Seite fragt Ihr Backend nach einem frischen Token.
  3. Ihre Seite sendet UPDATE_TOKEN in den Frame: { type: 'UPDATE_TOKEN', token: '<new>' }.
window.addEventListener('message', async (e) => {
if (e.origin !== 'https://cloud.vibedasher.com') return;
if (e.data?.type === 'EMBED_TOKEN_EXPIRED' || e.data?.type === 'EMBED_TOKEN_INVALID') {
const token = await fetch('/api/vibedasher-embed-token').then((r) => r.text());
frame.contentWindow.postMessage({ type: 'UPDATE_TOKEN', token }, 'https://cloud.vibedasher.com');
}
});

Prüfen Sie immer e.origin. UPDATE_TOKEN ist die einzige Nachricht, die ein Drittanbieter-Host nach innen senden darf — alles andere wird ignoriert.

Was Ihre Seite tatsächlich empfängt​

Die Nachrichten, die ein Drittanbieter-Host erhält, sind eine feste Zulassungsliste von dreizehn. Das ist die vollständige Liste:

NachrichtWann
READYDer Renderer ist gestartet.
LOADEDDas Dashboard hat das Laden abgeschlossen.
RESIZEDie Inhaltshöhe hat sich geändert — verwenden Sie es, um das Iframe zu dimensionieren.
BUNDLE_STARTED / BUNDLE_COMPLETED / BUNDLE_FAILED / BUNDLE_RETRYBuild-Lebenszyklus des Dashboard-Codes.
RUNTIME_ERRORDer Dashboard-Code hat einen Fehler geworfen.
EMPTY_DATAEine Abfrage hat keine Zeilen zurückgegeben.
RENDER_HEALTHRender-Gesundheit pro Kachel.
EMBED_TOKEN_EXPIRED / EMBED_TOKEN_INVALID / EMBED_UNAVAILABLEToken-Lebenszyklus — siehe oben.

Alles andere, das Sie referenziert sehen, ist intern. Insbesondere wird eine Reihe von Nachrichten explizit nie an einen Drittanbieter-Host weitergeleitet — VIZ_DATA, FILES, UPDATE_TOKEN ausgehend, SQL_ERROR, CONSOLE_ERROR, REQUEST_NEW_AUTH_TOKEN, ELEMENT_SELECTED, FILTER_STATE_CHANGED — weil sie entweder den Quellcode Ihres Dashboards oder dessen rohen Abfragezustand tragen.

Die Grenze des Rahmens​

Zwei Eigenschaften entscheiden, worauf eine Integration aufbauen kann, und beide sollten Sie lesen, bevor Sie um den Rahmen herum entwerfen, nicht danach.

Der Rahmen besitzt seinen eigenen Filterzustand. Die Nachrichten, die ein Drittanbieter-Host empfängt, sind die oben aufgeführten; der Filterzustand des Dashboards gehört in keiner Richtung dazu. Der Rahmen ist eine in sich geschlossene Ansicht: Steuern Sie ihn über die URL und das Token, das Sie ausstellen, und lassen Sie ihn besitzen, was innen passiert. Wenn Ihre Oberfläche eine Auswahl mit dem Dashboard teilen muss, legen Sie diese Auswahl in das Filterprädikat des Tokens, das die Plattform jeder von ihr autorisierten Abfrage anhängt — serverseitig und mit derselben Antwort für jede Kachel.

Wählen Sie die Zielgruppe mit dem Framing im Blick. Ein als public veröffentlichtes Dashboard ist eine öffentliche URL, mit allem, was daraus folgt; die Spur mit signiertem Token ist die richtige, wenn es darauf ankommt, wer schaut und von wo, und die Domain-Erlaubnisliste eines API-Schlüssels bindet seine Antworten an Ihren Ursprung.

Die Abfragespur mit signiertem Token läuft in eu-central-1. Dort lebt die Abfrage-Laufzeit für geschützte Embeds. Liegen die Daten Ihres Kontos in einer anderen Region, sprechen Sie mit uns, bevor Sie auf dieser Spur aufbauen.

Nächste Schritte​

  • Wer sieht was — wo zeilenbasierte Filterung in das breitere Berechtigungsmodell passt, und warum sie nur hier existiert.
  • Veröffentlichen und teilen — ein Dashboard auf published bringen und seinen view_mode wählen.
  • API Reference — die Operation embed-tokens in voller Länge.