mirror of
https://github.com/Lissy93/dashy.git
synced 2025-12-06 08:34:14 +01:00
🔀 Merge pull request #1932 from ga-lep/FEATURE/1924_uptime-kuma-status-page-widget
✨ Add Uptime Kuma Status Page Widget
This commit is contained in:
commit
996de036e8
3 changed files with 235 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
@ -2671,6 +2672,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.
|
||||
|
|
|
|||
199
src/components/Widgets/UptimeKumaStatusPage.vue
Normal file
199
src/components/Widgets/UptimeKumaStatusPage.vue
Normal file
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue