Skip to content
4 changes: 4 additions & 0 deletions src/dashboard/Data/Browser/Browser.scss
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,8 @@ body:global(.expanded) {

.noScroll {
overflow-x: hidden;
}

.confirmConfig {
padding: 10px 20px;
}
160 changes: 112 additions & 48 deletions src/dashboard/Data/Config/Config.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import Toolbar from 'components/Toolbar/Toolbar.react';
import browserStyles from 'dashboard/Data/Browser/Browser.scss';
import { CurrentApp } from 'context/currentApp';
import Modal from 'components/Modal/Modal.react';

@subscribeTo('Config', 'config')
class Config extends TableView {
Expand All @@ -38,6 +39,7 @@
modalValue: '',
modalMasterKeyOnly: false,
loading: false,
confirmModalOpen: false,
};
}

Expand All @@ -58,6 +60,7 @@
loadData() {
this.setState({ loading: true });
this.props.config.dispatch(ActionTypes.FETCH).finally(() => {
this.cacheData = new Map(this.props.config.data);
this.setState({ loading: false });
});
}
Expand Down Expand Up @@ -90,6 +93,7 @@
value={this.state.modalValue}
masterKeyOnly={this.state.modalMasterKeyOnly}
parseServerVersion={this.context.serverInfo?.parseServerVersion}
loading={this.state.loading}
/>
);
} else if (this.state.showDeleteParameterDialog) {
Expand All @@ -101,6 +105,30 @@
/>
);
}

if (this.state.confirmModalOpen) {
extras = (
<Modal
type={Modal.Types.INFO}
icon="warn-outline"
title={'Are you sure?'}
confirmText="Continue"
cancelText="Cancel"
onCancel={() => this.setState({ confirmModalOpen: false })}
onConfirm={() => {
this.setState({ confirmModalOpen: false });
this.saveParam({
...this.confirmData,
override: true,
});
}}
>
<div className={[browserStyles.confirmConfig]}>
The parameter you are trying to save has been modified while you were editing it. This means your edit is not based on the current parameter value. Do you want to continue and overwrite the other changes?
</div>
</Modal>
);
}
return extras;
}

Expand Down Expand Up @@ -244,58 +272,94 @@
return data;
}

saveParam({ name, value, type, masterKeyOnly }) {
this.props.config
.dispatch(ActionTypes.SET, {
async saveParam({ name, value, type, masterKeyOnly, override }) {
try {
this.setState({ loading: true });

Check failure on line 278 in src/dashboard/Data/Config/Config.react.js

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed
const fetchedParams = this.props.config.data.get('params');
const currentValue = fetchedParams.get(name);
await this.props.config.dispatch(ActionTypes.FETCH);
const fetchedParamsAfter = this.props.config.data.get('params');
const currentValueAfter = fetchedParamsAfter.get(name);

if (currentValue !== currentValueAfter && !override) {
this.setState({
confirmModalOpen: true,
modalOpen: false,
loading: false,
});
this.confirmData = {
name,
value,
type,
masterKeyOnly,
};
return;
}

await this.props.config.dispatch(ActionTypes.SET, {
param: name,
value: value,
masterKeyOnly: masterKeyOnly,
})
.then(
() => {
this.setState({ modalOpen: false });
const limit = this.context.cloudConfigHistoryLimit;
const applicationId = this.context.applicationId;
let transformedValue = value;
if (type === 'Date') {
transformedValue = { __type: 'Date', iso: value };
}
if (type === 'File') {
transformedValue = { name: value._name, url: value._url };
}
const configHistory = localStorage.getItem(`${applicationId}_configHistory`);
if (!configHistory) {
localStorage.setItem(
`${applicationId}_configHistory`,
JSON.stringify({
[name]: [
{
time: new Date(),
value: transformedValue,
},
],
})
);
} else {
const oldConfigHistory = JSON.parse(configHistory);
localStorage.setItem(
`${applicationId}_configHistory`,
JSON.stringify({
...oldConfigHistory,
[name]: !oldConfigHistory[name]
? [{ time: new Date(), value: transformedValue }]
: [
{ time: new Date(), value: transformedValue },
...oldConfigHistory[name],
].slice(0, limit || 100),
})
);
}
},
() => {
// Catch the error
}
});

// Update the cached data after successful save
const params = this.cacheData.get('params');
params.set(name, value);
if (masterKeyOnly) {
const masterKeyOnlyParams = this.cacheData.get('masterKeyOnly') || new Map();
masterKeyOnlyParams.set(name, masterKeyOnly);
this.cacheData.set('masterKeyOnly', masterKeyOnlyParams);
}

this.setState({ modalOpen: false });

Check failure on line 316 in src/dashboard/Data/Config/Config.react.js

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed
// Update config history in localStorage
const limit = this.context.cloudConfigHistoryLimit;
const applicationId = this.context.applicationId;
let transformedValue = value;

Check failure on line 321 in src/dashboard/Data/Config/Config.react.js

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed
if (type === 'Date') {
transformedValue = { __type: 'Date', iso: value };
}
if (type === 'File') {
transformedValue = { name: value._name, url: value._url };
}

const configHistory = localStorage.getItem(`${applicationId}_configHistory`);
const newHistoryEntry = {
time: new Date(),
value: transformedValue,
};

if (!configHistory) {
localStorage.setItem(
`${applicationId}_configHistory`,
JSON.stringify({
[name]: [newHistoryEntry],
})
);
} else {
const oldConfigHistory = JSON.parse(configHistory);
const updatedHistory = !oldConfigHistory[name]
? [newHistoryEntry]
: [newHistoryEntry, ...oldConfigHistory[name]].slice(0, limit || 100);

localStorage.setItem(
`${applicationId}_configHistory`,
JSON.stringify({
...oldConfigHistory,
[name]: updatedHistory,
})
);
}
} catch (error) {
this.context.showError?.(
`Failed to save parameter: ${error.message || 'Unknown error occurred'}`
);
} finally {
this.setState({ loading: false });
}
}

deleteParam(name) {
Expand Down
35 changes: 22 additions & 13 deletions src/dashboard/Data/Config/ConfigDialog.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import validateNumeric from 'lib/validateNumeric';
import styles from 'dashboard/Data/Browser/Browser.scss';
import semver from 'semver/preload.js';
import { dateStringUTC } from 'lib/DateUtils';
import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
import { CurrentApp } from 'context/currentApp';

const PARAM_TYPES = ['Boolean', 'String', 'Number', 'Date', 'Object', 'Array', 'GeoPoint', 'File'];
Expand Down Expand Up @@ -222,19 +223,8 @@ export default class ConfigDialog extends React.Component {
this.setState({ selectedIndex: index, value });
};

return (
<Modal
type={Modal.Types.INFO}
title={newParam ? 'New parameter' : 'Edit parameter'}
icon="gear-solid"
iconSize={30}
subtitle={'Dynamically configure parts of your app'}
disabled={!this.valid()}
confirmText={newParam ? 'Create' : 'Save'}
cancelText="Cancel"
onCancel={this.props.onCancel}
onConfirm={this.submit.bind(this)}
>
const dialogContent = (
<div>
<Field
label={<Label text="Parameter Name" description="A unique identifier for this value" />}
input={
Expand Down Expand Up @@ -305,6 +295,25 @@ export default class ConfigDialog extends React.Component {
className={styles.addColumnToggleWrapper}
/>
)}
</div>
);

return (
<Modal
type={Modal.Types.INFO}
title={newParam ? 'New parameter' : 'Edit parameter'}
icon="gear-solid"
iconSize={30}
subtitle={'Dynamically configure parts of your app'}
disabled={!this.valid() || this.props.loading}
confirmText={newParam ? 'Create' : 'Save'}
cancelText="Cancel"
onCancel={this.props.onCancel}
onConfirm={this.submit.bind(this)}
>
<LoaderContainer loading={this.props.loading}>
{dialogContent}
</LoaderContainer>
</Modal>
);
}
Expand Down
Loading