From 2278bffad44ed03e4b3f452d1ab75db8fc98566e Mon Sep 17 00:00:00 2001 From: Anik Date: Wed, 15 Oct 2025 16:18:15 +0530 Subject: [PATCH] fix: Laggy data loading between pages switches --- ENVEXAMPLE | 40 ------------------ docker-compose.yml | 4 +- src/components/api/ApiKey.tsx | 76 +++++++++++++++++------------------ src/pages/MainPage.tsx | 60 +++++++++++++++------------ 4 files changed, 72 insertions(+), 108 deletions(-) delete mode 100644 ENVEXAMPLE diff --git a/ENVEXAMPLE b/ENVEXAMPLE deleted file mode 100644 index db461f556..000000000 --- a/ENVEXAMPLE +++ /dev/null @@ -1,40 +0,0 @@ -# App Setup -NODE_ENV=production # Set to 'development' or 'production' as required -JWT_SECRET=a9Z$kLq7^f03GzNw!bP9dH4xV6sT2yXl3O8vR@uYq3 # Replace with a secure JWT secret key -DB_NAME=maxun # Your PostgreSQL database name -DB_USER=postgres # PostgreSQL username -DB_PASSWORD=postgres # PostgreSQL password -DB_HOST=postgres # Host for PostgreSQL in Docker -DB_PORT=5432 # Port for PostgreSQL (default: 5432) -ENCRYPTION_KEY=f4d5e6a7b8c9d0e1f23456789abcdef01234567890abcdef123456789abcdef0 # Key for encrypting sensitive data (passwords and proxies) -SESSION_SECRET=maxun_session # A strong, random string used to sign session cookies. Recommended to define your own session secret to avoid session hijacking. - -MINIO_ENDPOINT=minio # MinIO endpoint in Docker -MINIO_PORT=9000 # Port for MinIO (default: 9000) -MINIO_CONSOLE_PORT=9001 # Web UI Port for MinIO (default: 9001) -MINIO_ACCESS_KEY=minio_access_key # MinIO access key -MINIO_SECRET_KEY=minio_secret_key # MinIO secret key -REDIS_HOST=redis # Redis host in Docker -REDIS_PORT=6379 # Redis port (default: 6379) -REDIS_PASSWORD=redis_password # Redis password (This is optional. Needed to authenticate with a password-protected Redis instance; if not set, Redis will connect without authentication.) - -# Backend and Frontend URLs and Ports -BACKEND_PORT=8080 # Port to run backend on. Needed for Docker setup -FRONTEND_PORT=5173 # Port to run frontend on. Needed for Docker setup -BACKEND_URL=http://localhost:8080 # URL on which the backend runs. You can change it based on your needs. -PUBLIC_URL=http://localhost:5173 # URL on which the frontend runs. You can change it based on your needs. -VITE_BACKEND_URL=http://localhost:8080 # URL used by frontend to connect to backend. It should always have the same value as BACKEND_URL -VITE_PUBLIC_URL=http://localhost:5173 # URL used by backend to connect to frontend. It should always have the same value as PUBLIC_URL - -# Optional Google OAuth settings for Google Sheet Integration -GOOGLE_CLIENT_ID=your_google_client_id -GOOGLE_CLIENT_SECRET=your_google_client_secret -GOOGLE_REDIRECT_URI=your_google_redirect_uri - -# Optional Airtable OAuth settings for Airtable Integration - -AIRTABLE_CLIENT_ID=your_airtable_client_id -AIRTABLE_REDIRECT_URI=http://localhost:8080/auth/airtable/callback - -# Telemetry Settings - Please keep it enabled. Keeping it enabled helps us understand how the product is used and assess the impact of any new changes. -MAXUN_TELEMETRY=true diff --git a/docker-compose.yml b/docker-compose.yml index c1b4302e8..34284aecc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,11 +7,11 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} ports: - - "${DB_PORT:-5432}:${DB_PORT:-5432}" + - "${DB_HOST_PORT:-5433}:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"] interval: 10s timeout: 5s retries: 5 diff --git a/src/components/api/ApiKey.tsx b/src/components/api/ApiKey.tsx index 9feb9551e..bfe11866e 100644 --- a/src/components/api/ApiKey.tsx +++ b/src/components/api/ApiKey.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Box, Button, @@ -17,6 +17,7 @@ import { import { ContentCopy, Visibility, VisibilityOff, Delete } from '@mui/icons-material'; import styled from 'styled-components'; import axios from 'axios'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useGlobalInfoStore } from '../../context/globalInfo'; import { apiUrl } from '../../apiConfig'; import { useTranslation } from 'react-i18next'; @@ -31,55 +32,50 @@ const Container = styled(Box)` const ApiKeyManager = () => { const { t } = useTranslation(); - const [apiKey, setApiKey] = useState(null); const [apiKeyName, setApiKeyName] = useState(t('apikey.default_name')); - const [loading, setLoading] = useState(true); const [showKey, setShowKey] = useState(false); const [copySuccess, setCopySuccess] = useState(false); const { notify } = useGlobalInfoStore(); + const queryClient = useQueryClient(); - useEffect(() => { - const fetchApiKey = async () => { - try { - const { data } = await axios.get(`${apiUrl}/auth/api-key`); - setApiKey(data.api_key); - } catch (error: any) { - notify('error', t('apikey.notifications.fetch_error', { error: error.message })); - } finally { - setLoading(false); - } - }; + // Fetch API key with React Query + const { data: apiKey, isLoading } = useQuery({ + queryKey: ['api-key'], + queryFn: async () => { + const { data } = await axios.get(`${apiUrl}/auth/api-key`); + return data.api_key as string | null; + }, + staleTime: 10 * 60 * 1000, // 10 minutes + }); - fetchApiKey(); - - }, []); - - const generateApiKey = async () => { - setLoading(true); - try { + // Generate mutation + const { mutate: generateApiKey, isPending: isGenerating } = useMutation({ + mutationFn: async () => { const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`); - setApiKey(data.api_key); - + return data.api_key as string; + }, + onSuccess: (newKey) => { + queryClient.setQueryData(['api-key'], newKey); notify('success', t('apikey.notifications.generate_success')); - } catch (error: any) { + }, + onError: (error: any) => { notify('error', t('apikey.notifications.generate_error', { error: error.message })); - } finally { - setLoading(false); - } - }; + }, + }); - const deleteApiKey = async () => { - setLoading(true); - try { + // Delete mutation + const { mutate: deleteApiKey, isPending: isDeleting } = useMutation({ + mutationFn: async () => { await axios.delete(`${apiUrl}/auth/delete-api-key`); - setApiKey(null); + }, + onSuccess: () => { + queryClient.setQueryData(['api-key'], null); notify('success', t('apikey.notifications.delete_success')); - } catch (error: any) { + }, + onError: (error: any) => { notify('error', t('apikey.notifications.delete_error', { error: error.message })); - } finally { - setLoading(false); - } - }; + }, + }); const copyToClipboard = () => { if (apiKey) { @@ -90,7 +86,7 @@ const ApiKeyManager = () => { } }; - if (loading) { + if (isLoading) { return ( { - + deleteApiKey()} color="error" disabled={isDeleting}> @@ -167,7 +163,7 @@ const ApiKeyManager = () => { ) : ( <> {t('apikey.no_key_message')} - diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 861781172..0b52e0b06 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { MainMenu } from "../components/dashboard/MainMenu"; import { Stack } from "@mui/material"; @@ -293,37 +293,45 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps) } }, [user?.id, connectToQueueSocket, disconnectQueueSocket, t, setRerenderRuns, queuedRuns, setQueuedRuns]); - const DisplayContent = () => { - switch (content) { - case 'robots': - return ; - case 'runs': - return ; - case 'proxy': - return ; - case 'apikey': - return ; - default: - return null; - } - } + // Keep subviews mounted to avoid refetch/remount lag on tab switches + const robotsView = useMemo(() => ( + + ), [handleEditRecording, handleRunRecording, setRecordingInfo, handleScheduleRecording]); + + const runsView = useMemo(() => ( + + ), [currentInterpretationLog, abortRunHandler, ids.runId, runningRecordingName]); + + const proxyView = useMemo(() => (), []); + const apiKeyView = useMemo(() => (), []); return ( - {DisplayContent()} +
+ {robotsView} +
+
+ {runsView} +
+
+ {proxyView} +
+
+ {apiKeyView} +
); };