Sprucely title background

연동

인터랙티브한 Sprucely.io 대시보드를 자체 웹사이트나 웹 애플리케이션에 연동하는 방법은 세 가지입니다: 표준 HTML iframe으로 임베드하거나, 독립형 JavaScript 런타임으로 네이티브 렌더링하거나, 독립형 React 컴포넌트를 마운트하는 방식입니다. iframe은 설정이 가장 빠르며, 독립형 런타임은 대시보드를 페이지에 직접 그려주고 런타임에 새 데이터를 밀어 넣을 수 있게 해줍니다. 독립형 런타임이 읽어들이는 JSON 구조 - 대시보드, 차트, 데이터셋 - 는 데이터 형식 레퍼런스에 문서화되어 있습니다. AI 기반 대시보드 자동화에 대해서는 MCP API 섹션을 참고하십시오.

HTML로 대시보드 임베드하기

가장 간단한 연동 방식은 표준 HTML iframe 요소를 사용하는 것으로, 어떤 웹사이트에서도, 심지어 로컬 HTML 페이지에서도 동작합니다. 대시보드가 정상적으로 로드되려면 클라우드 또는 온프레미스로 공유되어 있어야 합니다. 온프레미스 대시보드는 접근하는 클라이언트가 동일한 사내 네트워크 안에 있을 때만 로드된다는 점에 유의하십시오.

설정 방법:

1) 대시보드 임베드 링크 추출하기 - 대시보드 페이지에서 대시보드가 공유되어 있는지 확인한 다음, 해당 대시보드의 아이콘을 클릭하십시오. 링크가 클립보드에 복사되었다는 초록색 팝업 배너가 표시됩니다. 이 링크를 아래 HTML iframe의 src 내용을 대체하는 데 사용합니다.

2) 고정 크기로 대시보드 임베드하기

<html>
  <head>
    <title>Sprucely.io Dashboard</title>
  </head>
  <body>
    <h1>Sprucely.io Dashboard</h1>
    <iframe src="https://www.sprucely.io/service/dashboards/embed/[userId]/[dashboardId]/"
            allow="clipboard-write"
            style="height: 500px; width: 700px;"
            title="Sprucely.io Dashboard"></iframe>
  </body>
</html>

(또는) 동적 너비로 대시보드 임베드하기 - 부모 문서의 너비를 기준으로 대시보드 크기가 자동으로 조정됩니다

aspect-ratio 스타일 파라미터를 사용하면 너비를 기준으로 높이를 자동으로 재계산할 수 있습니다.

<html>
  <head>
    <title>Sprucely.io Dashboard</title>
  </head>
  <body>
    <h1>Sprucely.io Dashboard</h1>
    <iframe src="https://www.sprucely.io/service/dashboards/embed/[userId]/[dashboardId]/"
            allow="clipboard-write"
            scrolling="no"
            style="width: 100%; aspect-ratio: 1.5; border: none;"
            title="Sprucely.io Dashboard"></iframe>
  </body>
</html>

3) 기타 유용한 임베드 옵션

iframe 요소의 style 블록에 사용자 지정 스타일 수정을 추가하여 프레임의 모양을 커스터마이즈할 수 있습니다. 이러한 파라미터는 표준 HTML CSS 가이드라인을 따릅니다. 아래 예시는 대시보드에 회색 테두리를 추가합니다:

<html>
  <head>
    <title>Sprucely.io Dashboard</title>
  </head>
  <body>
    <h1>Sprucely.io Dashboard</h1>
    <iframe src="https://www.sprucely.io/service/dashboards/embed/[userId]/[dashboardId]/"
            allow="clipboard-write"
            style="height: 100%; width: 100%; border: 2px solid grey;"
            title="Sprucely.io Dashboard"></iframe>
  </body>
</html>

독립형 JavaScript로 임베드하기

독립형 런타임은 iframe 없이 대시보드를 페이지 안에 직접 렌더링합니다. 데이터는 브라우저 내 데이터베이스에 로드되어 완전한 인터랙티브 차트로 그려지며, 런타임은 액세스 토큰을 검증하기 위해서만 Sprucely.io와 통신합니다.

설정 방법:

1) 액세스 토큰 생성하기 - 프로필의 액세스 토큰 섹션에서, 페이지가 제공되는 origin에 대한 액세스 토큰을 생성하십시오(예: https://www.yourdomain.com). 런타임은 렌더링 전에 임베드하는 페이지의 origin이 토큰과 일치하는지 검증합니다.

2) 런타임을 로드하고 대시보드 렌더링하기 - 페이지에 런타임 스크립트를 추가하고, sprucely_db로 브라우저 내 데이터베이스에 한 번 연결한 다음, sprucely_create로 각 대시보드를 렌더링하십시오. 아래의 전체 페이지 예시에는 행을 추가하는 버튼도 연결되어 있습니다:

<html>
  <head>
    <title>Orders Dashboard</title>
  </head>
  <body>
    <div id="database"></div>
    <div id="orders_dashboard"></div>
    <button onclick="addRows()">Add rows</button>

    <script src="https://www.sprucely.io/cross-origin/sprucely-runtime.min.js"></script>
    <script>
      const data = {
        name:    "Orders",
        headers: ["region", "amount", "items"],
        types:   ["VARCHAR", "FLOAT", "INTEGER"],
        entries: [
          ["North", 120.50, 2],
          ["South",  89.95, 1],
          ["North", 432.00, 5],
          ["South",  74.50, 1]
        ]
      };

      const dashboard = {
        config: {
          type: "dashboard",
          style: {
            widget: {
              color: "#667FFF",
              backgroundColor: "#0C0B29",
              primaryColor: "#01EEAE",
              primaryColorSubtle: "#C70584",
              secondaryColor: "#2C2B49"
            }
          }
        },
        data: {
          type: "dash",
          children: [
            {
              type: "dash_stacker_hor",
              children: [
                { type: "dash_chart", datasetId: "Orders", x: "region", chartType: "bar", dataFunction: "count" },
                { type: "dash_chart", datasetId: "Orders", x: "amount", y: "items", chartType: "hexbin", dataFunction: "count" }
              ]
            }
          ]
        }
      };

      sprucely_db({
        id: "database",
        host: "https://www.yourdomain.com",
        accessToken: "YOUR_ACCESS_TOKEN"
      });
      sprucely_create({ id: "orders_dashboard", dashboard, data });

      function addRows() {
        sprucely_add({
          name:    "Orders",
          headers: ["region", "amount", "items"],
          types:   ["VARCHAR", "FLOAT", "INTEGER"],
          entries: [
            ["East", 310.40, 3],
            ["West", 129.95, 1]
          ]
        });
      }
    </script>
  </body>
</html>

3) 런타임에 데이터 추가하기 - 언제든지 sprucely_add를 호출하여 추가 행을 전달할 수 있습니다. 해당 데이터셋을 사용하는 모든 대시보드가 자동으로 새로고침됩니다:

sprucely_add({
  name:    "Orders",
  headers: ["region", "amount", "items"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["East", 310.40, 3],
    ["West", 129.95, 1]
  ]
});

함수 레퍼런스

  • sprucely_db({ id, host, accessToken }) - 브라우저 내 데이터베이스에 연결하고 페이지 origin을 기준으로 액세스 토큰을 검증합니다. id로 지정된 요소에 상태 배너를 렌더링합니다. 대시보드를 생성하기 전에 페이지당 한 번 호출하십시오.
  • sprucely_create({ id, dashboard, data }) - id로 지정된 요소에 대시보드 하나를 렌더링합니다. dashboard 파라미터는 레이아웃과 스타일을 정의하며, data는 이름, 헤더, 컬럼 타입(VARCHAR, INTEGER, FLOAT 또는 TIMESTAMP)과 행으로 구성된 데이터셋을 제공합니다 - 자세한 내용은 데이터 형식 레퍼런스를 참고하십시오.
  • sprucely_add(data) - 이름이 이미 로드된 데이터셋과 일치하는 데이터셋에 행을 추가한 다음, 이를 사용하는 모든 대시보드를 새로고침합니다.

독립형 React로 임베드하기

사이트가 React로 구축되어 있다면, 스크립트를 수동으로 로드하는 대신 대시보드를 컴포넌트로 렌더링할 수 있습니다. 이 컴포넌트들은 독립형 JavaScript 런타임과 동일한 액세스 토큰, 대시보드 정의, 데이터셋 형식을 사용합니다.

설정 방법:

1) 액세스 토큰 생성하기 - 독립형 JavaScript와 마찬가지로, 프로필의 액세스 토큰 섹션에서 사이트의 origin에 대한 액세스 토큰을 생성하십시오.

2) 대시보드 컴포넌트 렌더링하기 - https://www.sprucely.io/cross-origin/sprucely-runtime.min.cjs.js에서 런타임을 받아 프로젝트에 배치하십시오. 페이지당 Sprucely.Database 컴포넌트 하나를 마운트하고, 대시보드당 Sprucely.Dashboard 컴포넌트 하나를 마운트합니다. 대시보드는 데이터베이스가 액세스 토큰을 검증할 때까지 로딩 표시기를 보여준 다음 렌더링됩니다. 아래의 전체 애플리케이션 예시는 서로 다른 데이터셋으로부터 두 개의 대시보드를 렌더링하고, 5초 후 첫 번째 대시보드에 새 행을 추가합니다:

// Get https://www.sprucely.io/cross-origin/sprucely-runtime.min.cjs.js
// and place it in your project, next to this file.

import React, { useEffect } from "react";
import { createRoot } from "react-dom/client";
import Sprucely, { sprucely_add } from "./sprucely-runtime.min.cjs.js";

const style = {
  widget: {
    color: "#667FFF",
    backgroundColor: "#0C0B29",
    primaryColor: "#01EEAE",
    primaryColorSubtle: "#C70584",
    secondaryColor: "#2C2B49"
  }
};

const orders = {
  name:    "Orders",
  headers: ["region", "amount", "items"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["North", 120.50, 2],
    ["South",  89.95, 1],
    ["North", 432.00, 5],
    ["South",  74.50, 1]
  ]
};

const returns = {
  name:    "Returns",
  headers: ["reason", "amount", "days"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["Damaged", 45.00, 3],
    ["Late",    12.50, 8],
    ["Changed", 99.90, 2]
  ]
};

const moreOrders = {
  name:    "Orders",
  headers: ["region", "amount", "items"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["East", 310.40, 3],
    ["West", 129.95, 1]
  ]
};

const ordersDashboard = {
  config: { type: "dashboard", style },
  data: {
    type: "dash",
    children: [
      {
        type: "dash_stacker_hor",
        children: [
          { type: "dash_chart", datasetId: "Orders", x: "region", chartType: "bar", dataFunction: "count" },
          { type: "dash_chart", datasetId: "Orders", x: "amount", y: "items", chartType: "hexbin", dataFunction: "count" }
        ]
      }
    ]
  }
};

const returnsDashboard = {
  config: { type: "dashboard", style },
  data: {
    type: "dash",
    children: [
      {
        type: "dash_stacker_hor",
        children: [
          { type: "dash_chart", datasetId: "Returns", x: "amount", y: "days", d: "amount", r: "days", chartType: "dot", dataFunction: "average" }
        ]
      }
    ]
  }
};

const App = () => {
  useEffect(() => {
    const timer = setTimeout(() => { sprucely_add(moreOrders); }, 5000);
    return () => clearTimeout(timer);
  }, []);

  return (
    <>
      <Sprucely.Database host="https://www.yourdomain.com" accessToken="YOUR_ACCESS_TOKEN" />
      <Sprucely.Dashboard id="orders_dashboard" dashboard={ordersDashboard} data={orders} />
      <Sprucely.Dashboard id="returns_dashboard" dashboard={returnsDashboard} data={returns} />
    </>
  );
};

createRoot(document.getElementById("root")).render(<App />);

단기 키 제공하기

위에서 생성한 액세스 토큰은 장기간 유효하며, 서버 측에서만 보관해야 하는 비밀 값입니다. 자체 백엔드가 없는 페이지가 아니라 자체 백엔드가 임베드 페이지를 직접 제공하는 경우라면, 이 장기 유효 토큰을 페이지에 전혀 넣을 필요가 없습니다 - 백엔드가 각 페이지를 제공하기 직전에 서버 간 통신으로 이 토큰을 단기(15분) 렌더 토큰으로 교환하면, 브라우저에는 이 렌더 토큰만 전달됩니다. 방문자가 페이지에서 추출한 렌더 토큰은 단 몇 분 동안만 유효할 뿐, 무기한 유효하지 않습니다.

설정 방법:

1) 액세스 토큰을 렌더 토큰으로 교환하기 - 백엔드에서 액세스 토큰을 베어러 자격 증명으로 사용하여 POST https://www.sprucely.io/api/auth/render_token을 호출하십시오. 응답에는 새 토큰, 연결된 호스트, 초 단위 유효 기간이 담겨 있습니다:

// On your server, immediately before serving each embedding page:
const response = await fetch("https://www.sprucely.io/api/auth/render_token", {
  method:  "POST",
  headers: { Authorization: "Bearer " + process.env.SPRUCELY_ACCESS_TOKEN }
});
const { token, expires_in } = await response.json(); // expires_in: 900 (15 minutes)

// Send only the render token to the browser - never the long-lived access
// token itself:
res.send(`
  <script src="https://www.sprucely.io/cross-origin/sprucely-runtime.min.js"></script>
  <script>
    sprucely_db({
      id: "database",
      host: "https://www.yourdomain.com",
      accessToken: "${token}"
    });
  </script>
`);

2) 렌더 토큰을 브라우저에 제공하기 - sprucely_db를 호출하거나 Sprucely.Database를 마운트할 때 액세스 토큰과 완전히 동일한 방식으로 사용하십시오. 각 페이지를 제공하기 전에(또는 렌더링된 페이지를 캐싱하는 경우 타이머를 기준으로) 이 교환을 반복하여, 브라우저가 15분보다 오래 유효한 토큰을 절대 받지 않도록 하십시오.

함수 레퍼런스

  • POST /api/auth/render_token - Authorization: Bearer <token> 형식으로 전송된 유효한 액세스 토큰을 렌더 토큰으로 교환합니다. { token, host, expires_in }을 반환하며, expires_in은 항상 900초로 고정되어 있습니다. 렌더 토큰은 다른 렌더 토큰으로 교환할 수 없습니다 - 장기 유효 액세스 토큰만 렌더 토큰을 요청할 수 있습니다.

보안 설정

사이트에서 콘텐츠 보안 정책(CSP)을 적용하고 있다면, 위의 독립형 JavaScript 및 React 연동이 정상적으로 대시보드를 로드하기 전에 몇 가지 소스를 허용해야 합니다 - 런타임은 Sprucely.io에서 스크립트를 로드하고, WebAssembly 기반의 브라우저 내 데이터베이스를 열며, 저희 API를 대상으로 액세스 토큰을 검증하는데, 이 각각에 대해 명시적인 CSP 허용이 필요합니다. 사이트에서 CSP를 사용하지 않는다면 이 섹션은 건너뛰어도 됩니다.

기존 정책에 다음 소스를 추가하십시오 - 이는 기존 정책에 병합할 디렉티브이며, 기존 정책 전체를 대체하는 것이 아닙니다:

script-src   'self' https://www.sprucely.io 'wasm-unsafe-eval';
connect-src  'self' https://www.sprucely.io;
worker-src   'self' blob: https://www.sprucely.io;

각 디렉티브의 용도:

  • script-src - https://www.sprucely.io는 런타임 스크립트를 로드합니다. 'wasm-unsafe-eval'은 WebAssembly로 빌드된 브라우저 내 데이터베이스를 컴파일하고 실행하는 데 필요합니다.
  • connect-src - 액세스 토큰을 검증하고 데이터베이스의 WebAssembly 및 워커 파일을 로드하기 위해 https://www.sprucely.io와 통신합니다.
  • worker-src - 브라우저 내 데이터베이스는 blob: URL로 생성된 백그라운드 스레드에서 쿼리를 실행합니다.

위 항목들은 모두 'unsafe-inline'를 필요로 하지 않습니다 - Sprucely 런타임 자체는 이를 전혀 필요로 하지 않습니다. 위의 독립형 JavaScript 예시처럼 자체 대시보드 및 데이터셋 정의를 인라인 <script> 태그에 두거나, 자체 페이지에서 인라인 스타일을 사용하는 경우에는 'unsafe-inline'script-src 또는 style-src에 추가하거나 CSP nonce 사용을 고려하십시오.