Skip to content

Recipes & examples

Enable trace logging of HTTP requests/responses

If you want to debug one of your monitors, you can enable trace logging of HTTP requests and responses. This will log all the requests and responses made by Kuvasz to your monitors. All you need to do is to add the following configuration to your YAML configuration file:

logger:
  levels:
    io.micronaut.http.client: TRACE # (1)!
  1. You can also use DEBUG, but it won't log the request and response bodies, only the headers and status codes.

Keeping secrets out of your configuration file

Your kuvasz.yml is a good candidate for version control, but it's also the place where quite a lot of sensitive data tends to end up: webhook URLs, PagerDuty integration keys, SMTP credentials, your OIDC client secret, and so on.

There are two built-in mechanisms to keep those out of the file you commit, and you can freely combine them.

Referencing environment variables from the YAML

Any value in your YAML configuration can reference an environment variable with the ${VARIABLE_NAME} syntax. This works everywhere, not only for the settings that have a dedicated environment variable, so it's a good fit for integrations, which can be configured only via the YAML file.

kuvasz.yml
integrations:
  slack:
    - name: alerts
      webhook-url: ${SLACK_WEBHOOK_URL}
  pagerduty:
    - name: oncall
      integration-key: ${PAGERDUTY_INTEGRATION_KEY}
smtp-config:
  host: 'smtp.your-domain.com'
  port: ${SMTP_PORT:`465`} # (1)!
  username: 'kuvasz@your-domain.com'
  password: ${SMTP_PASSWORD}
  1. With the ${VARIABLE_NAME:fallback} syntax you can provide a default value that is used when the variable isn't set. Placeholders are resolved before the value is converted to its target type, so they work for numbers and booleans too, even if you quote them.

The variables themselves can then come from wherever your orchestrator takes them, e.g. from an .env file that is not committed:

docker-compose.yml
services:
  kuvasz:
    # ...
    env_file:
      - ./kuvasz.env # (1)!
  1. Keep this file out of your repository (e.g. by adding it to your .gitignore).

Missing variables are fatal

If a referenced variable is not set and you didn't provide a default for it, Kuvasz refuses to start. This is intentional: it's much better to fail fast than to silently run with a half-configured integration.

Keep in mind that environment variables are readable for anyone who has access to your Docker daemon (e.g. via docker inspect). If you'd like your secrets to live on the disk only, take a look at the next option.

Splitting your configuration into multiple files

The official Docker image presets the MICRONAUT_CONFIG_FILES environment variable to /config/kuvasz.yml, which is why that's the path where your configuration file should be mounted by default. However, you can override this variable from the outside with a comma-separated list of files, and Kuvasz will load and merge all of them.

This lets you keep your regular configuration in a file you're happy to commit, and your secrets in a separate one that is provided as a Docker secret:

docker-compose.yml
services:
  kuvasz:
    image: kuvaszmonitoring/kuvasz:latest
    # ...
    environment:
      # ...
      MICRONAUT_CONFIG_FILES: /config/kuvasz.yml,/run/secrets/kuvasz-secrets.yml # (1)!
    volumes:
      - ./kuvasz.yml:/config/kuvasz.yml
    secrets:
      - kuvasz-secrets.yml # (2)!
secrets:
  kuvasz-secrets.yml: # (3)!
    file: ./kuvasz-secrets.yml
  1. The order matters: if the very same setting is present in more than one file, the last one wins.
  2. Docker mounts it into the container as /run/secrets/kuvasz-secrets.yml.
  3. The name of the secret determines the file name inside the container, so it has to end with .yml (or .yaml), see the note about the extension below.
kuvasz.yml (committed)
app-config:
  event-data-retention-days: 365
http-monitors:
  - name: my_website
    url: https://your-domain.com
    uptime-check-interval: 60
    integrations:
      - 'slack:alerts' # (1)!
  1. Referencing an integration that is defined in the other file works perfectly fine, the two files are merged before anything is validated.
kuvasz-secrets.yml (not committed)
integrations:
  slack:
    - name: alerts
      webhook-url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXX'
  pagerduty:
    - name: oncall
      integration-key: YourOwnIntegrationKey

Tip

Make sure that the file is readable inside the container, otherwise Kuvasz won't start!

This approach isn't tied to Docker secrets at all: any additional file that is readable inside the container works, no matter whether it comes from a plain bind mount, an encrypted volume, a Kubernetes Secret mounted as a file, or a secret manager that renders it to the disk.

Things to keep in mind

  • Every file in the list must exist, otherwise Kuvasz won't start (Failed to read configuration file: ...). The image ships an empty placeholder at /config/kuvasz.yml, so keeping that entry in the list is safe even if you don't mount anything there.
  • The extension matters: every file must end with .yml or .yaml, otherwise the startup fails with an Unsupported properties file format error. This is especially important for Docker secrets, where the file name is derived from the name of the secret.
  • Later files win over the earlier ones for the settings that are present in more than one of them, and the YAML configuration as a whole takes precedence over the environment variables.
  • Never split the very same list across multiple files! Lists (e.g. integrations.slack, http-monitors, status-pages) are merged by their position, so if integrations.slack is present in two files, the entries of the last one will overwrite the entries of the first one one by one, and the extra entries of the first file are dropped entirely. A given list should always live in exactly one file, but different lists (even under the same parent key, like integrations.slack and integrations.pagerduty) can be spread across different files without any problem.

Home Assistant RESTful integration

Warning

While the methods described below still work, there is a dedicated integration for Kuvasz, which is the recommended way to integrate your monitors with Home Assistant, as it provides a better user experience, more features, and it's easier to set up and maintain.

Kuvasz can be easily integrated with Home Assistant using the RESTful integration by using its API. This allows you to create sensors for your most precious monitors and use them in your automations, scripts, or just to visualize the status of your monitors. You can even build your own custom dashboard with the data from your monitors!

Tip

If you have the authentication disabled, you can skip setting up your API key as a secret and you can also omit the X-API-KEY header in your requests.

Define your secret in Home Assistant

secrets.yaml
kuvasz_api_key: "ThisShouldBeVeryVerySecure"

Sensor with JSON attributes

configuration.yaml
sensor:
  - name: "kuvasz docs metrics"
    unique_id: metrics_kuvasz_docs
    platform: rest
    verify_ssl: false
    scan_interval: 60
    resource: http://kuvasz.home/api/v2/http-monitors/107
    headers:
      X-API-KEY: !secret kuvasz_api_key
    value_template: "OK"
    json_attributes:
      - id
      - name
      - url
      - uptimeCheckInterval
      - enabled
      - sslCheckEnabled
      - createdAt
      - updatedAt
      - uptimeStatus
      - uptimeStatusStartedAt
      - lastUptimeCheck
      - nextUptimeCheck
      - sslStatus
      - sslStatusStartedAt
      - lastSSLCheck
      - nextSSLCheck
      - uptimeError
      - sslError
      - requestMethod
      - latencyHistoryEnabled
      - forceNoCache
      - followRedirects
      - sslExpiryThreshold
      - failureCountThreshold
      - sslValidUntil
      - integrations
      - effectiveIntegrations

Result:

HA JSON attributes

Binary sensor for uptime as connectivity

binary_sensor:
  - name: "kuvasz docs uptime status"
    unique_id: uptime_kuvasz_docs
    platform: rest
    verify_ssl: false
    scan_interval: 60
    resource: http://kuvasz.home/api/v2/http-monitors/107
    headers:
      X-API-KEY: !secret kuvasz_api_key
    device_class: connectivity
    value_template: >
      {% set status = value_json.uptimeStatus %}
      {{ status == 'UP' }}
    availability: >
      {{ value_json.uptimeStatus is not none }}

Result:

HA connectivity

Exposing status pages on subdomains behind a reverse proxy

If you want to expose your status pages on subdomains (e.g. status.yourdomain.com), you can do so by using a reverse proxy (e.g. Caddy, Nginx, Traefik, etc.). Here is an example configuration for Caddy:

status.your-domain.com {
    reverse_proxy {YOUR_KUVASZ_HOST}:8080
    rewrite /public/* {uri} # (1)!
    rewrite * /status{uri} # (2)!
}
  1. This is needed to serve the static assets (CSS, JS, images, etc.) correctly.
  2. This will rewrite all requests to /status, which is the path where the status pages are served.

The configuration snippet above exposes the default status page (that is located under /status on Kuvasz) on the root of the configured subdomain (i.e. status.your-domain.com), and also proxies the other status pages (e.g. /status/your-custom-status-page) on requesting status.your-domain.com/your-custom-status-page.

Providing a custom root certificate for SSL checks

If you want to use a custom root certificate for SSL checks (e.g. if you're using a self-signed certificate, or a private CA), you can provide it by modifying the Java Keystore (JKS) in use, to add your custom root certificate to it.

The advantage of this approach is, that you only need to do the following steps when:

  • you have a new cert, or you would like to update the existing custom one
  • we change the base image of the Docker build (should not happen in the near future)

Otherwise you can just use your own "patched" cacerts for every new version of Kuvasz.

Preparing the custom cacerts file

# 1. Pull the current image
docker pull kuvaszmonitoring/kuvasz:latest
# 2. Copy the "original" cacerts to a local file
docker run --rm --entrypoint cat kuvaszmonitoring/kuvasz:latest /opt/java/openjdk/lib/security/cacerts > cacerts
# 3. This is the tricky step: we attach back the current folder where the cacerts, and also the custom certificate should exist and we add the custom certificate to the keystore
docker run --rm -v `pwd`:/tmp/certs kuvaszmonitoring/kuvasz:latest sh -c 'cd /tmp/certs && keytool -keystore cacerts -storepass changeit -noprompt -trustcacerts -importcert -alias your-custom-alias -file your-custom-cert.crt'

Watch out for your-custom-alias and your-custom-cert.crt in the example, these are the moving parts, depending on your own preferences.

Attaching the modified cacerts to Kuvasz

This is easier, and quite straightforward, you just need to mount another volume with your cacerts file from the steps above:

# ...
volumes:
  - /path/to/your/patched/cacerts:/opt/java/openjdk/lib/security/cacerts:ro
# ...
Make sure that you completely re-create your container after these changes!

Backup & Restore with YAML

It might be useful to create sometimes a backup from your monitors and status pages in case you didn't configure them via a YAML file, because later you might want to switch to that method, or you just want to make it possible to restore them in case of an accidental deletion, for example.

  1. To do so, you can use the Web UI (Settings > Backup & Restore) or the API (Monitors, Status pages). The response in both cases will be a YAML file, which you can save to a safe place.
  2. To restore those files, you can just simply copy the content of them as-is into your own YAML configuration file, and restart your instance of Kuvasz.
  3. If you would like to continue using the UI or the API to manage your monitors and status pages, you need to remove the corresponding sections from your YAML configuration file after the successful restore and restart your instance once again. After that, you should be able to manage everything via the UI or the API as before.

Homepage integration

Homepage is a really nice open-source tool to create a personal dashboard with links, widgets, and more. You can integrate Kuvasz into your homepage dashboard by using their custom API widget.

The examples below doesn't necessarily map all the possible fields, but it gives you a good starting point to create your own widgets.

Global HTTP stats

global HTTP stats on homepage

Expand for example configuration
- Global HTTP stats:
    id: kuvasz-http-stats
    icon: sh-kuvasz
    widget:
      type: customapi
      display: list
      url: https://demo.kuvasz-uptime.dev/api/v2/http-monitors/stats
      refreshInterval: 300
      headers:
         X-Api-Key: KuvaszDemoAPIKey
      mappings:
        - label: Total
          field: actual.uptimeStats.total
        - label: Up
          field: actual.uptimeStats.up
        - label: Down
          field: actual.uptimeStats.down
        - label: Paused
          field: actual.uptimeStats.paused
        - label: Pending
          field: actual.uptimeStats.inProgress
        - label: Last incident
          field: actual.uptimeStats.lastIncident
          format: date
          locale: en
          dateStyle: long
          timeStyle: long
        - label: Incidents (last 7 days)
          field: history.uptimeStats.incidents
        - label: Affected monitors (last 7 days)
          field: history.uptimeStats.affectedMonitors
        - label: Uptime ratio (last 7 days)
          field: history.uptimeStats.uptimeRatio
          format: float
          scale: 100
          suffix: '%'
        - label: Total downtime (last 7 days)
          field: history.uptimeStats.totalDowntimeSeconds
          format: duration
        - label: SSL Valid
          field: actual.sslStats.valid
        - label: SSL Invalid
          field: actual.sslStats.invalid
        - label: SSL Expiring
          field: actual.sslStats.willExpire

Individual monitor stats

individual monitor stats on homepage

Expand for example configuration
- kuvasz-uptime.dev stats:
    id: kuvasz-uptime-http-stats
    icon: sh-kuvasz
    widget:
      type: customapi
      display: list
      url: https://demo.kuvasz-uptime.dev/api/v2/http-monitors/38/stats
      refreshInterval: 300
      headers:
        X-Api-Key: KuvaszDemoAPIKey
      mappings:
        - label: Average latency
          field: latencyStats.averageLatencyInMs
          format: number
          suffix: ms
        - label: P95 latency
          field: latencyStats.p95LatencyInMs
          format: number
          suffix: ms

        - label: Incidents (last 7 days)
          field: uptimeHistory.incidents
        - label: Uptime ratio (last 7 days)
          field: uptimeHistory.uptimeRatio
          format: float
          scale: 100
          suffix: '%'
        - label: Total downtime (last 7 days)
          field: uptimeHistory.totalDowntimeSeconds
          format: duration

Individual monitor details

individual monitor details on homepage

Expand for example configuration
- kuvasz-uptime.dev details:
    id: kuvasz-uptime-http-details
    icon: sh-kuvasz
    widget:
      type: customapi
      display: list
      url: https://demo.kuvasz-uptime.dev/api/v2/http-monitors/38
      refreshInterval: 300
      headers:
        X-Api-Key: KuvaszDemoAPIKey
      mappings:
        - label: Name
          field: name
        - label: Status
          field: uptimeStatus
          remap:
            - value: UP
              to: 🟢
            - value: DOWN
              to: 🔴
            - any: true
              to: 🟡
        - label: Status started
          field: uptimeStatusStartedAt
          format: relativeDate
          locale: en
          style: long
        - label: Last check
          field: lastUptimeCheck
          format: relativeDate
          locale: en
          style: long
        - label: Next check
          field: nextUptimeCheck
          format: relativeDate
          locale: en
          style: long
        - label: SSL status
          field: sslStatus
          remap:
            - value: VALID
              to: 🔒
            - value: INVALID
              to: 🔓
            - value: WILL_EXPIRE
              to: 🕣
            - any: true
              to: 🟡
        - label: Last SSL check
          field: lastSSLCheck
          format: relativeDate
          locale: en
          style: long

Overview with a clickable list of monitors

dynamic list of monitors on homepage

Expand for example configuration
- Monitors:
    id: kuvasz-uptime-http-dynamic-list
    icon: sh-kuvasz
    widget:
      type: customapi
      display: dynamic-list
      url: https://demo.kuvasz-uptime.dev/api/v2/http-monitors?enabled=true
      refreshInterval: 300
      headers:
        X-Api-Key: KuvaszDemoAPIKey
      mappings:
        name: name
        label: uptimeStatus
        limit: 5
        format: text
        remap:
          - value: UP
            to: 🟢
          - value: DOWN
            to: 🔴
          - any: true
            to: 🟡
        target: https://demo.kuvasz-uptime.dev/http-monitors/{id}

Glance custom widgets

Glance is a self-hosted dashboard that lets you put all your feeds, widgets and services on a single page. You can integrate Kuvasz into your Glance dashboard using its custom-api widget.

Tip

If you have the authentication disabled, you can skip the api-key option and the X-Api-Key header in the templates.

Across all the widgets it's better to set your host and API key via environment variables (e.g. KUVASZ_HOST and KUVASZ_API_KEY), so you don't have to repeat them in every widget.

Global stats

This widget can be used for both HTTP and push monitor stats, depending on the configuration. It shows the total count, the up and down monitors, the number of incidents, the affected monitors and the uptime ratio over a given period.

Kuvasz stats widget on Glance

Options

  • base-url: your Kuvasz host (mandatory)
  • api-key: your API key for Kuvasz (optional if you disabled authentication)
  • monitor-type: http, push, icmp, tcp or dns (mandatory)
  • period: an ISO-8601 period string for the cumulative stats (incidents, affected monitors, uptime ratio), e.g. PT24H or P7D. The widget default is 24 hours (PT24H).
Expand for example configuration
- type: custom-api
  title: Kuvasz HTTP stats
  cache: 5m
  options:
    base-url: ${KUVASZ_HOST}
    api-key: ${KUVASZ_API_KEY}
    period: P7D
    monitor-type: http
  template: |
    {{/* Required config options */}}
    {{ $baseURL := .Options.StringOr "base-url" "" }}
    {{ $monitorType := .Options.StringOr "monitor-type" "" }}

    {{/* Optional config options */}}
    {{ $apiKey := .Options.StringOr "api-key" "" }}
    {{ $period := .Options.StringOr "period" "PT24H" }}

    {{ $stats := newRequest (print $baseURL "/api/v2/" $monitorType "-monitors/stats/?period=" $period )
      | withHeader "X-Api-Key" $apiKey
      | getResponse }}
    {{ $uptimeValue := mul 100 ($stats.JSON.Float "history.uptimeStats.uptimeRatio") }}

    <div class="widget-small-content-bounds">
      <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; text-align: center;">
        <div>
          <p class="size-h3 color-highlight">{{ $stats.JSON.Int "actual.uptimeStats.total" }}</p>
          <p class="size-h6">TOTAL</p>
        </div>
        <div>
          <p class="size-h3 color-highlight">{{ $stats.JSON.Int "actual.uptimeStats.up" }}</p>
          <p class="size-h6">UP</p>
        </div>
        <div>
          <p class="size-h3 color-highlight">{{ $stats.JSON.Int "actual.uptimeStats.down" }}</p>
          <p class="size-h6">DOWN</p>
        </div>
      </div>
      <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; text-align: center;">
        <div>
          <p class="size-h3 color-highlight">{{ $stats.JSON.Int "history.uptimeStats.incidents" }}</p>
          <p class="size-h6">INCIDENTS</p>
        </div>
        <div>
          <p class="size-h3 color-highlight">{{ $stats.JSON.Int "history.uptimeStats.affectedMonitors" }}</p>
          <p class="size-h6">AFFECTED</p>
        </div>
        <div>
          <p class="size-h3 color-highlight">{{ printf "%.2f" $uptimeValue }}%</p>
          <p class="size-h6">UPTIME</p>
        </div>
      </div>
    </div>

HTTP monitors

Lists the HTTP monitors from Kuvasz with their uptime ratio, latency metrics (configurable) and state. You can also set up custom icons, URLs, or decide which monitors to show.

Kuvasz HTTP monitors widget (full style) on Glance

Kuvasz HTTP monitors widget (compact style) on Glance

Options

  • base-url: your Kuvasz host (mandatory)
  • api-key: your API key for Kuvasz (optional if you disabled authentication)
  • style: either full or compact. The full version can have custom icons and displays 2 metrics, while the compact variant only shows 1 metric and doesn't support custom icons. Default is full.
  • period: an ISO-8601 period string for the cumulative stats, e.g. PT24H or P7D. The widget default is 24 hours (PT24H).
  • show-metrics: whether to load and display metrics at all. Be aware that showing metrics for a lot of monitors could slow down your dashboard, since the metrics need to be fetched on a per-monitor basis.
  • compact-metric: the metric to show in the compact variant, either uptime or latency, default is uptime
  • latency-metric: the latency metric to show, one of average, min, max, p90, p95, p99, default is average
  • show-failing-only: if true, only the failing (down) monitors will be shown
  • show-configured-only: if true, only the explicitly configured monitors will be shown (see below)

Explicit monitor configs

Under options you can configure your monitors by their name, adding custom icons or overwriting their links (by default the Kuvasz monitor detail page is used as the link). To use a custom icon, use the monitor's name as the property key with any of the following:

  • Simple Icons with the si: prefix
  • Dashboard icons with di:
  • Material Design Icons with mdi:
  • Self-hosted icons with sh:
  • or a plain, direct URL to an image
'cnn.com': si:cnn # using Simple Icons
'cnn.com-url': https://cnn.com # overwriting the monitor's link on the dashboard

When show-configured-only is true, only the monitors that have a custom icon or a custom URL will be shown.

Expand for example configuration
- type: custom-api
  title: HTTP monitors
  cache: 5m
  options:
    base-url: ${KUVASZ_HOST}
    api-key: ${KUVASZ_API_KEY}
    style: full
    period: P1D
    show-metrics: true
    compact-metric: latency
    latency-metric: p95
    show-failing-only: false
    show-configured-only: true
    'cnn.com': si:cnn
    'cnn.com-url': https://cnn.com
    'google.de': si:google
    'DOWN monitor example-url': https://example.com
  template: |
    {{/* Required config options */}}
    {{ $baseURL := .Options.StringOr "base-url" "" }}

    {{/* Optional config options */}}
    {{ $apiKey := .Options.StringOr "api-key" "" }}
    {{ $period := .Options.StringOr "period" "PT24H" }}
    {{ $style := .Options.StringOr "style" "full" }}
    {{ $showMetrics := .Options.BoolOr "show-metrics" false }}
    {{ $compactMetric := .Options.StringOr "compact-metric" "uptime" }}
    {{ $latencyMetric := .Options.StringOr "latency-metric" "average" }}
    {{ $showFailingOnly := .Options.BoolOr "show-failing-only" false }}
    {{ $showOnlyConfigured := .Options.BoolOr "show-configured-only" false }}

    {{ $monitors := newRequest (print $baseURL "/api/v2/http-monitors?enabled=true")
      | withHeader "X-Api-Key" $apiKey
      | getResponse }}

    {{ $options := .Options }}
    {{ $displayedItems := 0 }}

    {{ if eq $style "compact" }}
      <ul class="dynamic-columns list-gap-8 ">
      {{ range $i, $monitor := $monitors.JSON.Array "" }}
          {{ $name := $monitor.String "name" }}
          {{ $key := $monitor.String "id" }}
          {{ $icon := $options.StringOr $name "" }}
          {{ $linkUrlOption := $options.StringOr (concat $name "-url") "" }}
          {{ $linkUrl := $options.StringOr (concat $name "-url") (concat $baseURL "/http-monitors/" $key) }}
          {{ $status := $monitor.String "uptimeStatus" }}
          {{ $isUp := eq $status "UP" }}
          {{ $isDown := eq $status "DOWN" }}
          {{ $hasLatencyMetrics := false }}

          {{ if and $showFailingOnly (not $isDown) }} {{ continue }} {{ end }}
          {{ if and $showOnlyConfigured (eq $linkUrlOption "") (eq $icon "") }} {{ continue }} {{ end }}
          {{ $displayedItems = add $displayedItems 1 }}

          {{ $metricValue := "" }}
          {{ $stats := "" }}

          {{ if $showMetrics }}
            {{ $stats = newRequest (print $baseURL "/api/v2/http-monitors/" $key "/stats/?period=" $period )
                | withHeader "X-Api-Key" $apiKey
                | getResponse }}
            {{ $hasLatencyMetrics = $stats.JSON.Exists "latencyStats.averageLatencyInMs" }}
          {{ end }}

          <div class="flex items-center gap-12">
            <a class="size-title-dynamic color-highlight text-truncate block grow" href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">{{ $name }}</a>
            <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
              {{ if eq $compactMetric "uptime" }}
                {{ $metricValue = mul 100 ($stats.JSON.Float "uptimeHistory.uptimeRatio") }}
                <div>{{ printf "%.2f" $metricValue }}%</div>
              {{ else if $hasLatencyMetrics }}
                <div>{{ $stats.JSON.Int (printf "latencyStats.%sLatencyInMs" $latencyMetric) }}ms</div>
              {{ end }}
            </a>

            {{ if $isUp }}
              <div class="monitor-site-status-icon-compact">
                <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                    <svg fill="var(--color-positive)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                      <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd" />
                    </svg>
                </a>
              </div>
            {{ else if $isDown }}
              <div class="monitor-site-status-icon-compact" title="{{ $monitor.String "uptimeError" }}">
                <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                    <svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                      <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd" />
                    </svg>
                </a>
              </div>
            {{ else }}
              <div class="monitor-site-status-icon-compact" title="Not checked yet">
                <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                    <svg fill="var(--color-text-subdue)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                      <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM7 9.25a.75.75 0 0 0 0 1.5h6a.75.75 0 0 0 0-1.5H7Z" clip-rule="evenodd" />
                    </svg>
                </a>
              </div>
            {{ end }}
          </div>
        {{ end }}
      </ul>
    {{ else }}
      <ul class="dynamic-columns list-gap-20 list-with-separator">
      {{ range $i, $monitor := $monitors.JSON.Array "" }}
          {{ $name := $monitor.String "name" }}
          {{ $key := $monitor.String "id" }}
          {{ $icon := $options.StringOr $name "" }}
          {{ $linkUrlOption := $options.StringOr (concat $name "-url") "" }}
          {{ $linkUrl := $options.StringOr (concat $name "-url") (concat $baseURL "/http-monitors/" $key) }}
          {{ $status := $monitor.String "uptimeStatus" }}
          {{ $isUp := eq $status "UP" }}
          {{ $isDown := eq $status "DOWN" }}
          {{ $hasLatencyMetrics := false }}

          {{ if and $showFailingOnly (not $isDown) }} {{ continue }} {{ end }}
          {{ if and $showOnlyConfigured (eq $linkUrlOption "") (eq $icon "") }} {{ continue }} {{ end }}
          {{ $displayedItems = add $displayedItems 1 }}

          {{ $uptimeValue := "" }}
          {{ $stats := "" }}

          {{ if $showMetrics }}
            {{ $stats = newRequest (print $baseURL "/api/v2/http-monitors/" $key "/stats/?period=" $period )
                | withHeader "X-Api-Key" $apiKey
                | getResponse }}
            {{ $hasLatencyMetrics = $stats.JSON.Exists "latencyStats.averageLatencyInMs" }}
            {{ $uptimeValue = mul 100 ($stats.JSON.Float "uptimeHistory.uptimeRatio") }}
          {{ end }}

          {{ $iconUrl := "" }}
          {{ if $icon }}
            {{ $iconPrefix := findMatch "^(si|di|mdi|sh):" $icon }}
            {{ $iconBase := replaceMatches "^(si|di|mdi|sh):" "" $icon }}

            {{ $iconExt := findMatch "\\.[a-z]+$" $iconBase }}
            {{ $iconExt := replaceMatches "\\." "" $iconExt }}
            {{ $iconBase = replaceMatches "\\.[a-z]+$" "" $iconBase }}
            {{ if eq $iconExt "" }} {{ $iconExt = "svg" }} {{ end }}

            {{ if eq $iconPrefix "si:" }}
              {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/" $iconBase ".svg" }}
            {{ else if eq $iconPrefix "di:" }}
              {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/" $iconExt "/" $iconBase "." $iconExt }}
            {{ else if eq $iconPrefix "mdi:" }}
              {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/" $iconBase ".svg" }}
            {{ else if eq $iconPrefix "sh:" }}
              {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/" $iconBase ".png" }}
            {{ else }}
              {{ $iconUrl = $icon }}
            {{ end }}
          {{ end }}

          <div class="monitor-site flex items-center gap-15">
            {{ if $iconUrl }}
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <img class="monitor-site-icon" src="{{ $iconUrl | safeURL }}" alt="" loading="lazy">
              </a>
            {{ end }}
            <div class="grow min-width-0">
              <a class="size-h3 color-highlight text-truncate block" href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">{{ $name }}</a>
              {{ if $showMetrics }}
                <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                  <ul class="list-horizontal-text">
                    <li class="{{ if $isDown }}color-negative{{ end }}">{{ printf "%.2f" $uptimeValue }}%</li>
                    {{ if $hasLatencyMetrics }}
                      <li>{{ $stats.JSON.Int (printf "latencyStats.%sLatencyInMs" $latencyMetric) }}ms</li>
                    {{ end }}
                  </ul>
                </a>
              {{ end }}
            </div>

            {{ if $isUp }}
              <div class="monitor-site-status-icon">
                <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                  <svg fill="var(--color-positive)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd" />
                  </svg>
                </a>
              </div>
            {{ else if $isDown }}
              <div class="monitor-site-status-icon" title="{{ $monitor.String "uptimeError" }}">
                <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                  <svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd" />
                  </svg>
                </a>
              </div>
            {{ else }}
              <div class="monitor-site-status-icon" title="Not checked yet">
                <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                  <svg fill="var(--color-text-subdue)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM7 9.25a.75.75 0 0 0 0 1.5h6a.75.75 0 0 0 0-1.5H7Z" clip-rule="evenodd" />
                  </svg>
                </a>
              </div>
            {{ end }}

          </div>
        {{ end }}
      </ul>
    {{ end }}

    {{ if eq $displayedItems 0 }}
      <div class="flex items-center justify-center gap-10 padding-block-5">
        <p>All sites are online</p>
        <svg class="shrink-0" style="width: 1.7rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--color-positive)">
          <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
        </svg>
      </div>
    {{ end }}

Push monitors

Very similar to the HTTP monitors widget, but latency is not relevant here.

Kuvasz push monitors widget on Glance

Options

  • base-url: your Kuvasz host (mandatory)
  • api-key: your API key for Kuvasz (optional if you disabled authentication)
  • period: an ISO-8601 period string for the cumulative stats, e.g. PT24H or P7D. The widget default is 24 hours (PT24H).
  • show-uptime: whether to load and display the uptime ratio. Be aware that showing it for a lot of monitors could slow down your dashboard, since it's fetched on a per-monitor basis.
  • show-failing-only: if true, only the failing (down) monitors will be shown
  • show-configured-only: if true, only the explicitly configured monitors will be shown. The explicit monitor config is the same as for the HTTP monitors.

Explicit monitor configs

Under options you can configure your monitors by their name, explicitly enabling them, or overwriting their links (by default the Kuvasz monitor detail page is used as the link).

'a failing job': true # explicitly adding the monitor to the displayed list
'a failing job-url': https://your-own-url.com # overwriting the link for it

When show-configured-only is true, only the monitors that have an explicit configuration entry (either for the visibility or for the URL) will be shown.

Expand for example configuration
- type: custom-api
  title: Push monitors
  cache: 5m
  options:
    base-url: ${KUVASZ_HOST}
    api-key: ${KUVASZ_API_KEY}
    period: P1D
    show-uptime: true
    show-failing-only: false
    show-configured-only: false
    'a failing job': true
    'a failing job-url': https://your-own-url.com
  template: |
    {{/* Required config options */}}
    {{ $baseURL := .Options.StringOr "base-url" "" }}

    {{/* Optional config options */}}
    {{ $apiKey := .Options.StringOr "api-key" "" }}
    {{ $period := .Options.StringOr "period" "PT24H" }}
    {{ $showUptime := .Options.BoolOr "show-uptime" false }}
    {{ $showFailingOnly := .Options.BoolOr "show-failing-only" false }}
    {{ $showOnlyConfigured := .Options.BoolOr "show-configured-only" false }}

    {{ $monitors := newRequest (print $baseURL "/api/v2/push-monitors?enabled=true")
      | withHeader "X-Api-Key" $apiKey
      | getResponse }}

    {{ $options := .Options }}
    {{ $displayedItems := 0 }}

    <ul class="dynamic-columns list-gap-8 ">
    {{ range $i, $monitor := $monitors.JSON.Array "" }}
        {{ $name := $monitor.String "name" }}
        {{ $key := $monitor.String "id" }}
        {{ $isConfigured := $options.BoolOr $name false }}
        {{ $linkUrlOption := $options.StringOr (concat $name "-url") "" }}
        {{ $linkUrl := $options.StringOr (concat $name "-url") (concat $baseURL "/push-monitors/" $key) }}
        {{ $status := $monitor.String "uptimeStatus" }}
        {{ $isUp := eq $status "UP" }}
        {{ $isDown := eq $status "DOWN" }}

        {{ if and $showFailingOnly (not $isDown) }} {{ continue }} {{ end }}
        {{ if and $showOnlyConfigured (eq $linkUrlOption "") (not $isConfigured) }} {{ continue }} {{ end }}
        {{ $displayedItems = add $displayedItems 1 }}

        {{ $stats := "" }}
        {{ $uptimeValue := "" }}

        {{ if $showUptime }}
          {{ $stats = newRequest (print $baseURL "/api/v2/push-monitors/" $key "/stats/?period=" $period )
              | withHeader "X-Api-Key" $apiKey
              | getResponse }}
          {{ $uptimeValue = mul 100 ($stats.JSON.Float "uptimeHistory.uptimeRatio") }}
        {{ end }}

        <div class="flex items-center gap-12">
          <a class="size-title-dynamic color-highlight text-truncate block grow" href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">{{ $name }}</a>
          {{ if $showUptime }}
            <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
              <div>{{ printf "%.2f" $uptimeValue }}%</div>
            </a>
          {{ end }}

          {{ if $isUp }}
            <div class="monitor-site-status-icon-compact">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                  <svg fill="var(--color-positive)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd" />
                  </svg>
              </a>
            </div>
          {{ else if $isDown }}
            <div class="monitor-site-status-icon-compact" title="{{ $monitor.String "uptimeError" }}">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                  <svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd" />
                  </svg>
              </a>
            </div>
          {{ else }}
            <div class="monitor-site-status-icon-compact" title="Not checked yet">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                  <svg fill="var(--color-text-subdue)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM7 9.25a.75.75 0 0 0 0 1.5h6a.75.75 0 0 0 0-1.5H7Z" clip-rule="evenodd" />
                  </svg>
              </a>
            </div>
          {{ end }}
        </div>
      {{ end }}
    </ul>

    {{ if eq $displayedItems 0 }}
      <div class="flex items-center justify-center gap-10 padding-block-5">
        <p>All sites are online</p>
        <svg class="shrink-0" style="width: 1.7rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--color-positive)">
          <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
        </svg>
      </div>
    {{ end }}

ICMP monitors

Lists the ping (ICMP) monitors from Kuvasz with their host, uptime ratio, latency and packet loss metrics (all configurable) and state. Just like the HTTP widget, it supports custom icons, custom links and filtering. Packet loss and latency metrics are only available when metrics history is enabled on the given monitor.

Options

  • base-url: your Kuvasz host (mandatory)
  • api-key: your API key for Kuvasz (optional if you disabled authentication)
  • period: an ISO-8601 period string for the cumulative stats, e.g. PT24H or P7D. The widget default is 24 hours (PT24H).
  • show-metrics: whether to load and display metrics at all. Be aware that showing metrics for a lot of monitors could slow down your dashboard, since the metrics need to be fetched on a per-monitor basis.
  • latency-metric: the latency metric to show, one of average, min, max, p90, p95, p99, default is average
  • packet-loss-metric: the packet loss metric to show, one of average, min, max, p90, p95, p99, default is average
  • show-packet-loss: whether to display the packet loss metric next to the latency, default is false
  • show-failing-only: if true, only the failing (down) monitors will be shown
  • show-configured-only: if true, only the explicitly configured monitors will be shown. The explicit monitor config is the same as for the HTTP monitors.
Expand for example configuration
- type: custom-api
  title: ICMP monitors
  cache: 5m
  options:
    base-url: ${KUVASZ_HOST}
    api-key: ${KUVASZ_API_KEY}
    period: P1D
    show-metrics: true
    latency-metric: average
    packet-loss-metric: max
    show-packet-loss: true
    show-failing-only: false
    show-configured-only: false
    'Local router': mdi:router-network
    'Local router-url': http://192.168.1.1
  template: |
    {{/* Required config options */}}
    {{ $baseURL := .Options.StringOr "base-url" "" }}

    {{/* Optional config options */}}
    {{ $apiKey := .Options.StringOr "api-key" "" }}
    {{ $period := .Options.StringOr "period" "PT24H" }}
    {{ $showMetrics := .Options.BoolOr "show-metrics" false }}
    {{ $latencyMetric := .Options.StringOr "latency-metric" "average" }}
    {{ $packetLossMetric := .Options.StringOr "packet-loss-metric" "average" }}
    {{ $showPacketLoss := .Options.BoolOr "show-packet-loss" false }}
    {{ $showFailingOnly := .Options.BoolOr "show-failing-only" false }}
    {{ $showOnlyConfigured := .Options.BoolOr "show-configured-only" false }}

    {{ $monitors := newRequest (print $baseURL "/api/v2/icmp-monitors?enabled=true")
      | withHeader "X-Api-Key" $apiKey
      | getResponse }}

    {{ $options := .Options }}
    {{ $displayedItems := 0 }}

    <ul class="dynamic-columns list-gap-20 list-with-separator">
    {{ range $i, $monitor := $monitors.JSON.Array "" }}
        {{ $name := $monitor.String "name" }}
        {{ $key := $monitor.String "id" }}
        {{ $host := $monitor.String "host" }}
        {{ $icon := $options.StringOr $name "" }}
        {{ $linkUrlOption := $options.StringOr (concat $name "-url") "" }}
        {{ $linkUrl := $options.StringOr (concat $name "-url") (concat $baseURL "/icmp-monitors/" $key) }}
        {{ $status := $monitor.String "uptimeStatus" }}
        {{ $isUp := eq $status "UP" }}
        {{ $isDown := eq $status "DOWN" }}
        {{ $hasLatency := false }}
        {{ $hasPacketLoss := false }}

        {{ if and $showFailingOnly (not $isDown) }} {{ continue }} {{ end }}
        {{ if and $showOnlyConfigured (eq $linkUrlOption "") (eq $icon "") }} {{ continue }} {{ end }}
        {{ $displayedItems = add $displayedItems 1 }}

        {{ $uptimeValue := "" }}
        {{ $stats := "" }}

        {{ if $showMetrics }}
          {{ $stats = newRequest (print $baseURL "/api/v2/icmp-monitors/" $key "/stats/?period=" $period )
              | withHeader "X-Api-Key" $apiKey
              | getResponse }}
          {{ $hasLatency = $stats.JSON.Exists "latencyStats.averageLatencyInMs" }}
          {{ $hasPacketLoss = $stats.JSON.Exists "packetLossStats.averagePacketLossPercentage" }}
          {{ $uptimeValue = mul 100 ($stats.JSON.Float "uptimeHistory.uptimeRatio") }}
        {{ end }}

        {{ $iconUrl := "" }}
        {{ if $icon }}
          {{ $iconPrefix := findMatch "^(si|di|mdi|sh):" $icon }}
          {{ $iconBase := replaceMatches "^(si|di|mdi|sh):" "" $icon }}

          {{ $iconExt := findMatch "\\.[a-z]+$" $iconBase }}
          {{ $iconExt := replaceMatches "\\." "" $iconExt }}
          {{ $iconBase = replaceMatches "\\.[a-z]+$" "" $iconBase }}
          {{ if eq $iconExt "" }} {{ $iconExt = "svg" }} {{ end }}

          {{ if eq $iconPrefix "si:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/" $iconBase ".svg" }}
          {{ else if eq $iconPrefix "di:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/" $iconExt "/" $iconBase "." $iconExt }}
          {{ else if eq $iconPrefix "mdi:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/" $iconBase ".svg" }}
          {{ else if eq $iconPrefix "sh:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/" $iconBase ".png" }}
          {{ else }}
            {{ $iconUrl = $icon }}
          {{ end }}
        {{ end }}

        <div class="monitor-site flex items-center gap-15">
          {{ if $iconUrl }}
            <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
              <img class="monitor-site-icon" src="{{ $iconUrl | safeURL }}" alt="" loading="lazy">
            </a>
          {{ end }}
          <div class="grow min-width-0">
            <a class="size-h3 color-highlight text-truncate block" href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">{{ $name }}</a>
            <ul class="list-horizontal-text">
              <li class="color-subdue">{{ $host }}</li>
              {{ if $showMetrics }}
                <li class="{{ if $isDown }}color-negative{{ end }}">{{ printf "%.2f" $uptimeValue }}%</li>
                {{ if $hasLatency }}
                  <li>{{ $stats.JSON.Int (printf "latencyStats.%sLatencyInMs" $latencyMetric) }}ms</li>
                {{ end }}
                {{ if and $showPacketLoss $hasPacketLoss }}
                  <li>{{ $stats.JSON.Int (printf "packetLossStats.%sPacketLossPercentage" $packetLossMetric) }}% loss</li>
                {{ end }}
              {{ end }}
            </ul>
          </div>

          {{ if $isUp }}
            <div class="monitor-site-status-icon">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-positive)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ else if $isDown }}
            <div class="monitor-site-status-icon" title="{{ $monitor.String "uptimeError" }}">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ else }}
            <div class="monitor-site-status-icon" title="Not checked yet">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-text-subdue)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM7 9.25a.75.75 0 0 0 0 1.5h6a.75.75 0 0 0 0-1.5H7Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ end }}

        </div>
      {{ end }}
    </ul>

    {{ if eq $displayedItems 0 }}
      <div class="flex items-center justify-center gap-10 padding-block-5">
        <p>All sites are online</p>
        <svg class="shrink-0" style="width: 1.7rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--color-positive)">
          <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
        </svg>
      </div>
    {{ end }}

TCP monitors

Lists the TCP (port) monitors from Kuvasz with their host:port, uptime ratio, connect latency metric (configurable) and state. Just like the HTTP widget, it supports custom icons, custom links and filtering. The latency metric is only available when metrics history is enabled on the given monitor.

Options

  • base-url: your Kuvasz host (mandatory)
  • api-key: your API key for Kuvasz (optional if you disabled authentication)
  • period: an ISO-8601 period string for the cumulative stats, e.g. PT24H or P7D. The widget default is 24 hours (PT24H).
  • show-metrics: whether to load and display metrics at all. Be aware that showing metrics for a lot of monitors could slow down your dashboard, since the metrics need to be fetched on a per-monitor basis.
  • latency-metric: the latency metric to show, one of average, min, max, p90, p95, p99, default is average
  • show-failing-only: if true, only the failing (down) monitors will be shown
  • show-configured-only: if true, only the explicitly configured monitors will be shown. The explicit monitor config is the same as for the HTTP monitors.
Expand for example configuration
- type: custom-api
  title: TCP monitors
  cache: 5m
  options:
    base-url: ${KUVASZ_HOST}
    api-key: ${KUVASZ_API_KEY}
    period: P1D
    show-metrics: true
    latency-metric: average
    show-failing-only: false
    show-configured-only: false
    'SMTP server': mdi:email-outline
    'SMTP server-url': http://192.168.1.10
  template: |
    {{/* Required config options */}}
    {{ $baseURL := .Options.StringOr "base-url" "" }}

    {{/* Optional config options */}}
    {{ $apiKey := .Options.StringOr "api-key" "" }}
    {{ $period := .Options.StringOr "period" "PT24H" }}
    {{ $showMetrics := .Options.BoolOr "show-metrics" false }}
    {{ $latencyMetric := .Options.StringOr "latency-metric" "average" }}
    {{ $showFailingOnly := .Options.BoolOr "show-failing-only" false }}
    {{ $showOnlyConfigured := .Options.BoolOr "show-configured-only" false }}

    {{ $monitors := newRequest (print $baseURL "/api/v2/tcp-monitors?enabled=true")
      | withHeader "X-Api-Key" $apiKey
      | getResponse }}

    {{ $options := .Options }}
    {{ $displayedItems := 0 }}

    <ul class="dynamic-columns list-gap-20 list-with-separator">
    {{ range $i, $monitor := $monitors.JSON.Array "" }}
        {{ $name := $monitor.String "name" }}
        {{ $key := $monitor.String "id" }}
        {{ $host := $monitor.String "host" }}
        {{ $port := $monitor.String "port" }}
        {{ $target := concat $host ":" $port }}
        {{ $icon := $options.StringOr $name "" }}
        {{ $linkUrlOption := $options.StringOr (concat $name "-url") "" }}
        {{ $linkUrl := $options.StringOr (concat $name "-url") (concat $baseURL "/tcp-monitors/" $key) }}
        {{ $status := $monitor.String "uptimeStatus" }}
        {{ $isUp := eq $status "UP" }}
        {{ $isDown := eq $status "DOWN" }}
        {{ $hasLatency := false }}

        {{ if and $showFailingOnly (not $isDown) }} {{ continue }} {{ end }}
        {{ if and $showOnlyConfigured (eq $linkUrlOption "") (eq $icon "") }} {{ continue }} {{ end }}
        {{ $displayedItems = add $displayedItems 1 }}

        {{ $uptimeValue := "" }}
        {{ $stats := "" }}

        {{ if $showMetrics }}
          {{ $stats = newRequest (print $baseURL "/api/v2/tcp-monitors/" $key "/stats/?period=" $period )
              | withHeader "X-Api-Key" $apiKey
              | getResponse }}
          {{ $hasLatency = $stats.JSON.Exists "latencyStats.averageLatencyInMs" }}
          {{ $uptimeValue = mul 100 ($stats.JSON.Float "uptimeHistory.uptimeRatio") }}
        {{ end }}

        {{ $iconUrl := "" }}
        {{ if $icon }}
          {{ $iconPrefix := findMatch "^(si|di|mdi|sh):" $icon }}
          {{ $iconBase := replaceMatches "^(si|di|mdi|sh):" "" $icon }}

          {{ $iconExt := findMatch "\\.[a-z]+$" $iconBase }}
          {{ $iconExt := replaceMatches "\\." "" $iconExt }}
          {{ $iconBase = replaceMatches "\\.[a-z]+$" "" $iconBase }}
          {{ if eq $iconExt "" }} {{ $iconExt = "svg" }} {{ end }}

          {{ if eq $iconPrefix "si:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/" $iconBase ".svg" }}
          {{ else if eq $iconPrefix "di:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/" $iconExt "/" $iconBase "." $iconExt }}
          {{ else if eq $iconPrefix "mdi:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/" $iconBase ".svg" }}
          {{ else if eq $iconPrefix "sh:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/" $iconBase ".png" }}
          {{ else }}
            {{ $iconUrl = $icon }}
          {{ end }}
        {{ end }}

        <div class="monitor-site flex items-center gap-15">
          {{ if $iconUrl }}
            <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
              <img class="monitor-site-icon" src="{{ $iconUrl | safeURL }}" alt="" loading="lazy">
            </a>
          {{ end }}
          <div class="grow min-width-0">
            <a class="size-h3 color-highlight text-truncate block" href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">{{ $name }}</a>
            <ul class="list-horizontal-text">
              <li class="color-subdue">{{ $target }}</li>
              {{ if $showMetrics }}
                <li class="{{ if $isDown }}color-negative{{ end }}">{{ printf "%.2f" $uptimeValue }}%</li>
                {{ if $hasLatency }}
                  <li>{{ $stats.JSON.Int (printf "latencyStats.%sLatencyInMs" $latencyMetric) }}ms</li>
                {{ end }}
              {{ end }}
            </ul>
          </div>

          {{ if $isUp }}
            <div class="monitor-site-status-icon">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-positive)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ else if $isDown }}
            <div class="monitor-site-status-icon" title="{{ $monitor.String "uptimeError" }}">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ else }}
            <div class="monitor-site-status-icon" title="Not checked yet">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-text-subdue)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM7 9.25a.75.75 0 0 0 0 1.5h6a.75.75 0 0 0 0-1.5H7Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ end }}

        </div>
      {{ end }}
    </ul>

    {{ if eq $displayedItems 0 }}
      <div class="flex items-center justify-center gap-10 padding-block-5">
        <p>All sites are online</p>
        <svg class="shrink-0" style="width: 1.7rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--color-positive)">
          <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
        </svg>
      </div>
    {{ end }}

DNS monitors

Lists the DNS monitors from Kuvasz with their queried name, uptime ratio, resolution latency metric (configurable) and state. Just like the HTTP widget, it supports custom icons, custom links and filtering. The latency metric is only available when metrics history is enabled on the given monitor.

Options

  • base-url: your Kuvasz host (mandatory)
  • api-key: your API key for Kuvasz (optional if you disabled authentication)
  • period: an ISO-8601 period string for the cumulative stats, e.g. PT24H or P7D. The widget default is 24 hours (PT24H).
  • show-metrics: whether to load and display metrics at all. Be aware that showing metrics for a lot of monitors could slow down your dashboard, since the metrics need to be fetched on a per-monitor basis.
  • latency-metric: the latency metric to show, one of average, min, max, p90, p95, p99, default is average
  • show-failing-only: if true, only the failing (down) monitors will be shown
  • show-configured-only: if true, only the explicitly configured monitors will be shown. The explicit monitor config is the same as for the HTTP monitors.
Expand for example configuration
- type: custom-api
  title: DNS monitors
  cache: 5m
  options:
    base-url: ${KUVASZ_HOST}
    api-key: ${KUVASZ_API_KEY}
    period: P1D
    show-metrics: true
    latency-metric: average
    show-failing-only: false
    show-configured-only: false
    'My DNS Monitor': mdi:dns-outline
    'My DNS Monitor-url': https://example.com
  template: |
    {{/* Required config options */}}
    {{ $baseURL := .Options.StringOr "base-url" "" }}

    {{/* Optional config options */}}
    {{ $apiKey := .Options.StringOr "api-key" "" }}
    {{ $period := .Options.StringOr "period" "PT24H" }}
    {{ $showMetrics := .Options.BoolOr "show-metrics" false }}
    {{ $latencyMetric := .Options.StringOr "latency-metric" "average" }}
    {{ $showFailingOnly := .Options.BoolOr "show-failing-only" false }}
    {{ $showOnlyConfigured := .Options.BoolOr "show-configured-only" false }}

    {{ $monitors := newRequest (print $baseURL "/api/v2/dns-monitors?enabled=true")
      | withHeader "X-Api-Key" $apiKey
      | getResponse }}

    {{ $options := .Options }}
    {{ $displayedItems := 0 }}

    <ul class="dynamic-columns list-gap-20 list-with-separator">
    {{ range $i, $monitor := $monitors.JSON.Array "" }}
        {{ $name := $monitor.String "name" }}
        {{ $key := $monitor.String "id" }}
        {{ $target := $monitor.String "host" }}
        {{ $icon := $options.StringOr $name "" }}
        {{ $linkUrlOption := $options.StringOr (concat $name "-url") "" }}
        {{ $linkUrl := $options.StringOr (concat $name "-url") (concat $baseURL "/dns-monitors/" $key) }}
        {{ $status := $monitor.String "uptimeStatus" }}
        {{ $isUp := eq $status "UP" }}
        {{ $isDown := eq $status "DOWN" }}
        {{ $hasLatency := false }}

        {{ if and $showFailingOnly (not $isDown) }} {{ continue }} {{ end }}
        {{ if and $showOnlyConfigured (eq $linkUrlOption "") (eq $icon "") }} {{ continue }} {{ end }}
        {{ $displayedItems = add $displayedItems 1 }}

        {{ $uptimeValue := "" }}
        {{ $stats := "" }}

        {{ if $showMetrics }}
          {{ $stats = newRequest (print $baseURL "/api/v2/dns-monitors/" $key "/stats/?period=" $period )
              | withHeader "X-Api-Key" $apiKey
              | getResponse }}
          {{ $hasLatency = $stats.JSON.Exists "latencyStats.averageLatencyInMs" }}
          {{ $uptimeValue = mul 100 ($stats.JSON.Float "uptimeHistory.uptimeRatio") }}
        {{ end }}

        {{ $iconUrl := "" }}
        {{ if $icon }}
          {{ $iconPrefix := findMatch "^(si|di|mdi|sh):" $icon }}
          {{ $iconBase := replaceMatches "^(si|di|mdi|sh):" "" $icon }}

          {{ $iconExt := findMatch "\\.[a-z]+$" $iconBase }}
          {{ $iconExt := replaceMatches "\\." "" $iconExt }}
          {{ $iconBase = replaceMatches "\\.[a-z]+$" "" $iconBase }}
          {{ if eq $iconExt "" }} {{ $iconExt = "svg" }} {{ end }}

          {{ if eq $iconPrefix "si:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/" $iconBase ".svg" }}
          {{ else if eq $iconPrefix "di:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/" $iconExt "/" $iconBase "." $iconExt }}
          {{ else if eq $iconPrefix "mdi:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/" $iconBase ".svg" }}
          {{ else if eq $iconPrefix "sh:" }}
            {{ $iconUrl = concat "https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/" $iconBase ".png" }}
          {{ else }}
            {{ $iconUrl = $icon }}
          {{ end }}
        {{ end }}

        <div class="monitor-site flex items-center gap-15">
          {{ if $iconUrl }}
            <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
              <img class="monitor-site-icon" src="{{ $iconUrl | safeURL }}" alt="" loading="lazy">
            </a>
          {{ end }}
          <div class="grow min-width-0">
            <a class="size-h3 color-highlight text-truncate block" href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">{{ $name }}</a>
            <ul class="list-horizontal-text">
              <li class="color-subdue">{{ $target }}</li>
              {{ if $showMetrics }}
                <li class="{{ if $isDown }}color-negative{{ end }}">{{ printf "%.2f" $uptimeValue }}%</li>
                {{ if $hasLatency }}
                  <li>{{ $stats.JSON.Int (printf "latencyStats.%sLatencyInMs" $latencyMetric) }}ms</li>
                {{ end }}
              {{ end }}
            </ul>
          </div>

          {{ if $isUp }}
            <div class="monitor-site-status-icon">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-positive)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ else if $isDown }}
            <div class="monitor-site-status-icon" title="{{ $monitor.String "uptimeError" }}">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ else }}
            <div class="monitor-site-status-icon" title="Not checked yet">
              <a href="{{ $linkUrl | safeURL }}" target="_blank" rel="noreferrer">
                <svg fill="var(--color-text-subdue)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM7 9.25a.75.75 0 0 0 0 1.5h6a.75.75 0 0 0 0-1.5H7Z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
          {{ end }}

        </div>
      {{ end }}
    </ul>

    {{ if eq $displayedItems 0 }}
      <div class="flex items-center justify-center gap-10 padding-block-5">
        <p>All sites are online</p>
        <svg class="shrink-0" style="width: 1.7rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="var(--color-positive)">
          <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
        </svg>
      </div>
    {{ end }}

Full YAML example (app-config + monitors + integrations)

This is just a full example of a YAML configuration file, which you can use as a starting point for your own configuration. You can copy and paste it into your own configuration file, and then modify it to suit your needs, but always make sure that you read the corresponding documentation sections for each feature or integration you want to use.

Warning

Be aware that if you define your monitors or your status pages via YAML, you cannot use the Web UI to modify them, you can only view them there!

micronaut.security.enabled: true
micronaut.security.token.generator.access-token.expiration: 86400 # 24 hours
admin-auth:
  username: YourSuperSecretUsername
  password: YourSuperSecretPassword
  api-key: ThisShouldBeVeryVerySecureToo
  mcp-api-key: ThisShouldBeVeryVerySecureToo
app-config:
  event-data-retention-days: 365
  latency-data-retention-days: 7
  log-event-handler: true
  language: en
  check-updates: true
  http-check-timeout-seconds: 30
---
smtp-config:
  host: 'your.smtp.server'
  port: 465
  transport-strategy: SMTP_TLS
  username: YourSMTPUsername
  password: YourSMTPPassword
---
integrations:
  pagerduty:
    - name: pd_global
      integration-key: YourOwnIntegrationKey
      global: true
      enabled: true
  slack:
    - name: slack_default
      webhook-url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXX'
  discord:
    - name: discord
      webhook-url: https://discord.com/api/webhooks/XXXXXXX/YYYYYYYYY
  email:
    - name: email_implicitly_enabled
      from-address: noreply@kuvasz-uptime.dev
      to-address: your@email.address
  telegram:
    - name: telegram_disabled
      api-token: 'YourToken'
      chat-id: '-1232642423121'
      enabled: false
  webhook:
    - name: webhook_templated
      url: https://any-other-http.service/webhooks
      http-method: POST # optional, defaults to POST; PUT, PATCH and GET are also supported
      excluded-events:
        - PUSH_UP
        - HTTP_UP
        - SSL_WILL_EXPIRE
      request-headers:
        Accept: '*/*'
        Authorization: Bearer your-webhook-secret-token
        X-Custom-Header: "This can be a template as well: {{ ctx.type }}"
      payload-template: |
        {
          "monitorName": "{{ctx.monitorName}}",
          "type": "{{ctx.type}}"
        }
---
http-monitors:
  - name: "full configuration example"
    url: "https://akobor.me"
    sensitive-url: false
    uptime-check-interval: 30
    enabled: true
    ssl-check-enabled: false
    request-method: "POST"
    latency-history-enabled: true
    follow-redirects: true
    force-no-cache: true
    ssl-expiry-threshold: 30
    failure-count-threshold: 2
    integrations:
      - "telegram:telegram_disabled"
      - "slack:slack_default"
    expected-status-codes:
      - 200
      - 201
      - 303
    expected-keyword: "akobor"
    expected-keyword-case-sensitive: true
    expected-keyword-negated: false
    response-time-threshold-millis: 500
    request-headers:
      Host: "example.com"
    expected-headers:
      Content-Type: "application/json"
    request-body: "{\"key\":\"value\"}"
  - name: "minimal configuration example"
    url: "https://kuvasz-uptime.dev"
    uptime-check-interval: 5
push-monitors:
  - name: "My Push Monitor"
    heartbeat-interval: 10
    grace-period: 2
    failure-count-threshold: 3
    client-secret: "d6d5a85c-82c0-4bea-9926-c3eed32de32b"
    enabled: true
    integrations: [ ]
  - name: "Another Push Monitor"
    heartbeat-interval: 86400
    grace-period: 3600
    client-secret: "7b2d5cb1-41bd-4067-9732-c79dbbf45286"
    enabled: false
    integrations:
      - "slack:slack_default"
icmp-monitors:
  - name: "My ICMP Monitor"
    host: "example.com"
    uptime-check-interval: 60
    packet-count: 3
    timeout-seconds: 5
    packet-loss-threshold: 100
    failure-count-threshold: 1
    enabled: true
    metrics-history-enabled: true
    integrations:
      - "slack:slack_default"
  - name: "Local router"
    host: "192.168.1.1"
    uptime-check-interval: 30
    enabled: true
tcp-monitors:
  - name: "My TCP Monitor"
    host: "example.com"
    port: 5432
    uptime-check-interval: 60
    timeout-ms: 5000
    latency-threshold-ms: 1000
    failure-count-threshold: 1
    enabled: true
    metrics-history-enabled: true
    integrations:
      - "slack:slack_default"
  - name: "SMTP server"
    host: "192.168.1.10"
    port: 25
    uptime-check-interval: 30
    enabled: true
dns-monitors:
  - name: "My DNS Monitor"
    host: "example.com"
    uptime-check-interval: 60
    resolver-host: "1.1.1.1"
    resolver-port: 53
    transport: "UDP"
    record-matchers:
      - record-type: "A"
        match-type: "EXACT"
        value: "93.184.216.34"
      - record-type: "TXT"
        match-type: "CONTAINS"
        value: "v=spf1"
    expected-response-code: "NOERROR"
    drift-detection-enabled: true
    drift-record-types:
      - "NS"
      - "MX"
    timeout-ms: 5000
    latency-threshold-ms: 1000
    failure-count-threshold: 1
    enabled: true
    metrics-history-enabled: true
    integrations:
      - "slack:slack_default"
  - name: "Decommissioned host must not resolve"
    host: "old.example.com"
    uptime-check-interval: 300
    expected-response-code: "NXDOMAIN"
    enabled: true
maintenance-windows:
  - name: "Nightly DB maintenance"
    description: "Recurring nightly database maintenance"
    enabled: true
    global: false
    show-on-status-pages: true
    cron: "0 2 * * *"
    duration: "PT1H"
    monitors:
      - "http:full configuration example"
      - "icmp:My ICMP Monitor"
      - "tcp:My TCP Monitor"
      - "dns:My DNS Monitor"
    integrations:
      - "slack:slack_default"
  - name: "Datacenter migration"
    description: "One-off, all monitors are paused during the migration"
    start: "2026-08-01T22:00:00Z"
    duration: "PT2H"
    global: true
---
default-status-page:
  public: true
  title: "Status - Kuvasz Uptime"
  custom-logo-url: "https://example.com/logo.png"
  custom-favicon-url: "https://example.com/favicon.png"
status-pages:
  - title: "Example Status Page"
    slug: "example-status"
    public: true
    custom-logo-url: "https://example.com/logo.png"
    custom-favicon-url: "https://example.com/favicon.png"
    monitors:
      - "http:full configuration example"
      - "http:minimal configuration example"
      - "push:My Push Monitor"
      - "icmp:My ICMP Monitor"
      - "tcp:My TCP Monitor"
      - "dns:My DNS Monitor"