# argus-board-vue

基于 Vue 3 的可嵌入监控仪表盘组件库。

## 构建产物

```
dist/
  argus-board.es.js          # ES Module，ant-design-vue 外置（宿主自备）
  argus-board.js             # IIFE，ant-design-vue 外置
  argus-board.css            # 样式
  argus-board.bundled.es.js  # ES Module，ant-design-vue 已内置
  argus-board.bundled.js     # IIFE，ant-design-vue 已内置
  argus-board.bundled.css    # 样式（bundled 版）
```

**两个版本的区别：**

| 版本 | 文件 | ant-design-vue | 宿主需提供 |
|------|------|---------------|----------|
| 标准版 | `argus-board.es.js` | 外置，需宿主提供 | `vue` `pinia` `dayjs` `lodash` `ant-design-vue` |
| bundled 版 | `argus-board.bundled.es.js` | 已打包 | `vue` `pinia` `dayjs` `lodash` |

---

## 快速开始

### 在 Vue 3 项目中使用（标准版）

宿主项目需安装 `ant-design-vue`：

```bash
npm install ant-design-vue
```

```ts
// main.ts
import 'ant-design-vue/dist/reset.css'
import 'argus-board/dist/argus-board.css'
```

```vue
<script setup lang="ts">
import { ArgusBoard } from 'argus-board/dist/argus-board.es.js'
import type { ArgusBoardConfig } from 'argus-board/dist/argus-board.es.js'

const config: ArgusBoardConfig = {
  hideHeader: false,
  canEdit: true,
  theme: null,
  lang: 'zh',
  isEditing: false,
  initialEditingPanelId: null,
  datasourceConfig: [
    {
      type: 'zabbix',
      uid: 'zabbix-prod',
      url: 'https://zabbix.example.com',
      token: 'your-api-token',
      name: '生产 Zabbix',
      default: true,
    },
  ],
}

function onSave(json: string) {
  console.log('dashboard saved:', json)
}
</script>

<template>
  <ArgusBoard
    :config="config"
    style="width: 100%; height: 100vh"
    @save="onSave"
  />
</template>
```

### 在 Vue 3 项目中使用（bundled 版，宿主无 antd）

```ts
// main.ts
import 'argus-board/dist/argus-board.bundled.css'
```

```vue
<script setup lang="ts">
import { ArgusBoard } from 'argus-board/dist/argus-board.bundled.es.js'
</script>
```

### IIFE（无构建工具环境）

```html
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="dist/argus-board.css" />
</head>
<body>
  <!-- 宿主提供 Vue、pinia、dayjs、lodash、ant-design-vue -->
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script src="https://unpkg.com/ant-design-vue/dist/antd.min.js"></script>

  <div id="app"></div>
  <script src="dist/argus-board.js"></script>
  <script>
    const { createApp } = Vue
    const { ArgusBoard } = window.ArgusBoard

    createApp({
      template: '<ArgusBoard :config="config" style="width:100%;height:100vh" @save="onSave" />',
      components: { ArgusBoard },
      setup() {
        return {
          config: {
            canEdit: true,
            lang: 'zh',
            isEditing: false,
            theme: null,
            hideHeader: false,
            initialEditingPanelId: null,
            datasourceConfig: null,
          },
          onSave: (json) => console.log(json),
        }
      },
    }).use(AntDesignVue).mount('#app')
  </script>
</body>
</html>
```

---

## ArgusBoardConfig

`ArgusBoard` 组件接收 `:config` prop，完整类型定义如下。

### 基础配置

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `hideHeader` | `boolean` | `false` | 隐藏顶部工具栏 |
| `canEdit` | `boolean` | `true` | 是否允许进入编辑模式，`false` 时强制锁定为查看模式 |
| `theme` | `'dark' \| 'light' \| null` | `null` | 主题，`null` 时使用用户上次选择 |
| `lang` | `'zh' \| 'en' \| null` | `null`（默认 `zh`） | 界面语言 |

### 初始状态

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `isEditing` | `boolean \| null` | `null` | 初始编辑/查看模式，`null` 时使用组件内部默认值 |
| `initialEditingPanelId` | `string \| null` | `null` | 挂载时自动打开指定面板的编辑器，仅生效一次 |

### 仪表盘数据

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `initialConfig` | `string` | — | 初始加载的仪表盘 JSON 字符串，仅挂载时生效一次 |
| `renderDefaultDashboard` | `boolean` | `true` | 无 `initialConfig` 时是否渲染内置示例面板，`false` 显示空白画布 |
| `presets` | `PresetItem[]` | — | 预设列表，用户可从工具栏下拉菜单加载 |
| `onSave` | `(json: string) => void` | — | 用户点击"保存"时的回调，也可通过 `@save` 事件监听 |

**`initialConfig` 格式：**

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

> 通过工具栏 `... → 导出` 可获取当前仪表盘 JSON，直接用作 `initialConfig`。

**`PresetItem` 格式：**

```ts
interface PresetItem {
  label: string   // 下拉菜单显示名
  config: string  // 仪表盘 JSON 字符串
}
```

```ts
presets: [
  { label: '网络设备面板', config: networkDashboardJson },
  { label: '服务器监控',   config: serverDashboardJson },
]
```

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

### 数据源

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `datasourceConfig` | `DataSourceConfig[] \| null` | `null` | 数据源配置列表 |

**`DataSourceConfig` 格式：**

```ts
interface DataSourceConfig {
  type: 'zabbix' | 'argus'
  uid: string       // 唯一标识，面板绑定数据源时使用
  url: string       // 数据源服务地址
  token: string     // 鉴权 Token
  name: string      // 显示名称
  default?: boolean // 标记为默认数据源，新建查询时优先选中
}
```

```ts
datasourceConfig: [
  {
    type: 'zabbix',
    uid: 'zabbix-prod',
    url: 'https://zabbix.example.com',
    token: 'xxx',
    name: '生产 Zabbix',
    default: true,
  },
  {
    type: 'argus',
    uid: 'argus-main',
    url: 'https://argus.example.com',
    token: 'xxx',
    name: 'Argus 主集群',
  },
]
```

---

## 事件

### `save`

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

```vue
<ArgusBoard :config="config" @save="handleSave" />
```

```ts
function handleSave(json: string) {
  fetch('/api/dashboard/save', {
    method: 'POST',
    body: json,
    headers: { 'Content-Type': 'application/json' },
  })
}
```

---

## 完整示例

### 空白画布

```vue
<ArgusBoard
  :config="{
    hideHeader: false,
    canEdit: true,
    renderDefaultDashboard: false,
    isEditing: true,
    lang: 'zh',
    theme: null,
    initialEditingPanelId: null,
    datasourceConfig: null,
  }"
  style="width: 100%; height: 100vh"
/>
```

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

```vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ArgusBoard } from 'argus-board/dist/argus-board.es.js'
import type { ArgusBoardConfig } from 'argus-board/dist/argus-board.es.js'

const config = ref<ArgusBoardConfig>({
  hideHeader: false,
  canEdit: true,
  isEditing: false,
  lang: 'zh',
  theme: null,
  initialEditingPanelId: null,
  datasourceConfig: null,
})

onMounted(async () => {
  const json = await fetch('/api/dashboard/1').then(r => r.text())
  config.value = { ...config.value, initialConfig: json }
})

async function handleSave(json: string) {
  await fetch('/api/dashboard/1', {
    method: 'POST',
    body: json,
    headers: { 'Content-Type': 'application/json' },
  })
}
</script>

<template>
  <ArgusBoard
    :config="config"
    style="width: 100%; height: 100vh"
    @save="handleSave"
  />
</template>
```

### 暗色主题 + 数据源 + 预设

```vue
<script setup lang="ts">
import { ArgusBoard } from 'argus-board/dist/argus-board.es.js'

const config = {
  hideHeader: false,
  canEdit: true,
  theme: 'dark' as const,
  lang: 'zh' as const,
  isEditing: false,
  initialEditingPanelId: null,
  renderDefaultDashboard: false,
  datasourceConfig: [
    { type: 'zabbix' as const, uid: 'z1', url: 'https://zabbix.example.com', token: 'xxx', name: 'Zabbix', default: true },
  ],
  presets: [
    { label: '网络设备', config: networkJson },
    { label: '服务器',   config: serverJson },
  ],
}
</script>

<template>
  <ArgusBoard :config="config" style="width: 100%; height: 100vh" />
</template>
```

---

## 查询选项（Query Options）

每个面板支持 `queryOptions` 字段，对应 Query Editor 中的 "Query options" 区域：

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

---

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

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `tableShowHeader` | `boolean` | `true` | 是否显示列标题 |
| `tableCellHeight` | `'small' \| 'medium' \| 'large'` | `'medium'` | 行高 |
| `tableFrozenColumns` | `number` | `0` | 左侧冻结列数 |
| `tableColumnFilter` | `boolean` | `false` | 启用列过滤器 |
| `tableWrapText` | `boolean` | `false` | 文本换行 |
| `tableEnablePagination` | `boolean` | `false` | 启用分页 |
| `tablePageSize` | `number` | `20` | 每页行数 |
| `tableFooterEnabled` | `boolean` | `false` | 显示底部统计行 |
| `tableFooterReducers` | `string[]` | `[]` | 底部聚合函数，支持 `sum` `mean` `min` `max` `count` 等 |
| `tableMinColumnWidth` | `number` | `100` | 列最小宽度（px） |
| `tableColumnOverrides` | `Record<string, TableColumnOverride>` | `{}` | 按列名覆盖显示配置 |

**`TableColumnOverride` 字段：**

| 字段 | 类型 | 说明 |
|------|------|------|
| `hidden` | `boolean` | 隐藏该列 |
| `label` | `string` | 覆盖列标题显示名 |
| `width` | `number` | 固定列宽（px） |
| `alignment` | `'auto' \| 'left' \| 'center' \| 'right'` | 列对齐 |
| `displayMode` | `string` | 单元格模式：`auto` `colored-text` `colored-background` `gauge` `json-view` `pill` |
| `wrapText` | `boolean` | 该列文本换行 |

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

---

## 变量

在编辑模式下，`VariableBar` 右侧显示 ⚙ 图标，点击后可新增、编辑、删除仪表盘变量。

支持的变量类型：`custom` `query` `textbox` `datasource` `constant`

---

## 开发

```bash
pnpm install

# 本地预览（apps/board）
pnpm dev

# 类型检查
pnpm typecheck

# 构建库（antd 外置版）
pnpm build:lib

# 构建库（antd 内置版）
pnpm build:lib-bundled
```
