Adds an (optional) status check feature, plus some refactoring
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
122
src/components/LinkItems/StatusIndicator.vue
Normal file
122
src/components/LinkItems/StatusIndicator.vue
Normal 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>
|
||||
Reference in New Issue
Block a user