Adds an (optional) status check feature, plus some refactoring

This commit is contained in:
Alicia Sykes
2021-06-14 20:44:07 +01:00
parent 195d433f75
commit 0b1f66b7b7
8 changed files with 254 additions and 67 deletions

View File

@@ -20,12 +20,20 @@
<!-- Small icon, showing opening method on hover -->
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon" :openingMethod="target"
:position="itemSize === 'medium'? 'bottom right' : 'top right'"/>
<StatusIndicator
class="status-indicator"
v-if="enableStatusCheck"
:statusSuccess="statusResponse ? statusResponse.successStatus : undefined"
:statusText="statusResponse ? statusResponse.message : undefined"
/>
</a>
</template>
<script>
import axios from 'axios';
import Icon from '@/components/LinkItems/ItemIcon.vue';
import ItemOpenMethodIcon from '@/components/LinkItems/ItemOpenMethodIcon';
import StatusIndicator from '@/components/LinkItems/StatusIndicator';
export default {
name: 'Item',
@@ -44,6 +52,7 @@ export default {
validator: (value) => ['newtab', 'sametab', 'iframe'].indexOf(value) !== -1,
},
itemSize: String,
enableStatusCheck: Boolean,
},
data() {
return {
@@ -52,11 +61,13 @@ export default {
color: this.color,
background: this.backgroundColor,
},
statusResponse: undefined,
};
},
components: {
Icon,
ItemOpenMethodIcon,
StatusIndicator,
},
methods: {
/* Called when an item is clicked, manages the opening of iframe & resets the search field */
@@ -88,9 +99,11 @@ export default {
trigger: 'hover focus',
hideOnTargetClick: true,
html: false,
placement: this.statusResponse ? 'left' : 'auto',
delay: { show: 600, hide: 200 },
};
},
/* Used by certain themes, which display an icon with animated CSS */
getUnicodeOpeningIcon() {
switch (this.target) {
case 'newtab': return '"\\f360"';
@@ -99,9 +112,24 @@ export default {
default: return '"\\f054"';
}
},
checkWebsiteStatus() {
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
const endpoint = `${baseUrl}/ping?url=${this.url}`;
axios.get(endpoint)
.then((response) => {
if (response.data) this.statusResponse = response.data;
})
.catch(() => {
this.statusResponse = {
statusText: 'Failed to make request',
statusSuccess: false,
};
});
},
},
mounted() {
this.manageTitleEllipse();
if (this.enableStatusCheck) this.checkWebsiteStatus();
},
};
</script>
@@ -122,6 +150,7 @@ export default {
box-shadow: var(--item-shadow);
cursor: pointer;
text-decoration: none;
position: relative;
&:hover {
box-shadow: var(--item-hover-shadow);
background: var(--item-background-hover);
@@ -175,6 +204,13 @@ export default {
}
}
/* Colored dot showing service status */
.status-indicator {
position: absolute;
top: 0;
right: 0;
}
.opening-method-icon {
display: none; // Hidden by default, visible on hover
}

View File

@@ -28,6 +28,7 @@
:color="item.color"
:backgroundColor="item.backgroundColor"
:itemSize="newItemSize"
:enableStatusCheck="shouldEnableStatusCheck(item.statusCheck)"
@itemClicked="$emit('itemClicked')"
@triggerModal="triggerModal"
/>
@@ -49,6 +50,7 @@ import IframeModal from '@/components/LinkItems/IframeModal.vue';
export default {
name: 'ItemGroup',
inject: ['config'],
props: {
groupId: String,
title: String,
@@ -92,6 +94,10 @@ export default {
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
shouldEnableStatusCheck(itemPreference) {
const globalPreference = this.config.appConfig.statusCheck || false;
return itemPreference !== undefined ? itemPreference : globalPreference;
},
},
};
</script>

View File

@@ -0,0 +1,122 @@
<template>
<div
v-tooltip="{
content: statusText || otherStatusText, classes: ['status-tooltip', `tip-${color()}`] }"
class="indicator"
@click="showToast()">
<div :class="`dot dot-${color()}`">
<span><span></span></span>
</div>
</div>
</template>
<script>
export default {
name: 'StatusIndicator',
props: {
statusText: String,
statusSuccess: Boolean,
},
methods: {
/* Returns a color, based on success status */
color() {
switch (this.statusSuccess) {
case undefined: return ((new Date() - this.startTime) > 2000) ? 'grey' : 'yellow';
case true: return 'green'; // Success!
default: return 'red'; // Not success, therefore failure
}
},
},
data() {
return {
startTime: new Date(), // Used for timeout
otherStatusText: 'Checking...', // Used before server has responded
};
},
mounted() {
setTimeout(() => {
if (!this.statusText) this.otherStatusText = 'Request timed out';
}, 2000);
},
};
</script>
<style scoped lang="scss">
.indicator {
padding: 5px;
transition: all .2s ease-in-out;
cursor: help;
&:hover {
transform: scale(1.25);
filter: saturate(2);
opacity: 1;
}
}
@keyframes pulse {
0% { opacity: .75; transform: scale(1); }
25% { opacity: 0.75; transform: scale(1); }
100% { opacity: 0; transform: scale(1.8); }
}
@keyframes applyOpacity {
50% { opacity: 0.9; }
to { opacity: 0.8; }
}
.dot {
border-radius: 50%;
height: 12px;
width: 12px;
animation: applyOpacity 1s ease-in 8s forwards;
> span, > span span, > span span:after {
animation: pulse 1s linear 0.5s 2;
border-radius: 50%;
display: block;
height: 12px;
width: 12px;
content: '';
}
&.dot-green {
background-color: var(--success);
span, span:after {
background-color: var(--success);
opacity: 0.4;
}
}
&.dot-red {
background-color: var(--danger);
span, span:after {
background-color: var(--danger);
opacity: 0.4;
}
}
&.dot-yellow {
background-color: var(--warning);
span, span:after {
background-color: var(--warning);
opacity: 0.4;
}
}
&.dot-grey {
background-color: var(--medium-grey);
span, span:after {
background-color: var(--medium-grey);
opacity: 0.4;
}
}
}
</style>
<style lang="scss">
.status-tooltip {
background: var(--background-darker) !important;
font-size: 1rem;
z-index: 10;
&.tip-green { border: 1px solid var(--success); }
&.tip-yellow { border: 1px solid var(--warning); }
&.tip-red { border: 1px solid var(--danger); }
}
</style>