Skip to content
Open
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
35 changes: 35 additions & 0 deletions docs/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Dashy has support for displaying dynamic content in the form of widgets. There a
- [Drone CI Build](#drone-ci-builds)
- [Linkding](#linkding)
- [Uptime Kuma](#uptime-kuma)
- [Uptime Kuma Status Page](#uptime-kuma-status-page)
- [Tactical RMM](#tactical-rmm)
- **[System Resource Monitoring](#system-resource-monitoring)**
- [CPU Usage Current](#current-cpu-usage)
Expand Down Expand Up @@ -2670,6 +2671,40 @@ Linkding is a self-hosted bookmarking service, which has a clean interface and i

---

### Uptime Kuma Status Page

[Uptime Kuma](https://github.com/louislam/uptime-kuma) is an easy-to-use self-hosted monitoring tool.

#### Options

| **Field** | **Type** | **Required** | **Description** |
| ------------------ | -------- | ------------ | --------------------------------------------------------------------------------- |
| **`host`** | `string` | Required | The URL of the Uptime Kuma instance |
| **`slug`** | `string` | Required | The slug of the status page |
| **`monitorNames`** | `strins` | _Optional_ | Names of monitored services (in the same order as on the kuma uptime status page) |

#### Example

```yaml
- type: uptime-kuma-status-page
options:
host: http://localhost:3001
slug: another-beautiful-status-page
monitorNames:
- "Name1"
- "Name2"
```

#### Info

- **CORS**: 🟢 Enabled
- **Auth**: 🟢 Not Needed
- **Price**: 🟢 Free
- **Host**: Self-Hosted (see [Uptime Kuma](https://github.com/louislam/uptime-kuma) )
- **Privacy**: _See [Uptime Kuma](https://github.com/louislam/uptime-kuma)_

---

### Tactical RMM

[Tactical RMM](https://github.com/amidaware/tacticalrmm) is a self-hosted remote monitoring & management tool.
Expand Down
199 changes: 199 additions & 0 deletions src/components/Widgets/UptimeKumaStatusPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<template>
<div @click="openStatusPage" class="clickable-widget">
<template v-if="errorMessage">
<div class="error-message">
<span class="text">{{ errorMessage }}</span>
</div>
</template>
<template v-else-if="lastHeartbeats">
<div
v-for="(heartbeat, index) in lastHeartbeats"
:key="index"
class="item-wrapper"
>
<div class="item monitor-row">
<div class="title-title">
<span class="text">
{{
monitorNames && monitorNames[index]
? monitorNames[index]
: `Monitor ${index + 1}`
}}
</span>
</div>
<div class="monitors-container">
<div class="status-container">
<span
class="status-pill"
:class="getStatusClass(heartbeat.status)"
>
{{ getStatusText(heartbeat.status) }}
</span>
</div>
</div>
</div>
</div>
</template>
</div>
</template>

<script>
import WidgetMixin from '@/mixins/WidgetMixin';

export default {
mixins: [WidgetMixin],
data() {
return {
lastHeartbeats: null,
errorMessage: null,
errorMessageConstants: {
missingHost: 'No host set',
missingSlug: 'No slug set',
},
};
},
computed: {
host() {
return this.parseAsEnvVar(this.options.host);
},
slug() {
return this.parseAsEnvVar(this.options.slug);
},
monitorNames() {
return this.options.monitorNames || [];
},
endpoint() {
return `${this.host}/api/status-page/heartbeat/${this.slug}`;
},
statusPageUrl() {
return `${this.host}/status/${this.slug}`;
},
},
mounted() {
this.fetchData();
},
methods: {
update() {
this.startLoading();
this.fetchData();
},
fetchData() {
const { host, slug } = this;
if (!this.optionsValid({ host, slug })) {
return;
}
this.makeRequest(this.endpoint)
.then(this.processData)
.catch((error) => {
this.errorMessage = error.message || 'Failed to fetch data';
});
},
processData(response) {
const { heartbeatList } = response;
const lastHeartbeats = [];
// Use Object.keys to safely iterate over heartbeatList
Object.keys(heartbeatList).forEach((monitorId) => {
const heartbeats = heartbeatList[monitorId];
if (heartbeats.length > 0) {
const lastHeartbeat = heartbeats[heartbeats.length - 1];
lastHeartbeats.push(lastHeartbeat);
}
});
this.lastHeartbeats = lastHeartbeats;
},
optionsValid({ host, slug }) {
const errors = [];
if (!host) errors.push(this.errorMessageConstants.missingHost);
if (!slug) errors.push(this.errorMessageConstants.missingSlug);
if (errors.length > 0) {
this.errorMessage = errors.join('\n');
return false;
}
return true;
},
openStatusPage() {
window.open(this.statusPageUrl, '_blank');
},
getStatusText(status) {
switch (status) {
case 1:
return 'Up';
case 0:
return 'Down';
case 2:
return 'Pending';
case 3:
return 'Maintenance';
default:
return 'Unknown';
}
},
getStatusClass(status) {
switch (status) {
case 1:
return 'up';
case 0:
return 'down';
case 2:
return 'pending';
case 3:
return 'maintenance';
default:
return 'unknown';
}
},
},
};
</script>

<style scoped lang="scss">
.clickable-widget {
cursor: pointer;
}
.status-pill {
border-radius: 50em;
box-sizing: border-box;
font-size: 0.75em;
display: inline-block;
font-weight: 700;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
padding: 0.35em 0.65em;
margin: 0.1em 0.5em;
min-width: 64px;
&.up {
background-color: #5cdd8b;
color: black;
}
&.down {
background-color: #dc3545;
color: white;
}
&.pending {
background-color: #f8a306;
color: black;
}
&.maintenance {
background-color: #1747f5;
color: white;
}
&.unknown {
background-color: gray;
color: white;
}
}
.monitor-row {
display: flex;
justify-content: space-between;
padding: 0.35em 0.5em;
align-items: center;
}
.title-title {
font-weight: bold;
}
.error-message {
color: red;
font-weight: bold;
}
</style>
1 change: 1 addition & 0 deletions src/components/Widgets/WidgetBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const COMPAT = {
'tfl-status': 'TflStatus',
trmm: 'TacticalRMM',
'uptime-kuma': 'UptimeKuma',
'uptime-kuma-status-page': 'UptimeKumaStatusPage',
'wallet-balance': 'WalletBalance',
weather: 'Weather',
'weather-forecast': 'WeatherForecast',
Expand Down