diff options
-rw-r--r-- | docs/customservices.md | 15 | ||||
-rw-r--r-- | src/components/services/UptimeKuma.vue | 152 |
2 files changed, 167 insertions, 0 deletions
diff --git a/docs/customservices.md b/docs/customservices.md index 509278f..709b16b 100644 --- a/docs/customservices.md +++ b/docs/customservices.md | |||
@@ -179,3 +179,18 @@ You need to set the type to Emby, provide an api key and choose which stats to s | |||
179 | apikey: "MY-SUPER-SECRET-API-KEY" | 179 | apikey: "MY-SUPER-SECRET-API-KEY" |
180 | libraryType: "music" #Choose which stats to show. Can be one of: music, series or movies. | 180 | libraryType: "music" #Choose which stats to show. Can be one of: music, series or movies. |
181 | ``` | 181 | ``` |
182 | |||
183 | ## Uptime Kuma | ||
184 | |||
185 | Using the Uptime Kuma service you can display info about your instance uptime right on your Homer dashboard. | ||
186 | |||
187 | The following configuration is available for the UptimeKuma service. Needs v1.13.1 or later because of the change in APIs due to [multiple status pages support](https://github.com/louislam/uptime-kuma/releases/tag/1.13.1). | ||
188 | |||
189 | ```yaml | ||
190 | - name: "Uptime Kuma" | ||
191 | logo: "assets/tools/sample.png" | ||
192 | # subtitle: "A fancy self-hosted monitoring tool" # optional, if no subtitle is defined, Uptime Kuma incidents, if any, will be shown | ||
193 | url: "http://192.168.0.151:3001" | ||
194 | slug: "myCustomDashboard" # Defaults to "default" if not provided. | ||
195 | type: "UptimeKuma" | ||
196 | ``` | ||
diff --git a/src/components/services/UptimeKuma.vue b/src/components/services/UptimeKuma.vue new file mode 100644 index 0000000..5dd75f3 --- /dev/null +++ b/src/components/services/UptimeKuma.vue | |||
@@ -0,0 +1,152 @@ | |||
1 | <template> | ||
2 | <Generic :item="item"> | ||
3 | <template #content> | ||
4 | <p class="title is-4">{{ item.name }}</p> | ||
5 | <p class="subtitle is-6"> | ||
6 | <template v-if="item.subtitle"> | ||
7 | {{ item.subtitle }} | ||
8 | </template> | ||
9 | <template v-else-if="status"> | ||
10 | {{ statusMessage }} | ||
11 | </template> | ||
12 | </p> | ||
13 | </template> | ||
14 | <template #indicator> | ||
15 | <div v-if="status" class="status" :class="status"> | ||
16 | {{ uptime }}% | ||
17 | </div> | ||
18 | </template> | ||
19 | </Generic> | ||
20 | </template> | ||
21 | |||
22 | <script> | ||
23 | import service from "@/mixins/service.js"; | ||
24 | import Generic from "./Generic.vue"; | ||
25 | |||
26 | export default { | ||
27 | name: "UptimeKuma", | ||
28 | mixins: [service], | ||
29 | props: { | ||
30 | item: Object, | ||
31 | }, | ||
32 | components: { | ||
33 | Generic, | ||
34 | }, | ||
35 | data: () => ({ | ||
36 | incident: null, | ||
37 | heartbeat: null, | ||
38 | }), | ||
39 | computed: { | ||
40 | dashboard: function () { | ||
41 | return this.item.slug ? this.item.slug : "default"; | ||
42 | }, | ||
43 | status: function () { | ||
44 | if (!this.incident) { | ||
45 | return ""; | ||
46 | } | ||
47 | return this.incident.incident == null ? this.pageStatus : "bad"; | ||
48 | }, | ||
49 | lastHeartBeatList: function () { | ||
50 | let result = {}; | ||
51 | |||
52 | for (let id in this.heartbeat.heartbeatList) { | ||
53 | let index = this.heartbeat.heartbeatList[id].length - 1; | ||
54 | result[id] = this.heartbeat.heartbeatList[id][index]; | ||
55 | } | ||
56 | |||
57 | return result; | ||
58 | }, | ||
59 | pageStatus: function () { | ||
60 | if (!this.heartbeat) { | ||
61 | return ""; | ||
62 | } | ||
63 | if (Object.keys(this.heartbeat.heartbeatList).length === 0) { | ||
64 | return ""; | ||
65 | } | ||
66 | let result = "good"; | ||
67 | let hasUp = false; | ||
68 | for (let id in this.lastHeartBeatList) { | ||
69 | let beat = this.lastHeartBeatList[id]; | ||
70 | if (beat.status == 1) { | ||
71 | hasUp = true; | ||
72 | } else { | ||
73 | result = "warn"; | ||
74 | } | ||
75 | } | ||
76 | if (!hasUp) { | ||
77 | result = "bad"; | ||
78 | } | ||
79 | return result; | ||
80 | }, | ||
81 | statusMessage: function () { | ||
82 | if (!this.incident) { | ||
83 | return ""; | ||
84 | } | ||
85 | if (this.incident.incident) { | ||
86 | return this.incident.incident.title; | ||
87 | } | ||
88 | return this.pageStatus == "warn" | ||
89 | ? "Partially Degraded Service" | ||
90 | : "All Systems Operational"; | ||
91 | }, | ||
92 | uptime: function () { | ||
93 | if (!this.heartbeat) { | ||
94 | return 0; | ||
95 | } | ||
96 | const data = Object.values(this.heartbeat.uptimeList); | ||
97 | const percent = data.reduce((a, b) => a + b, 0) / data.length || 0; | ||
98 | return (percent * 100).toFixed(1); | ||
99 | }, | ||
100 | }, | ||
101 | created() { | ||
102 | this.item.url = `${this.item.url}/status/${this.dashboard}`; | ||
103 | this.fetchStatus(); | ||
104 | }, | ||
105 | methods: { | ||
106 | fetchStatus: function () { | ||
107 | this.fetch(`/api/status-page/${this.dashboard}?cachebust=${Date.now()}`) | ||
108 | .catch((e) => console.error(e)) | ||
109 | .then((resp) => (this.incident = resp)); | ||
110 | |||
111 | this.fetch(`/api/status-page/heartbeat/${this.dashboard}?cachebust=${Date.now()}`) | ||
112 | .catch((e) => console.error(e)) | ||
113 | .then((resp) => (this.heartbeat = resp)); | ||
114 | }, | ||
115 | }, | ||
116 | }; | ||
117 | </script> | ||
118 | |||
119 | <style scoped lang="scss"> | ||
120 | .status { | ||
121 | font-size: 0.8rem; | ||
122 | color: var(--text-title); | ||
123 | |||
124 | &.good:before { | ||
125 | background-color: #94e185; | ||
126 | border-color: #78d965; | ||
127 | box-shadow: 0 0 5px 1px #94e185; | ||
128 | } | ||
129 | |||
130 | &.warn:before { | ||
131 | background-color: #f8a306; | ||
132 | border-color: #e1b35e; | ||
133 | box-shadow: 0 0 5px 1px #f8a306; | ||
134 | } | ||
135 | |||
136 | &.bad:before { | ||
137 | background-color: #c9404d; | ||
138 | border-color: #c42c3b; | ||
139 | box-shadow: 0 0 5px 1px #c9404d; | ||
140 | } | ||
141 | |||
142 | &:before { | ||
143 | content: " "; | ||
144 | display: inline-block; | ||
145 | width: 7px; | ||
146 | height: 7px; | ||
147 | margin-right: 10px; | ||
148 | border: 1px solid #000; | ||
149 | border-radius: 7px; | ||
150 | } | ||
151 | } | ||
152 | </style> | ||