Grafana Time Range Duration Display

Shows the selected time range duration in seconds/minutes next to the date picker

Size

7.0 KB

Version

1.0.1

Created

Nov 9, 2025

Updated

15 days ago

1// ==UserScript==
2// @name		Grafana Time Range Duration Display
3// @description		Shows the selected time range duration in seconds/minutes next to the date picker
4// @version		1.0.1
5// @match		https://*.impala.app.cx498.coralogix.com/*
6// @icon		https://impala.app.cx498.coralogix.com/grafana/public/img/fav32.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Grafana Time Range Duration Display extension loaded');
12
13    // Debounce function to avoid excessive updates
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25
26    // Format duration into human-readable format
27    function formatDuration(milliseconds) {
28        const seconds = Math.floor(milliseconds / 1000);
29        const minutes = Math.floor(seconds / 60);
30        const hours = Math.floor(minutes / 60);
31        const days = Math.floor(hours / 24);
32
33        if (days > 0) {
34            const remainingHours = hours % 24;
35            return `${days}d ${remainingHours}h (${seconds.toLocaleString()}s)`;
36        } else if (hours > 0) {
37            const remainingMinutes = minutes % 60;
38            return `${hours}h ${remainingMinutes}m (${seconds.toLocaleString()}s)`;
39        } else if (minutes > 0) {
40            const remainingSeconds = seconds % 60;
41            return `${minutes}m ${remainingSeconds}s (${seconds.toLocaleString()}s)`;
42        } else {
43            return `${seconds}s`;
44        }
45    }
46
47    // Extract time range from URL
48    function getTimeRangeFromURL() {
49        const urlParams = new URLSearchParams(window.location.search);
50        const from = urlParams.get('from');
51        const to = urlParams.get('to');
52
53        if (from && to) {
54            const fromMs = parseInt(from);
55            const toMs = parseInt(to);
56            const duration = toMs - fromMs;
57            return { from: fromMs, to: toMs, duration };
58        }
59        return null;
60    }
61
62    // Create and insert the duration display element
63    function createDurationDisplay() {
64        const timeRange = getTimeRangeFromURL();
65        if (!timeRange) {
66            console.log('No time range found in URL');
67            return;
68        }
69
70        // Find the time picker button
71        const timePickerButton = document.querySelector('button[data-testid="data-testid TimePicker Open Button"]');
72        if (!timePickerButton) {
73            console.log('Time picker button not found');
74            return;
75        }
76
77        // Remove existing duration display if present
78        const existingDisplay = document.getElementById('time-range-duration-display');
79        if (existingDisplay) {
80            existingDisplay.remove();
81        }
82
83        // Create the duration display element
84        const durationDisplay = document.createElement('div');
85        durationDisplay.id = 'time-range-duration-display';
86        durationDisplay.style.cssText = `
87            display: inline-flex;
88            align-items: center;
89            margin-left: 8px;
90            padding: 6px 12px;
91            background: rgba(36, 41, 46, 0.9);
92            border: 1px solid rgba(110, 159, 255, 0.4);
93            border-radius: 4px;
94            color: #6e9fff;
95            font-size: 13px;
96            font-weight: 500;
97            font-family: inherit;
98            white-space: nowrap;
99            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
100        `;
101
102        const formattedDuration = formatDuration(timeRange.duration);
103        durationDisplay.innerHTML = `
104            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
105                <circle cx="12" cy="12" r="10"></circle>
106                <polyline points="12 6 12 12 16 14"></polyline>
107            </svg>
108            <span style="color: #c7d0d9;">Duration:</span>
109            <span style="margin-left: 6px; color: #6e9fff; font-weight: 600;">${formattedDuration}</span>
110        `;
111
112        // Insert after the time picker button group
113        const buttonGroup = timePickerButton.closest('.button-group');
114        if (buttonGroup && buttonGroup.parentElement) {
115            buttonGroup.parentElement.insertBefore(durationDisplay, buttonGroup.nextSibling);
116            console.log('Duration display added:', formattedDuration);
117        }
118    }
119
120    // Initialize the extension
121    function init() {
122        console.log('Initializing Grafana Time Range Duration Display');
123        
124        // Wait for the page to be fully loaded
125        if (document.readyState === 'loading') {
126            document.addEventListener('DOMContentLoaded', init);
127            return;
128        }
129
130        // Initial creation with a delay to ensure Grafana UI is ready
131        setTimeout(() => {
132            createDurationDisplay();
133        }, 1000);
134
135        // Watch for URL changes (Grafana is a SPA)
136        let lastUrl = window.location.href;
137        const urlObserver = debounce(() => {
138            const currentUrl = window.location.href;
139            if (currentUrl !== lastUrl) {
140                lastUrl = currentUrl;
141                console.log('URL changed, updating duration display');
142                setTimeout(createDurationDisplay, 500);
143            }
144        }, 300);
145
146        // Use MutationObserver to detect DOM changes
147        const observer = new MutationObserver(debounce(() => {
148            urlObserver();
149            // Also check if the time picker exists and duration display doesn't
150            const timePickerExists = document.querySelector('button[data-testid="data-testid TimePicker Open Button"]');
151            const durationExists = document.getElementById('time-range-duration-display');
152            if (timePickerExists && !durationExists) {
153                console.log('Time picker found but duration display missing, recreating');
154                createDurationDisplay();
155            }
156        }, 500));
157
158        observer.observe(document.body, {
159            childList: true,
160            subtree: true
161        });
162
163        // Listen for popstate events (browser back/forward)
164        window.addEventListener('popstate', () => {
165            console.log('Navigation detected via popstate');
166            setTimeout(createDurationDisplay, 500);
167        });
168
169        // Listen for pushstate/replacestate (SPA navigation)
170        const originalPushState = history.pushState;
171        const originalReplaceState = history.replaceState;
172
173        history.pushState = function() {
174            originalPushState.apply(this, arguments);
175            console.log('Navigation detected via pushState');
176            setTimeout(createDurationDisplay, 500);
177        };
178
179        history.replaceState = function() {
180            originalReplaceState.apply(this, arguments);
181            console.log('Navigation detected via replaceState');
182            setTimeout(createDurationDisplay, 500);
183        };
184    }
185
186    // Start the extension
187    init();
188})();
Grafana Time Range Duration Display | Robomonkey