import {useState, useRef, useEffect} from 'react'; import {useSelector, useDispatch} from 'react-redux'; import {saveConfig, resetConfig, selectConfig, rotateWopiKeysAction} from '../../store/slices/configSlice'; import {getNestedValue} from '../../utils/getNestedValue'; import {mergeNestedObjects} from '../../utils/mergeNestedObjects'; import {useFieldValidation} from '../../hooks/useFieldValidation'; import {maskKey} from '../../utils/maskKey'; import PageHeader from '../../components/PageHeader/PageHeader'; import PageDescription from '../../components/PageDescription/PageDescription'; import ToggleSwitch from '../../components/ToggleSwitch/ToggleSwitch'; import Input from '../../components/Input/Input'; import Checkbox from '../../components/Checkbox/Checkbox'; import FixedSaveButtonGroup from '../../components/FixedSaveButtonGroup/FixedSaveButtonGroup'; import Note from '../../components/Note/Note'; import Section from '../../components/Section/Section'; import styles from './WOPISettings.module.scss'; function WOPISettings() { const dispatch = useDispatch(); const config = useSelector(selectConfig); const {validateField, hasValidationErrors} = useFieldValidation(); // Local state for WOPI settings const [localWopiEnabled, setLocalWopiEnabled] = useState(false); const [localRotateKeys, setLocalRotateKeys] = useState(false); const [localRefreshLockInterval, setLocalRefreshLockInterval] = useState(''); const [hasChanges, setHasChanges] = useState(false); const hasInitialized = useRef(false); // Get the actual config values const configWopiEnabled = getNestedValue(config, 'wopi.enable', false); const wopiPublicKey = getNestedValue(config, 'wopi.publicKey', ''); const configRefreshLockInterval = getNestedValue(config, 'wopi.refreshLockInterval', '10m'); const resetToGlobalConfig = source => { const src = source || config; if (src) { const srcEnabled = getNestedValue(src, 'wopi.enable'); const srcInterval = getNestedValue(src, 'wopi.refreshLockInterval'); setLocalWopiEnabled(srcEnabled); setLocalRotateKeys(false); setLocalRefreshLockInterval(srcInterval); setHasChanges(false); validateField('wopi.enable', srcEnabled); validateField('wopi.refreshLockInterval', srcInterval); } }; // Initialize settings from config when component loads (only once) if (config && !hasInitialized.current) { resetToGlobalConfig(); hasInitialized.current = true; } // Sync from Redux when config changes (e.g., after reset), unless user has local edits useEffect(() => { if (config && !hasChanges) { resetToGlobalConfig(); } }, [config]); // eslint-disable-line react-hooks/exhaustive-deps const handleWopiEnabledChange = enabled => { setLocalWopiEnabled(enabled); // If WOPI is disabled, uncheck rotate keys if (!enabled) { setLocalRotateKeys(false); } setHasChanges(enabled !== configWopiEnabled || localRotateKeys || localRefreshLockInterval !== configRefreshLockInterval); // Validate the boolean field validateField('wopi.enable', enabled); }; const handleRotateKeysChange = checked => { setLocalRotateKeys(checked); setHasChanges(localWopiEnabled !== configWopiEnabled || checked || localRefreshLockInterval !== configRefreshLockInterval); }; const handleRefreshLockIntervalChange = value => { setLocalRefreshLockInterval(value); setHasChanges(localWopiEnabled !== configWopiEnabled || localRotateKeys || value !== configRefreshLockInterval); validateField('wopi.refreshLockInterval', value); }; const handleSave = async () => { if (!hasChanges) return; try { const enableChanged = localWopiEnabled !== configWopiEnabled; const rotateRequested = localRotateKeys; const refreshLockIntervalChanged = localRefreshLockInterval !== configRefreshLockInterval; // Build config update object const configUpdates = {}; if (enableChanged) { configUpdates['wopi.enable'] = localWopiEnabled; } if (refreshLockIntervalChanged) { configUpdates['wopi.refreshLockInterval'] = localRefreshLockInterval; } // If only rotate requested, just rotate keys if (!enableChanged && !refreshLockIntervalChanged && rotateRequested) { await dispatch(rotateWopiKeysAction()).unwrap(); } // If config changes (enable or refreshLockInterval) but no rotate else if ((enableChanged || refreshLockIntervalChanged) && !rotateRequested) { const updatedConfig = mergeNestedObjects([configUpdates]); await dispatch(saveConfig(updatedConfig)).unwrap(); } // If both config changes and rotate requested, make two requests else if ((enableChanged || refreshLockIntervalChanged) && rotateRequested) { // First update the config settings const updatedConfig = mergeNestedObjects([configUpdates]); await dispatch(saveConfig(updatedConfig)).unwrap(); // Then rotate keys await dispatch(rotateWopiKeysAction()).unwrap(); } setHasChanges(false); setLocalRotateKeys(false); } catch (error) { console.error('Failed to save WOPI settings:', error); // Revert local state on error setLocalWopiEnabled(configWopiEnabled); setLocalRotateKeys(false); setLocalRefreshLockInterval(configRefreshLockInterval); setHasChanges(false); } }; const handleReset = async () => { const confirmed = window.confirm('Reset WOPI settings to defaults?'); if (!confirmed) return; try { const merged = await dispatch(resetConfig(['wopi.enable', 'wopi.refreshLockInterval'])).unwrap(); resetToGlobalConfig(merged); setHasChanges(false); } catch (e) { console.error('Failed to reset WOPI settings:', e); alert('Failed to reset settings. Please try again.'); } }; return (
WOPI Settings Configure WOPI (Web Application Open Platform Interface) support for document editing
{localWopiEnabled && ( <>
Do not rotate keys more than once per 24 hours; storage may not refresh in time and authentication can fail.
)}
); } export default WOPISettings;