Add page for individual service details

This commit is contained in:
TwinProduction
2021-01-27 18:25:37 -05:00
parent 2ccd656386
commit dcbbec7931
20 changed files with 391 additions and 101 deletions

View File

@ -12,11 +12,13 @@
"core-js": "^3.6.5",
"postcss": "^7.0.35",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2",
"vue": "^3.0.0"
"vue": "^3.0.0",
"vue-router": "^4.0.0-0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
@ -14086,6 +14088,14 @@
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
"dev": true
},
"node_modules/vue-router": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.3.tgz",
"integrity": "sha512-AD1OjtVPyQHTSpoRsEGfPpxRQwhAhxcacOYO3zJ3KNkYP/r09mileSp6kdMQKhZWP2cFsPR3E2M3PZguSN5/ww==",
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
@ -17085,8 +17095,7 @@
"version": "4.5.11",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.11.tgz",
"integrity": "sha512-JBPeZLubiSHbRkEKDj0tnLiU43AJ3vt6JULn4IKWH1XWZ6MFC8vElaP5/AA4O3Zko5caamDDBq3TRyxdA2ncUQ==",
"dev": true,
"requires": {}
"dev": true
},
"@vue/cli-service": {
"version": "4.5.11",
@ -17325,8 +17334,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
"integrity": "sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==",
"dev": true,
"requires": {}
"dev": true
},
"@vue/reactivity": {
"version": "3.0.5",
@ -17572,8 +17580,7 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true,
"requires": {}
"dev": true
},
"acorn-node": {
"version": "1.8.2",
@ -17622,15 +17629,13 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
"integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
"dev": true,
"requires": {}
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
"dev": true
},
"alphanum-sort": {
"version": "1.0.2",
@ -26881,6 +26886,11 @@
}
}
},
"vue-router": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.3.tgz",
"integrity": "sha512-AD1OjtVPyQHTSpoRsEGfPpxRQwhAhxcacOYO3zJ3KNkYP/r09mileSp6kdMQKhZWP2cFsPR3E2M3PZguSN5/ww=="
},
"vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",

View File

@ -13,11 +13,13 @@
"core-js": "^3.6.5",
"postcss": "^7.0.35",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2",
"vue": "^3.0.0"
"vue": "^3.0.0",
"vue-router": "^4.0.0-0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",

View File

@ -1,51 +1,42 @@
<template>
<Services :serviceStatuses="serviceStatuses" :showStatusOnHover="true" @showTooltip="showTooltip"/>
<div class="container container-xs relative mx-auto rounded shadow-xl border my-3 p-5 text-left" id="global">
<div class="mb-2">
<div class="flex flex-wrap">
<div class="w-2/3 text-left my-auto">
<div class="title text-5xl font-light">Health Status</div>
</div>
<div class="w-1/3 flex justify-end">
<img src="./assets/logo.png" alt="Gatus" style="min-width: 50px; max-width: 200px; width: 20%;"/>
</div>
</div>
</div>
<router-view @showTooltip="showTooltip"/>
</div>
<Tooltip :result="tooltip.result" :event="tooltip.event"/>
<Social/>
<Settings @refreshStatuses="fetchStatuses"/>
</template>
<script>
import Social from './components/Social.vue'
import Settings from './components/Settings.vue'
import Services from './components/Services.vue';
import Tooltip from './components/Tooltip.vue';
import {SERVER_URL} from "./main.js";
export default {
name: 'App',
components: {
Services,
Social,
Settings,
Tooltip
},
methods: {
fetchStatuses() {
console.log("[App][fetchStatuses] Fetching statuses");
fetch(`${SERVER_URL}/api/v1/statuses`)
.then(response => response.json())
.then(data => {
if (JSON.stringify(this.serviceStatuses) !== JSON.stringify(data)) {
console.log(data);
this.serviceStatuses = data;
}
});
},
showTooltip(result, event) {
this.tooltip = {result: result, event: event};
}
},
data() {
return {
serviceStatuses: {},
tooltip: {}
}
},
created() {
this.fetchStatuses();
}
}
</script>
@ -53,9 +44,10 @@ export default {
<style>
html, body {
background-color: #f7f9fb;
}
html, body {
height: 100%;
}
#global, #results {
max-width: 1200px;
}
</style>

View File

@ -1,8 +1,8 @@
<template>
<div class='container px-3 py-3 border-l border-r border-t rounded-none'>
<div class='service container px-3 py-3 border-l border-r border-t rounded-none' v-if="data && data.results && data.results.length">
<div class='flex flex-wrap mb-2'>
<div class='w-3/4'>
<span class='font-bold'>{{ data.name }}</span> <span class='text-gray-500 font-light'>- {{ data.results[data.results.length - 1].hostname }}</span>
<router-link :to="generatePath()" class="font-bold transition duration-200 ease-in-out hover:text-blue-900">{{ data.name }}</router-link> <span class='text-gray-500 font-light'>- {{ data.results[data.results.length - 1].hostname }}</span>
</div>
<div class='w-1/4 text-right'>
<span class='font-light status-min-max-ms'>
@ -41,6 +41,7 @@ export default {
maximumNumberOfResults: Number,
data: Object,
},
emits: ['showTooltip'],
methods: {
updateMinAndMaxResponseTimes() {
let minResponseTime = null;
@ -73,6 +74,12 @@ export default {
}
return (differenceInMs/1000).toFixed(0) + " seconds ago";
},
generatePath() {
if (!this.data) {
return "/";
}
return "/services/" + this.data.key;
},
showTooltip(result, event) {
this.$emit('showTooltip', result, event);
}
@ -96,6 +103,19 @@ export default {
<style>
.service:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.service:last-child {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-width: 1px;
border-color: #dee2e6;
border-style: solid;
}
.status {
cursor: pointer;
transition: all 500ms ease-in-out;

View File

@ -33,6 +33,7 @@ export default {
name: String,
services: Array
},
emits: ['showTooltip'],
methods: {
healthCheck() {
if (this.services) {

View File

@ -1,20 +1,8 @@
<template>
<div class="container mx-auto rounded shadow-xl border my-3 p-5 text-left" id="global">
<div class="mb-2">
<div class="flex flex-wrap">
<div class="w-2/3 text-left my-auto">
<div class="title font-light">Health Status</div>
</div>
<div class="w-1/3 flex justify-end">
<img src="../assets/logo.png" alt="Gatus" style="min-width: 50px; max-width: 200px; width: 20%;"/>
</div>
</div>
</div>
<div id="results">
<slot v-for="serviceGroup in serviceGroups" :key="serviceGroup">
<ServiceGroup :services="serviceGroup.services" :name="serviceGroup.name" @showTooltip="showTooltip" />
</slot>
</div>
<div id="results">
<slot v-for="serviceGroup in serviceGroups" :key="serviceGroup">
<ServiceGroup :services="serviceGroup.services" :name="serviceGroup.name" @showTooltip="showTooltip"/>
</slot>
</div>
</template>
@ -31,6 +19,7 @@ export default {
showStatusOnHover: Boolean,
serviceStatuses: Object
},
emits: ['showTooltip'],
methods: {
process() {
let outputByGroup = {};
@ -45,7 +34,7 @@ export default {
let serviceGroups = [];
for (let name in outputByGroup) {
if (name !== 'undefined') {
serviceGroups.push({ name: name, services: outputByGroup[name]})
serviceGroups.push({name: name, services: outputByGroup[name]})
}
}
// Add all services that don't have a group at the end
@ -74,29 +63,8 @@ export default {
<style>
#global {
max-width: 1140px;
}
#results div.container:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
#results div.container:last-child {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-width: 1px;
border-color: #dee2e6;
border-style: solid;
}
#results .service-group-content > div:nth-child(1) {
.service-group-content > div:nth-child(1) {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.title {
font-size: 2.5rem;
}
</style>

View File

@ -25,14 +25,14 @@ export default {
setRefreshInterval(seconds) {
let that = this;
this.refreshIntervalHandler = setInterval(function() {
that.refreshStatuses();
that.refreshData();
}, seconds * 1000);
},
refreshStatuses() {
this.$emit('refreshStatuses');
refreshData() {
this.$emit('refreshData');
},
handleChangeRefreshInterval() {
this.refreshStatuses();
this.refreshData();
clearInterval(this.refreshIntervalHandler);
this.setRefreshInterval(this.$refs.refreshInterval.value);
}
@ -40,6 +40,9 @@ export default {
created() {
this.setRefreshInterval(this.refreshInterval);
},
unmounted() {
clearInterval(this.refreshIntervalHandler);
},
data() {
return {
refreshInterval: 30,

View File

@ -1,7 +1,8 @@
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router'
export const SERVER_URL = process.env.NODE_ENV === 'production' ? '.' : 'http://localhost:8080'
createApp(App).mount('#app')
createApp(App).use(router).mount('#app')

View File

@ -0,0 +1,23 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Details from "@/views/Details";
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/services/:key',
name: 'Details',
component: Details
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router

View File

@ -0,0 +1,90 @@
<template>
<router-link to="/" class="absolute top-2 left-2 inline-block px-2 py-0 text-lg font-medium leading-6 text-center text-black transition bg-gray-100 rounded shadow ripple hover:shadow-lg hover:bg-gray-200 focus:outline-none">
&#8592;
</router-link>
<div class="container mx-auto">
<slot v-if="serviceStatus">
<h1 class="text-3xl text-monospace text-gray-400">RECENT CHECKS</h1>
<hr class="mb-4" />
<Service :data="serviceStatus" :maximumNumberOfResults="20" @showTooltip="showTooltip" />
</slot>
<!-- print table of each results in table? that'd be sick as fuck -->
<div v-if="serviceStatus.uptime" class="mt-5">
<h1 class="text-3xl text-monospace text-gray-400">UPTIME</h1>
<hr />
<div class="flex space-x-4 text-center text-2xl mt-5">
<div class="flex-1">
{{ prettifyUptime(serviceStatus.uptime['7d']) }}
<h2 class="text-sm text-gray-400">Last 7 days</h2>
</div>
<div class="flex-1">
{{ prettifyUptime(serviceStatus.uptime['24h']) }}
<h2 class="text-sm text-gray-400">Last 24 hours</h2>
</div>
<div class="flex-1">
{{ prettifyUptime(serviceStatus.uptime['1h']) }}
<h2 class="text-sm text-gray-400">Last hour</h2>
</div>
</div>
</div>
</div>
<Settings @refreshData="fetchData"/>
</template>
<script>
import Settings from '@/components/Settings.vue'
import Service from '@/components/Service.vue';
import {SERVER_URL} from "@/main.js";
export default {
name: 'Details',
components: {
Service,
Settings,
},
emits: ['showTooltip'],
methods: {
fetchData() {
console.log("[Details][fetchData] Fetching data");
fetch(`${SERVER_URL}/api/v1/statuses/${this.$route.params.key}`)
.then(response => response.json())
.then(data => {
if (JSON.stringify(this.serviceStatus) !== JSON.stringify(data)) {
console.log(data);
this.serviceStatus = data;
}
});
},
prettifyUptime(uptime) {
if (!uptime) {
return "0%";
}
return (uptime * 100).toFixed(2) + "%"
},
showTooltip(result, event) {
this.$emit('showTooltip', result, event);
}
},
data() {
return {
serviceStatus: {}
}
},
created() {
this.fetchData();
}
}
</script>
<style scoped>
.service {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-width: 1px;
border-color: #dee2e6;
border-style: solid;
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<Services :serviceStatuses="serviceStatuses" :showStatusOnHover="true" @showTooltip="showTooltip"/>
<Settings @refreshData="fetchData"/>
</template>
<script>
import Settings from '@/components/Settings.vue'
import Services from '@/components/Services.vue';
import {SERVER_URL} from "@/main.js";
export default {
name: 'Home',
components: {
Services,
Settings,
},
emits: ['showTooltip'],
methods: {
fetchData() {
console.log("[Home][fetchData] Fetching data");
fetch(`${SERVER_URL}/api/v1/statuses`)
.then(response => response.json())
.then(data => {
if (JSON.stringify(this.serviceStatuses) !== JSON.stringify(data)) {
console.log(data);
this.serviceStatuses = data;
}
});
},
showTooltip(result, event) {
this.$emit('showTooltip', result, event);
}
},
data() {
return {
serviceStatuses: {}
}
},
created() {
this.fetchData();
}
}
</script>