Skip to content

Commit

Permalink
feat: Add Cloud Config change history to roll back to previous values (
Browse files Browse the repository at this point in the history
  • Loading branch information
iMac7 authored May 19, 2024
1 parent 15d0dfb commit a784129
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 5 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [Deploying Parse Dashboard](#deploying-parse-dashboard)
- [Preparing for Deployment](#preparing-for-deployment)
- [Security Considerations](#security-considerations)
- [Security Checks](#security-checks)
- [Configuring Basic Authentication](#configuring-basic-authentication)
- [Multi-Factor Authentication (One-Time Password)](#multi-factor-authentication-one-time-password)
- [Separating App Access Based on User Identity](#separating-app-access-based-on-user-identity)
Expand Down Expand Up @@ -123,6 +124,7 @@ Parse Dashboard is continuously tested with the most recent releases of Node.js
| `apps.scripts.cloudCodeFunction` | String | no | - | `'deleteUser'` | The name of the Parse Cloud Function to execute. |
| `apps.scripts.showConfirmationDialog` | Bool | yes | `false` | `true` | Is `true` if a confirmation dialog should be displayed before the script is executed, `false` if the script should be executed immediately. |
| `apps.scripts.confirmationDialogStyle` | String | yes | `info` | `critical` | The style of the confirmation dialog. Valid values: `info` (blue style), `critical` (red style). |
| `apps.cloudConfigHistoryLimit` | Integer | yes | `100` | `100` | The number of historic values that should be saved in the Cloud Config change history. Valid values: `0`...`Number.MAX_SAFE_INTEGER`. |

### File

Expand Down Expand Up @@ -539,7 +541,7 @@ var dashboard = new ParseDashboard({
});
```

## Security Checks
### Security Checks

You can view the security status of your Parse Server by enabling the dashboard option `enableSecurityChecks`, and visiting App Settings > Security.

Expand All @@ -557,8 +559,6 @@ const dashboard = new ParseDashboard({
});
```



### Configuring Basic Authentication
You can configure your dashboard for Basic Authentication by adding usernames and passwords your `parse-dashboard-config.json` configuration file:

Expand Down
39 changes: 38 additions & 1 deletion src/dashboard/Data/Config/Config.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import TableHeader from 'components/Table/TableHeader.react';
import TableView from 'dashboard/TableView.react';
import Toolbar from 'components/Toolbar/Toolbar.react';
import browserStyles from 'dashboard/Data/Browser/Browser.scss';
import { CurrentApp } from 'context/currentApp';

@subscribeTo('Config', 'config')
class Config extends TableView {
static contextType = CurrentApp;
constructor() {
super();
this.section = 'Core';
Expand Down Expand Up @@ -242,7 +244,7 @@ class Config extends TableView {
return data;
}

saveParam({ name, value, masterKeyOnly }) {
saveParam({ name, value, type, masterKeyOnly }) {
this.props.config
.dispatch(ActionTypes.SET, {
param: name,
Expand All @@ -252,6 +254,32 @@ class Config extends TableView {
.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
Expand All @@ -263,6 +291,15 @@ class Config extends TableView {
this.props.config.dispatch(ActionTypes.DELETE, { param: name }).then(() => {
this.setState({ showDeleteParameterDialog: false });
});
const configHistory = localStorage.getItem('configHistory') && JSON.parse(localStorage.getItem('configHistory'));
if(configHistory) {
delete configHistory[name];
if(Object.keys(configHistory).length === 0) {
localStorage.removeItem('configHistory');
} else {
localStorage.setItem('configHistory', JSON.stringify(configHistory));
}
}
}

createParameter() {
Expand Down
51 changes: 51 additions & 0 deletions src/dashboard/Data/Config/ConfigDialog.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import Toggle from 'components/Toggle/Toggle.react';
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 { CurrentApp } from 'context/currentApp';

const PARAM_TYPES = ['Boolean', 'String', 'Number', 'Date', 'Object', 'Array', 'GeoPoint', 'File'];

Expand Down Expand Up @@ -90,20 +92,23 @@ const GET_VALUE = {
};

export default class ConfigDialog extends React.Component {
static contextType = CurrentApp;
constructor(props) {
super();
this.state = {
value: null,
type: 'String',
name: '',
masterKeyOnly: false,
selectedIndex: null,
};
if (props.param.length > 0) {
this.state = {
name: props.param,
type: props.type,
value: props.value,
masterKeyOnly: props.masterKeyOnly,
selectedIndex: 0,
};
}
}
Expand Down Expand Up @@ -169,6 +174,7 @@ export default class ConfigDialog extends React.Component {
submit() {
this.props.onConfirm({
name: this.state.name,
type: this.state.type,
value: GET_VALUE[this.state.type](this.state.value),
masterKeyOnly: this.state.masterKeyOnly,
});
Expand All @@ -190,6 +196,28 @@ export default class ConfigDialog extends React.Component {
))}
</Dropdown>
);
const configHistory = localStorage.getItem(`${this.context.applicationId}_configHistory`) && JSON.parse(localStorage.getItem(`${this.context.applicationId}_configHistory`))[this.state.name];
const handleIndexChange = index => {
if(this.state.type === 'Date'){
return;
}
let value = configHistory[index].value;
if(this.state.type === 'File'){
const fileJSON = {
__type: 'File',
name: value.name,
url: value.url
};
const file = Parse.File.fromJSON(fileJSON);
this.setState({ selectedIndex: index, value: file });
return;
}
if(typeof value === 'object'){
value = JSON.stringify(value);
}
this.setState({ selectedIndex: index, value });
};

return (
<Modal
type={Modal.Types.INFO}
Expand Down Expand Up @@ -253,6 +281,29 @@ export default class ConfigDialog extends React.Component {
/>
) : null
}
{
configHistory?.length > 0 &&
<Field
label={
<Label
text="Change History"
description="Select a timestamp in the change history to preview the value in the 'Value' field before saving."
/>
}
input={
<Dropdown
value={this.state.selectedIndex}
onChange={handleIndexChange}>
{configHistory.map((value, i) =>
<Option key={i} value={i}>
{dateStringUTC(new Date(value.time))}
</Option>
)}
</Dropdown>
}
className={styles.addColumnToggleWrapper}
/>
}
</Modal>
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/lib/ParseApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export default class ParseApp {
columnPreference,
scripts,
classPreference,
enableSecurityChecks
enableSecurityChecks,
cloudConfigHistoryLimit
}) {
this.name = appName;
this.createdAt = created_at ? new Date(created_at) : new Date();
Expand Down Expand Up @@ -77,6 +78,7 @@ export default class ParseApp {
this.columnPreference = columnPreference;
this.scripts = scripts;
this.enableSecurityChecks = !!enableSecurityChecks;
this.cloudConfigHistoryLimit = cloudConfigHistoryLimit;

if (!supportedPushLocales) {
console.warn(
Expand Down

0 comments on commit a784129

Please sign in to comment.