add new data 2025 05 12
This commit is contained in:
No files matched your search
@@ -110,7 +110,7 @@
|
||||
}
|
||||
|
||||
.hospital-logo {
|
||||
height: 232px;
|
||||
height: 332px;
|
||||
width: auto;
|
||||
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
+34
-12
@@ -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()
|
||||
|
||||
+16
-12
@@ -34,7 +34,7 @@
|
||||
<div class="d-flex flex-column align-center text-center mb-6">
|
||||
<span class="text-h5 font-weight-bold app-title-dark">Antrean RSSA</span>
|
||||
<img
|
||||
src="/AntreanLogobg.png"
|
||||
src="/AntreanLogobg.png"
|
||||
alt="Antrean Logo"
|
||||
class="mt-3 hospital-logo"
|
||||
/>
|
||||
@@ -48,9 +48,7 @@
|
||||
{{ successMessage }}
|
||||
</v-alert>
|
||||
|
||||
<div class="text-center mb-6">
|
||||
<p class="sso-text-dark">Login menggunakan Single Sign-On</p>
|
||||
</div>
|
||||
|
||||
|
||||
<v-btn
|
||||
@click="handleLogin"
|
||||
@@ -69,8 +67,14 @@
|
||||
</v-btn>
|
||||
|
||||
<v-divider class="my-6 custom-divider-dark"></v-divider>
|
||||
|
||||
<div class="text-center mb-6">
|
||||
<p class="sso-text-dark">Login menggunakan Single Sign-On</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
|
||||
<!-- <div class="text-center">
|
||||
<v-btn
|
||||
@click="showRegistrationDialog = true"
|
||||
class="register-btn-dark"
|
||||
@@ -97,11 +101,11 @@
|
||||
Hubungi Administrator
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<!-- <div class="text-center mt-4">
|
||||
<span class="help-link-dark">Masalah dengan kata sandi Anda?</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -109,7 +113,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-dialog v-model="showRegistrationDialog" max-width="500">
|
||||
<!-- <v-dialog v-model="showRegistrationDialog" max-width="500">
|
||||
<v-card class="white-dialog rounded-xl">
|
||||
<v-card-title class="text-h5 text-center pa-6 bg-grey-lighten-4">
|
||||
<v-icon left color="#FF9B1B">mdi-account-plus</v-icon> Pendaftaran Akun Baru
|
||||
@@ -169,9 +173,9 @@
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-dialog> -->
|
||||
|
||||
<v-dialog v-model="showAdminContact" max-width="500">
|
||||
<!-- <v-dialog v-model="showAdminContact" max-width="500">
|
||||
<v-card class="white-dialog rounded-xl">
|
||||
<v-card-title class="text-h5 text-center pa-6 bg-grey-lighten-4">
|
||||
<v-icon left color="#FF9B1B">mdi-account-tie</v-icon> Kontak Administrator
|
||||
@@ -265,7 +269,7 @@
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-dialog> -->
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
@@ -152,10 +152,10 @@
|
||||
<div v-else>
|
||||
<v-card class="d-flex flex-wrap justify-space-between align-center pa-4 mb-4 rounded-lg bg-blue-darken-2 text-white elevation-2">
|
||||
<v-card-title class="d-flex align-center text-h5 font-weight-bold pa-0">
|
||||
<v-icon icon="mdi-account-group-outline" class="mr-2" size="40"></v-icon>
|
||||
<v-icon icon="mdi-account-group-outline" class="mr-2" size="40"></v-icon>
|
||||
<span>User Login Management</span>
|
||||
</v-card-title>
|
||||
<v-btn
|
||||
<!-- <v-btn
|
||||
color="success"
|
||||
prepend-icon="mdi-plus"
|
||||
rounded
|
||||
@@ -164,7 +164,7 @@
|
||||
:disabled="isPending"
|
||||
>
|
||||
Tambah User
|
||||
</v-btn>
|
||||
</v-btn> -->
|
||||
</v-card>
|
||||
|
||||
<v-card class="pa-4 rounded-lg elevation-2">
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<v-btn
|
||||
v-if="patient.status === 'Belum Terverifikasi'"
|
||||
color="#FFA532"
|
||||
size="x-large"
|
||||
size="x-medium"
|
||||
@click="openDaftarModal(patient)"
|
||||
prepend-icon="mdi-qrcode-scan"
|
||||
variant="flat"
|
||||
@@ -182,7 +182,7 @@
|
||||
|
||||
<div v-else class="text-center mt-6 pa-4 pa-sm-7 qr-container rounded-xl">
|
||||
<div class="pulse-icon mb-3 mb-sm-4">
|
||||
<v-icon color="#4A5F7A" :size="display.xs.value ? 48 : 64">mdi-cellphone-check</v-icon>
|
||||
<v-icon color="#4A5F7A" :size="display.xs.value ? 32 : 48">mdi-cellphone-check</v-icon>
|
||||
</div>
|
||||
<h3 class="text-h5 text-sm-h4 font-weight-black mb-2 mb-sm-3" style="color: #4A5F7A;">Pindai QR Code</h3>
|
||||
<p class="text-body-2 text-sm-body-1 secondary-text mb-4 mb-sm-6">Arahkan kamera smartphone ke QR code</p>
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user