From d0518b9b80bd0f534c3f2a5a92cc8d669ee3709a Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Thu, 26 Jun 2025 23:52:55 +0530 Subject: [PATCH 01/14] Solve some errors in database step --- .../src/components/steps/DatabaseStep.jsx | 125 +++++++++++------- istsos4-wizard/src/utils/constants.js | 50 ------- istsos4-wizard/src/utils/fieldValidations.js | 1 + 3 files changed, 79 insertions(+), 97 deletions(-) diff --git a/istsos4-wizard/src/components/steps/DatabaseStep.jsx b/istsos4-wizard/src/components/steps/DatabaseStep.jsx index d085571..8738b7f 100644 --- a/istsos4-wizard/src/components/steps/DatabaseStep.jsx +++ b/istsos4-wizard/src/components/steps/DatabaseStep.jsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; -import { Eye, EyeOff } from 'lucide-react'; -import { useWizard } from '../../hooks/useWizard'; -import FormField from '../common/FormField'; +import React, { useState } from "react"; +import { Eye, EyeOff } from "lucide-react"; +import { useWizard } from "../../hooks/useWizard"; +import FormField from "../common/FormField"; function DatabaseStep() { const { state, dispatch } = useWizard(); @@ -11,27 +11,27 @@ function DatabaseStep() { const updateConfig = (field, value) => { dispatch({ - type: 'UPDATE_CONFIG', - payload: { [field]: value } + type: "UPDATE_CONFIG", + payload: { [field]: value }, }); }; const handleBlur = (field) => { - dispatch({ type: 'SET_FIELD_TOUCHED', payload: field }); + dispatch({ type: "SET_FIELD_TOUCHED", payload: field }); }; // Password strength indicator const getPasswordStrength = (password) => { - if (!password) return { strength: 0, label: '' }; - + if (!password) return { strength: 0, label: "" }; + let strength = 0; if (password.length >= 8) strength++; if (password.length >= 12) strength++; if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++; if (/\d/.test(password)) strength++; if (/[^a-zA-Z0-9]/.test(password)) strength++; - - const labels = ['', 'Weak', 'Fair', 'Good', 'Strong', 'Very Strong']; + + const labels = ["", "Weak", "Fair", "Good", "Strong", "Very Strong"]; return { strength, label: labels[strength] }; }; @@ -39,10 +39,12 @@ function DatabaseStep() { return (
-

Database Configuration

- +

+ Database Configuration +

+
- updateConfig('postgresDb', e.target.value)} - onBlur={() => handleBlur('postgresDb')} + onChange={(e) => updateConfig("postgresDb", e.target.value)} + onBlur={() => handleBlur("postgresDb")} placeholder="istsos" /> - updateConfig('postgresUser', e.target.value)} - onBlur={() => handleBlur('postgresUser')} + onChange={(e) => updateConfig("postgresUser", e.target.value)} + onBlur={() => handleBlur("postgresUser")} placeholder="postgres" /> -
updateConfig('postgresPassword', e.target.value)} - onBlur={() => handleBlur('postgresPassword')} + onChange={(e) => + updateConfig("postgresPassword", e.target.value) + } + onBlur={() => handleBlur("postgresPassword")} placeholder="Enter secure password" />
- + {/* Password strength indicator */} {configuration.postgresPassword && (
Password strength: - + {passwordStrength.label}
-
@@ -136,13 +152,13 @@ function DatabaseStep() { className="text-blue-600 hover:text-blue-800 font-medium" onClick={() => setShowAdvanced(!showAdvanced)} > - {showAdvanced ? 'Hide' : 'Show'} Advanced Settings + {showAdvanced ? "Hide" : "Show"} Advanced Settings {showAdvanced && (
- updateConfig('pgPoolSize', parseInt(e.target.value) || 0)} - onBlur={() => handleBlur('pgPoolSize')} + onChange={(e) => { + const value = e.target.value; + updateConfig( + "pgPoolSize", + value === "" ? "" : parseInt(value) + ); + }} + onBlur={() => handleBlur("pgPoolSize")} /> - updateConfig('pgMaxOverflow', parseInt(e.target.value) || 0)} - onBlur={() => handleBlur('pgMaxOverflow')} + onChange={(e) => { + const value = e.target.value; + updateConfig( + "pgMaxOverflow", + value === "" ? "" : parseInt(value) + ); + }} + onBlur={() => handleBlur("pgMaxOverflow")} /> - updateConfig('pgPoolTimeout', parseInt(e.target.value) || 0)} - onBlur={() => handleBlur('pgPoolTimeout')} + onChange={(e) => { + const value = e.target.value; + updateConfig('pgPoolTimeout', value === '' ? '' : parseInt(value));} + } + onBlur={() => handleBlur("pgPoolTimeout")} />
@@ -198,4 +229,4 @@ function DatabaseStep() { ); } -export default DatabaseStep; \ No newline at end of file +export default DatabaseStep; diff --git a/istsos4-wizard/src/utils/constants.js b/istsos4-wizard/src/utils/constants.js index ab91802..d366e12 100644 --- a/istsos4-wizard/src/utils/constants.js +++ b/istsos4-wizard/src/utils/constants.js @@ -1,53 +1,3 @@ -// // src/utils/constants.js -// export const initialState = { -// currentStep: 1, -// totalSteps: 9, -// configuration: { -// // Basic Server Configuration -// hostname: 'http://localhost:8018', -// subpath: '/istsos4', -// version: '/v1.1', -// debug: 0, - -// // Database Configuration -// postgresDb: 'istsos', -// postgresUser: 'admin', -// postgresPassword: 'admin', -// pgMaxOverflow: 0, -// pgPoolSize: 10, -// pgPoolTimeout: 30, - -// // Data Management -// dummyData: 0, -// clearData: 0, -// versioning: false, -// duplicates: false, - -// // Sample Data (conditional) -// nThings: 3, -// nObservedProperties: 2, -// interval: 'P1Y', -// frequency: 'PT5M', -// startDatetime: '2020-01-01T12:00:00.000+01:00', - -// // Performance Settings -// countMode: 'FULL', -// countEstimateThreshold: 10000, -// topValue: 100, -// partitionChunk: 10000, -// chunkInterval: 'P1Y', - -// // Additional Services -// redis: 1, -// epsg: 4326 -// }, -// validation: { -// errors: {}, -// touched: {}, -// isValid: true -// } -// }; - export const initialState = { // Wizard State currentStep: 1, diff --git a/istsos4-wizard/src/utils/fieldValidations.js b/istsos4-wizard/src/utils/fieldValidations.js index c08d589..bad684f 100644 --- a/istsos4-wizard/src/utils/fieldValidations.js +++ b/istsos4-wizard/src/utils/fieldValidations.js @@ -32,6 +32,7 @@ export const fieldValidations = { ValidationRules.maxValue(50) ], pgMaxOverflow: [ + ValidationRules.required, ValidationRules.minValue(0), ValidationRules.maxValue(20) ], From 0eb75d56aa17278dfe1b9bcda9c526485dcd7a61 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Fri, 27 Jun 2025 11:11:20 +0530 Subject: [PATCH 02/14] Solved Date/time error in sample data --- .../src/components/steps/SampleDataStep.jsx | 149 ++++++++++++------ istsos4-wizard/src/utils/fieldValidations.js | 2 + 2 files changed, 105 insertions(+), 46 deletions(-) diff --git a/istsos4-wizard/src/components/steps/SampleDataStep.jsx b/istsos4-wizard/src/components/steps/SampleDataStep.jsx index 0a7d50e..fceae60 100644 --- a/istsos4-wizard/src/components/steps/SampleDataStep.jsx +++ b/istsos4-wizard/src/components/steps/SampleDataStep.jsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { useWizard } from '../../hooks/useWizard'; -import FormField from '../common/FormField'; +import React from "react"; +import { useWizard } from "../../hooks/useWizard"; +import FormField from "../common/FormField"; function SampleDataStep() { const { state, dispatch } = useWizard(); @@ -8,38 +8,57 @@ function SampleDataStep() { const updateConfig = (field, value) => { dispatch({ - type: 'UPDATE_CONFIG', - payload: { [field]: value } + type: "UPDATE_CONFIG", + payload: { [field]: value }, }); }; const handleBlur = (field) => { - dispatch({ type: 'SET_FIELD_TOUCHED', payload: field }); + dispatch({ type: "SET_FIELD_TOUCHED", payload: field }); + }; + + const getDateTimeValue = () => { + if (!configuration.startDatetime) return ""; + + try { + if (configuration.startDatetime.includes("T")) { + return configuration.startDatetime.slice(0, 19); + } + const date = new Date(configuration.startDatetime); + if (!isNaN(date.getTime())) { + return date.toISOString().slice(0, 19); + } + } catch (error) { + console.warn("Invalid datetime format:", configuration.startDatetime); + } + + return ""; }; const calculateDataPoints = () => { const intervalDays = { - 'P1D': 1, - 'P1W': 7, - 'P1M': 30, - 'P1Y': 365 + P1D: 1, + P1W: 7, + P1M: 30, + P1Y: 365, }; - + const frequencyMinutes = { - 'PT1M': 1, - 'PT5M': 5, - 'PT15M': 15, - 'PT1H': 60 + PT1M: 1, + PT5M: 5, + PT15M: 15, + PT1H: 60, }; - + const days = intervalDays[configuration.interval] || 365; - const samplesPerDay = (24 * 60) / (frequencyMinutes[configuration.frequency] || 5); - + const samplesPerDay = + (24 * 60) / (frequencyMinutes[configuration.frequency] || 5); + return Math.round( - configuration.nThings * - configuration.nObservedProperties * - days * - samplesPerDay + configuration.nThings * + configuration.nObservedProperties * + days * + samplesPerDay ); }; @@ -49,12 +68,15 @@ function SampleDataStep() {
-

Sample Data Configuration

+

+ Sample Data Configuration +

- Sample data generation is disabled. Enable "Generate dummy data" in the Data Management step to configure these options. + Sample data generation is disabled. Enable "Generate dummy data" in + the Data Management step to configure these options.

+
+ + + +
); } diff --git a/istsos4-wizard/src/components/common/SessionRecovery.jsx b/istsos4-wizard/src/components/common/SessionRecovery.jsx new file mode 100644 index 0000000..70f71aa --- /dev/null +++ b/istsos4-wizard/src/components/common/SessionRecovery.jsx @@ -0,0 +1,43 @@ +import React, { useState, useEffect } from 'react'; +import { AlertCircle, X } from 'lucide-react'; +import { useWizard } from '../../hooks/useWizard'; + +function SessionRecovery() { + const { state } = useWizard(); + const [showNotification, setShowNotification] = useState(false); + + useEffect(() => { + + const savedState = localStorage.getItem('istsos4-wizard-state'); + if (savedState && state.currentStep > 1) { + setShowNotification(true); + + const timer = setTimeout(() => setShowNotification(false), 5000); + return () => clearTimeout(timer); + } + }, []); + + if (!showNotification) return null; + + return ( +
+
+ +
+

Session Recovered

+

+ Your previous progress has been restored. You're on step {state.currentStep} of {state.totalSteps}. +

+
+ +
+
+ ); +} + +export default SessionRecovery; \ No newline at end of file diff --git a/istsos4-wizard/src/components/steps/CompletionStep.jsx b/istsos4-wizard/src/components/steps/CompletionStep.jsx index a80d310..91f00a1 100644 --- a/istsos4-wizard/src/components/steps/CompletionStep.jsx +++ b/istsos4-wizard/src/components/steps/CompletionStep.jsx @@ -1,7 +1,10 @@ import React from 'react'; -import { Check } from 'lucide-react' +import { Check, RefreshCw } from 'lucide-react'; +import { useWizardPersistence } from '../../hooks/useWizardPersistence'; function CompletionStep() { + const { resetWizard } = useWizardPersistence(); + return (
@@ -20,7 +23,16 @@ function CompletionStep() {
  • • Community Support
  • + +
    ); } + export default CompletionStep; \ No newline at end of file diff --git a/istsos4-wizard/src/components/wizard/WizardProvider.jsx b/istsos4-wizard/src/components/wizard/WizardProvider.jsx index 6ec1651..20cd8d6 100644 --- a/istsos4-wizard/src/components/wizard/WizardProvider.jsx +++ b/istsos4-wizard/src/components/wizard/WizardProvider.jsx @@ -1,11 +1,80 @@ - -import React, { useReducer } from 'react'; +import React, { useReducer, useEffect, useRef, useState } from 'react'; import { WizardContext } from '../../context/WizardContext'; import { wizardReducer } from '../../reducers/wizardReducer'; import { initialState } from '../../utils/constants'; +const STORAGE_KEY = 'istsos4-wizard-state'; +const SAVE_DELAY = 1000; + function WizardProvider({ children }) { - const [state, dispatch] = useReducer(wizardReducer, initialState); + const [lastSaved, setLastSaved] = useState(null); + const saveTimeoutRef = useRef(null); + + const loadInitialState = () => { + try { + const savedState = localStorage.getItem(STORAGE_KEY); + if (savedState) { + const parsedState = JSON.parse(savedState); + return { + ...initialState, + ...parsedState, + validation: { + ...initialState.validation, + ...parsedState.validation + }, + configuration: { + ...initialState.configuration, + ...parsedState.configuration + } + }; + } + } catch (error) { + console.error('Error loading saved state:', error); + } + return initialState; + }; + + const [state, dispatch] = useReducer(wizardReducer, loadInitialState()); + + // Debounced save to localStorage + useEffect(() => { + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current); + } + + saveTimeoutRef.current = setTimeout(() => { + try { + const stateToSave = { + ...state, + savedAt: Date.now() + }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave)); + setLastSaved(Date.now()); + } catch (error) { + console.error('Error saving state:', error); + } + }, SAVE_DELAY); + + return () => { + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current); + } + }; + }, [state]); + + // Listen for storage changes from other tabs + useEffect(() => { + const handleStorageChange = (e) => { + if (e.key === STORAGE_KEY && e.newValue && e.newValue !== e.oldValue) { + if (window.confirm('The wizard has been updated in another tab. Do you want to load those changes?')) { + window.location.reload(); + } + } + }; + + window.addEventListener('storage', handleStorageChange); + return () => window.removeEventListener('storage', handleStorageChange); + }, []); return ( @@ -14,4 +83,4 @@ function WizardProvider({ children }) { ); } -export default WizardProvider; \ No newline at end of file +export default WizardProvider; diff --git a/istsos4-wizard/src/hooks/useWizardPersistence.js b/istsos4-wizard/src/hooks/useWizardPersistence.js new file mode 100644 index 0000000..e65fbc4 --- /dev/null +++ b/istsos4-wizard/src/hooks/useWizardPersistence.js @@ -0,0 +1,18 @@ +import { useWizard } from './useWizard'; + +export function useWizardPersistence() { + const { state, dispatch } = useWizard(); + + const resetWizard = () => { + dispatch({ type: 'RESET_WIZARD' }); + }; + + const clearPersistedData = () => { + localStorage.removeItem('istsos4-wizard-state'); + }; + + return { + resetWizard, + clearPersistedData, + }; +} diff --git a/istsos4-wizard/src/index.css b/istsos4-wizard/src/index.css index a461c50..b685ac1 100644 --- a/istsos4-wizard/src/index.css +++ b/istsos4-wizard/src/index.css @@ -1 +1,15 @@ -@import "tailwindcss"; \ No newline at end of file +@import "tailwindcss"; +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fade-in 0.3s ease-out; +} \ No newline at end of file diff --git a/istsos4-wizard/src/reducers/wizardReducer.js b/istsos4-wizard/src/reducers/wizardReducer.js index 1a4326a..0839af3 100644 --- a/istsos4-wizard/src/reducers/wizardReducer.js +++ b/istsos4-wizard/src/reducers/wizardReducer.js @@ -1,25 +1,27 @@ import { validateField, validateStep } from '../utils/fieldValidations'; +import { initialState } from '../utils/constants'; + export function wizardReducer(state, action) { switch (action.type) { case 'SET_STEP': return { ...state, currentStep: action.payload }; - + case 'UPDATE_CONFIG': const newConfig = { ...state.configuration, ...action.payload }; const fieldName = Object.keys(action.payload)[0]; const fieldValue = action.payload[fieldName]; - - const fieldError = validateField(fieldName, fieldValue); - - + + const fieldError = validateField(fieldName, fieldValue); + + const newErrors = { ...state.validation.errors }; if (fieldError) { newErrors[fieldName] = fieldError; } else { delete newErrors[fieldName]; } - + return { ...state, configuration: newConfig, @@ -29,7 +31,7 @@ export function wizardReducer(state, action) { touched: { ...state.validation.touched, [fieldName]: true } } }; - + case 'SET_FIELD_TOUCHED': return { ...state, @@ -38,7 +40,7 @@ export function wizardReducer(state, action) { touched: { ...state.validation.touched, [action.payload]: true } } }; - + case 'TOUCH_MULTIPLE_FIELDS': const newTouched = { ...state.validation.touched }; action.payload.forEach(field => { @@ -51,7 +53,7 @@ export function wizardReducer(state, action) { touched: newTouched } }; - + case 'VALIDATE_CURRENT_STEP': const stepErrors = validateStep(state.currentStep, state.configuration); return { @@ -62,19 +64,19 @@ export function wizardReducer(state, action) { isValid: Object.keys(stepErrors).length === 0 } }; - + case 'NEXT_STEP': - + const currentStepErrors = validateStep(state.currentStep, state.configuration); - + if (Object.keys(currentStepErrors).length > 0) { - + const errorFields = Object.keys(currentStepErrors); const touchedFields = { ...state.validation.touched }; errorFields.forEach(field => { touchedFields[field] = true; }); - + return { ...state, validation: { @@ -84,19 +86,23 @@ export function wizardReducer(state, action) { } }; } - + return { ...state, currentStep: Math.min(state.currentStep + 1, state.totalSteps), validation: { ...state.validation, isValid: true } }; - + case 'PREV_STEP': return { ...state, currentStep: Math.max(state.currentStep - 1, 1) }; - + + case 'RESET_WIZARD': + localStorage.removeItem('istsos4-wizard-state'); + return initialState; + default: return state; } diff --git a/istsos4-wizard/src/utils/fieldValidations.js b/istsos4-wizard/src/utils/fieldValidations.js index 6366082..e12bd5a 100644 --- a/istsos4-wizard/src/utils/fieldValidations.js +++ b/istsos4-wizard/src/utils/fieldValidations.js @@ -52,8 +52,12 @@ export const fieldValidations = { ValidationRules.minValue(1), ValidationRules.maxValue(10) ], - - // Performance Settings + partitionChunk: [ + ValidationRules.minValue(1000), + ValidationRules.maxValue(50000) + ], + + // Performance Settings countEstimateThreshold: [ ValidationRules.minValue(1000), ValidationRules.maxValue(100000) @@ -61,10 +65,6 @@ export const fieldValidations = { topValue: [ ValidationRules.minValue(10), ValidationRules.maxValue(1000) - ], - partitionChunk: [ - ValidationRules.minValue(1000), - ValidationRules.maxValue(50000) ] }; @@ -99,11 +99,11 @@ export const validateStep = (stepNumber, configuration) => { break; case 5: // Sample Data Configuration if (configuration.dummyData === 1) { - fieldsToValidate = ['nThings', 'nObservedProperties']; + fieldsToValidate = ['nThings', 'nObservedProperties', 'partitionChunk']; } break; case 6: // Performance Settings - fieldsToValidate = ['countEstimateThreshold', 'topValue', 'partitionChunk']; + fieldsToValidate = ['countEstimateThreshold', 'topValue']; break; } From 4865aae380d9f90b6ee2dfefdf51135d2a74c421 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Tue, 1 Jul 2025 22:59:02 +0530 Subject: [PATCH 05/14] Remove password from local storage --- .../src/components/wizard/WizardProvider.jsx | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/istsos4-wizard/src/components/wizard/WizardProvider.jsx b/istsos4-wizard/src/components/wizard/WizardProvider.jsx index 20cd8d6..f3134e1 100644 --- a/istsos4-wizard/src/components/wizard/WizardProvider.jsx +++ b/istsos4-wizard/src/components/wizard/WizardProvider.jsx @@ -11,28 +11,33 @@ function WizardProvider({ children }) { const saveTimeoutRef = useRef(null); const loadInitialState = () => { - try { - const savedState = localStorage.getItem(STORAGE_KEY); - if (savedState) { - const parsedState = JSON.parse(savedState); - return { - ...initialState, - ...parsedState, - validation: { - ...initialState.validation, - ...parsedState.validation - }, - configuration: { - ...initialState.configuration, - ...parsedState.configuration - } - }; - } - } catch (error) { - console.error('Error loading saved state:', error); + try { + const savedState = localStorage.getItem(STORAGE_KEY); + if (savedState) { + const parsedState = JSON.parse(savedState); + + const savedConfig = { ...parsedState.configuration }; + delete savedConfig.postgresPassword; + + return { + ...initialState, + ...parsedState, + validation: { + ...initialState.validation, + ...parsedState.validation + }, + configuration: { + ...initialState.configuration, + ...savedConfig + } + }; } - return initialState; - }; + } catch (error) { + console.error('Error loading saved state:', error); + } + return initialState; +}; + const [state, dispatch] = useReducer(wizardReducer, loadInitialState()); From d0392a8857c56b9da7fadf393c855653e87d94b7 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Fri, 4 Jul 2025 12:01:33 +0530 Subject: [PATCH 06/14] Refactor DatabaseStep & ServicesStep; remove password toggle, improve EPSG input --- .../src/components/steps/DatabaseStep.jsx | 19 +--- .../src/components/steps/ServicesStep.jsx | 91 ++++++++++++------- 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/istsos4-wizard/src/components/steps/DatabaseStep.jsx b/istsos4-wizard/src/components/steps/DatabaseStep.jsx index 8738b7f..7060180 100644 --- a/istsos4-wizard/src/components/steps/DatabaseStep.jsx +++ b/istsos4-wizard/src/components/steps/DatabaseStep.jsx @@ -1,12 +1,10 @@ import React, { useState } from "react"; -import { Eye, EyeOff } from "lucide-react"; import { useWizard } from "../../hooks/useWizard"; import FormField from "../common/FormField"; function DatabaseStep() { const { state, dispatch } = useWizard(); const { configuration, validation } = state; - const [showPassword, setShowPassword] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false); const updateConfig = (field, value) => { @@ -87,8 +85,8 @@ function DatabaseStep() {
    updateConfig("postgresPassword", e.target.value) @@ -96,17 +94,6 @@ function DatabaseStep() { onBlur={() => handleBlur("postgresPassword")} placeholder="Enter secure password" /> -
    {/* Password strength indicator */} @@ -156,7 +143,7 @@ function DatabaseStep() { {showAdvanced && ( -
    +

    Additional Services

    @@ -63,30 +54,67 @@ function ServicesStep() { label="Coordinate Reference System (EPSG)" info="Spatial reference system for geographic data" > -
    - - -
    - {configuration.epsg === 4326 && ( -

    - ✓ WGS 84 is recommended for global applications and GPS data +

    + {/* EPSG Input */} +
    + updateConfig('epsg', e.target.value ? parseInt(e.target.value) : '')} + min="1" + max="99999" + /> +
    + + {/* EPSG code validation */} + {configuration.epsg && ( +
    +

    + Selected EPSG Code: {configuration.epsg}

    - )} - {configuration.epsg === 3857 && ( -

    - ℹ Web Mercator is ideal for web mapping applications +

    + Make sure this code is valid for your geographic area and data requirements.

    - )} +
    + )} + + {/* Common EPSG codes hints */} +
    + Common EPSG Codes: +
    +
    +

    Global:

    +
      +
    • 4326 - WGS 84 (GPS coordinates)
    • +
    • 3857 - Web Mercator (Google Maps)
    • +
    • 4269 - NAD83 (North America)
    • +
    • 4258 - ETRS89 (Europe)
    • +
    +
    +
    +

    Regional:

    +
      +
    • 2154 - Lambert-93 (France)
    • +
    • 27700 - British National Grid (UK)
    • +
    • 32633 - UTM Zone 33N (Europe)
    • +
    • 3035 - ETRS89 LAEA (Europe)
    • +
    +
    +
    +
    + + {/* How to find EPSG codes */} +
    + How to find your EPSG code: +
      +
    • • Visit epsg.io to search by location or name
    • +
    • • Use spatialreference.org for detailed information
    • +
    • • Check your existing GIS data properties
    • +
    • • For GPS data, use 4326 (WGS 84)
    • +
    • • For web mapping, use 3857 (Web Mercator)
    • +
    @@ -110,6 +138,7 @@ function ServicesStep() {
    +
    ); From 56ddcf8dfe5e0d9deb4b4975eca91e380b27cb6c Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Fri, 4 Jul 2025 12:25:32 +0530 Subject: [PATCH 07/14] add EPSG validation and improve field handling --- .../src/components/steps/ServicesStep.jsx | 205 ++++++++++++------ istsos4-wizard/src/utils/fieldValidations.js | 40 +++- 2 files changed, 162 insertions(+), 83 deletions(-) diff --git a/istsos4-wizard/src/components/steps/ServicesStep.jsx b/istsos4-wizard/src/components/steps/ServicesStep.jsx index d38c2b0..3fbb1ca 100644 --- a/istsos4-wizard/src/components/steps/ServicesStep.jsx +++ b/istsos4-wizard/src/components/steps/ServicesStep.jsx @@ -1,42 +1,52 @@ -import React from 'react'; -import { useWizard } from '../../hooks/useWizard'; -import FormField from '../common/FormField'; -import Toggle from '../common/Toggle'; +import React from "react"; +import { useWizard } from "../../hooks/useWizard"; +import FormField from "../common/FormField"; +import Toggle from "../common/Toggle"; function ServicesStep() { const { state, dispatch } = useWizard(); - const { configuration } = state; + const { configuration, validation } = state; const updateConfig = (field, value) => { dispatch({ - type: 'UPDATE_CONFIG', - payload: { [field]: value } + type: "UPDATE_CONFIG", + payload: { [field]: value }, }); }; + const handleBlur = (field) => { + dispatch({ type: "SET_FIELD_TOUCHED", payload: field }); + }; + return (

    Additional Services

    -

    Configure optional services and coordinate systems

    - +

    + Configure optional services and coordinate systems +

    +
    updateConfig('redis', e.target.checked ? 1 : 0)} + onChange={(e) => + updateConfig("redis", e.target.checked ? 1 : 0) + } label="Enable Redis Caching" />

    - Redis improves performance by caching frequently accessed data and API responses + Redis improves performance by caching frequently accessed data + and API responses

    - + {configuration.redis === 1 && (

    - Note: Ensure Redis is included in your Docker Compose configuration + Note: Ensure Redis is included in your + Docker Compose configuration

      @@ -50,8 +60,10 @@ function ServicesStep() {
    -
    @@ -61,72 +73,124 @@ function ServicesStep() { type="number" placeholder="Enter EPSG code (e.g., 4326)" className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" - value={configuration.epsg || ''} - onChange={(e) => updateConfig('epsg', e.target.value ? parseInt(e.target.value) : '')} - min="1" - max="99999" + value={configuration.epsg || ""} + onChange={(e) => { + const value = e.target.value; + updateConfig("epsg", value === "" ? "" : parseInt(value)); + }} + onBlur={() => handleBlur("epsg")} />
    +
    + + + {/* EPSG code validation */} + {configuration.epsg && ( +
    +

    + Selected EPSG Code: {configuration.epsg} +

    +

    + Make sure this code is valid for your geographic area and data + requirements. +

    +
    + )} - {/* EPSG code validation */} - {configuration.epsg && ( -
    -

    - Selected EPSG Code: {configuration.epsg} -

    -

    - Make sure this code is valid for your geographic area and data requirements. -

    -
    - )} - - {/* Common EPSG codes hints */} -
    - Common EPSG Codes: -
    -
    -

    Global:

    -
      -
    • 4326 - WGS 84 (GPS coordinates)
    • -
    • 3857 - Web Mercator (Google Maps)
    • -
    • 4269 - NAD83 (North America)
    • -
    • 4258 - ETRS89 (Europe)
    • -
    -
    -
    -

    Regional:

    -
      -
    • 2154 - Lambert-93 (France)
    • -
    • 27700 - British National Grid (UK)
    • -
    • 32633 - UTM Zone 33N (Europe)
    • -
    • 3035 - ETRS89 LAEA (Europe)
    • -
    -
    -
    + {/* Common EPSG codes hints */} +
    + Common EPSG Codes: +
    +
    +

    Global:

    +
      +
    • + • 4326 - WGS 84 (GPS coordinates) +
    • +
    • + • 3857 - Web Mercator (Google Maps) +
    • +
    • + • 4269 - NAD83 (North America) +
    • +
    • + • 4258 - ETRS89 (Europe) +
    • +
    - - {/* How to find EPSG codes */} -
    - How to find your EPSG code: -
      -
    • • Visit epsg.io to search by location or name
    • -
    • • Use spatialreference.org for detailed information
    • -
    • • Check your existing GIS data properties
    • -
    • • For GPS data, use 4326 (WGS 84)
    • -
    • • For web mapping, use 3857 (Web Mercator)
    • +
      +

      Regional:

      +
        +
      • + • 2154 - Lambert-93 (France) +
      • +
      • + • 27700 - British National Grid (UK) +
      • +
      • + • 32633 - UTM Zone 33N (Europe) +
      • +
      • + • 3035 - ETRS89 LAEA (Europe) +
    - +
    + + {/* How to find EPSG codes */} +
    + How to find your EPSG code: +
      +
    • + • Visit{" "} + + epsg.io + {" "} + to search by location or name +
    • +
    • + • Use{" "} + + spatialreference.org + {" "} + for detailed information +
    • +
    • • Check your existing GIS data properties
    • +
    • + • For GPS data, use 4326 (WGS 84) +
    • +
    • + • For web mapping, use 3857 (Web Mercator) +
    • +
    +
    -

    Service Architecture

    +

    + Service Architecture +

    -
    - Redis Cache {configuration.redis === 1 ? '(Enabled)' : '(Disabled)'} +
    + + Redis Cache{" "} + {configuration.redis === 1 ? "(Enabled)" : "(Disabled)"} +
    @@ -138,10 +202,9 @@ function ServicesStep() {
    -
    ); } -export default ServicesStep; \ No newline at end of file +export default ServicesStep; diff --git a/istsos4-wizard/src/utils/fieldValidations.js b/istsos4-wizard/src/utils/fieldValidations.js index e12bd5a..865e5d8 100644 --- a/istsos4-wizard/src/utils/fieldValidations.js +++ b/istsos4-wizard/src/utils/fieldValidations.js @@ -10,7 +10,7 @@ export const fieldValidations = { ValidationRules.required, ValidationRules.pattern(/^\/[a-zA-Z0-9_-]+$/, 'Must start with / and contain only letters, numbers, hyphens, and underscores') ], - + // Database Configuration postgresDb: [ ValidationRules.required, @@ -40,7 +40,7 @@ export const fieldValidations = { ValidationRules.minValue(10), ValidationRules.maxValue(120) ], - + // Sample Data Configuration nThings: [ ValidationRules.required, @@ -52,12 +52,12 @@ export const fieldValidations = { ValidationRules.minValue(1), ValidationRules.maxValue(10) ], - partitionChunk: [ + partitionChunk: [ ValidationRules.minValue(1000), ValidationRules.maxValue(50000) ], - // Performance Settings + // Performance Settings countEstimateThreshold: [ ValidationRules.minValue(1000), ValidationRules.maxValue(100000) @@ -65,19 +65,28 @@ export const fieldValidations = { topValue: [ ValidationRules.minValue(10), ValidationRules.maxValue(1000) - ] + ], + + // Additional Services + epsg: [ + ValidationRules.required, + ValidationRules.minValue(1000), + ValidationRules.maxValue(999999) + ], + + }; // Validate a single field export const validateField = (fieldName, value) => { const validators = fieldValidations[fieldName]; if (!validators) return null; - + for (const validator of validators) { const error = validator(value); if (error) return error; } - + return null; }; @@ -85,11 +94,12 @@ export const validateField = (fieldName, value) => { export const validateStep = (stepNumber, configuration) => { const errors = {}; let fieldsToValidate = []; - + switch (stepNumber) { case 2: // Basic Server Configuration fieldsToValidate = ['hostname', 'subpath']; break; + case 3: // Database Configuration fieldsToValidate = ['postgresDb', 'postgresUser', 'postgresPassword']; // Include advanced fields if shown @@ -97,36 +107,42 @@ export const validateStep = (stepNumber, configuration) => { fieldsToValidate.push('pgPoolSize', 'pgMaxOverflow', 'pgPoolTimeout'); } break; + case 5: // Sample Data Configuration if (configuration.dummyData === 1) { fieldsToValidate = ['nThings', 'nObservedProperties', 'partitionChunk']; } break; + case 6: // Performance Settings fieldsToValidate = ['countEstimateThreshold', 'topValue']; break; + + case 7: // Additional Services + fieldsToValidate = ['epsg']; + break; } - + fieldsToValidate.forEach(field => { const error = validateField(field, configuration[field]); if (error) { errors[field] = error; } }); - + return errors; }; // Validate entire configuration export const validateConfiguration = (configuration) => { const errors = {}; - + Object.keys(fieldValidations).forEach(field => { const error = validateField(field, configuration[field]); if (error) { errors[field] = error; } }); - + return errors; }; \ No newline at end of file From 15e9da3d2c29635cfc54e790ab4d2a38f1c348bf Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Mon, 7 Jul 2025 22:15:30 +0530 Subject: [PATCH 08/14] Update Data Management and Review steps to handle versioning and duplicates as integers --- istsos4-wizard/src/components/steps/DataManagementStep.jsx | 4 ++-- istsos4-wizard/src/components/steps/ReviewStep.jsx | 6 +++--- istsos4-wizard/src/utils/constants.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/istsos4-wizard/src/components/steps/DataManagementStep.jsx b/istsos4-wizard/src/components/steps/DataManagementStep.jsx index 2b712e1..4d72918 100644 --- a/istsos4-wizard/src/components/steps/DataManagementStep.jsx +++ b/istsos4-wizard/src/components/steps/DataManagementStep.jsx @@ -54,7 +54,7 @@ function DataManagementStep() {
    updateConfig('versioning', e.target.checked)} + onChange={(e) => updateConfig('versioning', e.target.checked ? 1 : 0)} label="Enable data versioning" />

    @@ -63,7 +63,7 @@ function DataManagementStep() { updateConfig('duplicates', e.target.checked)} + onChange={(e) => updateConfig('duplicates', e.target.checked ? 1 : 0)} label="Allow duplicate entries" />

    diff --git a/istsos4-wizard/src/components/steps/ReviewStep.jsx b/istsos4-wizard/src/components/steps/ReviewStep.jsx index 583a819..550bfbd 100644 --- a/istsos4-wizard/src/components/steps/ReviewStep.jsx +++ b/istsos4-wizard/src/components/steps/ReviewStep.jsx @@ -8,7 +8,7 @@ function ReviewStep() { const { configuration } = state; const [copySuccess, setCopySuccess] = useState(false); const [validationErrors, setValidationErrors] = useState({}); - + useEffect(() => { // Validate entire configuration const errors = validateConfiguration(configuration); @@ -39,8 +39,8 @@ PG_POOL_TIMEOUT=${configuration.pgPoolTimeout} # Data Management DUMMY_DATA=${configuration.dummyData} CLEAR_DATA=${configuration.clearData} -VERSIONING=${configuration.versioning.toString()} -DUPLICATES=${configuration.duplicates.toString()} +VERSIONING=${configuration.versioning} +DUPLICATES=${configuration.duplicates} # Sample Data Configuration N_THINGS=${configuration.nThings} diff --git a/istsos4-wizard/src/utils/constants.js b/istsos4-wizard/src/utils/constants.js index d366e12..6bbab38 100644 --- a/istsos4-wizard/src/utils/constants.js +++ b/istsos4-wizard/src/utils/constants.js @@ -27,8 +27,8 @@ export const initialState = { // Data Management dummyData: 0, clearData: 0, - versioning: false, - duplicates: false, + versioning: 0, + duplicates: 0, // Sample Data nThings: 3, From d94dc947576d1cfd71fcf9f8a4e84a8788b8eb33 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Tue, 8 Jul 2025 22:57:44 +0530 Subject: [PATCH 09/14] Enhance toggle functionality for versioning and duplicates --- .../components/steps/DataManagementStep.jsx | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/istsos4-wizard/src/components/steps/DataManagementStep.jsx b/istsos4-wizard/src/components/steps/DataManagementStep.jsx index 4d72918..7d2ce4a 100644 --- a/istsos4-wizard/src/components/steps/DataManagementStep.jsx +++ b/istsos4-wizard/src/components/steps/DataManagementStep.jsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { useWizard } from '../../hooks/useWizard'; -import Toggle from '../common/Toggle'; +import React from "react"; +import { useWizard } from "../../hooks/useWizard"; +import Toggle from "../common/Toggle"; function DataManagementStep() { const { state, dispatch } = useWizard(); @@ -8,34 +8,43 @@ function DataManagementStep() { const updateConfig = (field, value) => { dispatch({ - type: 'UPDATE_CONFIG', - payload: { [field]: value } + type: "UPDATE_CONFIG", + payload: { [field]: value }, }); }; return (

    -

    Data Management Configuration

    - +

    + Data Management Configuration +

    +
    -

    Data Initialization

    +

    + Data Initialization +

    updateConfig('dummyData', e.target.checked ? 1 : 0)} + onChange={(e) => + updateConfig("dummyData", e.target.checked ? 1 : 0) + } label="Generate dummy data for testing" />

    - Automatically populate the database with sample sensor data for testing + Automatically populate the database with sample sensor data for + testing

    - + updateConfig('clearData', e.target.checked ? 1 : 0)} + onChange={(e) => + updateConfig("clearData", e.target.checked ? 1 : 0) + } label="Clear existing data on startup" /> - + {configuration.clearData === 1 && (

    @@ -54,21 +63,47 @@ function DataManagementStep() {

    updateConfig('versioning', e.target.checked ? 1 : 0)} + onChange={(e) => + updateConfig("versioning", e.target.checked ? 1 : 0) + } label="Enable data versioning" />

    Keep historical versions of all data changes

    - + {configuration.versioning === 1 && ( +
    +

    + ⚠ Warning: This option cannot be modified once the setup is + complete! +

    +

    + Enabling this will alter your system's data handling behavior + permanently. +

    +
    + )} updateConfig('duplicates', e.target.checked ? 1 : 0)} + onChange={(e) => + updateConfig("duplicates", e.target.checked ? 1 : 0) + } label="Allow duplicate entries" />

    Permit multiple identical observations at the same timestamp

    + {configuration.duplicates === 1 && ( +
    +

    + ⚠ Warning: This option cannot be modified once the setup is + complete! +

    +

    + Enabling this will permanently change how your system validates and stores incoming data. +

    +
    + )}
    @@ -76,4 +111,4 @@ function DataManagementStep() { ); } -export default DataManagementStep; \ No newline at end of file +export default DataManagementStep; From 0a889b9e45d64d81e44e9ae424daac07cf50eea2 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Wed, 9 Jul 2025 17:54:39 +0530 Subject: [PATCH 10/14] Add Authorization step and update wizard navigation --- .../src/components/common/Navigation.jsx | 1 + .../components/steps/AuthorizationStep.jsx | 292 ++++++++++++++++++ .../src/components/wizard/StepRenderer.jsx | 2 + istsos4-wizard/src/utils/constants.js | 11 +- istsos4-wizard/src/utils/fieldValidations.js | 33 +- 5 files changed, 335 insertions(+), 4 deletions(-) create mode 100644 istsos4-wizard/src/components/steps/AuthorizationStep.jsx diff --git a/istsos4-wizard/src/components/common/Navigation.jsx b/istsos4-wizard/src/components/common/Navigation.jsx index 74fd24e..0ec4fff 100644 --- a/istsos4-wizard/src/components/common/Navigation.jsx +++ b/istsos4-wizard/src/components/common/Navigation.jsx @@ -7,6 +7,7 @@ const steps = [ { title: 'Welcome' }, { title: 'Server Config' }, { title: 'Database' }, + { title: 'Authorization' }, { title: 'Data Management' }, { title: 'Sample Data' }, { title: 'Performance' }, diff --git a/istsos4-wizard/src/components/steps/AuthorizationStep.jsx b/istsos4-wizard/src/components/steps/AuthorizationStep.jsx new file mode 100644 index 0000000..f7a9fdd --- /dev/null +++ b/istsos4-wizard/src/components/steps/AuthorizationStep.jsx @@ -0,0 +1,292 @@ +import React from "react"; +import { useWizard } from "../../hooks/useWizard"; +import FormField from "../common/FormField"; +import Toggle from "../common/Toggle"; + +function AuthorizationStep() { + const { state, dispatch } = useWizard(); + const { configuration, validation } = state; + + const updateConfig = (field, value) => { + dispatch({ + type: "UPDATE_CONFIG", + payload: { [field]: value }, + }); + }; + + const handleBlur = (field) => { + dispatch({ type: "SET_FIELD_TOUCHED", payload: field }); + }; + + // Function to generate a new secret key + const generateSecretKey = () => { + const chars = "0123456789abcdef"; + let result = ""; + for (let i = 0; i < 64; i++) { + result += chars[Math.floor(Math.random() * chars.length)]; + } + updateConfig("secretKey", result); + }; + + // Password strength indicator + const getPasswordStrength = (password) => { + if (!password) return { strength: 0, label: "" }; + + let strength = 0; + if (password.length >= 8) strength++; + if (password.length >= 12) strength++; + if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++; + if (/\d/.test(password)) strength++; + if (/[^a-zA-Z0-9]/.test(password)) strength++; + + const labels = ["", "Weak", "Fair", "Good", "Strong", "Very Strong"]; + return { strength, label: labels[strength] }; + }; + + const passwordStrength = getPasswordStrength( + configuration.istsosAdminPassword + ); + + return ( +
    +

    + Authorization Configuration +

    +

    + Configure authentication and authorization settings for your istSOS4 + instance. +

    + +
    +
    + {/* Authorization Toggle */} + + + updateConfig("authorization", e.target.checked ? 1 : 0) + } + label={configuration.authorization === 1 ? "Enabled" : "Disabled"} + /> + + + + {/* Admin Username */} + + updateConfig("istsosAdmin", e.target.value)} + onBlur={() => handleBlur("istsosAdmin")} + placeholder="admin" + /> + + {/* Admin Password */} + +
    +
    + + updateConfig("istsosAdminPassword", e.target.value) + } + onBlur={() => handleBlur("istsosAdminPassword")} + placeholder="Enter administrator password" + /> +
    + + {/* Password strength indicator */} + {configuration.istsosAdminPassword && ( +
    +
    + Password strength: + + {passwordStrength.label} + +
    +
    +
    +
    +
    + )} +
    + +
    + +
    + {/* Anonymous Viewer */} + + + updateConfig("anonymousViewer", e.target.checked ? 1 : 0) + } + label={ + configuration.anonymousViewer === 1 ? "Enabled" : "Disabled" + } + /> + + + {/* Access Token Expire Minutes */} + + { + const value = e.target.value; + updateConfig( + "accessTokenExpireMinutes", + value === "" ? "" : parseInt(value) + ); + }} + onBlur={() => handleBlur("accessTokenExpireMinutes")} + placeholder="5" + /> + + {/* Algorithm */} + + + +
    +
    + + {/* Secret Key */} +
    + +
    +
    + updateConfig("secretKey", e.target.value)} + onBlur={() => handleBlur("secretKey")} + placeholder="09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" + /> + +
    +

    + Use 'openssl rand -hex 32' to generate a secure key, or click + "Generate New" +

    +
    +
    +
    + +
    +
    +
    + + + +
    +
    +

    + Security Best Practices +

    +
    +
      +
    • Use a strong, randomly generated secret key
    • +
    • + Set appropriate token expiration times for your use case +
    • +
    • Use a strong password for the administrator account
    • +
    • + Consider disabling anonymous viewer in production environments +
    • +
    +
    +
    +
    +
    +
    + ); +} + +export default AuthorizationStep; diff --git a/istsos4-wizard/src/components/wizard/StepRenderer.jsx b/istsos4-wizard/src/components/wizard/StepRenderer.jsx index b9be377..d18213e 100644 --- a/istsos4-wizard/src/components/wizard/StepRenderer.jsx +++ b/istsos4-wizard/src/components/wizard/StepRenderer.jsx @@ -11,6 +11,7 @@ import PerformanceStep from '../steps/PerformanceStep'; import ServicesStep from '../steps/ServicesStep'; import ReviewStep from '../steps/ReviewStep'; import CompletionStep from '../steps/CompletionStep'; +import AuthorizationStep from '../steps/AuthorizationStep'; function StepRenderer() { const { state } = useWizard(); @@ -19,6 +20,7 @@ function StepRenderer() { WelcomeStep, BasicServerStep, DatabaseStep, + AuthorizationStep, DataManagementStep, SampleDataStep, PerformanceStep, diff --git a/istsos4-wizard/src/utils/constants.js b/istsos4-wizard/src/utils/constants.js index 6bbab38..9fc3c88 100644 --- a/istsos4-wizard/src/utils/constants.js +++ b/istsos4-wizard/src/utils/constants.js @@ -1,7 +1,7 @@ export const initialState = { // Wizard State currentStep: 1, - totalSteps: 9, + totalSteps: 10, validation: { errors: {}, touched: {}, @@ -23,6 +23,15 @@ export const initialState = { pgMaxOverflow: 0, pgPoolSize: 10, pgPoolTimeout: 30, + + // Authentication Configuration + istsosAdmin: 'admin', + istsosAdminPassword: '', + authorization: 0, + secretKey: '09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7', + algorithm: 'HS256', + accessTokenExpireMinutes: '5', + anonymousViewer: 0, // Data Management dummyData: 0, diff --git a/istsos4-wizard/src/utils/fieldValidations.js b/istsos4-wizard/src/utils/fieldValidations.js index 865e5d8..ecc2317 100644 --- a/istsos4-wizard/src/utils/fieldValidations.js +++ b/istsos4-wizard/src/utils/fieldValidations.js @@ -41,6 +41,29 @@ export const fieldValidations = { ValidationRules.maxValue(120) ], + // Authentication Configuration + istsosAdmin: [ + ValidationRules.required, + ValidationRules.alphanumeric, + ValidationRules.minLength(1), + ValidationRules.maxLength(63) + ], + istsosAdminPassword: [ + ValidationRules.required, + ValidationRules.password + ], + secretKey: [ + ValidationRules.required, + ValidationRules.minLength(32), + ValidationRules.maxLength(64) + ], + accessTokenExpireMinutes: [ + ValidationRules.required, + ValidationRules.minValue(1), + ValidationRules.maxValue(1440) // 1 day in minutes + ], + + // Sample Data Configuration nThings: [ ValidationRules.required, @@ -107,18 +130,22 @@ export const validateStep = (stepNumber, configuration) => { fieldsToValidate.push('pgPoolSize', 'pgMaxOverflow', 'pgPoolTimeout'); } break; + + case 4: // Authentication Configuration + fieldsToValidate = ['istsosAdmin', 'istsosAdminPassword', 'secretKey', 'accessTokenExpireMinutes' ]; + break; - case 5: // Sample Data Configuration + case 6: // Sample Data Configuration if (configuration.dummyData === 1) { fieldsToValidate = ['nThings', 'nObservedProperties', 'partitionChunk']; } break; - case 6: // Performance Settings + case 7: // Performance Settings fieldsToValidate = ['countEstimateThreshold', 'topValue']; break; - case 7: // Additional Services + case 8: // Additional Services fieldsToValidate = ['epsg']; break; } From d8467d49ec06be51127fd216ca68f80f86daaf18 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Wed, 9 Jul 2025 18:10:14 +0530 Subject: [PATCH 11/14] Add authentication configuration to ReviewStep and update WizardProvider to handle sensitive data securely --- .../src/components/steps/ReviewStep.jsx | 9 ++ .../src/components/wizard/WizardProvider.jsx | 96 +++++++++++-------- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/istsos4-wizard/src/components/steps/ReviewStep.jsx b/istsos4-wizard/src/components/steps/ReviewStep.jsx index 550bfbd..541cbfc 100644 --- a/istsos4-wizard/src/components/steps/ReviewStep.jsx +++ b/istsos4-wizard/src/components/steps/ReviewStep.jsx @@ -36,6 +36,15 @@ PG_MAX_OVERFLOW=${configuration.pgMaxOverflow} PG_POOL_SIZE=${configuration.pgPoolSize} PG_POOL_TIMEOUT=${configuration.pgPoolTimeout} +# Authentication Configuration +ISTSOS_ADMIN=${configuration.istsosAdmin} +ISTSOS_ADMIN_PASSWORD=${configuration.istsosAdminPassword} +AUTHORIZATION=${configuration.authorization} +SECRET_KEY=${configuration.secretKey} +ALGORITHM=${configuration.algorithm} +ACCESS_TOKEN_EXPIRE_MINUTES=${configuration.accessTokenExpireMinutes} +ANONYMOUS_VIEWER=${configuration.anonymousViewer} + # Data Management DUMMY_DATA=${configuration.dummyData} CLEAR_DATA=${configuration.clearData} diff --git a/istsos4-wizard/src/components/wizard/WizardProvider.jsx b/istsos4-wizard/src/components/wizard/WizardProvider.jsx index f3134e1..14cc420 100644 --- a/istsos4-wizard/src/components/wizard/WizardProvider.jsx +++ b/istsos4-wizard/src/components/wizard/WizardProvider.jsx @@ -1,86 +1,100 @@ -import React, { useReducer, useEffect, useRef, useState } from 'react'; -import { WizardContext } from '../../context/WizardContext'; -import { wizardReducer } from '../../reducers/wizardReducer'; -import { initialState } from '../../utils/constants'; +import React, { useReducer, useEffect, useRef, useState } from "react"; +import { WizardContext } from "../../context/WizardContext"; +import { wizardReducer } from "../../reducers/wizardReducer"; +import { initialState } from "../../utils/constants"; -const STORAGE_KEY = 'istsos4-wizard-state'; +const STORAGE_KEY = "istsos4-wizard-state"; const SAVE_DELAY = 1000; function WizardProvider({ children }) { const [lastSaved, setLastSaved] = useState(null); const saveTimeoutRef = useRef(null); - + const loadInitialState = () => { - try { - const savedState = localStorage.getItem(STORAGE_KEY); - if (savedState) { - const parsedState = JSON.parse(savedState); + try { + const savedState = localStorage.getItem(STORAGE_KEY); + if (savedState) { + const parsedState = JSON.parse(savedState); - const savedConfig = { ...parsedState.configuration }; - delete savedConfig.postgresPassword; + const savedConfig = { ...parsedState.configuration }; + delete savedConfig.postgresPassword; + delete savedConfig.istsosAdminPassword; + delete savedConfig.secretKey; - return { - ...initialState, - ...parsedState, - validation: { - ...initialState.validation, - ...parsedState.validation - }, - configuration: { - ...initialState.configuration, - ...savedConfig - } - }; + return { + ...initialState, + ...parsedState, + validation: { + ...initialState.validation, + ...parsedState.validation, + }, + configuration: { + ...initialState.configuration, + ...savedConfig, + }, + }; + } + } catch (error) { + console.error("Error loading saved state:", error); } - } catch (error) { - console.error('Error loading saved state:', error); - } - return initialState; -}; - + return initialState; + }; const [state, dispatch] = useReducer(wizardReducer, loadInitialState()); - + // Debounced save to localStorage useEffect(() => { if (saveTimeoutRef.current) { clearTimeout(saveTimeoutRef.current); } - + saveTimeoutRef.current = setTimeout(() => { try { const stateToSave = { ...state, - savedAt: Date.now() + configuration: { + ...state.configuration, + }, + savedAt: Date.now(), }; + + // Remove sensitive fields before saving + delete stateToSave.configuration.postgresPassword; + delete stateToSave.configuration.istsosAdminPassword; + delete stateToSave.configuration.secretKey; + localStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave)); setLastSaved(Date.now()); } catch (error) { - console.error('Error saving state:', error); + console.error("Error saving state:", error); } }, SAVE_DELAY); - + return () => { if (saveTimeoutRef.current) { clearTimeout(saveTimeoutRef.current); } }; }, [state]); - + // Listen for storage changes from other tabs useEffect(() => { const handleStorageChange = (e) => { if (e.key === STORAGE_KEY && e.newValue && e.newValue !== e.oldValue) { - if (window.confirm('The wizard has been updated in another tab. Do you want to load those changes?')) { + if ( + window.confirm( + "The wizard has been updated in another tab. Do you want to load those changes?" + ) + ) { window.location.reload(); } } }; - - window.addEventListener('storage', handleStorageChange); - return () => window.removeEventListener('storage', handleStorageChange); + + window.addEventListener("storage", handleStorageChange); + return () => window.removeEventListener("storage", handleStorageChange); }, []); - + return ( {children} From 9a46e52061a8ac0d430694989f4ddd4596183581 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Tue, 15 Jul 2025 22:57:47 +0530 Subject: [PATCH 12/14] Update navigation in SampleDataStep to go back to the correct step in the wizard --- istsos4-wizard/src/components/steps/SampleDataStep.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/istsos4-wizard/src/components/steps/SampleDataStep.jsx b/istsos4-wizard/src/components/steps/SampleDataStep.jsx index 48f8c22..9bbb4da 100644 --- a/istsos4-wizard/src/components/steps/SampleDataStep.jsx +++ b/istsos4-wizard/src/components/steps/SampleDataStep.jsx @@ -76,7 +76,7 @@ function SampleDataStep() { the Data Management step to configure these options.

    + + {showAdvanced && ( +
    + - + updateConfig("frequency", e.target.value)} - > - - - - - - +
    + )}
    -

    Data Generation Summary @@ -266,6 +276,8 @@ function SampleDataStep() {

    )}

    + +
    ); } diff --git a/istsos4-wizard/src/components/steps/ServicesStep.jsx b/istsos4-wizard/src/components/steps/ServicesStep.jsx index 3fbb1ca..9d3b965 100644 --- a/istsos4-wizard/src/components/steps/ServicesStep.jsx +++ b/istsos4-wizard/src/components/steps/ServicesStep.jsx @@ -26,45 +26,12 @@ function ServicesStep() {

    -
    -
    -
    - - updateConfig("redis", e.target.checked ? 1 : 0) - } - label="Enable Redis Caching" - /> -

    - Redis improves performance by caching frequently accessed data - and API responses -

    - - {configuration.redis === 1 && ( -
    -
    -

    - Note: Ensure Redis is included in your - Docker Compose configuration -

    -
    -
      -
    • • Default connection: redis://redis:6379
    • -
    • • Recommended memory: 512MB minimum
    • -
    • • Cache TTL: 1 hour (configurable)
    • -
    -
    - )} -
    -
    -
    +
    {/* EPSG Input */} @@ -181,7 +148,7 @@ function ServicesStep() { Service Architecture
    -
    + {/*
    -
    +
    */}
    PostgreSQL Database (Required) diff --git a/istsos4-wizard/src/utils/constants.js b/istsos4-wizard/src/utils/constants.js index 9fc3c88..d63aadb 100644 --- a/istsos4-wizard/src/utils/constants.js +++ b/istsos4-wizard/src/utils/constants.js @@ -52,9 +52,9 @@ export const initialState = { topValue: 100, partitionChunk: 10000, chunkInterval: 'P1Y', + redis: 0, // Additional Services - redis: 0, epsg: 4326 } }; \ No newline at end of file From 3a0c0d3a60ad0307090a2bfaed0e316aac440e62 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Thu, 17 Jul 2025 23:35:00 +0530 Subject: [PATCH 14/14] Refactor Navigation and CompletionStep components --- .../src/components/common/Navigation.jsx | 112 ++++++++++++------ .../src/components/steps/CompletionStep.jsx | 12 +- 2 files changed, 75 insertions(+), 49 deletions(-) diff --git a/istsos4-wizard/src/components/common/Navigation.jsx b/istsos4-wizard/src/components/common/Navigation.jsx index 0ec4fff..099a7ca 100644 --- a/istsos4-wizard/src/components/common/Navigation.jsx +++ b/istsos4-wizard/src/components/common/Navigation.jsx @@ -1,49 +1,55 @@ -import React, { useState } from 'react'; -import { ChevronLeft, ChevronRight, RotateCcw } from 'lucide-react'; -import { useWizard } from '../../hooks/useWizard'; -import { useWizardPersistence } from '../../hooks/useWizardPersistence'; +import React, { useState } from "react"; +import { ChevronLeft, ChevronRight, RotateCcw } from "lucide-react"; +import { useWizard } from "../../hooks/useWizard"; +import { useWizardPersistence } from "../../hooks/useWizardPersistence"; const steps = [ - { title: 'Welcome' }, - { title: 'Server Config' }, - { title: 'Database' }, - { title: 'Authorization' }, - { title: 'Data Management' }, - { title: 'Sample Data' }, - { title: 'Performance' }, - { title: 'Services' }, - { title: 'Review' }, - { title: 'Complete' } + { title: "Welcome" }, + { title: "Server Config" }, + { title: "Database" }, + { title: "Authorization" }, + { title: "Data Management" }, + { title: "Sample Data" }, + { title: "Performance" }, + { title: "Services" }, + { title: "Review" }, + { title: "Complete" }, ]; function Navigation() { const { state, dispatch } = useWizard(); const { resetWizard } = useWizardPersistence(); const [showResetConfirm, setShowResetConfirm] = useState(false); - + const [showResetWarning, setShowResetWarning] = useState(false); + const hasErrors = Object.keys(state.validation.errors).length > 0; const hasTouchedErrors = Object.keys(state.validation.errors).some( - field => state.validation.touched[field] + (field) => state.validation.touched[field] ); const canGoNext = state.currentStep < state.totalSteps; const canGoPrev = state.currentStep > 1; const handleNext = () => { - dispatch({ type: 'NEXT_STEP' }); + dispatch({ type: "NEXT_STEP" }); }; const handlePrev = () => { - dispatch({ type: 'PREV_STEP' }); + dispatch({ type: "PREV_STEP" }); }; const handleReset = () => { if (showResetConfirm) { resetWizard(); setShowResetConfirm(false); + setShowResetWarning(false); } else { + setShowResetWarning(true); setShowResetConfirm(true); - setTimeout(() => setShowResetConfirm(false), 3000); + setTimeout(() => { + setShowResetConfirm(false); + setShowResetWarning(false); + }, 5000); } }; @@ -53,9 +59,9 @@ function Navigation() { onClick={handlePrev} disabled={!canGoPrev} className={`flex items-center px-4 py-2 rounded-md transition-colors ${ - canGoPrev - ? 'bg-gray-200 hover:bg-gray-300 text-gray-700' - : 'bg-gray-100 text-gray-400 cursor-not-allowed' + canGoPrev + ? "bg-gray-200 hover:bg-gray-300 text-gray-700" + : "bg-gray-100 text-gray-400 cursor-not-allowed" }`} > @@ -74,25 +80,55 @@ function Navigation() {
    - - +
    + + + {showResetWarning && showResetConfirm && ( +
    +
    + + + +
    +

    Warning!

    +

    + This will permanently delete all your configuration data and + restart the wizard from the beginning. +

    +

    + Click again to confirm reset +

    +
    +
    +
    +
    + )} +
    +
    ); }