Fundamental workshop 2 — Grafana: one source, one panel, one dashboard

Guided practice17 min
Duration
20 min
Module
1/7
Prerequisites
the lab has been running for at least two minutes (etat displays 8/8 cibles up), Grafana open at http://localhost:3000 with admin / aiopsatlas2026, workshop 1 done (you know what count(up) returns)
You will build
a single-panel dashboard, created by hand in the interface, saved, re-read as JSON, then deleted
Deliverable
the JSON block of the panel as Grafana saved it (step 9), with three words circled: the visualization type, the query, the data source

How to read this page. Ten steps, one gesture at a time. For each: where to click, what Grafana displays word for word, and what to look at. The interface labels are in English in Grafana 13.2.2 (the kit's version); they are quoted as they are, in bold. The numbers will be different on your machine; the shapes (number of series, panel value, messages) must be the same. The "To understand better" blocks are optional. If the lab is not started, go back to the guided practice: the In short section gives the commands, kit included (https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr). Everything you create here is destroyed in step 10: the three dashboards shipped with the kit are not touched.

Goal

In the guided practice, you typed eight queries in Explore and looked at three ready-made dashboards. So you know how to read Grafana. But if you are asked to add a number to a dashboard, do you know where to start? Here, you start from scratch and assemble yourself the three objects of Grafana, from the smallest to the largest: a data source (Grafana stores nothing, it queries Prometheus), a panel (a query plus a way to draw it), a dashboard (panels arranged on a grid, with a name and an address). One step is a deliberate trap: you break a query to see how the Prometheus error passes through Grafana. At the end, you read the JSON Grafana wrote for you and find the three objects in it, then you delete everything.

The vocabulary in one image

Grafana is a digital photo frame. It takes no photos: it goes and fetches them from a photographer (the data source: Prometheus, Loki) and displays them. Each photo is a panel: a question asked to the photographer (the query) and a display format (a number, a curve, a gauge). The frame itself, with its photos laid out on a grid, is the dashboard: it has a name, an address, and it is saved in Grafana's database as a JSON document.

GrafanaClassic SQL databaseIn this workshop
data sourcethe connection to the databasePrometheus (http://prometheus:9090)
querySELECT …count(up)
panela saved viewCibles surveillées (monitored targets)
visualizationthe way to display the resultStat (a big number)
dashboarda report that assembles several viewsAtelier M1 - Cibles Prometheus
uidprimary keyadc947c (yours will be different)
dashboard JSONthe exported schema of the reportstep 9
folderdatabase schemaDashboards (the root); Labo observabilite for the kit's three

Where to click, and how to read an answer

Open http://localhost:3000. The main menu is the three-line icon at the top left: it gives access to Dashboards, Explore and Connections. The breadcrumb at the top (for example Dashboards › New dashboard) always says where you are. When an action succeeds, Grafana displays for a few seconds a banner at the bottom right (a toast): Dashboard saved, Dashboard deleted. When a query fails, it displays a red box under the query field, with Prometheus's message copied as is.

Once the dashboard is saved, you will re-read it as JSON through Grafana's API. In PowerShell or in a Linux terminal, the same command:

bash
curl -s -u admin:aiopsatlas2026 http://localhost:3000/api/dashboards/uid/<uid>

<uid> is the identifier Grafana gave your dashboard; you will read it in the page address in step 8.

Step 1 — Check the data source

Main menu → ConnectionsData sources. Three sources are listed: Alertmanager, Loki and Prometheus (marked default). Click Prometheus. The Settings page opens, with Prometheus server URL filled in: http://prometheus:9090. Change nothing. Scroll to the very bottom and click Save & test.

text
Successfully queried the Prometheus API.
Next, you can start to visualize data by building a dashboard from scratch or by querying data in the Explore view.

What to look at: the green banner. Grafana has just sent a query to Prometheus and Prometheus answered. This is the first object: a data source is an address and a type. The address is http://prometheus:9090 and not http://localhost:9090: Grafana runs in a container, and from that container, Prometheus is called prometheus. Your browser, for its part, reaches it through localhost:9090. Two names for the same machine, depending on who is talking.

To understand better: who created these three sources?

Not you. The kit describes them in grafana/provisioning/datasources/sources.yml and Grafana reads them at startup: this is provisioning. The file is short, open it: name: Prometheus, uid: prometheus, type: prometheus, url: http://prometheus:9090, isDefault: true. Without this file, the first thing to do in a fresh Grafana would be Add new data source, and you would type these four lines by hand. The uid: prometheus will serve you again in step 9: it is through it that your panel will designate its source.

Step 2 — Query the source in Explore

Main menu → Explore. At the top, the source selector displays Prometheus. To the right of the field, switch to Code (and not Builder). Type:

promql
up

then Run query (or Shift+Enter).

text
up{instance="alertmanager:9093", job="alertmanager"}
up{instance="alloy:12345", job="alloy"}
up{instance="api:8000", job="api", service="api"}
up{instance="cadvisor:8080", job="cadvisor"}
up{instance="grafana:3000", job="grafana"}
up{instance="localhost:9090", job="prometheus"}
up{instance="loki:3100", job="loki"}
up{instance="node-exporter:9100", job="node-exporter"}

What to look at: eight lines in the legend under the graph, the eight series of step 2 of workshop 1, in alphabetical order of instance. The query is exactly the one you typed in Prometheus; Grafana forwarded it and drew the answer. Explore is a scratchpad: nothing is saved there, it is where you fine-tune a query before putting it on a panel.

Step 3 — The trap: one parenthesis too few

Still in Explore, replace the query with a wrong version, on purpose:

promql
count(up

Run query:

text
bad_data: invalid parameter "query": 1:9: parse error: unclosed left parenthesis

What to look at: a red box under the field, and No data in the graph. The message is not from Grafana: parse error and the position 1:9 are Prometheus's, which you learned to read in workshop 1. Grafana only prefixes bad_data:, the error category returned by Prometheus's API. Rule: when a Grafana panel displays a PromQL error, fix it in Prometheus first (http://localhost:9090), then copy it back into Grafana. Close the parenthesis, Run query: a single line in the legend, which Grafana names count(up) for lack of labels (Prometheus, for its part, wrote {}), at 8. This is the query you are going to put on your panel.

Step 4 — Create an empty dashboard

Main menu → Dashboards. The page lists a single folder, Labo observabilite, which contains the kit's three dashboards. At the top right, NewNew dashboard.

text
New dashboard
Add a panel to visualize your data

What to look at: the breadcrumb says Dashboards › New dashboard, the page is empty, and the Save button is already there at the top right. Nothing is saved yet: if you close the tab now, nothing remains. A dashboard only exists from the moment it is saved (step 8). On the right, an Add side panel offers Panel ("Drag or click to add a panel").

Step 5 — Add a panel without a query

In the Add side panel, click Panel. A frame appears on the grid, titled New panel, with the text No visualization configured. On the right, the Title field contains New panel: replace it with:

text
Cibles surveillées

What to look at: the frame's title changes live. You have a panel, but it is empty: neither query nor visualization. This is the second object, reduced to its minimum: a slot on the grid and a name. Click Edit visualization in the side panel: the Edit panel page opens, with the Queries 1 tab at the bottom and, on the right, Suggestions / All visualizations.

Step 6 — Put down the query

In Queries, line A is already linked to Data source: Prometheus (the default source). Switch to Code, type in the Enter a PromQL query… field:

promql
count(up)

then Run queries (or Shift+Enter).

text
Suggestions
Time series · Stat · Gauge · Bar gauge · Table · State timeline · Heatmap · Histogram

What to look at: the right-hand pane changes: under Suggestions, Grafana offers a dozen visualizations, and each thumbnail already displays your data: count(up) and 8. The top panel, for its part, still says "Run a query to visualize it here or go to all visualizations": it has the query, it has not yet chosen how to draw it. Query and visualization are two separate settings of the same panel.

To understand better: Builder or Code?

Builder builds the query with menus (choose the metric, add a label, stack a function). Code lets you type it. Both produce the same PromQL string, and you can switch from one to the other. In this course, we stay in Code: the query you type here is word for word Prometheus's, and you will find it as is in the JSON of step 9 ("editorMode": "code").

Step 7 — Choose the visualization

In Suggestions, click the Stat thumbnail.

text
Stat
Value options
  Calculate | All values
  Calculation: Last *
Thresholds
  80  (rouge)
  Base  (vert)

What to look at: the top panel now displays a big green 8, with the small background curve. The right-hand pane lists the Stat options, two of which are worth knowing: under Value options, Calculate is checked and Calculation is Last * (the panel shows the last value of the series, not an average); under Thresholds, two steps, Base in green and 80 in red: the value would turn red from 80 upwards. These thresholds are the ones Grafana puts by default on every new panel; they make no sense for a target count (you would see red at 80 targets), you would read and change them in a real dashboard. Here, leave them: what matters is recognizing them in the JSON.

Step 8 — Save: a name, an address

At the top right, Save. The Save dashboard dialog opens, with two tabs, Details and Changes 3, and three fields: Title (New dashboard), Description, Folder (Dashboards).

What to look at first: clear the title and click Save: the word Required appears in red under Title and the Save button grays out. A dashboard must have a name. Type:

text
Atelier M1 - Cibles Prometheus

The button becomes active again. Click Save.

text
Dashboard saved

What to look at next: the Dashboard saved banner, then the page address:

text
http://localhost:3000/d/adc947c/atelier-m1-cibles-prometheus?orgId=1&from=now-6h&to=now&timezone=browser

Three things in this address. adc947c is the uid, drawn at random by Grafana: it is the identity of the dashboard, note it down. atelier-m1-cibles-prometheus is the title formatted as an address; it is only there for readability. from=now-6h&to=now is the period displayed, Last 6 hours by default for a new dashboard. The pencil at the top right (Edit) has replaced Save: you are in read mode. Main menu → Dashboards: your dashboard is in the list, at the root, next to the Labo observabilite folder.

To understand better: the Changes 3 tab

Before clicking Save, open Changes 3: Grafana shows, as JSON, the difference between a fresh dashboard and the one you are saving. The number is the count of modifications it tallied; on an empty dashboard just opened, the tab says Changes 1. This is the first time you see the JSON that will be written; step 9 has you re-read it in full.

Step 9 — Re-read the JSON: the three objects are inside

In a terminal, with your uid instead of adc947c:

bash
curl -s -u admin:aiopsatlas2026 http://localhost:3000/api/dashboards/uid/adc947c

The answer is a JSON document. Its meta part:

json
"meta": {
  "slug": "atelier-m1-cibles-prometheus",
  "url": "/d/adc947c/atelier-m1-cibles-prometheus",
  "created": "2026-09-15T21:21:02Z",
  "version": 1,
  "folderTitle": "General"
}

And in dashboard, the panels array contains a single element, your panel (shortened to the fields that matter):

json
{
  "type": "stat",
  "title": "Cibles surveillées",
  "datasource": { "type": "prometheus", "uid": "prometheus" },
  "targets": [
    {
      "datasource": { "type": "prometheus", "uid": "prometheus" },
      "editorMode": "code",
      "expr": "count(up)",
      "legendFormat": "__auto",
      "range": true,
      "refId": "A"
    }
  ],
  "fieldConfig": {
    "defaults": {
      "color": { "mode": "thresholds" },
      "thresholds": {
        "mode": "absolute",
        "steps": [
          { "color": "green", "value": 0 },
          { "color": "red", "value": 80 }
        ]
      }
    }
  },
  "options": {
    "colorMode": "value",
    "graphMode": "area",
    "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }
  },
  "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
  "id": 1,
  "pluginVersion": "13.2.2"
}

What to look at: the three objects of the workshop, written in black and white. The source: "datasource": { "type": "prometheus", "uid": "prometheus" }, the uid from the provisioning file of step 1. The query: "expr": "count(up)", in targets, with "refId": "A" (the letter of the line in Queries) and "editorMode": "code". The visualization: "type": "stat", and its settings from step 7: "calcs": ["lastNotNull"] is the Last * of the screen, steps are the two Thresholds, green at the base and red at 80. gridPos says where the frame is on the grid: top left, 12 columns wide out of 24, 8 rows high. A Grafana dashboard is nothing but that: this document, saved under a uid. This is your deliverable: this block, with stat, count(up) and prometheus circled.

To understand better: the same JSON in the interface

On the dashboard page, Edit (the pencil) → in the right sidebar, Options (the gear) → View all settingsJSON Model tab. It is the same document, without the meta part, and you can modify it there then Save. It is also this document that the kit ships for its three dashboards, in grafana/provisioning/dashboards/*.json: open api-catalogue.json and search for "expr": you will read queries from the guided practice there, exactly in the format above. A dashboard is shared by sending this file.

Step 10 — Delete everything, and prove it

On the dashboard page, EditOptionsView all settings. The Settings page opens (tabs General, Annotations, Variables, Links, Versions, Permissions, JSON Model). At the very bottom, the red Delete dashboard button.

text
Delete
Do you want to delete this dashboard?
Atelier M1 - Cibles Prometheus
Type "Delete" to confirm

What to look at: the dialog's Delete button is grayed out as long as you have not typed the word Delete in the field. Type it, click Delete.

text
Dashboard deleted
View deleted dashboards

Grafana takes you back to the home page. Proof through the API, with your uid:

bash
curl -s -u admin:aiopsatlas2026 http://localhost:3000/api/dashboards/uid/adc947c
json
{"message":"Dashboard not found"}

And the complete list of dashboards:

bash
curl -s -u admin:aiopsatlas2026 "http://localhost:3000/api/search?type=dash-db"

Three entries, the kit's three, all in the Labo observabilite folder: api-catalogue (« API catalogue — signaux dorés », catalog API — golden signals), hote-conteneurs (« Hôte et conteneurs », host and containers), journaux-api (« Journaux de l'API », API logs). Yours is no longer there. The deletion dialog said so: Grafana keeps deleted dashboards in a history for up to twelve months (Recently deleted on the Dashboards page), from where they can be restored; for what you do in this course, deleted means deleted.

Final check

Redo the ten gestures from memory, in order, and tick:

  • Connections → Data sources → Prometheus → Save & test returns Successfully queried the Prometheus API.
  • In Explore, up in Code mode returns 8 series.
  • count(up returns a red box bad_data: … parse error: unclosed left parenthesis; count(up) returns {} and 8.
  • Dashboards → New → New dashboard opens an empty New dashboard page, with Save already visible.
  • Add → Panel creates New panel; the Title field renames the frame live.
  • count(up) then Run queries fills Suggestions with the value 8 on every thumbnail.
  • The Stat thumbnail displays a big 8; Calculation is Last *; Thresholds: Base green, 80 red.
  • Empty title: Required and Save grayed out; title Atelier M1 - Cibles Prometheus: Dashboard saved banner and a uid in the address.
  • curl … /api/dashboards/uid/<uid> contains "type": "stat", "expr": "count(up)", "uid": "prometheus".
  • After Delete dashboard, the same curl returns {"message":"Dashboard not found"} and /api/search?type=dash-db lists exactly three dashboards.

On the lab side, nothing has moved: etat still displays 10/10 services, 8/8 cibles up, 0 alertes actives. Grafana wrote then erased one row in its own database; Prometheus saw nothing go by.

If it breaks

Show the cases where it breaks

The login page appears and admin / admin is refused. The kit's password is aiopsatlas2026 (set in docker-compose.yml, GF_SECURITY_ADMIN_PASSWORD variable). On the command line, the same pair: -u admin:aiopsatlas2026; with a wrong password, the API returns {"message":"Invalid username or password", …, "statusCode":401}.

Save & test returns an error instead of the green banner. Prometheus is stopped or restarting: etat, then wait for labo-prometheus to be healthy and retry. If you changed the URL by mistake, restore http://prometheus:9090 (not localhost: from the Grafana container, localhost is Grafana itself).

Explore returns No data on up without a red box. Look at the source selector at the top: you may be on Loki (the guided practice left you there). Switch back to Prometheus. Also check the period at the top right: it must end at now.

The query field refuses what I type, or the text appears twice. You are in Builder: the free field only exists in Code. Switch, clear, retype.

The panel stays on "Run a query to visualize it here". The query is written but has not been run: Run queries or Shift+Enter. If the Suggestions pane stays empty after that, the query has an error: the red box is under the field, scroll down.

The dialog's Save stays grayed out, with Required under the title. The title is empty. Type one. If you already saved a dashboard with the same name in a previous attempt, Grafana accepts it anyway: two dashboards can bear the same title, they have different uids. You will then have two entries in the list; delete the extra one (step 10).

I cannot find the uid. It is in the address, right after /d/: http://localhost:3000/d/adc947c/…. Otherwise, curl -s -u admin:aiopsatlas2026 "http://localhost:3000/api/search?query=Atelier" returns the "uid" field of every dashboard whose title contains Atelier.

curl returns {"message":"Dashboard not found"} in step 9 while the dashboard is on screen. The uid is miscopied (capitals, extra character). Copy it again from the address.

I deleted a kit dashboard by mistake. It is in Dashboards → Recently deleted: restore it. Otherwise, docker compose restart grafana: provisioning re-reads grafana/provisioning/dashboards/ at startup and recreates the three dashboards from their JSON files (tableaux-de-bord.yml, updateIntervalSeconds: 30).

I modified a kit dashboard and Save refuses. Normal: allowUiUpdates: false in tableaux-de-bord.yml. The kit's three dashboards are read, not overwritten; save your version under another name.