diff --git a/docs/privacy.md b/docs/privacy.md index df0d4e6d6d..4d1b82ab14 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -204,7 +204,7 @@ Dashy supports [Widgets](/docs/widgets.md) for displaying dynamic content. Below | [Astronomy Picture of the Day](/docs/widgets.md#astronomy-picture-of-the-day) | `https://apod.as93.net` | [NASA's Privacy Policy](https://www.nasa.gov/about/highlights/HP_Privacy.html) | | [GitHub Trending](/docs/widgets.md#github-trending) | `https://trend.doforce.xyz` | No Policy Available | | [GitHub Profile Stats](/docs/widgets.md#github-profile-stats) | `https://github-readme-stats.vercel.app` | [GitHub's Privacy Policy](https://docs.github.com/en/github/site-policy/github-privacy-statement) | -| [Cron Monitoring (Health Checks)](/docs/widgets.md#cron-monitoring-health-checks) | `https://healthchecks.io` | [Health-Checks Privacy Policy](https://healthchecks.io/privacy/) | +| [Healthchecks Status](/docs/widgets.md#healthchecks-status) | `https://healthchecks.io` | [Health-Checks Privacy Policy](https://healthchecks.io/privacy/) | | [Hacker News Trending](/docs/widgets.md#hacker-news-trending) | `https://hacker-news.firebaseio.com` | [Y Combinator Privacy Policy](https://www.ycombinator.com/legal#privacy) | | [Minecraft Server Status](/docs/widgets.md#minecraft-server-status) | `https://api.mcsrvstat.us` | No Policy Available | | [MVG](/docs/widgets.md#mvg) | `https://www.mvg.de/api/fib/v2/` | No Policy Available | diff --git a/docs/widgets.md b/docs/widgets.md index 65cabbd1e5..6f49c40539 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -44,7 +44,6 @@ Dashy has support for displaying dynamic content in the form of widgets. There a - [Minecraft Server](#minecraft-server) - **[Self-Hosted Services Widgets](#self-hosted-services-widgets)** - [System Info](#system-info) - - [Cron Monitoring](#cron-monitoring-health-checks) - [CPU History](#cpu-history-netdata) - [Memory History](#memory-history-netdata) - [System Load History](#load-history-netdata) @@ -1227,19 +1226,21 @@ Display stats from your GitHub profile, using embedded cards from [anuraghazra/g ### HealthChecks Status -Display status of one or more HealthChecks project(s). Works with healthchecks.io and your selfhosted instance. +Display cron job monitor status of one or more HealthChecks project(s). Works with healthchecks.io and your selfhosted instance. -

+

#### Options **Field** | **Type** | **Required** | **Description** --- | --- | --- | --- **`host`** | `string` | Optional | The base url of your instance, default is `https://healthchecks.io` -**`apiKey`** | `string` or `array` | Required | One or more API keys for your healthcheck projects. (Read-only works fine) +**`apiKey`** | `string` (or `string[]` for multiple) | Required | API key(s) for project(s) to monitor. Generate a separate key per project (read-only is fine), under Project --> Settings --> API Access + +##### Example ```yaml -- type: HealthChecks +- type: health-checks options: host: https://healthcheck.your-domain.de apiKey: @@ -1247,9 +1248,17 @@ Display status of one or more HealthChecks project(s). Works with healthchecks.i - zxywvu... ``` +Or, a single project: +```yaml + - type: health-checks + options: + host: https://healthchecks.io + apiKey: hcw_xxxxxxxxxxxxxxxxxxxxxxxx +``` + #### Info -- **CORS**: 🟢 Enabled +- **CORS**: 🟠 Proxied - **Auth**: 🟢 Required - **Price**: 🟢 Free / Paid / Self-hosted - **Host**: Managed Instance or Self-Hosted (see [healthchecks/healthchecks](https://github.com/healthchecks/healthchecks)) @@ -1661,37 +1670,6 @@ Note that this widget is not available if you are running Dashy in a container o --- -### Cron Monitoring (Health Checks) - -Cron job monitoring using [Health Checks](https://github.com/healthchecks/healthchecks). Both managed and self-hosted instances are supported. - -

- -#### Options - -**Field** | **Type** | **Required** | **Description** ---- | --- | --- | --- -**`apiKey`** | `string` | Required | A read-only API key for the project to monitor. You can generate this by selecting a Project --> Settings --> API Access. Note that you must generate a separate key for each project -**`host`** | `string` | _Optional_ | If you're self-hosting, or using any instance other than the official (healthchecks.io), you will need to specify the host address. E.g. `https://healthchecks.example.com` or `http://cron-monitoing.local` - -#### Example - -```yaml -- type: health-checks - options: - apiKey: XXXXXXXXX -``` - -#### Info - -- **CORS**: 🟢 Enabled -- **Auth**: 🔴 Required -- **Price**: 🟠 Free plan (up to 20 services, or self-host for unlimited) -- **Host**: Managed Instance or Self-Hosted (see [GitHub - HealthChecks](https://github.com/healthchecks/healthchecks)) -- **Privacy**: _See [Health-Checks Privacy Policy](https://healthchecks.io/privacy/)_ - ---- - ### CPU History (NetData) Pull recent CPU usage history from NetData. diff --git a/src/components/Widgets/HealthChecks.vue b/src/components/Widgets/HealthChecks.vue index 2201b11a5e..31e6133274 100644 --- a/src/components/Widgets/HealthChecks.vue +++ b/src/components/Widgets/HealthChecks.vue @@ -5,7 +5,7 @@ :key="cron.id" >
-

{{ formatStatus(cron.status) }}

+

{{ formatStatus(cron.status) }}

import WidgetMixin from '@/mixins/WidgetMixin'; -import { widgetApiEndpoints } from '@/utils/config/defaults'; import { capitalize, timestampToDateTime } from '@/utils/MiscHelpers'; export default { @@ -32,49 +31,60 @@ export default { }; }, computed: { + baseUrl() { + return this.options.host || 'https://healthchecks.io'; + }, /* API endpoint, either for self-hosted or managed instance */ endpoint() { - if (this.options.host) return `${this.options.host}/api/v1/checks`; - return `${widgetApiEndpoints.healthChecks}`; + return `${this.baseUrl}/api/v1/checks/`; }, - apiKey() { - if (!this.options.apiKey) { - this.error('An API key is required, please see the docs for more info'); - } - if (typeof this.options.apiKey === 'string') { - return [this.parseAsEnvVar(this.options.apiKey)]; - } - return this.options.apiKey; + /* User's API key(s), normalised to an array, or null if unset */ + apiKeys() { + const { apiKey } = this.options; + if (!apiKey) return null; + const keys = Array.isArray(apiKey) ? apiKey : [apiKey]; + return keys.map((key) => this.parseAsEnvVar(key)); }, }, methods: { + /* Make status summary (emoji + name) */ formatStatus(status) { - let symbol = ''; - if (status === 'up') symbol = '✔'; - if (status === 'down') symbol = '✘'; - if (status === 'new') symbol = '❖'; - if (status === 'paused') symbol = '⏸'; - if (status === 'running') symbol = '▶'; - return `${symbol} ${capitalize(status)}`; + const symbols = { + up: '✔', down: '✘', new: '❖', paused: '⏸', grace: '⚠', started: '▶', + }; + return `${symbols[status] || '❔'} ${capitalize(status || 'unknown')}`; }, formatDate(timestamp) { return timestampToDateTime(timestamp); }, - /* Make GET request to CoinGecko API endpoint */ fetchData() { + if (!this.apiKeys) { + this.error('An API key is required, please see the docs for more info'); + this.finishLoading(); + return; + } this.overrideProxyChoice = true; - const results = []; - this.apiKey.forEach((key) => { - const authHeaders = { 'X-Api-Key': key }; - this.makeRequest(this.endpoint, authHeaders).then( - (response) => { this.processData(response, results); }, - ); + const requests = this.apiKeys.map( + (key) => this.makeRequest(this.endpoint, { 'X-Api-Key': key }), + ); + Promise.allSettled(requests).then((outcomes) => { + const results = []; + outcomes.forEach((outcome) => { + if (outcome.status === 'fulfilled') this.processData(outcome.value, results); + }); + results.sort((a, b) => (a.name > b.name ? 1 : -1)); + this.crons = results; }); - results.sort((a, b) => ((a.name > b.name) ? 1 : -1)); - this.crons = results; }, - /* Assign data variables to the returned data */ + /* Map the API response into the cron list, guarding against bad responses */ processData(data, results) { + if (!data || !Array.isArray(data.checks)) { + this.error( + 'Unexpected response, please check your host URL and API key are correct.' + + ' useProxy may be required if your Healchecks API is blocking Dashy with CORS' + ); + return; + } data.checks.forEach((cron) => { results.push({ id: cron.slug, @@ -87,11 +97,9 @@ export default { url: this.makeUrl(cron.unique_key), }); }); - return results; }, makeUrl(cronId) { - const base = this.options.host || 'https://healthchecks.io'; - return `${base}/checks/${cronId}/details`; + return `${this.baseUrl}/checks/${cronId}/details`; }, pingTimeTooltip(cron) { const { lastPing, nextPing, pingCount } = cron; @@ -123,7 +131,9 @@ export default { &.up { color: var(--success); } &.down { color: var(--danger); } &.new { color: var(--widget-text-color); } - &.running { color: var(--warning); } + &.grace { color: var(--warning); } + &.unknown { color: var(--warning); } + &.started { color: var(--info); } &.paused { color: var(--info); } } }