pull/941/merge
miggland 2025-05-26 14:38:39 +00:00 committed by GitHub
commit 7bbecc3ca5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 391 additions and 0 deletions

View File

@ -18,6 +18,7 @@ within Homer:
- [Docker Socket Proxy](#docker-socket-proxy)
- [Emby / Jellyfin](#emby--jellyfin)
- [FreshRSS](#freshrss)
- [Gatus](#gatus)
- [Gitea / Forgejo](#gitea--forgejo)
- [Glances](#glances)
- [Gotify](#gotify)
@ -154,6 +155,32 @@ The FreshRSS service displays unread and subscriptions counts from your FreshRSS
updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
```
## Gatus
The Gatus service displays information about the configured services from the defined Gatus server.
Two lines are needed in the config.yml :
```yaml
type: "Gatus"
url: "http://192.168.0.151/gatus"
```
Optionally, the results can be filtered to only include jobs in the defined groups:
```yaml
groups: [Services, External]
```
The status can be checked regularly by defining an update Interval in ms:
```yaml
updateInterval: 5000
```
The average times can be hidden (saves their calculation also) by setting the following:
```yaml
hideaverages: true
```
## Gitea / Forgejo
This service displays a version string instead of a subtitle. Example configuration:

View File

@ -0,0 +1,211 @@
[
{
"name": "Gateway",
"group": "Services",
"key": "services_gateway",
"results": [
{
"status": 200,
"hostname": "gateway.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": true
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": true
}
],
"success": true,
"timestamp": "2025-05-26T07:35:41.784208588Z"
},
{
"status": 200,
"hostname": "gateway.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": true
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": true
}
],
"success": true,
"timestamp": "2025-05-26T07:40:41.804489793Z"
},
{
"status": 200,
"hostname": "gateway.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": true
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": true
}
],
"success": true,
"timestamp": "2025-05-26T07:45:41.837925713Z"
},
{
"status": 200,
"hostname": "gateway.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": true
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": true
}
],
"success": true,
"timestamp": "2025-05-26T07:50:41.848391366Z"
}
]
},
{
"name": "Website",
"group": "External",
"key": "external_website",
"results": [
{
"status": 200,
"hostname": "www.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": true
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": false
}
],
"success": false,
"timestamp": "2025-05-26T07:35:41.784208588Z"
},
{
"status": 200,
"hostname": "gateway.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": false
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": true
}
],
"success": false,
"timestamp": "2025-05-26T07:40:41.804489793Z"
},
{
"status": 200,
"hostname": "gateway.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": true
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": true
}
],
"success": true,
"timestamp": "2025-05-26T07:45:41.837925713Z"
},
{
"status": 200,
"hostname": "gateway.example.com",
"duration": 10000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": true
},
{
"condition": "[RESPONSE_TIME] < 500",
"success": true
}
],
"success": true,
"timestamp": "2025-05-26T07:50:41.848391366Z"
}
]
},
{
"name": "DNS",
"group": "Services",
"key": "services_dns",
"results": [
{
"status": 200,
"hostname": "ns1.example",
"duration": 20000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": false
}
],
"success": false,
"timestamp": "2025-05-26T07:35:41.784208588Z"
},
{
"status": 200,
"hostname": "ns1.example.com",
"duration": 20000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": false
}
],
"success": false,
"timestamp": "2025-05-26T07:40:41.804489793Z"
},
{
"status": 200,
"hostname": "ns1.example.com",
"duration": 20000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": false
}
],
"success": false,
"timestamp": "2025-05-26T07:45:41.837925713Z"
},
{
"status": 200,
"hostname": "ns1.example.com",
"duration": 20000,
"conditionResults": [
{
"condition": "[STATUS] == 200",
"success": false
}
],
"success": false,
"timestamp": "2025-05-26T07:50:41.848391366Z"
}
]
}
]

View File

@ -0,0 +1,153 @@
<template>
<Generic :item="item">
<template #content>
<p class="title is-4">{{ item.name }}</p>
<p class="subtitle is-6">
<template v-if="item.subtitle">
{{ item.subtitle }}
</template>
<i class="fa-solid fa-signal"></i> {{ up }}/{{ total }}
<template v-if="avgRespTime > 0">
<span class="separator"> | </span>
<i class="fa-solid fa-stopwatch"></i> {{ avgRespTime }} ms avg.
</template>
</p>
</template>
<template #indicator>
<div v-if="status !== false" class="status" :class="status">
{{ percentageGood }}&percnt;
</div>
</template>
</Generic>
</template>
<script>
import service from "@/mixins/service.js";
export default {
name: "Gatus",
mixins: [service],
props: {
item: Object,
},
data: () => ({
up: 0,
down: 0,
total: 0,
avgRespTime: NaN,
percentageGood: NaN,
status: false,
statusMessage: false
}),
created() {
const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
if (updateInterval > 0) {
setInterval(() => this.fetchStatus(), updateInterval);
}
this.fetchStatus();
},
methods: {
fetchStatus: async function () {
this.fetch("/api/v1/endpoints/statuses", { method: "GET", cache: "no-cache" })
.then((response) => {
// Apply filtering by groups, if defined
if (this.item.groups) {
response = response?.filter((job) => {
return this.item.groups.includes(job.group) === true;
})
}
// Initialise counts, avg times
this.total = response.length;
this.up = 0;
let totalrestime = 0;
let totalresults = 0;
response.forEach((job) => {
if (job.results[job.results.length - 1].success === true) {
this.up++;
};
if (!this.item.hideaverages) {
// Update array of average times
let totalduration = 0;
let rescounter = 0;
job.results.forEach((res) => {
totalduration += parseInt(res.duration, 10) / 1000;
rescounter++;
})
totalrestime += totalduration;
totalresults += rescounter;
} else {
totalrestime = 0;
totalresults = 1;
}
})
// Rest are down
this.down = this.total - this.up;
// Calculate overall average response time
this.avgRespTime = (totalrestime / totalresults).toFixed(2);
// Update representations
if (this.up == 0 || this.total == 0) {
this.percentageGood = 0;
} else {
this.percentageGood = Math.round((this.up / this.total) * 100);
}
// Status flag
if (this.up == 0 && this.down == 0) {
this.status = false;
} else if (this.down == this.total) {
this.status = "bad";
} else if (this.up == this.total) {
this.status = "good";
} else {
this.status = "warn";
}
})
.catch((e) => {
console.error(e);
});
},
},
};
</script>
<style scoped lang="scss">
.status {
font-size: 0.8rem;
color: var(--text-title);
&.good:before {
background-color: #94e185;
border-color: #78d965;
box-shadow: 0 0 5px 1px #94e185;
}
&.warn:before {
background-color: #f8a306;
border-color: #e1b35e;
box-shadow: 0 0 5px 1px #f8a306;
}
&.bad:before {
background-color: #c9404d;
border-color: #c42c3b;
box-shadow: 0 0 5px 1px #c9404d;
}
&:before {
content: " ";
display: inline-block;
width: 7px;
height: 7px;
margin-right: 10px;
border: 1px solid #000;
border-radius: 7px;
}
}
</style>