# Modify Dashboard

> Modify an existing monitoring dashboard on Nightingale (n9e). Use when the user asks to change a dashboard's variables, check and fix variables, modify charts/series (change PromQL, legend, unit, add/remove series), rename a chart, or change the chart type (e.g., turn a stat chart into a timeseries chart). Distinct from "creating a dashboard from scratch" (that is create-dashboard).

- Skill: `ccfos/modify-dashboard` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ccfos/modify-dashboard`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ccfos/modify-dashboard/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ccfos (https://skillmd.com/u/ccfos)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ccfos/modify-dashboard

---


# Skill: Nightingale (N9E) Modify an Existing Dashboard

Help users **modify an existing dashboard** using natural language — not create a new one. Three typical kinds of requests:

| Request | What you change |
|------|-------------|
| **Change variables** | A template variable's value expression (definition), default value, whether it is multi-select (multi), display label (label) |
| **Check and fix variables** | Scan variable definitions and the references to variables inside charts, detect smells (a chart references an undefined variable, a variable returns no value, datasource references are inconsistent, etc.) and fix them |
| **Change chart series** | The series of a chart (panel): query expression (PromQL), legend, unit, adding/removing series, changing the title, changing the chart type (e.g., stat→timeseries) |

> Editing series queries (`queries`) is **only supported for Prometheus/VictoriaMetrics panels**; SQL/log panels (mysql, ck, es, etc.) can only have their unit, title, description changed or be deleted — passing `queries` will be rejected by the tool.

## Iron rule: a single proposal call wraps it up; confirmation is done by the system

`update_dashboard` is a **proposal-style write**: after you call it (passing only the part you want to change), the tool computes the diff, **directly shows the user the list of changes and pauses the conversation**; once the user confirms, the system automatically persists it to the database — the confirmation step neither needs nor goes through you. Therefore:

1. First call `get_dashboard_detail(id, include_config=true)` to read the current variables, chart summaries, and variable health check.
2. After working out the part you want to change, call `update_dashboard` once, **passing only** the `variables`/`panels`/`fix_datasource` you want to change. This call is the last step of this turn; the system takes over the display and confirmation.
3. **Do not** render the change table yourself (the system will show the list generated by the tool), and **do not** pass `proposal_id`/`confirmed` (those are parameters for the system's confirmation channel).
4. When the user rejects or raises a new requirement, you will receive feedback in a new turn: just recompute the changes per the feedback and call `update_dashboard` again (the old proposal is automatically voided).

## Step 1: Locate the dashboard

- If the context already carries `dashboard_id` (injected by the frontend from `/dashboards/<id>`, or already determined in a previous turn) → use it directly, don't call `list_dashboards` again.
- The user pasted a `/dashboards/<id>` link → take the id from it.
- Only a name was given → call `list_dashboards(query="...")` to match by name; if there are multiple candidates or no match, list them and ask the user — **do not** guess.

Read the business group and the datasources from the dashboard itself; **do not** ask the user for them.

## Step 2: Read the current state (always pass include_config=true)

```
get_dashboard_detail(id=<id>, include_config=true)
```

In the response:
- `variables`: for each variable, `name / type / label / definition / multi / default_value / datasource_value`
- `panels`: for each chart, `id / name / type / unit / queries` (each series contains `ref / promql / legend / instant / step / hide`; fields that are not set are omitted); charts inside collapsed rows (row) have been flattened in
- `variable_lint`: the list of smells caught by the variable health check (e.g., "the query expression of chart X references an undefined variable $foo")

**Prefer `id` when locating a chart** (e.g., `panel-3`); names may be duplicated.

## Step 3: Validate that the query can return a value (optional but recommended)

When changing PromQL or fixing a variable's definition, you can first use `query_prometheus` / `list_metrics` / `get_metric_labels` to verify that the new expression really has data before proposing, to avoid the chart still being empty after the change.

For variable-fix tasks: for each item caught in `variable_lint`, decide on a fix action (change the definition / rename the reference / fix the datasource reference) and put them all into the same proposal.

## Step 4: Submit the proposal (call update_dashboard, just once)

Call `update_dashboard`, **passing only** the part you want to change (everything else is preserved as-is; the tool won't touch it). The tool will show the user the list of changes and wait for confirmation; this turn ends here:

- **Change/add/delete variables** → `variables` (a JSON array, matched by `name`):
  ```json
  [{"name":"ident","default_value":"web01","multi":false}]
  ```
  - Write only the fields you want to change; if `name` does not exist it is treated as adding a new query variable, and **an addition must carry `definition`** (omitting it raises an error — a misspelled name falls into the addition branch, and this guard catches it); `delete:true` deletes it.
- **Change chart series** → `panels` (a JSON array, located by `id` first, otherwise by `name`):
  ```json
  [{"id":"panel-2","unit":"percent","queries":[{"promql":"avg(cpu_usage_active{cpu=\"cpu-total\",ident=~\"$ident\"})","legend":"{{ident}}"}]}]
  ```
  - When `queries` is passed in it is **incrementally merged** with the existing series: matched by `ref` (the original series' refId), only the fields you write are overwritten; the rest (step/hide/__mode__, the refId-associated overrides, etc.) are preserved as-is; **anything without a `ref` is always treated as a new series (no position match)**. **Existing series that do not appear in `queries` will not be deleted and are preserved as-is** — so when changing only one series, pass just that one (with its `ref`); you don't need to list all the series of the whole chart.
  - **Changing an existing series must carry its `ref`** (without `ref` it adds a series instead of changing the original one); to delete a series, put its `ref` on that series item and write `delete:true`.
  - `new_name` changes the title, `unit` changes the unit, `description` changes the description, `type` changes the chart type (only `timeseries`/`stat`/`gauge`/`barGauge`/`pie`/`table`; changing the type resets the chart's type-style options to the defaults of the new type, and changing to `timeseries` also clears the `instant` flag on series to restore range queries; a row layout row cannot be changed), `delete:true` (on the panel item) deletes the whole chart. Not passing `queries` leaves the series untouched.
  - **Only the fields above are valid inside a panel item**: colors, thresholds, layout coordinates, and other fields are not supported. A patch that **only contains** unsupported fields is directly rejected by the tool; if mixed in among supported fields, the unsupported parts are silently dropped — so you **must restate to the user strictly based on the change list returned by the tool**; changes outside that list did not happen. When the user wants to change something unsupported, just say it can't be done and suggest editing it manually on the page.
- **Fix datasource references** → `fix_datasource: true`: re-points dangling or hard-coded datasource references in charts/variables uniformly to the dashboard's datasource variable. Suitable for fixing smells like "chart returns no data / datasource references are inconsistent."

## Step 5: Responding to special cases

- After reading the current state, if you find **no change is needed**, the query validation **returns no data**, or the target chart/variable **cannot be located**: do not call `update_dashboard`; just state the reason in a single sentence and give a suggestion.
- When the user gives feedback in a new turn that "the proposal is stale/invalid" (the dashboard was changed by someone else in the meantime): just re-read the current state and re-propose.

The `changes` list returned by the tool is the set of changes actually persisted to the database; restate to the user based on it.

## Notes

- For multi-select variables, use `=~` instead of `=` in PromQL, e.g., `ident=~"$ident"`.
- When changing/adding a series, use the `{{label}}` form for the legend template, e.g., `{{ident}}`.
- When unsure of a metric name/label, probe with `list_metrics` / `get_metric_labels` first; don't make it up.
- When the user raises multiple changes in a row within one conversation, you can merge them into a single `update_dashboard` call (pass `variables` and `panels` together); the change list and confirmation are gated by the system and won't be skipped.

