# argus-board-react

基于 React 的可嵌入监控仪表盘 Web Component。

## 快速开始

构建产物包含三个文件：

| 文件 | 说明 |
|------|------|
| `dist/argus-board.js` | IIFE 格式，直接用 `<script>` 引入 |
| `dist/argus-board.es.js` | ES Module 格式，用于 `import` |
| `dist/style.css` | 样式文件，两种方式都需要引入 |

### IIFE（`<script>` 标签）

```html
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="dist/style.css" />
</head>
<body>
  <argus-board style="width:100%;height:100vh;display:block;"></argus-board>

  <script src="dist/argus-board.js"></script>
</body>
</html>
```

### ES Module（`import`）

```js
import 'dist/style.css';
import 'dist/argus-board.es.js';

// 注册完成后即可使用 <argus-board> 标签
document.body.innerHTML = '<argus-board style="width:100%;height:100vh;display:block;"></argus-board>';
```

或在 Vue / React 等框架中：

```js
// main.js / main.ts
import 'dist/style.css';
import 'dist/argus-board.es.js';
```

```html
<!-- 任意组件模板中 -->
<argus-board style="width:100%;height:600px;display:block;"></argus-board>
```

---

## 配置方式

`<argus-board>` 支持两种配置方式，可混合使用：

- **HTML Attribute**：适合简单标量值（布尔、枚举、短字符串），可在 HTML 中静态声明
- **JS Property**：适合大型 JSON 数据（仪表盘配置、预设列表、数据源列表），直接赋值给元素属性，避免 HTML 膨胀

**优先级：JS Property > HTML Attribute**

---

## 配置属性（HTML Attributes）

适合简单标量值，属性变化时组件自动重新渲染。

### 基础配置

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `hide-header` | `"true" \| "false"` | `"false"` | 是否隐藏顶部工具栏 |
| `can-edit` | `"true" \| "false"` | `"true"` | 是否允许进入编辑模式 |
| `theme` | `"dark" \| "light"` | — | 主题，不传时使用用户上次选择 |
| `lang` | `"zh" \| "en"` | `"zh"` | 界面语言 |

### 初始状态

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `locked` | `"true" \| "false"` | — | 初始是否锁定（查看模式）。`true` = 查看，`false` = 编辑，不传时使用上次状态 |
| `editing-panel` | `string` | — | 初始打开编辑器的面板 ID，仅挂载时生效一次 |

### 仪表盘数据

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `initial-config` | `string`（JSON） | — | 初始加载的仪表盘 JSON 字符串，传入时替换默认面板，仅挂载时生效一次。格式见下方说明 |
| `render-default-dashboard` | `"true" \| "false"` | `"true"` | 没有 `initial-config` 时是否渲染内置默认示例面板。设为 `"false"` 时显示空白画布 |
| `presets` | `string`（JSON 数组） | — | 预设列表，用户可从下拉菜单加载。格式见下方说明 |

**`initial-config` 格式：**

```json
{
  "title": "我的仪表盘",
  "panels": { ... },
  "layouts": [ ... ],
  "variables": [ ... ]
}
```

> 可通过页面右上角"导出"按钮获取当前仪表盘的 JSON，直接作为 `initial-config` 的值传入。

**`presets` 格式：**

```json
[
  { "label": "网络设备面板", "config": "{...仪表盘JSON字符串...}" },
  { "label": "服务器监控",   "config": "{...仪表盘JSON字符串...}" }
]
```

> 下拉菜单中始终包含内置"默认模板"选项，用户自定义预设追加在其后。

### 数据源

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `datasource-config` | `string`（JSON 数组） | — | 数据源配置列表，格式见下方说明 |

**`datasource-config` 格式：**

```json
[
  {
    "type": "zabbix",
    "uid": "zabbix-prod",
    "url": "https://zabbix.example.com",
    "token": "your-api-token",
    "name": "生产 Zabbix",
    "default": true
  },
  {
    "type": "argus",
    "uid": "argus-main",
    "url": "https://argus.example.com",
    "token": "your-api-token",
    "name": "Argus 主集群"
  }
]
```

- `type`：数据源类型，支持 `"zabbix"` / `"argus"`
- `uid`：唯一标识，用于面板绑定；不填时自动用数组下标作为 uid
- `default`：标记为默认数据源，新建查询时优先选中

---

## JS Property API

大型 JSON 数据推荐通过 JS 属性赋值，不写入 HTML attribute，避免 DOM 膨胀。

| 属性 | 类型 | 对应 attribute |
|------|------|----------------|
| `element.initialConfig` | `DashboardConfig \| string \| undefined` | `initial-config` |
| `element.presets` | `PresetConfig[] \| undefined` | `presets` |
| `element.datasourceConfig` | `DataSourceConfig[] \| null` | `datasource-config` |

赋值后组件立即重新渲染，JS Property 优先级高于同名 attribute。

`initialConfig` 接受对象或 JSON 字符串，传对象时内部自动 `JSON.stringify`。

```js
const board = document.querySelector('argus-board');

// 直接传对象，无需手动 JSON.stringify
board.initialConfig = {
  title: '我的仪表盘',
  panels: { ... },
  layouts: [ ... ],
};

// 也可以传 JSON 字符串（如从接口直接拿到的响应体）
const res = await fetch('/api/dashboard/1');
board.initialConfig = await res.text();

// 设置数据源（无需 JSON.stringify）
board.datasourceConfig = [
  { type: 'zabbix', uid: 'z1', url: 'https://zabbix.example.com', token: 'xxx', name: 'Zabbix', default: true }
];

// 设置预设列表
board.presets = [
  { label: '网络设备面板', config: networkDashboardJson },
  { label: '服务器监控',   config: serverDashboardJson },
];
```

---

## 事件

### `save`

用户点击"保存"按钮时触发，携带当前仪表盘的完整 JSON。

```js
const board = document.querySelector('argus-board');
board.addEventListener('save', (e) => {
  const json = e.detail.json; // 仪表盘 JSON 字符串
  // 持久化到后端...
});
```

事件属性：`bubbles: true, composed: true`，可穿透 Shadow DOM 冒泡。

---

## 完整示例

### 空白画布（不渲染默认面板）

```html
<argus-board
  render-default-dashboard="false"
  locked="true">
</argus-board>
```

### 加载已保存的仪表盘

```html
<argus-board
  id="board"
  locked="true"
  render-default-dashboard="false">
</argus-board>

<script>
  fetch('/api/dashboard/1').then(r => r.text()).then(json => {
    document.getElementById('board').setAttribute('initial-config', json);
  });
</script>
```

### 带数据源 + 保存回调

```html
<argus-board
  id="board"
  theme="dark"
  locked="true"
  datasource-config='[{"type":"zabbix","uid":"z1","url":"https://zabbix.example.com","token":"xxx","name":"Zabbix","default":true}]'>
</argus-board>

<script>
  document.getElementById('board').addEventListener('save', async (e) => {
    await fetch('/api/dashboard/save', {
      method: 'POST',
      body: e.detail.json,
      headers: { 'Content-Type': 'application/json' }
    });
  });
</script>
```

### 带预设的嵌入场景

```html
<argus-board
  render-default-dashboard="false"
  presets='[{"label":"网络设备","config":"{...}"},{"label":"服务器","config":"{...}"}]'>
</argus-board>
```

---

## 查询选项配置（Query Options）

每个面板支持 `queryOptions` 字段，对应 Query Editor 工具栏下方的 "Query options" 折叠区域：

| 字段 | 类型 | 说明 |
|------|------|------|
| `queryOptions.maxDataPoints` | `number` | 覆盖面板默认 maxDataPoints（默认值：1000）。影响降采样步长 |
| `queryOptions.minIntervalMs` | `number` | 查询最小间隔（ms），0 = 不限制 |
| `queryOptions.relativeTime` | `string` | 相对时间覆盖，如 `"1h"`、`"30m"`，将查询窗口向前偏移 |
| `queryOptions.timeShift` | `string` | 时间偏移，如 `"1h"`，将 from/to 整体平移 |

`queryOptions.minIntervalMs` 输入框支持 `s / m / h / ms` 后缀（例如 `"30s"` = 30000ms）。  
`Interval` 行为只读，显示当前计算值：`ceil((to - from) * 1000 / maxDataPoints)`。

---

## 表格面板配置（Table Panel）

表格面板支持以下 `PanelConfig` 字段：

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `tableShowHeader` | `boolean` | `true` | 是否显示列标题 |
| `tableCellHeight` | `'small' \| 'medium' \| 'large'` | `'medium'` | 行高 |
| `tableFrozenColumns` | `number` | `0` | 左侧冻结列数 |
| `tableColumnFilter` | `boolean` | `false` | 启用列过滤器 |
| `tableWrapText` | `boolean` | `false` | 文本换行 |
| `tableEnablePagination` | `boolean` | `false` | 启用分页 |
| `tablePageSize` | `number` | `20` | 每页行数（`tableEnablePagination` 为 `true` 时生效） |
| `tableFooterEnabled` | `boolean` | `false` | 显示底部统计行 |
| `tableFooterReducers` | `string[]` | `[]` | 底部聚合函数，取第一个值作为统计方式，支持所有 Grafana ReducerID |
| `tableFooterFields` | `string[]` | `undefined` | 参与底部计算的字段名；空/未设置 = 所有数值字段 |
| `tableMinColumnWidth` | `number` | `100` | 列最小宽度 |
| `tableColumnOverrides` | `Record<string, TableColumnOverride>` | `{}` | 按列名覆盖显示配置 |

### `tableFooterReducers` 支持的值

| 分组 | 值 |
|------|----|
| Standard | `lastNotNull`, `last`, `firstNotNull`, `first`, `min`, `max`, `mean`, `sum`, `count`, `range` |
| Extended | `median`, `stdDev`, `variance`, `delta`, `diff`, `diffperc`, `step`, `logmin`, `countAll`, `changeCount`, `distinctCount` |
| Boolean | `allIsZero`, `allIsNull` |
| Percentile | `p1`–`p99` |

### `TableColumnOverride` 字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `hidden` | `boolean` | 隐藏该列 |
| `label` | `string` | 覆盖列标题显示名 |
| `width` | `number` | 固定列宽（px） |
| `alignment` | `'auto' \| 'left' \| 'center' \| 'right'` | 列对齐方式 |
| `displayMode` | `TableCellDisplayMode` | 单元格显示模式 |
| `wrapText` | `boolean` | 该列文本换行 |
| `valueRegex` | `{ pattern: string; replace: string }` | 正则替换单元格值 |

**`displayMode` 支持的值：** `auto`, `colored-text`, `colored-background`, `gauge`, `data-links`, `json-view`, `pill`

```json
{
  "type": "table",
  "title": "API 请求日志",
  "tableFooterEnabled": true,
  "tableFooterReducers": ["mean"],
  "tableColumnOverrides": {
    "latency": { "displayMode": "gauge", "label": "延迟 (ms)" },
    "status": { "alignment": "center" },
    "endpoint": { "width": 200 }
  }
}
```

---

## 变量配置弹窗

在编辑模式下（`isEditing === true`），`VariableBar` 右侧会显示 ⚙ 设置图标。点击后弹出变量配置弹窗，支持：

- 查看、新增、编辑、删除所有仪表盘变量
- 支持类型：`custom`、`query`、`textbox`、`datasource`、`constant`
- 所有修改在点击"保存"后统一提交，取消则丢弃草稿

### VariableConfig 新增字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `description` | `string?` | 变量描述 |
| `showOnDashboard` | `'label-and-value' \| 'value' \| 'nothing'` | 显示模式，`nothing` 时自动设置 `hide: true` |
| `customOptions` | `Array<{text, value}>?` | custom 类型选项（替代 customValues 字符串） |
| `dsType` | `string?` | datasource 类型的数据源类型 |
| `constantValue` | `string?` | constant 类型的常量值 |

---

## 开发

```bash
npm install
npm run dev      # 开发模式
npm run build    # 构建 Web Component
npm run lint     # 代码检查
```
