@@ -192,18 +192,192 @@ function getCookie(name) {
192192// Database Tab Functions
193193// =============================================================================
194194
195- async function executeSQL ( sql ) {
195+ // MSA authentication credentials (stored in memory for session)
196+ let msaCredentials = null ;
197+ let loginModalOpen = false ;
198+
199+ function getDbAuthHeader ( ) {
200+ if ( msaCredentials ) {
201+ return 'Basic ' + btoa ( msaCredentials . username + ':' + msaCredentials . password ) ;
202+ }
203+ return null ;
204+ }
205+
206+ function showLoginModal ( ) {
207+ // Don't reopen or refocus if already open
208+ if ( loginModalOpen ) {
209+ return ;
210+ }
211+
212+ loginModalOpen = true ;
213+ const modal = document . getElementById ( 'loginModal' ) ;
214+ const errorDiv = document . getElementById ( 'loginError' ) ;
215+ errorDiv . textContent = '' ;
216+ errorDiv . style . display = 'none' ;
217+ modal . classList . add ( 'active' ) ;
218+ document . getElementById ( 'loginUsername' ) . focus ( ) ;
219+ }
220+
221+ function closeLoginModal ( ) {
222+ loginModalOpen = false ;
223+ const modal = document . getElementById ( 'loginModal' ) ;
224+ modal . classList . remove ( 'active' ) ;
225+ document . getElementById ( 'loginForm' ) . reset ( ) ;
226+ }
227+
228+ async function handleLogin ( event ) {
229+ event . preventDefault ( ) ;
230+
231+ const username = document . getElementById ( 'loginUsername' ) . value ;
232+ const password = document . getElementById ( 'loginPassword' ) . value ;
233+ const errorDiv = document . getElementById ( 'loginError' ) ;
234+
235+ // Test credentials with a simple query
196236 try {
237+ const authHeader = 'Basic ' + btoa ( username + ':' + password ) ;
197238 const response = await fetch ( `${ API_BASE } /v1/execute` , {
198239 method : 'POST' ,
199240 headers : {
200241 'Content-Type' : 'application/json' ,
242+ 'X-Requested-With' : 'XMLHttpRequest' ,
243+ 'Authorization' : authHeader
201244 } ,
245+ body : JSON . stringify ( {
246+ stmt : [ 'SELECT 1' ]
247+ } )
248+ } ) ;
249+
250+ if ( response . status === 401 ) {
251+ errorDiv . textContent = 'Invalid username or password' ;
252+ errorDiv . style . display = 'block' ;
253+ return ;
254+ }
255+
256+ if ( ! response . ok ) {
257+ errorDiv . textContent = 'Connection error: ' + response . status ;
258+ errorDiv . style . display = 'block' ;
259+ return ;
260+ }
261+
262+ // Credentials are valid - store them
263+ msaCredentials = { username, password } ;
264+ closeLoginModal ( ) ;
265+ updateAuthMenuItem ( ) ;
266+
267+ // Refresh data with new credentials
268+ dbConnState ( ) ;
269+ loadMessages ( ) ;
270+
271+ // Connect MQTT if on Broker tab
272+ const activeTab = document . querySelector ( '.tab-content.active' ) ;
273+ if ( activeTab && activeTab . id === 'broker-tab' && ! window . mqttConnected ) {
274+ initMqttConnection ( ) ;
275+ window . mqttConnected = true ;
276+ }
277+ // Refresh broker display if on that tab
278+ if ( activeTab && activeTab . id === 'broker-tab' ) {
279+ displayMqttMessages ( ) ;
280+ }
281+
282+ } catch ( error ) {
283+ errorDiv . textContent = 'Connection failed: ' + error . message ;
284+ errorDiv . style . display = 'block' ;
285+ }
286+ }
287+
288+ // Update the Login/Logout menu item and button based on auth state
289+ function updateAuthMenuItem ( ) {
290+ const menuItem = document . getElementById ( 'authMenuItem' ) ;
291+ const authButton = document . getElementById ( 'authButton' ) ;
292+ const label = msaCredentials ? 'Logout' : 'Login' ;
293+
294+ if ( menuItem ) {
295+ menuItem . textContent = label ;
296+ }
297+ if ( authButton ) {
298+ authButton . textContent = label ;
299+ authButton . title = label ;
300+ }
301+ }
302+
303+ // Handle Login/Logout menu click
304+ function handleAuthMenuClick ( ) {
305+ toggleSettingsMenu ( ) ;
306+
307+ if ( msaCredentials ) {
308+ performLogout ( ) ;
309+ } else {
310+ showLoginModal ( ) ;
311+ }
312+ }
313+
314+ // Handle Login/Logout button click (same as menu but no menu toggle)
315+ function handleAuthButtonClick ( ) {
316+ if ( msaCredentials ) {
317+ performLogout ( ) ;
318+ } else {
319+ showLoginModal ( ) ;
320+ }
321+ }
322+
323+ // Perform logout - clear credentials and data
324+ function performLogout ( ) {
325+ msaCredentials = null ;
326+ loginModalOpen = false ;
327+ updateAuthMenuItem ( ) ;
328+
329+ // Clear database tab data
330+ //document.getElementById('results').innerHTML = '<div class="no-results">Please log in to view data</div>';
331+ document . getElementById ( 'results' ) . innerHTML = '<div class="no-results"></div>' ;
332+ document . getElementById ( 'dbStatusIcon' ) . textContent = '⚫' ;
333+
334+ // Clear broker tab data and disconnect MQTT
335+ mqttMessagesMap . clear ( ) ;
336+ if ( mqttClient && mqttClient . connected ) {
337+ mqttClient . end ( ) ;
338+ }
339+ window . mqttConnected = false ;
340+ updateMqttStatus ( 'Disconnected' , '⚫' , 'var(--ctp-overlay0)' ) ;
341+
342+ const brokerTbody = document . querySelector ( '#mqtt-messages-table tbody' ) ;
343+ if ( brokerTbody ) {
344+ //brokerTbody.innerHTML = '<tr><td colspan="6" class="login-required">Please log in to view data</td></tr>';
345+ brokerTbody . innerHTML = '<tr><td colspan="6" class="login-required"></td></tr>' ;
346+ }
347+
348+ // Stop auto-refresh if running
349+ if ( isAutoRefreshEnabled ) {
350+ toggleAutoRefresh ( true ) ;
351+ }
352+ }
353+
354+ async function executeSQL ( sql ) {
355+ try {
356+ const headers = {
357+ 'Content-Type' : 'application/json' ,
358+ 'X-Requested-With' : 'XMLHttpRequest' , // Identify as AJAX to prevent browser auth dialog
359+ } ;
360+
361+ // Add auth header if we have credentials
362+ const authHeader = getDbAuthHeader ( ) ;
363+ if ( authHeader ) {
364+ headers [ 'Authorization' ] = authHeader ;
365+ }
366+
367+ const response = await fetch ( `${ API_BASE } /v1/execute` , {
368+ method : 'POST' ,
369+ headers : headers ,
202370 body : JSON . stringify ( {
203371 stmt : [ sql ]
204372 } )
205373 } ) ;
206374
375+ // If unauthorized, show login modal
376+ if ( response . status === 401 ) {
377+ showLoginModal ( ) ;
378+ throw new Error ( 'Authentication required' ) ;
379+ }
380+
207381 if ( ! response . ok ) {
208382 throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
209383 }
@@ -296,6 +470,12 @@ async function loadMessages() {
296470}
297471
298472async function executeCustomQuery ( ) {
473+ // Check if user is logged in
474+ if ( ! msaCredentials ) {
475+ showLoginModal ( ) ;
476+ return ;
477+ }
478+
299479 let query = document . getElementById ( 'customQuery' ) . value . trim ( ) ;
300480 if ( ! query ) {
301481 showMessage ( 'Please enter a SQL query' , 'error' ) ;
@@ -525,8 +705,8 @@ function switchTab(tabName) {
525705 loadBrokerConfig ( ) ;
526706 }
527707
528- // Auto-connect MQTT if switching to Broker tab
529- if ( tabName === 'broker' && ! window . mqttConnected ) {
708+ // Auto-connect MQTT if switching to Broker tab (only if logged in)
709+ if ( tabName === 'broker' && ! window . mqttConnected && msaCredentials ) {
530710 setTimeout ( ( ) => {
531711 initMqttConnection ( ) ;
532712 window . mqttConnected = true ;
@@ -561,7 +741,21 @@ function restoreActiveTab() {
561741
562742async function loadBrokerConfig ( ) {
563743 try {
564- const response = await fetch ( '/broker-config' ) ;
744+ const headers = {
745+ 'X-Requested-With' : 'XMLHttpRequest'
746+ } ;
747+ const authHeader = getDbAuthHeader ( ) ;
748+ if ( authHeader ) {
749+ headers [ 'Authorization' ] = authHeader ;
750+ }
751+
752+ const response = await fetch ( '/broker-config' , { headers } ) ;
753+
754+ if ( response . status === 401 ) {
755+ showLoginModal ( ) ;
756+ return ;
757+ }
758+
565759 if ( ! response . ok ) {
566760 throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
567761 }
@@ -661,7 +855,15 @@ async function initMqttConnection() {
661855 let password = 'admin' ;
662856
663857 try {
664- const credResponse = await fetch ( '/mqtt-credentials' ) ;
858+ const credHeaders = {
859+ 'X-Requested-With' : 'XMLHttpRequest'
860+ } ;
861+ const authHeader = getDbAuthHeader ( ) ;
862+ if ( authHeader ) {
863+ credHeaders [ 'Authorization' ] = authHeader ;
864+ }
865+
866+ const credResponse = await fetch ( '/mqtt-credentials' , { headers : credHeaders } ) ;
665867 if ( credResponse . ok ) {
666868 const credentials = await credResponse . json ( ) ;
667869 username = credentials . username ;
@@ -795,6 +997,12 @@ function updateMqttStatus(text, icon, color) {
795997
796998// Publish a message to the MQTT broker
797999function publishMessage ( ) {
1000+ // Check if user is logged in
1001+ if ( ! msaCredentials ) {
1002+ showLoginModal ( ) ;
1003+ return ;
1004+ }
1005+
7981006 if ( ! mqttClient || ! mqttClient . connected ) {
7991007 console . error ( 'MQTT client not connected, cannot publish message' ) ;
8001008 alert ( 'MQTT client not connected. Please wait for connection.' ) ;
@@ -884,6 +1092,13 @@ function displayMqttMessages() {
8841092 return ;
8851093 }
8861094
1095+ // Show login required message if not authenticated
1096+ if ( ! msaCredentials ) {
1097+ //tbody.innerHTML = '<tr><td colspan="6" class="login-required">Please log in to view data</td></tr>';
1098+ tbody . innerHTML = '<tr><td colspan="6" class="login-required"></td></tr>' ;
1099+ return ;
1100+ }
1101+
8871102 console . log ( 'displayMqttMessages called, total topics:' , mqttMessagesMap . size ) ;
8881103
8891104 // Get filter values
@@ -1323,4 +1538,52 @@ function setupEventListeners() {
13231538 displayMqttMessages ( ) ;
13241539 } ) ;
13251540 }
1541+
1542+ // Global keyboard shortcuts
1543+ document . addEventListener ( 'keydown' , ( e ) => {
1544+ // Escape - close modals and clear filters
1545+ if ( e . key === 'Escape' ) {
1546+ closeAboutModal ( ) ;
1547+ closeConfirmModal ( ) ;
1548+ return ;
1549+ }
1550+
1551+ // Ctrl+Enter - Execute query (Database) or Refresh (Broker)
1552+ if ( e . ctrlKey && e . key === 'Enter' ) {
1553+ e . preventDefault ( ) ;
1554+ if ( document . getElementById ( 'database-tab' ) . classList . contains ( 'active' ) ) {
1555+ const customQuery = document . getElementById ( 'customQuery' ) . value . trim ( ) ;
1556+ if ( customQuery ) {
1557+ executeCustomQuery ( ) ;
1558+ } else {
1559+ loadMessages ( ) ;
1560+ }
1561+ } else if ( document . getElementById ( 'broker-tab' ) . classList . contains ( 'active' ) ) {
1562+ displayMqttMessages ( ) ;
1563+ }
1564+ return ;
1565+ }
1566+
1567+ // Ctrl+1/2/3 - Switch tabs
1568+ if ( e . ctrlKey && ! e . shiftKey && [ '1' , '2' , '3' ] . includes ( e . key ) ) {
1569+ e . preventDefault ( ) ;
1570+ const tabs = document . querySelectorAll ( '.tab' ) ;
1571+ const tabIndex = parseInt ( e . key ) - 1 ;
1572+ if ( tabs [ tabIndex ] ) {
1573+ tabs [ tabIndex ] . click ( ) ;
1574+ }
1575+ return ;
1576+ }
1577+
1578+ // Ctrl+Shift+R - Toggle auto-refresh
1579+ if ( e . ctrlKey && e . shiftKey && e . key === 'R' ) {
1580+ e . preventDefault ( ) ;
1581+ const checkbox = document . getElementById ( 'autoRefreshCheckbox' ) ;
1582+ if ( checkbox ) {
1583+ checkbox . checked = ! checkbox . checked ;
1584+ toggleAutoRefresh ( ) ;
1585+ }
1586+ return ;
1587+ }
1588+ } ) ;
13261589}
0 commit comments