diff --git a/assets/styles/loginpage/login.css b/assets/styles/loginpage/login.css index 9ee2aa8..c8035bd 100644 --- a/assets/styles/loginpage/login.css +++ b/assets/styles/loginpage/login.css @@ -110,7 +110,7 @@ } .hospital-logo { - height: 232px; + height: 332px; width: auto; filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.1)); } diff --git a/pages/Dashboard.vue b/pages/Dashboard.vue index 45b68e1..2b8a34a 100644 --- a/pages/Dashboard.vue +++ b/pages/Dashboard.vue @@ -261,7 +261,7 @@ const mockQueueData = ref([ { date: '2025-10-18', registrants: 435, attendees: 380 }, ]); -// --- REALTIME CHART LOGIC - FIXED --- +// --- OPTIMIZED REALTIME CHART LOGIC --- const realtimeChart = ref(null); const maxDataPoints = 10; let realtimeInterval = null; @@ -289,7 +289,7 @@ const realtimeLineData = ref({ const lineOptions = ref({ responsive: true, maintainAspectRatio: false, - animation: { duration: 300 }, + animation: { duration: 0 }, // Disable animation for better performance plugins: { legend: { display: true }, title: { display: false } @@ -309,24 +309,46 @@ const lineOptions = ref({ }); const updateRealtimeData = () => { - const data = realtimeLineData.value.datasets[0].data; - const labels = realtimeLineData.value.labels; + if (!realtimeChart.value) return; // Guard clause - // Remove first data point - data.shift(); - labels.shift(); + const chart = realtimeChart.value.chart; + if (!chart) return; + + // Direct chart data manipulation (more efficient) + chart.data.labels.shift(); + chart.data.datasets[0].data.shift(); - // Add new data point const newDataPoint = Math.floor(Math.random() * 20) + 40; const newTimeLabel = dayjs().format('HH:mm:ss'); - data.push(newDataPoint); - labels.push(newTimeLabel); + chart.data.labels.push(newTimeLabel); + chart.data.datasets[0].data.push(newDataPoint); - console.log('Realtime chart updated:', newTimeLabel, newDataPoint); + chart.update('none'); // Update without animation }; -// --- END REALTIME CHART LOGIC --- +// Start/stop interval management +const startRealtimeUpdates = () => { + if (realtimeInterval) return; // Prevent multiple intervals + realtimeInterval = setInterval(updateRealtimeData, 3000); // Update every 3 seconds +}; + +const stopRealtimeUpdates = () => { + if (realtimeInterval) { + clearInterval(realtimeInterval); + realtimeInterval = null; + } +}; + +// Lifecycle hooks +onMounted(() => { + startRealtimeUpdates(); +}); + +onUnmounted(() => { + stopRealtimeUpdates(); +}); +// --- END OPTIMIZED REALTIME CHART LOGIC --- onMounted(async () => { try { const sessionUser = await checkAuth() diff --git a/pages/LoginPage.vue b/pages/LoginPage.vue index c426491..b4da23d 100644 --- a/pages/LoginPage.vue +++ b/pages/LoginPage.vue @@ -34,7 +34,7 @@
@@ -48,9 +48,7 @@
{{ successMessage }}
- Login menggunakan Single Sign-On
-Login menggunakan Single Sign-On
+Arahkan kamera smartphone ke QR code
@@ -310,9 +310,9 @@ const qrCodeData = computed(() => { // Adaptive QR code size based on screen size const qrSize = computed(() => { - if (display.xs.value) return 200; // Extra small screens (mobile) - if (display.sm.value) return 240; // Small screens (large phones) - return 280; // Medium and larger screens + if (display.xs.value) return 120; // Extra small screens (mobile) + if (display.sm.value) return 200; // Small screens (large phones) + return 220; // Medium and larger screens }); // --- LOGIC MODAL --- diff --git a/server/api/auth/keycloak-callback.get.ts b/server/api/auth/keycloak-callback.get.ts index 4803d3e..c7e10fe 100644 --- a/server/api/auth/keycloak-callback.get.ts +++ b/server/api/auth/keycloak-callback.get.ts @@ -1,4 +1,12 @@ -// server/api/auth/keycloak-callback.ts - FIX APPLIED +// server/api/auth/keycloak-callback.ts - EXTENDED SESSION FIX + +// Add this at the top of the file (after imports) +const SESSION_DURATION = 24 * 60 * 60; // 7 days in seconds (customize as needed) +// Or use one of these alternatives: +// const SESSION_DURATION = 24 * 60 * 60; // 1 day +// const SESSION_DURATION = 30 * 24 * 60 * 60; // 30 days +// const SESSION_DURATION = 12 * 60 * 60; // 12 hours + export default defineEventHandler(async (event) => { try { const config = useRuntimeConfig(); @@ -28,8 +36,8 @@ export default defineEventHandler(async (event) => { if (!state || state !== storedState) { console.error('โ Invalid state parameter - possible CSRF attack'); - console.error(' ย Expected:', storedState); - console.error(' ย Received:', state); + console.error(' Expected:', storedState); + console.error(' Received:', state); const errorMsg = encodeURIComponent('Security validation failed. Please try logging in again.'); return sendRedirect(event, `/LoginPage?error=${errorMsg}`); @@ -46,7 +54,6 @@ export default defineEventHandler(async (event) => { const tokenUrl = `${config.keycloakIssuer}/protocol/openid-connect/token`; const redirectUri = `${config.public.authUrl}/api/auth/keycloak-callback`; - // ... (Token exchange logic remains the same) ... const tokenPayload = new URLSearchParams({ grant_type: 'authorization_code', client_id: config.keycloakClientId, @@ -72,7 +79,6 @@ export default defineEventHandler(async (event) => { const tokens = await tokenResponse.json(); - // ... (Token decoding and sessionData creation remains the same) ... let idTokenPayload; try { idTokenPayload = JSON.parse( @@ -96,33 +102,28 @@ export default defineEventHandler(async (event) => { accessToken: tokens.access_token, idToken: tokens.id_token, refreshToken: tokens.refresh_token, - expiresAt: Date.now() + (tokens.expires_in * 1000), + // CHANGED: Use custom session duration instead of Keycloak's token expiry + expiresAt: Date.now() + (SESSION_DURATION * 1000), createdAt: Date.now(), }; - // ---------------------------------------------------- - // ๐ CRITICAL FIX FOR DEPLOYED HTTPS ENVIRONMENTS ๐ - // ---------------------------------------------------- - // Check if the request was originally HTTPS (via proxy) const isSecure = process.env.NODE_ENV === 'production' || event.node.req.headers['x-forwarded-proto'] === 'https'; console.log('๐ Setting session cookie with secure flag:', isSecure); + console.log('โฑ๏ธ Session duration:', SESSION_DURATION, 'seconds'); setCookie(event, 'user_session', JSON.stringify(sessionData), { httpOnly: true, - // CRITICAL: Must be TRUE when operating over HTTPS (deployed) secure: isSecure, - // Ensures cookie is sent on cross-site redirects (Keycloak -> Your App) sameSite: 'lax', - maxAge: tokens.expires_in, + // CHANGED: Use custom session duration (7 days default) + maxAge: SESSION_DURATION, path: '/', }); console.log('โ Session cookie created successfully'); - // Note: The following line will still log false because the cookie - // is in the response header, not the request header yet. This is expected. const testCookie = getCookie(event, 'user_session'); console.log('๐งช Cookie test - can read back in this handler (Expected False):', !!testCookie);