Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/privacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
52 changes: 15 additions & 37 deletions docs/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1227,29 +1226,39 @@ 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.

<p align="center"><img width="380" src="https://pixelflare.cc/alicia/dashy/healthchecks" /></p>
<p align="center"><img width="400" src="https://pixelflare.cc/alicia/dashy/cron-monitor" /></p>

#### 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:
- abcdefg...
- 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))
Expand Down Expand Up @@ -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.

<p align="center"><img width="400" src="https://pixelflare.cc/alicia/dashy/cron-monitor" /></p>

#### 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.
Expand Down
76 changes: 43 additions & 33 deletions src/components/Widgets/HealthChecks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:key="cron.id"
>
<div class="status">
<p :class="cron.status">{{ formatStatus(cron.status) }}</p>
<p :class="cron.status || 'unknown'">{{ formatStatus(cron.status) }}</p>
</div>
<div
class="info"
Expand All @@ -20,7 +20,6 @@

<script>
import WidgetMixin from '@/mixins/WidgetMixin';
import { widgetApiEndpoints } from '@/utils/config/defaults';
import { capitalize, timestampToDateTime } from '@/utils/MiscHelpers';

export default {
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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); }
}
}
Expand Down