Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions web/app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
</div>

<!-- Tooltip -->
<Tooltip :result="tooltip.result" :event="tooltip.event" />
<Tooltip :result="tooltip.result" :event="tooltip.event" :isPersistent="tooltipIsPersistent" />
</div>
</template>

Expand All @@ -173,6 +173,7 @@ const announcements = ref([])
const tooltip = ref({})
const mobileMenuOpen = ref(false)
const isOidcLoading = ref(false)
const tooltipIsPersistent = ref(false)
let configInterval = null

// Computed properties
Expand Down Expand Up @@ -209,15 +210,48 @@ const fetchConfig = async () => {
}
}

const showTooltip = (result, event) => {
tooltip.value = { result, event }
const showTooltip = (result, event, action = 'hover') => {
if (action === 'click') {
if (!result) {
// Deselecting
tooltip.value = {}
tooltipIsPersistent.value = false
} else {
// Selecting new data point
tooltip.value = { result, event }
tooltipIsPersistent.value = true
}
} else if (action === 'hover') {
// Only update tooltip on hover if not in persistent mode
if (!tooltipIsPersistent.value) {
tooltip.value = { result, event }
}
}
}

const handleDocumentClick = (event) => {
// Close persistent tooltip when clicking outside
if (tooltipIsPersistent.value) {
const tooltipEl = document.getElementById('tooltip')
// Check if click is on a data point bar or inside tooltip
const clickedDataPoint = event.target.closest('.flex-1.h-6, .flex-1.h-8')

if (tooltipEl && !tooltipEl.contains(event.target) && !clickedDataPoint) {
tooltip.value = {}
tooltipIsPersistent.value = false
// Emit event to clear selections in child components
window.dispatchEvent(new CustomEvent('clear-data-point-selection'))
}
}
}

// Fetch config on mount and set up interval
onMounted(() => {
fetchConfig()
// Refresh config every 10 minutes for announcements
configInterval = setInterval(fetchConfig, 600000)
// Add click listener for closing persistent tooltips
document.addEventListener('click', handleDocumentClick)
})

// Clean up interval on unmount
Expand All @@ -226,5 +260,7 @@ onUnmounted(() => {
clearInterval(configInterval)
configInterval = null
}
// Remove click listener
document.removeEventListener('click', handleDocumentClick)
})
</script>
49 changes: 45 additions & 4 deletions web/app/src/components/EndpointCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@
:key="index"
:class="[
'flex-1 h-6 sm:h-8 rounded-sm transition-all',
result ? (result.success ? 'bg-green-500 hover:bg-green-700' : 'bg-red-500 hover:bg-red-700') : 'bg-gray-200 dark:bg-gray-700'
result ? 'cursor-pointer' : '',
result ? (
result.success
? (selectedResultIndex === index ? 'bg-green-700' : 'bg-green-500 hover:bg-green-700')
: (selectedResultIndex === index ? 'bg-red-700' : 'bg-red-500 hover:bg-red-700')
) : 'bg-gray-200 dark:bg-gray-700'
]"
@mouseenter="result && emit('showTooltip', result, $event)"
@mouseleave="result && emit('showTooltip', null, $event)"
@mouseenter="result && handleMouseEnter(result, $event)"
@mouseleave="result && handleMouseLeave(result, $event)"
@click.stop="result && handleClick(result, $event, index)"
/>
</div>
<div class="flex items-center justify-between text-xs text-muted-foreground mt-1">
Expand All @@ -57,7 +63,7 @@

<script setup>
/* eslint-disable no-undef */
import { computed } from 'vue'
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import StatusBadge from '@/components/StatusBadge.vue'
Expand All @@ -82,6 +88,9 @@ const props = defineProps({

const emit = defineEmits(['showTooltip'])

// Track selected data point
const selectedResultIndex = ref(null)

const latestResult = computed(() => {
if (!props.endpoint.results || props.endpoint.results.length === 0) {
return null
Expand Down Expand Up @@ -156,4 +165,36 @@ const newestResultTime = computed(() => {
const navigateToDetails = () => {
router.push(`/endpoints/${props.endpoint.key}`)
}

const handleMouseEnter = (result, event) => {
emit('showTooltip', result, event, 'hover')
}

const handleMouseLeave = (result, event) => {
emit('showTooltip', null, event, 'hover')
}

const handleClick = (result, event, index) => {
// Toggle selection
if (selectedResultIndex.value === index) {
selectedResultIndex.value = null
emit('showTooltip', null, event, 'click')
} else {
selectedResultIndex.value = index
emit('showTooltip', result, event, 'click')
}
}

// Listen for clear selection event
const handleClearSelection = () => {
selectedResultIndex.value = null
}

onMounted(() => {
window.addEventListener('clear-data-point-selection', handleClearSelection)
})

onUnmounted(() => {
window.removeEventListener('clear-data-point-selection', handleClearSelection)
})
</script>
30 changes: 26 additions & 4 deletions web/app/src/components/Tooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ const props = defineProps({
result: {
type: Object,
default: null
},
isPersistent: {
type: Boolean,
default: false
}
})

Expand Down Expand Up @@ -138,7 +142,7 @@ const reposition = async () => {

await nextTick()

if (props.event.type === 'mouseenter' && tooltip.value) {
if ((props.event.type === 'mouseenter' || props.event.type === 'click') && tooltip.value) {
const target = props.event.target
const targetRect = target.getBoundingClientRect()

Expand Down Expand Up @@ -190,18 +194,24 @@ const reposition = async () => {
top.value = Math.round(newTop)
left.value = Math.round(newLeft)
} else if (props.event.type === 'mouseleave') {
hidden.value = true
// Only hide on mouseleave if not in persistent mode
if (!props.isPersistent) {
hidden.value = true
}
}
}

// Watchers
watch(() => props.event, (newEvent) => {
if (newEvent && newEvent.type) {
if (newEvent.type === 'mouseenter') {
if (newEvent.type === 'mouseenter' || newEvent.type === 'click') {
hidden.value = false
nextTick(() => reposition())
} else if (newEvent.type === 'mouseleave') {
hidden.value = true
// Only hide on mouseleave if not in persistent mode
if (!props.isPersistent) {
hidden.value = true
}
}
}
}, { immediate: true })
Expand All @@ -211,4 +221,16 @@ watch(() => props.result, () => {
nextTick(() => reposition())
}
})

// Watch for persistent state changes and result changes
watch(() => [props.isPersistent, props.result], ([isPersistent, result]) => {
if (!isPersistent && !result) {
// Hide tooltip when both persistent mode is off and no result
hidden.value = true
} else if (result && (isPersistent || props.event?.type === 'mouseenter')) {
// Show tooltip when there's a result and either persistent or hovering
hidden.value = false
nextTick(() => reposition())
}
})
</script>
4 changes: 2 additions & 2 deletions web/app/src/views/EndpointDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,8 @@ const changePage = (page) => {
fetchData()
}

const showTooltip = (result, event) => {
emit('showTooltip', result, event)
const showTooltip = (result, event, action = 'hover') => {
emit('showTooltip', result, event, action)
}

const prettifyTimestamp = (timestamp) => {
Expand Down
4 changes: 2 additions & 2 deletions web/app/src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,8 @@ const toggleShowAverageResponseTime = () => {
showAverageResponseTime.value = !showAverageResponseTime.value
}

const showTooltip = (result, event) => {
emit('showTooltip', result, event)
const showTooltip = (result, event, action = 'hover') => {
emit('showTooltip', result, event, action)
}

const calculateUnhealthyCount = (endpoints) => {
Expand Down
2 changes: 1 addition & 1 deletion web/static/css/app.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion web/static/js/app.js

Large diffs are not rendered by default.