33< head >
44 < meta charset ="UTF-8 ">
55 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6- < meta http-equiv ="Content-Security-Policy " content ="default-src 'self'; script-src 'self' 'nonce-{{nonce}}' https://github.com; style-src 'self' 'unsafe-inline'; connect-src 'self' wss://webxos.netlify.app https://api.github.com; img-src 'self'; ">
6+ < meta http-equiv ="Content-Security-Policy " content ="default-src 'self'; script-src 'self' 'nonce-{{nonce}}' https://github.com; style-src 'self' 'unsafe-inline'; connect-src 'self' wss://webxos.netlify.app https://api.github.com; img-src 'self' data: ; ">
77 < meta http-equiv ="X-Frame-Options " content ="DENY ">
88 < meta http-equiv ="X-Content-Type-Options " content ="nosniff ">
99 < meta http-equiv ="Referrer-Policy " content ="strict-origin-when-cross-origin ">
@@ -39,6 +39,16 @@ <h1 class="text-2xl font-bold mb-4">Vial MCP Controller</h1>
3939 < p > < strong > Vial 4:</ strong > < span id ="vial4-status "> Stopped (Balance: 0)</ span > </ p >
4040 </ div >
4141
42+ < div class ="mb-4 ">
43+ < h2 class ="text-xl font-bold mb-2 "> Two-Factor Authentication</ h2 >
44+ < div id ="2fa-setup " class ="bg-white p-4 rounded shadow ">
45+ < img id ="2fa-qr-code " class ="hidden " src ="" alt ="2FA QR Code ">
46+ < input id ="2fa-code " class ="w-full p-2 border rounded mb-2 " placeholder ="Enter 2FA code ">
47+ < button id ="enable-2fa-btn " class ="bg-blue-500 text-white px-4 py-2 rounded " disabled > Enable 2FA</ button >
48+ < button id ="verify-2fa-btn " class ="bg-green-500 text-white px-4 py-2 rounded " disabled > Verify 2FA</ button >
49+ </ div >
50+ </ div >
51+
4252 < div class ="mb-4 ">
4353 < h2 class ="text-xl font-bold mb-2 "> Transaction History</ h2 >
4454 < div id ="transaction-history " class ="bg-white p-4 rounded shadow "> </ div >
@@ -57,7 +67,12 @@ <h2 class="text-xl font-bold mb-2">Security KPIs</h2>
5767 < div class ="mb-4 ">
5868 < h2 class ="text-xl font-bold mb-2 "> User Action History</ h2 >
5969 < div id ="action-history " class ="bg-white p-4 rounded shadow "> </ div >
60- < button id ="load-actions-btn " class ="bg-blue-500 text-white px-4 py-2 rounded " disabled > Load Action History</ button >
70+ < div class ="flex space-x-2 ">
71+ < button id ="load-actions-btn " class ="bg-blue-500 text-white px-4 py-2 rounded " disabled > Load Action History</ button >
72+ < button id ="prev-page-btn " class ="bg-gray-500 text-white px-4 py-2 rounded " disabled > Previous Page</ button >
73+ < button id ="next-page-btn " class ="bg-gray-500 text-white px-4 py-2 rounded " disabled > Next Page</ button >
74+ < span id ="page-info " class ="p-2 "> Page 1</ span >
75+ </ div >
6176 </ div >
6277
6378 < div class ="mb-4 ">
@@ -92,6 +107,9 @@ <h2 class="text-xl font-bold mb-2">User Action History</h2>
92107 import { updateNetworkStatus } from '/js/vial_controller.js' ;
93108 import { wsHandler } from '/js/websocket_handler.js' ;
94109
110+ let currentPage = 1 ;
111+ let totalPages = 1 ;
112+
95113 updateNetworkStatus ( ) ;
96114 window . addEventListener ( 'online' , updateNetworkStatus ) ;
97115 window . addEventListener ( 'offline' , updateNetworkStatus ) ;
@@ -107,6 +125,59 @@ <h2 class="text-xl font-bold mb-2">User Action History</h2>
107125 document . getElementById ( 'logout-btn' ) . disabled = false ;
108126 document . getElementById ( 'data-erasure-btn' ) . disabled = false ;
109127 document . getElementById ( 'load-actions-btn' ) . disabled = false ;
128+ document . getElementById ( 'enable-2fa-btn' ) . disabled = false ;
129+ } ) ;
130+
131+ async function loadActionHistory ( page ) {
132+ const userId = document . getElementById ( 'user-id' ) . innerText ;
133+ if ( userId === 'Not logged in' ) {
134+ document . getElementById ( 'output' ) . innerText = 'Error: Please authenticate first' ;
135+ return ;
136+ }
137+ try {
138+ const accessToken = localStorage . getItem ( 'access_token' ) ;
139+ const sessionId = document . cookie . match ( / s e s s i o n _ i d = ( [ ^ ; ] + ) / ) ?. [ 1 ] ;
140+ const res = await fetch ( 'https://webxos.netlify.app/mcp/execute' , {
141+ method : 'POST' ,
142+ headers : {
143+ 'Content-Type' : 'application/json' ,
144+ 'Authorization' : `Bearer ${ accessToken } ` ,
145+ 'X-Session-ID' : sessionId
146+ } ,
147+ body : JSON . stringify ( {
148+ jsonrpc : '2.0' ,
149+ method : 'security.getUserActions' ,
150+ params : { user_id : userId , page, page_size : 50 } ,
151+ id : Math . floor ( Math . random ( ) * 1000 )
152+ } )
153+ } ) ;
154+ const data = await res . json ( ) ;
155+ if ( data . error ) throw new Error ( data . error . message ) ;
156+ const actions = data . result . actions ;
157+ totalPages = data . result . total_pages ;
158+ currentPage = data . result . current_page ;
159+ const actionHistory = document . getElementById ( 'action-history' ) ;
160+ actionHistory . innerHTML = actions . map ( action => `
161+ <div class="p-2 border-b">
162+ <p><strong>Action:</strong> ${ action . action } </p>
163+ <p><strong>Details:</strong> ${ JSON . stringify ( action . details ) } </p>
164+ <p><strong>Timestamp:</strong> ${ new Date ( action . created_at ) . toLocaleString ( ) } </p>
165+ </div>
166+ ` ) . join ( '' ) ;
167+ document . getElementById ( 'page-info' ) . innerText = `Page ${ currentPage } ` ;
168+ document . getElementById ( 'prev-page-btn' ) . disabled = currentPage === 1 ;
169+ document . getElementById ( 'next-page-btn' ) . disabled = currentPage === totalPages ;
170+ } catch ( error ) {
171+ document . getElementById ( 'output' ) . innerText = `Error loading action history: ${ error . message } ` ;
172+ }
173+ }
174+
175+ document . getElementById ( 'load-actions-btn' ) . addEventListener ( 'click' , ( ) => loadActionHistory ( 1 ) ) ;
176+ document . getElementById ( 'prev-page-btn' ) . addEventListener ( 'click' , ( ) => {
177+ if ( currentPage > 1 ) loadActionHistory ( currentPage - 1 ) ;
178+ } ) ;
179+ document . getElementById ( 'next-page-btn' ) . addEventListener ( 'click' , ( ) => {
180+ if ( currentPage < totalPages ) loadActionHistory ( currentPage + 1 ) ;
110181 } ) ;
111182
112183 document . getElementById ( 'logout-btn' ) . addEventListener ( 'click' , async ( ) => {
@@ -149,6 +220,8 @@ <h2 class="text-xl font-bold mb-2">User Action History</h2>
149220 document . getElementById ( 'logout-btn' ) . disabled = true ;
150221 document . getElementById ( 'data-erasure-btn' ) . disabled = true ;
151222 document . getElementById ( 'load-actions-btn' ) . disabled = true ;
223+ document . getElementById ( 'enable-2fa-btn' ) . disabled = true ;
224+ document . getElementById ( 'verify-2fa-btn' ) . disabled = true ;
152225 } catch ( error ) {
153226 document . getElementById ( 'output' ) . innerText = `Logout error: ${ error . message } ` ;
154227 }
@@ -192,12 +265,14 @@ <h2 class="text-xl font-bold mb-2">User Action History</h2>
192265 document . getElementById ( 'logout-btn' ) . disabled = true ;
193266 document . getElementById ( 'data-erasure-btn' ) . disabled = true ;
194267 document . getElementById ( 'load-actions-btn' ) . disabled = true ;
268+ document . getElementById ( 'enable-2fa-btn' ) . disabled = true ;
269+ document . getElementById ( 'verify-2fa-btn' ) . disabled = true ;
195270 } catch ( error ) {
196271 document . getElementById ( 'output' ) . innerText = `Data erasure error: ${ error . message } ` ;
197272 }
198273 } ) ;
199274
200- document . getElementById ( 'load-actions -btn' ) . addEventListener ( 'click' , async ( ) => {
275+ document . getElementById ( 'enable-2fa -btn' ) . addEventListener ( 'click' , async ( ) => {
201276 const userId = document . getElementById ( 'user-id' ) . innerText ;
202277 if ( userId === 'Not logged in' ) {
203278 document . getElementById ( 'output' ) . innerText = 'Error: Please authenticate first' ;
@@ -215,24 +290,52 @@ <h2 class="text-xl font-bold mb-2">User Action History</h2>
215290 } ,
216291 body : JSON . stringify ( {
217292 jsonrpc : '2.0' ,
218- method : 'security.getUserActions ' ,
293+ method : 'auth.enable2FA ' ,
219294 params : { user_id : userId } ,
220295 id : Math . floor ( Math . random ( ) * 1000 )
221296 } )
222297 } ) ;
223298 const data = await res . json ( ) ;
224299 if ( data . error ) throw new Error ( data . error . message ) ;
225- const actions = data . result . actions ;
226- const actionHistory = document . getElementById ( 'action-history' ) ;
227- actionHistory . innerHTML = actions . map ( action => `
228- <div class="p-2 border-b">
229- <p><strong>Action:</strong> ${ action . action } </p>
230- <p><strong>Details:</strong> ${ JSON . stringify ( action . details ) } </p>
231- <p><strong>Timestamp:</strong> ${ new Date ( action . created_at ) . toLocaleString ( ) } </p>
232- </div>
233- ` ) . join ( '' ) ;
300+ document . getElementById ( '2fa-qr-code' ) . src = data . result . qr_code_url ;
301+ document . getElementById ( '2fa-qr-code' ) . classList . remove ( 'hidden' ) ;
302+ document . getElementById ( 'verify-2fa-btn' ) . disabled = false ;
234303 } catch ( error ) {
235- document . getElementById ( 'output' ) . innerText = `Error loading action history: ${ error . message } ` ;
304+ document . getElementById ( 'output' ) . innerText = `2FA setup error: ${ error . message } ` ;
305+ }
306+ } ) ;
307+
308+ document . getElementById ( 'verify-2fa-btn' ) . addEventListener ( 'click' , async ( ) => {
309+ const userId = document . getElementById ( 'user-id' ) . innerText ;
310+ const totpCode = document . getElementById ( '2fa-code' ) . value ;
311+ if ( ! totpCode ) {
312+ document . getElementById ( 'output' ) . innerText = 'Error: Please enter 2FA code' ;
313+ return ;
314+ }
315+ try {
316+ const accessToken = localStorage . getItem ( 'access_token' ) ;
317+ const sessionId = document . cookie . match ( / s e s s i o n _ i d = ( [ ^ ; ] + ) / ) ?. [ 1 ] ;
318+ const res = await fetch ( 'https://webxos.netlify.app/mcp/execute' , {
319+ method : 'POST' ,
320+ headers : {
321+ 'Content-Type' : 'application/json' ,
322+ 'Authorization' : `Bearer ${ accessToken } ` ,
323+ 'X-Session-ID' : sessionId
324+ } ,
325+ body : JSON . stringify ( {
326+ jsonrpc : '2.0' ,
327+ method : 'auth.verify2FA' ,
328+ params : { user_id : userId , totp_code : totpCode } ,
329+ id : Math . floor ( Math . random ( ) * 1000 )
330+ } )
331+ } ) ;
332+ const data = await res . json ( ) ;
333+ if ( data . error ) throw new Error ( data . error . message ) ;
334+ document . getElementById ( 'output' ) . innerText = '2FA verified successfully' ;
335+ document . getElementById ( '2fa-qr-code' ) . classList . add ( 'hidden' ) ;
336+ document . getElementById ( 'verify-2fa-btn' ) . disabled = true ;
337+ } catch ( error ) {
338+ document . getElementById ( 'output' ) . innerText = `2FA verification error: ${ error . message } ` ;
236339 }
237340 } ) ;
238341 </ script >
0 commit comments