diff options
Diffstat (limited to 'src/components/services/UptimeKuma.vue')
-rw-r--r-- | src/components/services/UptimeKuma.vue | 152 |
1 files changed, 152 insertions, 0 deletions
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> | ||