Add tooltip

This commit is contained in:
TwinProduction 2021-01-25 20:54:57 -05:00
parent 668ed3b1a2
commit 67a3e4e330
6 changed files with 172 additions and 19 deletions

View File

@ -1,7 +1,8 @@
<template> <template>
<Services :serviceStatuses="serviceStatuses" :maximumNumberOfResults="20" :showStatusOnHover="true" /> <Services :serviceStatuses="serviceStatuses" :showStatusOnHover="true" @showTooltip="showTooltip"/>
<Social /> <Tooltip :result="tooltip.result" :event="tooltip.event"/>
<Settings @refreshStatuses="fetchStatuses" /> <Social/>
<Settings @refreshStatuses="fetchStatuses"/>
</template> </template>
@ -9,13 +10,15 @@
import Social from './components/Social.vue' import Social from './components/Social.vue'
import Settings from './components/Settings.vue' import Settings from './components/Settings.vue'
import Services from './components/Services.vue'; import Services from './components/Services.vue';
import Tooltip from './components/Tooltip.vue';
export default { export default {
name: 'App', name: 'App',
components: { components: {
Services, Services,
Social, Social,
Settings Settings,
Tooltip
}, },
methods: { methods: {
fetchStatuses() { fetchStatuses() {
@ -28,11 +31,15 @@ export default {
this.serviceStatuses = data; this.serviceStatuses = data;
} }
}); });
},
showTooltip(result, event) {
this.tooltip = {result: result, event: event};
} }
}, },
data() { data() {
return { return {
serviceStatuses: {} serviceStatuses: {},
tooltip: {}
} }
}, },
created() { created() {
@ -43,10 +50,11 @@ export default {
<style> <style>
html, body { html, body {
background-color: #f7f9fb; background-color: #f7f9fb;
} }
html {
html, body {
height: 100%; height: 100%;
} }
</style> </style>

View File

@ -12,16 +12,17 @@
</div> </div>
<div> <div>
<div class='status-over-time flex flex-row'> <div class='status-over-time flex flex-row'>
<slot v-for="filler in 20 - data.results.length" :key="filler"> <slot v-for="filler in maximumNumberOfResults - data.results.length" :key="filler">
<span class="status rounded border border-dashed"> </span> <span class="status rounded border border-dashed"> </span>
</slot> </slot>
<slot v-for="result in data.results" :key="result"> <slot v-for="result in data.results" :key="result">
<span v-if="result.success" class="status rounded bg-success">&#10003;</span> <span v-if="result.success" class="status rounded bg-success" @mouseenter="showTooltip(result, $event)" @mouseleave="showTooltip(null, $event)">&#10003;</span>
<span v-else class="status rounded bg-red-600">X</span> <span v-else class="status rounded bg-red-600" @mouseenter="showTooltip(result, $event)" @mouseleave="showTooltip(null, $event)">X</span>
</slot> </slot>
</div> </div>
</div> </div>
<div class='flex flex-wrap status-time-ago'> <div class='flex flex-wrap status-time-ago'>
<!-- Show "Last update at" instead? -->
<div class='w-1/2'> <div class='w-1/2'>
{{ generatePrettyTimeAgo(data.results[0].timestamp) }} {{ generatePrettyTimeAgo(data.results[0].timestamp) }}
</div> </div>
@ -37,7 +38,8 @@
export default { export default {
name: 'Service', name: 'Service',
props: { props: {
data: Object maximumNumberOfResults: Number,
data: Object,
}, },
methods: { methods: {
updateMinAndMaxResponseTimes() { updateMinAndMaxResponseTimes() {
@ -70,6 +72,9 @@ export default {
return minutes + " minute" + (minutes !== "1" ? "s" : "") + " ago"; return minutes + " minute" + (minutes !== "1" ? "s" : "") + " ago";
} }
return (differenceInMs/1000).toFixed(0) + " seconds ago"; return (differenceInMs/1000).toFixed(0) + " seconds ago";
},
showTooltip(result, event) {
this.$emit('showTooltip', result, event);
} }
}, },
watch: { watch: {

View File

@ -14,7 +14,7 @@
</slot> </slot>
<div v-if="!collapsed" :class="name === 'undefined' ? '' : 'service-group-content'"> <div v-if="!collapsed" :class="name === 'undefined' ? '' : 'service-group-content'">
<slot v-for="service in services" :key="service"> <slot v-for="service in services" :key="service">
<Service :data="service"/> <Service :data="service" @showTooltip="showTooltip" :maximumNumberOfResults="50" />
</slot> </slot>
</div> </div>
</div> </div>
@ -55,6 +55,9 @@ export default {
}, },
toggleGroup() { toggleGroup() {
this.collapsed = !this.collapsed; this.collapsed = !this.collapsed;
},
showTooltip(result, event) {
this.$emit('showTooltip', result, event);
} }
}, },
watch: { watch: {

View File

@ -12,7 +12,7 @@
</div> </div>
<div id="results"> <div id="results">
<slot v-for="serviceGroup in serviceGroups" :key="serviceGroup"> <slot v-for="serviceGroup in serviceGroups" :key="serviceGroup">
<ServiceGroup :services="serviceGroup.services" :name="serviceGroup.name" /> <ServiceGroup :services="serviceGroup.services" :name="serviceGroup.name" @showTooltip="showTooltip" />
</slot> </slot>
</div> </div>
</div> </div>
@ -28,7 +28,6 @@ export default {
ServiceGroup ServiceGroup
}, },
props: { props: {
maximumNumberOfResults: Number,
showStatusOnHover: Boolean, showStatusOnHover: Boolean,
serviceStatuses: Object serviceStatuses: Object
}, },
@ -54,6 +53,9 @@ export default {
serviceGroups.push({name: 'undefined', services: outputByGroup['undefined']}) serviceGroups.push({name: 'undefined', services: outputByGroup['undefined']})
} }
this.serviceGroups = serviceGroups; this.serviceGroups = serviceGroups;
},
showTooltip(result, event) {
this.$emit('showTooltip', result, event);
} }
}, },
watch: { watch: {

View File

@ -29,7 +29,7 @@ export default {
}, seconds * 1000); }, seconds * 1000);
}, },
refreshStatuses() { refreshStatuses() {
this.$emit('refreshStatuses') this.$emit('refreshStatuses');
}, },
handleChangeRefreshInterval() { handleChangeRefreshInterval() {
this.refreshStatuses(); this.refreshStatuses();

View File

@ -0,0 +1,135 @@
<template>
<div id="tooltip" ref="tooltip" :class="hidden ? 'invisible' : ''" :style="'top:' + top + 'px; left:' + left + 'px'">
<slot v-if="result">
<div class="tooltip-title">Timestamp:</div>
<code id="tooltip-timestamp">{{ prettifyTimestamp(result.timestamp) }}</code>
<div class="tooltip-title">Response time:</div>
<code id="tooltip-response-time">{{ (result.duration / 1000000).toFixed(0) }}ms</code>
<div class="tooltip-title">Conditions:</div>
<code id="tooltip-conditions">
<slot v-for="conditionResult in result.conditionResults" :key="conditionResult">
{{ conditionResult.success ? "&#10003;" : "X" }} ~ {{ conditionResult.condition }}<br/>
</slot>
</code>
<div id="tooltip-errors-container" v-if="result.errors && result.errors.length">
<div class="tooltip-title">Errors:</div>
<code id="tooltip-errors">
<slot v-for="error in result.errors" :key="error">
- {{ error }}<br/>
</slot>
</code>
</div>
</slot>
</div>
</template>
<script>
export default {
name: 'Services',
props: {
event: Event,
result: Object
},
methods: {
prettifyTimestamp(timestamp) {
let date = new Date(timestamp);
let YYYY = date.getFullYear();
let MM = ((date.getMonth() + 1) < 10 ? "0" : "") + "" + (date.getMonth() + 1);
let DD = ((date.getDate()) < 10 ? "0" : "") + "" + (date.getDate());
let hh = ((date.getHours()) < 10 ? "0" : "") + "" + (date.getHours());
let mm = ((date.getMinutes()) < 10 ? "0" : "") + "" + (date.getMinutes());
let ss = ((date.getSeconds()) < 10 ? "0" : "") + "" + (date.getSeconds());
return YYYY + "-" + MM + "-" + DD + " " + hh + ":" + mm + ":" + ss;
},
htmlEntities(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
},
reposition() {
if (this.event && this.event.type) {
if (this.event.type === 'mouseenter') {
let targetTopPosition = this.event.target.getBoundingClientRect().y + 30;
let targetLeftPosition = this.event.target.getBoundingClientRect().x;
let tooltipBoundingClientRect = this.$refs.tooltip.getBoundingClientRect();
if (targetLeftPosition + window.scrollX + tooltipBoundingClientRect.width + 50 > document.body.getBoundingClientRect().width) {
targetLeftPosition = this.event.target.getBoundingClientRect().x - tooltipBoundingClientRect.width + this.event.target.getBoundingClientRect().width;
if (targetLeftPosition < 0) {
targetLeftPosition += -targetLeftPosition;
}
}
if (targetTopPosition + window.scrollY + tooltipBoundingClientRect.height + 50 > document.body.getBoundingClientRect().height && targetTopPosition >= 0) {
targetTopPosition = this.event.target.getBoundingClientRect().y - (tooltipBoundingClientRect.height + 10);
if (targetTopPosition < 0) {
targetTopPosition = this.event.target.getBoundingClientRect().y + 30;
}
}
this.top = targetTopPosition;
this.left = targetLeftPosition;
} else if (this.event.type === 'mouseleave') {
this.hidden = true;
}
}
}
},
watch: {
event: function (value) {
if (value && value.type) {
if (value.type === 'mouseenter') {
this.hidden = false;
} else if (value.type === 'mouseleave') {
this.hidden = true;
}
}
}
},
updated() {
this.reposition();
},
created() {
this.reposition();
},
data() {
return {
hidden: false,
top: 0,
left: 0
}
}
}
</script>
<style>
#tooltip {
position: fixed;
background-color: white;
border: 1px solid lightgray;
border-radius: 4px;
padding: 6px;
font-size: 13px;
}
#tooltip code {
color: #212529;
line-height: 1;
}
#tooltip .tooltip-title {
font-weight: bold;
margin-bottom: 0;
display: block;
}
#tooltip .tooltip-title {
margin-top: 8px;
}
#tooltip > .tooltip-title:first-child {
margin-top: 0;
}
</style>