mcpField
C
custom-visualization-builder

custom-visualization-builder

github.com/splunk开发63163 次安装7 周前更新
代码仓库

关于 custom-visualization-builder

用于构建 Splunk 自定义仪表板可视化的技能,面向开发者,支持 JavaScript 或 React,并可将结果打包为 Splunk 应用。

主要功能

  1. 01创建可视化项目骨架
  2. 02使用 JavaScript 或 React 开发
  3. 03接收 Splunk 搜索数据
  4. 04构建并打包为 .spl
  5. 05迁移旧版 RequireJS 可视化

适用场景

  1. 01为 Dashboard Studio 新建自定义图表
  2. 02向现有扩展项目添加可视化
  3. 03将可视化打包为 Splunk 应用
  4. 04迁移旧版 RequireJS 可视化

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/splunk/splunk-agent-skills --skill custom-visualization-builder

安装后在 Claude Code 中输入/custom-visualization-builder确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
references/
api-reference.md
canvas.md
config-json.md
drilldown-and-tokens.md
migration.md
troubleshooting.md

详细介绍

自定义可视化构建器

使用 @splunk/dashboard-studio-extension 框架搭建、构建、打包并安装自定义 Splunk 仪表板可视化。可视化在沙盒化 iframe 中运行,并接收 Splunk 搜索的数据。你可以使用纯 JavaScript 或 React 构建,具体取决于可视化的需求。

开发时不需要本地 Splunk 实例。在本地构建并打包,然后将 .spl 安装到受支持的实例中。需要 Splunk Enterprise 10.4+ 或 Splunk Cloud Platform(同等版本)。

适用场景

当用户要求创建新的 Splunk 自定义可视化、向现有 dashboard-studio-extension 项目添加可视化、将可视化打包为 Splunk 应用,或将旧版 RequireJS 自定义可视化迁移到当前扩展框架时,请使用此技能。

除非用户明确确认目标环境,否则不要在生产环境或共享 Splunk 环境中安装或更新可视化。不要向用户索取原始凭据;使用已配置的本地 Splunk 身份验证,或让用户自行运行安装命令。


前置条件

  • Node.js >= 22
  • Yarn >= 1.22
  • 目标实例:Splunk Enterprise 10.4+ 或 Splunk Cloud Platform(同等版本)

选择框架

JavaScript(@splunk/dashboard-studio-extension)React(@splunk/dashboard-studio-extension/react)
适用场景Canvas 渲染、D3、纯 DOM、依赖项最少组件较多的 UI、基于 hooks 的状态管理、大多数新项目
API 风格事件侦听器 + getterHooks(useDataSources、useDimensions、useOptions、…)
入口文件visualization.jsvisualization.jsx
额外依赖无react、react-dom(peer 依赖,由脚手架添加)

项目结构

项目是一个 Splunk 应用,可在 visualizations/ 下包含一个或多个可视化。每个可视化都有自己的目录,其中包含 config.json、src/ 和 SPL.md。

my-viz-project/
├── package.json
├── build.mjs                    # esbuild orchestrator
├── build-plugins/
│   └── css-and-size.mjs         # CSS injection + asset inlining plugin
├── package.mjs                  # .spl packager
├── package/
│   └── app/
│       └── app.conf             # Splunk app identity (source of truth for id/version)
├── visualizations/
│   ├── <viz-one>/
│   │   ├── SPL.md               # SPL reference for this viz (you create this)
│   │   ├── config.json
│   │   └── src/
│   │       ├── visualization.js(x)
│   │       └── visualization.css
│   └── <viz-two>/
│       ├── SPL.md
│       ├── config.json
│       └── src/
│           ├── visualization.js(x)
│           └── visualization.css
└── dist/                        # build output (generated)

构建系统会自动发现 visualizations/ 下的所有目录,并构建其中的所有项目。项目文件夹应以可视化集合命名,而不是以单个可视化命名(例如,splunk-custom-vizzes,而不是 gauge)。


工作流概览

1. 收集需求

生成任何代码之前,先询问用户(或从上下文中提取)以下信息:

  1. 项目名称 — 用作 Splunk 应用 ID。只能包含字母、数字、点和下划线。不能以点或下划线开头或结尾。例如:my_custom_viz。
  2. 可视化 slug — visualizations/ 下的目录名。只能包含小写字母、数字和下划线([a-z0-9_])。不能包含点、斜杠或空格。拒绝任何会将文件放到 visualizations/<viz-slug>/ 之外的值。例如:network_graph。
  3. 可视化显示标签 — 在 Splunk UI 中显示的易读名称,例如“Network Graph”。
  4. 描述 — 用一行描述该可视化的功能。
  5. 作者 — 用于 app.conf。
  6. 预期 SPL 列 — 搜索必须生成哪些字段。区分必需字段和可选字段。
  7. 可配置设置 — 用户应能够调整的内容(颜色、阈值、开关)。对每项设置说明名称、类型和默认值。
  8. 渲染方式 — 要绘制或渲染的内容(Canvas 形状、图表、表格、地图等),用于确定框架选择。

除非用户的请求或上下文明确不适用,否则请采用无障碍设计与安全渲染中的默认设置。

如果用户的请求含糊不清,请在搭建项目前先询问。

2. 确定项目位置

如果用户尚未指定,请询问项目的创建位置。可以是任何空目录——不需要现有的 dashboard-enterprise 仓库。

向现有项目添加可视化:如果项目已有构建基础设施,则完全跳过第 3 步。只需创建 visualizations/<viz-name>/,并添加 config.json、src/visualization.js(x)、src/visualization.css 和 SPL.md。构建系统会自动发现所有可视化目录,无需注册。然后转到第 5 步。

3. 搭建项目脚手架

@splunk/create CLI 目前需要通过交互式提示输入 label、description、author 和 template——只能将项目名称作为位置参数传入。完整的非交互式支持计划后续推出。请让用户运行命令并回答其余提示:

“请在终端中运行此命令并按照提示操作,完成后告知我:”

mkdir <project-name> && cd <project-name>
npx @splunk/create@latest --mode=dashboard-studio-extension <app_id>

遇到提示时,请使用以下值:

  • 显示标签:<Human Readable Label>
  • 描述:<description>
  • 作者:<author>
  • 模板:JavaScript 或 React(根据上文选择的框架)

然后运行 yarn 安装依赖项。

请等待用户确认脚手架搭建完成后再继续。

搭建脚手架后,请验证 package/app/app.conf 中的应用身份信息是否正确。 脚手架生成的结构如下(值由你在提示中提供的答案填入):

[package]
id = <app_id>

[id]
version = 0.1.0
name = <app_id>

[ui]
is_visible = 1
label = <Human Readable Label>
show_in_nav = 0

[launcher]
author = <author>
description = <description>
version = 0.1.0

[install]
is_configured = 0
build = <@- buildNumber @>
check_for_updates = 0

[manifest]
category = Custom

应用 id:只能包含字母、数字、点和下划线。不能以点或下划线开头或结尾。

请原样保留 build = <@- buildNumber @>。 这是有意使用的模板标签—— 打包工具(yarn package)会在暂存应用时替换为实际的构建编号。 不要将其替换为固定值。

然后在 viz 目录中创建 SPL.md(visualizations/<viz-name>/SPL.md):

# <Display Label> — SPL Reference

## Expected Columns

| Column | Type | Required? | Description |
|--------|------|-----------|-------------|
| <col>  | <type> | Yes/Optional | <what it is> |

## Notes

- Key data assumptions, units, fallback behavior, etc.

## Full SPL

\`\`\`spl
<full working SPL query>
\`\`\`

## Time range

`rt-1m` to `rt` (or `-60m` to `now` for historical)
4. 创建可视化

visualizations/<viz-name>/config.json — 完整字段参考(包括 optionsSchema 和 editorConfig)请参阅 references/config-json.md:

{
  "showTitleAndDescription": true,
  "includeInToolbar": true,
  "includeInVizSwitcher": true,
  "showDrilldown": false,
  "canSetTokens": [],
  "hasEventHandlers": false,
  "config": {
    "name": "<Human Readable Name>",
    "description": "<description>",
    "category": "Custom",
    "icon": null,
    "dataContract": {
      "requiredDataSources": ["primary"],
      "optionalDataSources": []
    },
    "size": { "initialWidth": 600, "initialHeight": 500 },
    "optionsSchema": {},
    "editorConfig": []
  }
}

JavaScript — visualization.js:

数据始终以列式格式到达:{ fields: [{name}, ...], columns: [[col0val0, col0val1, ...], ...] }。所有值都是字符串——请显式解析数值。监听器方法会返回一个清理函数。

import { VisualizationAPI } from '@splunk/dashboard-studio-extension';
import './visualization.css';

const container = document.createElement('div');
container.className = 'viz-container';
document.body.appendChild(container);

const state = { data: null, loading: false, options: {}, width: 0, height: 0, theme: 'light' };

function render() {
    if (state.loading) { container.innerHTML = '<div class="loading">Loading...</div>'; return; }
    if (!state.data)   { container.innerHTML = '<div class="empty">No data</div>'; return; }

    const { fields, columns } = state.data;
    if (!columns?.length || !columns[0].length) {
        container.innerHTML = '<div class="empty">No data</div>';
        return;
    }

    // Always provide fallbacks — options may be unset even if optionsSchema declares defaults
    const myOption = state.options.myOption ?? 'default';
    const isDark = state.theme === 'dark';

    // implement visualization here
}

VisualizationAPI.addDataSourcesListener(
    ({ dataSources, loading }) => {
        state.loading = loading;
        state.data = dataSources?.primary?.data ?? null;
        render();
    },
    { invokeImmediately: true }
);

VisualizationAPI.addOptionsListener(({ options }) => {
    state.options = options;
    render();
});

VisualizationAPI.addDimensionsListener(({ width, height }) => {
    state.width = width;
    state.height = height;
    render();
}, { invokeImmediately: true });

VisualizationAPI.addThemeListener(({ theme }) => {
    state.theme = theme;
    render();
}, { invokeImmediately: true });

React — visualization.jsx:

直接在组件中调用这些钩子。

import {
    useDataSources,
    useDimensions,
    useOptions,
    useTheme,
} from '@splunk/dashboard-studio-extension/react';
import { createRoot } from 'react-dom/client';
import './visualization.css';

function Viz() {
    const { dataSources, loading } = useDataSources();
    const { width, height } = useDimensions();
    const { options } = useOptions();
    const { theme } = useTheme();

    // Always provide fallbacks — options may be unset even if optionsSchema declares defaults
    const myOption = options?.myOption ?? 'default';
    const isDark = theme === 'dark';

    if (loading) return <div className="loading">Loading...</div>;

    const data = dataSources?.primary?.data;
    if (!data?.columns?.length || !data.columns[0].length)
        return <div className="empty">No data</div>;

    const { fields, columns } = data;

    // implement visualization here
    return <div style={{ width, height }} />;
}

createRoot(document.getElementById('root')).render(<Viz />);

5. 开发
# Watch mode — rebuilds on file changes
yarn dev

请确认已创建 dist/<viz-name>/visualization.js。硬刷新浏览器以查看更改。

6. 构建生产版本
yarn build:prod
7. 打包
yarn package

输出:dist/<app-id>-<version>-<hash>.spl

打包工具会根据在 dist/ 中找到的可视化自动生成 default/visualizations.conf(其中包含 framework_type = studio_visualization)和 metadata/default.meta。

8. 安装到 Splunk

Splunk Enterprise(10.4+):依次选择 Apps > Manage Apps > Install app from file → 选择 .spl。无需重启——应用会立即加载。

Splunk Cloud Platform:安装私有应用需要 ACS CLI 和 AppInspect 审核。不要引导 Cloud 用户使用 Splunk Web 安装界面——该界面无法安装私有应用。请引导用户查阅 Splunk ACS 文档,了解私有应用提交流程。

本地 Docker:将 .spl 复制到容器中,然后优先使用 splunkctl 通过 REST 接口安装到已配置的本地测试实例:

docker cp dist/<app-id>-<version>-<hash>.spl <container>:/tmp/app.spl
splunkctl --yes app install /tmp/app.spl --update 1 --output json

splunkctl v0.1.0 要求此安装 POST 命令带有 --yes。只有在用户明确批准确切的本地实例、软件包、更新行为和范围受限的非生产环境安装后,才能使用此选项。此选项绝不授权在生产环境或共享环境中安装。

如果无法使用 splunkctl,请说明这一点,并且仅在能够保持相同的非生产目标和安装范围时,使用客户明确批准且客户首选的现有本地安装方式。绝不要擅自安装或配置客户端;生产环境或共享环境中的安装必须另行获得明确批准。

9. 告知用户
  • Splunk 应用 ID
  • 用于仪表板 JSON 的可视化类型字符串:<app-id>.<viz-name>
  • 用于接入数据的 SPL 查询示例

在报告可视化已完成之前,请验证以下内容(除非用户的请求明确表明不适用):

  • 将不可信的搜索值写入 DOM 前已进行转义
  • 交互元素可通过键盘访问,并具有无障碍标签
  • 文本和图标颜色在浅色和深色主题下均符合 WCAG AA 对比度要求
  • 动画遵循 prefers-reduced-motion

示例

从头开始创建新的画布可视化:

“为我构建一个显示主机之间连接关系的网络图可视化。”

技能会收集需求、请用户搭建脚手架、创建使用画布渲染的 visualization.js,接入数据/维度/主题监听器,并打包 .spl。

向现有项目添加可视化:

“向我现有的自定义可视化项目添加一个仪表盘可视化。”

技能会跳过搭建脚手架,在 visualizations/gauge/ 中创建 config.json、src/visualization.js 和 SPL.md,并指导用户重新构建。

迁移旧版可视化:

“将我的 RequireJS 自定义可视化迁移到新框架。”

技能会读取现有可视化,将 updateView 映射为 render(),用 VisualizationAPI 监听器替换 SplunkVisualizationBase,移除 RequireJS 封装,并重新搭建项目脚手架。


更新可视化(快速迭代)

仅修改可视化代码时,可直接复制构建好的 JS,无需重新安装:

yarn dev  # or yarn build:prod

cp dist/<viz-name>/visualization.js \
   $SPLUNK_HOME/etc/apps/<app-id>/appserver/static/visualizations/<viz-name>/visualization.js

对浏览器执行硬刷新。无需重启 Splunk。

如需修改 app.conf、config.json 或添加新的可视化,请完整重新安装(步骤 7–8)。

需要重启才能启用 showDrilldown:在已安装的应用中启用 showDrilldown: true 后,必须重启 Splunk — REST 层会在启动时缓存 config.json,仅重新安装无法更新“交互”面板。


主题支持

可视化应响应 Splunk 的浅色/深色主题。iframe 处于沙盒中,不会继承宿主页面的 CSS 自定义属性。请使用硬编码的企业主题值。

JavaScript:

const THEME_COLORS = {
    light: { background: '#ffffff', text: '#1a1a1a', accent: '#4e9cf5' },
    dark:  { background: '#1a1c21', text: '#c3cbd4', accent: '#4e9cf5' },
};

VisualizationAPI.addThemeListener(({ theme }) => {
    state.theme = theme;
    render();
}, { invokeImmediately: true });

function render() {
    const colors = THEME_COLORS[state.theme] ?? THEME_COLORS.light;
    container.style.background = colors.background;
    // use colors.text, colors.accent in drawing logic
}

React:

const THEME_COLORS = {
    light: { background: '#ffffff', text: '#1a1a1a', accent: '#4e9cf5' },
    dark:  { background: '#1a1c21', text: '#c3cbd4', accent: '#4e9cf5' },
};

function Viz() {
    const { theme } = useTheme();
    const colors = THEME_COLORS[theme] ?? THEME_COLORS.light;
    return <div style={{ background: colors.background, color: colors.text }}>...</div>;
}

无障碍与安全渲染

以下是默认做法,除非用户的请求或上下文明确表明不适用,否则请遵循。

将不可信的搜索值写入 DOM 前先进行转义。 切勿将来自 dataSources 的原始字段值与 innerHTML 或 insertAdjacentHTML 一起使用。请改用 textContent、createElement 或清理辅助函数。

// safe
const el = document.createElement('div');
el.textContent = row.label;

// unsafe — do not do this
container.innerHTML = `<div>${row.label}</div>`;

确保交互元素可通过键盘访问。 如果点击或悬停会触发行为,请确保元素具有 tabIndex="0",并能处理 Enter/Space 对应的 keydown 事件。

el.setAttribute('tabindex', '0');
el.setAttribute('role', 'button');
el.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') handleClick();
});

提供文本替代内容。 画布元素和仅含图标的控件需要无障碍标签。

canvas.setAttribute('role', 'img');
canvas.setAttribute('aria-label', 'Network graph showing host connections');

符合 WCAG AA 对比度要求。 前景文本和图标相对于背景的对比度至少为 4.5:1(大号文本为 3:1)。主题支持 中硬编码的 THEME_COLORS 值已满足默认文本的要求,请以此为基准,并验证任何自定义强调色或状态颜色。

遵循 prefers-reduced-motion。 如果可视化使用动画,请检查媒体查询;启用后应跳过或尽量减少动画。

const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduceMotion) requestAnimationFrame(animate);

画布

有关尺寸、交互和下钻模式,请参阅 references/canvas.md。


脚手架生成的构建如何选择入口文件

脚手架生成的 build.mjs 不会自动检测每个 viz 的扩展名。每个 package.json 脚本都会传入一个全局的 --entry=<filename>,构建过程会将 这个文件名应用于 visualizations/ 下的每个目录:

const entryArg = process.argv.find((a) => a.startsWith('--entry='));
const entryFileName = entryArg ? entryArg.split('=')[1] : 'visualization.js';
// for each viz: build visualizations/<viz>/src/<entryFileName>
// if that file is missing: "Warning: <viz>/src/<entryFileName> not found, skipping"

只有当 entryFileName 以 .jsx 结尾时,才会启用 jsx: 'automatic'。

需要据此进行设计:

  • 同一个脚手架项目中的所有 viz 共用一种扩展名。 使用 JavaScript 脚手架生成的 项目(--entry=visualization.js)只会构建 visualization.js 文件;使用 React 脚手架生成的 项目(--entry=visualization.jsx)只会构建 visualization.jsx 文件。
  • 扩展名不匹配的文件会被静默跳过——构建会发出警告并以状态码 0 退出,因此某个 viz 可能会在没有明显错误的情况下从 dist/ 中消失。如果某个 viz 没有出现在 dist/ 中,请检查 它的入口文件是否与项目的 --entry 扩展名匹配。
添加 React viz
  • **React 脚手架项目:**只需添加 visualizations/<viz>/src/visualization.jsx—— 它会自动构建。

  • **JavaScript 脚手架项目,且没有值得保留的 JS viz:**最简单的方法是将整个项目切换为 .jsx。添加 React(yarn add react react-dom),将现有入口文件重命名为 visualization.jsx,并在 package.json 中将每个 --entry=visualization.js 改为 --entry=visualization.jsx。jsx: 'automatic' 会自动启用。

  • **确实需要在一个项目中同时使用 .js 和 .jsx viz:**脚手架生成的单一 --entry 构建方式无法实现这一点。请将 build.mjs 中的入口查找逻辑替换为逐个 viz 解析(优先使用 .jsx,找不到时回退到 .js),并始终设置 jsx: 'automatic':

    const buildOptions = { jsx: 'automatic', /* ... */ };
    
    function resolveEntry(vizName) {
        for (const name of ['visualization.jsx', 'visualization.js']) {
            const p = join(visualizationsDir, vizName, 'src', name);
            if (existsSync(p)) return p;
        }
        return null;
    }
    

    然后从 package.json 脚本中移除 --entry 标志。(这是本地修改,并非 @splunk/create 生成的内容。)

**避免在入口文件中使用 @splunk/react-ui。**脚手架 React 模板中的默认 viz 会导入 @splunk/react-ui 组件(Table、Paragraph、WaitSpinner),但这些组件是预先打包的 CommonJS 模块,其默认导出无法通过 脚手架的 esbuild ESM 流程保留下来——构建后的 viz 加载时会抛出 Minified React error #130 (收到的是对象而不是组件)。请使用普通元素(或你自己的组件)构建 UI。如果必须使用 @splunk/react-ui,请做好先向 build.mjs 添加 esbuild 互操作 配置的准备。


迁移旧版自定义 viz

请参阅 references/migration.md,了解从 SplunkVisualizationBase 迁移到新框架的完整对应关系和逐步迁移说明。


故障排除

请参阅 references/troubleshooting.md。


关键约束

  • app.conf 使用字面值,但 build = <@- buildNumber @> 除外——该模板标签是有意保留的,由打包工具填入。请勿将其替换为字面值。
  • 应用 id:只能包含字母、数字、点和下划线。不能以点或下划线开头或结尾。
  • Viz slug:只能包含 [a-z0-9_]。拒绝包含点、斜杠或空格的值。确认解析后的路径为 visualizations/<viz-slug>/,且不包含路径遍历。
  • CSS 由构建插件注入 iframe——无需单独的 <link> 标签
  • 图片、SVG 和字体会由构建过程内联为 data URL——直接导入即可
  • 运行时不要从外部 URL 加载字体或其他资源。请将它们存放在本地,并从 src/ 导入,以便构建过程将其内联为 data URL。
  • 在视图模式下,setOptions 会被静默忽略——它仅在编辑模式下有效
  • 始终提供选项回退值——运行时不保证 optionsSchema 默认值可用
  • viz 在沙盒 iframe 中运行——无法访问 window.parent,也不会继承宿主页面的 CSS 自定义属性

参考资料

  • references/api-reference.md — JavaScript 和 React 的完整 API 列表
  • references/config-json.md — config.json 字段参考、optionsSchema、editorConfig 编辑器类型
  • references/drilldown-and-tokens.md — 下钻模式和令牌读写
  • references/canvas.md — 画布尺寸、交互性和下钻模式
  • references/migration.md — 从旧版 SplunkVisualizationBase 可视化组件迁移
  • references/troubleshooting.md — 常见错误及修复方法

评论

支持 Markdown