[feature] Reset config settings

This commit is contained in:
PauI Ostrovckij
2025-11-19 11:32:48 +03:00
parent 8c0c1ef66d
commit d897080a4d
5 changed files with 205 additions and 44 deletions

View File

@ -1,6 +1,6 @@
import {useState, useRef} from 'react';
import {useState, useRef, useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {saveConfig, selectConfig} from '../../store/slices/configSlice';
import {saveConfig, resetConfig, selectConfig} from '../../store/slices/configSlice';
import {getNestedValue} from '../../utils/getNestedValue';
import {mergeNestedObjects} from '../../utils/mergeNestedObjects';
import {useFieldValidation} from '../../hooks/useFieldValidation';
@ -8,7 +8,7 @@ import PageHeader from '../../components/PageHeader/PageHeader';
import PageDescription from '../../components/PageDescription/PageDescription';
import Tabs from '../../components/Tabs/Tabs';
import Input from '../../components/Input/Input';
import FixedSaveButton from '../../components/FixedSaveButton/FixedSaveButton';
import FixedSaveButtonGroup from '../../components/FixedSaveButtonGroup/FixedSaveButtonGroup';
import styles from './Expiration.module.scss';
const expirationTabs = [
@ -46,11 +46,12 @@ function Expiration() {
};
// Reset state and errors to global config
const resetToGlobalConfig = () => {
if (config) {
const resetToGlobalConfig = source => {
const src = source || config;
if (src) {
const settings = {};
Object.keys(CONFIG_PATHS).forEach(key => {
const value = getNestedValue(config, CONFIG_PATHS[key], '');
const value = getNestedValue(src, CONFIG_PATHS[key], '');
settings[key] = value;
});
setLocalSettings(settings);
@ -74,6 +75,12 @@ function Expiration() {
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
// Handle field changes
const handleFieldChange = (field, value) => {
setLocalSettings(prev => ({
@ -129,6 +136,24 @@ function Expiration() {
setHasChanges(false);
};
// Handle reset for active tab
const handleReset = async () => {
const confirmed = window.confirm('Reset settings on this tab to defaults?');
if (!confirmed) return;
try {
const paths =
activeTab === 'garbage-collection'
? [CONFIG_PATHS.filesCron, CONFIG_PATHS.documentsCron, CONFIG_PATHS.files, CONFIG_PATHS.filesremovedatonce]
: [CONFIG_PATHS.sessionidle, CONFIG_PATHS.sessionabsolute];
const merged = await dispatch(resetConfig(paths)).unwrap();
resetToGlobalConfig(merged);
setHasChanges(false);
} catch (e) {
console.error('Failed to reset expiration settings:', e);
alert('Failed to reset settings. Please try again.');
}
};
// Render tab content
const renderTabContent = () => {
switch (activeTab) {
@ -224,9 +249,20 @@ function Expiration() {
{renderTabContent()}
</Tabs>
<FixedSaveButton onClick={handleSave} disabled={!hasChanges || hasValidationErrors()}>
Save Changes
</FixedSaveButton>
<FixedSaveButtonGroup
buttons={[
{
text: 'Save Changes',
onClick: handleSave,
disabled: !hasChanges || hasValidationErrors()
},
{
text: 'Reset to Defaults',
onClick: handleReset,
disabled: false
}
]}
/>
</div>
);
}

View File

@ -1,13 +1,13 @@
import {useState, useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {saveConfig, selectConfig} from '../../store/slices/configSlice';
import {saveConfig, resetConfig, selectConfig} from '../../store/slices/configSlice';
import {getNestedValue} from '../../utils/getNestedValue';
import {mergeNestedObjects} from '../../utils/mergeNestedObjects';
import {useFieldValidation} from '../../hooks/useFieldValidation';
import PageHeader from '../../components/PageHeader/PageHeader';
import PageDescription from '../../components/PageDescription/PageDescription';
import Input from '../../components/Input/Input';
import FixedSaveButton from '../../components/FixedSaveButton/FixedSaveButton';
import FixedSaveButtonGroup from '../../components/FixedSaveButtonGroup/FixedSaveButtonGroup';
import styles from './FileLimits.module.scss';
function FileLimits() {
@ -163,6 +163,20 @@ function FileLimits() {
setHasChanges(false);
};
// Handle reset to defaults
const handleReset = async () => {
const confirmed = window.confirm('Reset file limits to defaults? This cannot be undone.');
if (!confirmed) return;
try {
await dispatch(resetConfig(['FileConverter.converter.maxDownloadBytes', 'FileConverter.converter.inputLimits'])).unwrap();
setHasChanges(false);
} catch (error) {
console.error('Error resetting file limits:', error);
alert('Failed to reset file limits. Please try again.');
}
};
return (
<div className={`${styles.fileLimits} ${styles.pageWithFixedSave}`}>
<PageHeader>File Size Limits</PageHeader>
@ -230,9 +244,20 @@ function FileLimits() {
</div>
</div>
<FixedSaveButton onClick={handleSave} disabled={!hasChanges || hasValidationErrors()}>
Save Changes
</FixedSaveButton>
<FixedSaveButtonGroup
buttons={[
{
text: 'Save Changes',
onClick: handleSave,
disabled: !hasChanges || hasValidationErrors()
},
{
text: 'Reset to Defaults',
onClick: handleReset,
disabled: false
}
]}
/>
</div>
);
}

View File

@ -1,11 +1,11 @@
import {useState, useRef} from 'react';
import {useState, useRef, useEffect} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {saveConfig, selectConfig} from '../../store/slices/configSlice';
import {saveConfig, resetConfig, selectConfig} from '../../store/slices/configSlice';
import {getNestedValue} from '../../utils/getNestedValue';
import {mergeNestedObjects} from '../../utils/mergeNestedObjects';
import {useFieldValidation} from '../../hooks/useFieldValidation';
import Checkbox from '../../components/Checkbox/Checkbox';
import FixedSaveButton from '../../components/FixedSaveButton/FixedSaveButton';
import FixedSaveButtonGroup from '../../components/FixedSaveButtonGroup/FixedSaveButtonGroup';
import PageHeader from '../../components/PageHeader/PageHeader';
import PageDescription from '../../components/PageDescription/PageDescription';
import styles from './RequestFiltering.module.scss';
@ -28,12 +28,13 @@ function RequestFiltering() {
};
const hasInitialized = useRef(false);
const resetToGlobalConfig = () => {
if (config) {
const resetToGlobalConfig = source => {
const src = source || config;
if (src) {
const newSettings = {};
Object.keys(CONFIG_PATHS).forEach(key => {
const path = CONFIG_PATHS[key];
newSettings[key] = getNestedValue(config, path, false);
newSettings[key] = getNestedValue(src, path, false);
});
setLocalSettings(newSettings);
}
@ -44,6 +45,12 @@ function RequestFiltering() {
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
// Handle field changes
const handleFieldChange = (field, value) => {
setLocalSettings(prev => ({
@ -82,6 +89,19 @@ function RequestFiltering() {
setHasChanges(false);
};
const handleReset = async () => {
const confirmed = window.confirm('Reset request filtering settings to defaults?');
if (!confirmed) return;
try {
const merged = await dispatch(resetConfig(Object.values(CONFIG_PATHS))).unwrap();
resetToGlobalConfig(merged);
setHasChanges(false);
} catch (e) {
console.error('Failed to reset request filtering:', e);
alert('Failed to reset settings. Please try again.');
}
};
return (
<div className={`${styles.requestFiltering} ${styles.pageWithFixedSave}`}>
<PageHeader>Request Filtering</PageHeader>
@ -114,9 +134,20 @@ function RequestFiltering() {
</div>
</div>
<FixedSaveButton onClick={handleSave} disabled={!hasChanges || hasValidationErrors()}>
Save Changes
</FixedSaveButton>
<FixedSaveButtonGroup
buttons={[
{
text: 'Save Changes',
onClick: handleSave,
disabled: !hasChanges || hasValidationErrors()
},
{
text: 'Reset to Defaults',
onClick: handleReset,
disabled: false
}
]}
/>
</div>
);
}

View File

@ -1,6 +1,6 @@
import {useState, useRef} from 'react';
import {useState, useRef, useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {saveConfig, selectConfig} from '../../store/slices/configSlice';
import {saveConfig, resetConfig, selectConfig} from '../../store/slices/configSlice';
import {getNestedValue} from '../../utils/getNestedValue';
import {mergeNestedObjects} from '../../utils/mergeNestedObjects';
import {useFieldValidation} from '../../hooks/useFieldValidation';
@ -8,7 +8,7 @@ import PageHeader from '../../components/PageHeader/PageHeader';
import PageDescription from '../../components/PageDescription/PageDescription';
import Tabs from '../../components/Tabs/Tabs';
import AccessRules from '../../components/AccessRules/AccessRules';
import FixedSaveButton from '../../components/FixedSaveButton/FixedSaveButton';
import FixedSaveButtonGroup from '../../components/FixedSaveButtonGroup/FixedSaveButtonGroup';
import styles from './SecuritySettings.module.scss';
const securityTabs = [{key: 'ip-filtering', label: 'IP Filtering'}];
@ -23,9 +23,10 @@ function SecuritySettings() {
const [hasChanges, setHasChanges] = useState(false);
// Reset state and errors to global config
const resetToGlobalConfig = () => {
if (config) {
const ipFilterRules = getNestedValue(config, 'services.CoAuthoring.ipfilter.rules', []);
const resetToGlobalConfig = source => {
const src = source || config;
if (src) {
const ipFilterRules = getNestedValue(src, 'services.CoAuthoring.ipfilter.rules', []);
const uiRules = ipFilterRules.map(rule => ({
type: rule.allowed ? 'Allow' : 'Deny',
value: rule.address
@ -50,6 +51,12 @@ function SecuritySettings() {
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
// Handle rules changes
const handleRulesChange = newRules => {
setLocalRules(newRules);
@ -85,6 +92,23 @@ function SecuritySettings() {
setHasChanges(false);
};
const handleReset = async () => {
const confirmed = window.confirm('Reset security settings on this tab to defaults?');
if (!confirmed) return;
try {
// Only reset active tab settings
const paths = activeTab === 'ip-filtering' ? ['services.CoAuthoring.ipfilter.rules'] : [];
if (paths.length > 0) {
const merged = await dispatch(resetConfig(paths)).unwrap();
resetToGlobalConfig(merged);
}
setHasChanges(false);
} catch (e) {
console.error('Failed to reset security settings:', e);
alert('Failed to reset settings. Please try again.');
}
};
const renderTabContent = () => {
switch (activeTab) {
case 'ip-filtering':
@ -110,9 +134,20 @@ function SecuritySettings() {
{renderTabContent()}
</Tabs>
<FixedSaveButton onClick={handleSave} disabled={!hasChanges || hasValidationErrors()}>
Save Changes
</FixedSaveButton>
<FixedSaveButtonGroup
buttons={[
{
text: 'Save Changes',
onClick: handleSave,
disabled: !hasChanges || hasValidationErrors()
},
{
text: 'Reset to Defaults',
onClick: handleReset,
disabled: false
}
]}
/>
</div>
);
}

View File

@ -1,6 +1,6 @@
import {useState, useRef} from 'react';
import {useState, useRef, useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {saveConfig, selectConfig, rotateWopiKeysAction} from '../../store/slices/configSlice';
import {saveConfig, resetConfig, selectConfig, rotateWopiKeysAction} from '../../store/slices/configSlice';
import {getNestedValue} from '../../utils/getNestedValue';
import {mergeNestedObjects} from '../../utils/mergeNestedObjects';
import {useFieldValidation} from '../../hooks/useFieldValidation';
@ -10,7 +10,7 @@ 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 FixedSaveButton from '../../components/FixedSaveButton/FixedSaveButton';
import FixedSaveButtonGroup from '../../components/FixedSaveButtonGroup/FixedSaveButtonGroup';
import Note from '../../components/Note/Note';
import styles from './WOPISettings.module.scss';
@ -31,14 +31,17 @@ function WOPISettings() {
const wopiPublicKey = getNestedValue(config, 'wopi.publicKey', '');
const configRefreshLockInterval = getNestedValue(config, 'wopi.refreshLockInterval', '10m');
const resetToGlobalConfig = () => {
if (config) {
setLocalWopiEnabled(configWopiEnabled);
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(configRefreshLockInterval);
setLocalRefreshLockInterval(srcInterval);
setHasChanges(false);
validateField('wopi.enable', configWopiEnabled);
validateField('wopi.refreshLockInterval', configRefreshLockInterval);
validateField('wopi.enable', srcEnabled);
validateField('wopi.refreshLockInterval', srcInterval);
}
};
@ -48,6 +51,12 @@ function WOPISettings() {
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
@ -118,6 +127,20 @@ function WOPISettings() {
}
};
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);
setLocalRotateKeys(false);
} catch (e) {
console.error('Failed to reset WOPI settings:', e);
alert('Failed to reset settings. Please try again.');
}
};
return (
<div className={`${styles.wopiSettings} ${styles.pageWithFixedSave}`}>
<PageHeader>WOPI Settings</PageHeader>
@ -175,9 +198,20 @@ function WOPISettings() {
</>
)}
<FixedSaveButton onClick={handleSave} disabled={!hasChanges || hasValidationErrors()}>
Save Changes
</FixedSaveButton>
<FixedSaveButtonGroup
buttons={[
{
text: 'Save Changes',
onClick: handleSave,
disabled: !hasChanges || hasValidationErrors()
},
{
text: 'Reset to Defaults',
onClick: handleReset,
disabled: false
}
]}
/>
</div>
);
}