Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add optional restriction of script execution to certain object fields and values #2488

Merged
merged 12 commits into from
Jun 27, 2024
20 changes: 16 additions & 4 deletions Parse-Dashboard/parse-dashboard-config.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
{
"apps": [
{
"serverURL": "http://localhost:1337/parse",
dblythy marked this conversation as resolved.
Show resolved Hide resolved
"appId": "hello",
"masterKey": "world",
"serverURL": "http://localhost:8378/1",
"appId": "test",
"masterKey": "test",
"appName": "",
"iconName": "",
"primaryBackgroundColor": "",
"secondaryBackgroundColor": ""
"secondaryBackgroundColor": "",
"scripts": [
{
"title": "Delete account",
"classes": [
{
"name": "_User",
"fields": [{ "name": "createdAt", "validator": "(row, field) => row.get(field) > new Date(\"2025\")" }]
}
],
"cloudCodeFunction": "deleteAccount"
}
]
}
],
"iconsFolder": "icons"
Expand Down
69 changes: 47 additions & 22 deletions src/components/BrowserCell/BrowserCell.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,34 +334,59 @@ export default class BrowserCell extends Component {
});
}

const { className, objectId } = this.props;
const validScripts = (this.props.scripts || []).filter(script =>
script.classes?.includes(this.props.className)
);
const { className, objectId,field, scripts = [], rowValue } = this.props;
let validator = null;
const validScripts = (scripts || []).filter(script => {
if (script.classes?.includes(className)) {
return true;
}
for (const script of script?.classes || []) {
if (script?.name !== className) {
continue;
}
const fields = script?.fields || [];
if (script?.fields.includes(field) || script?.fields.includes('*')) {
return true;
}
for (const currentField of fields) {
if (Object.prototype.toString.call(currentField) === '[object Object]') {
if (currentField.name === field) {
if (typeof currentField.validator === 'string') {
validator = eval(currentField.validator);
} else {
validator = currentField.validator;
}
return true;
}
}
}
}
});
if (validScripts.length) {
onEditSelectedRow &&
contextMenuOptions.push({
text: 'Scripts',
items: validScripts.map(script => {
return {
text: script.title,
callback: () => {
this.selectedScript = { ...script, className, objectId };
if (script.showConfirmationDialog) {
this.toggleConfirmationDialog();
} else {
this.executeSript(script);
}
},
};
}),
});
onEditSelectedRow && contextMenuOptions.push({
text: 'Scripts',
items: validScripts.map(script => {
return {
text: script.title,
disabled: validator?.(rowValue, field) === false,
callback: () => {
this.selectedScript = { ...script, className, objectId };
if (script.showConfirmationDialog) {
this.toggleConfirmationDialog();
}
else {
this.executeScript(script);
}
}
}
})
});
}

return contextMenuOptions;
}

async executeSript(script) {
async executeScript(script) {
try {
const object = Parse.Object.extend(this.props.className).createWithoutData(
this.props.objectId
Expand Down
25 changes: 2 additions & 23 deletions src/components/BrowserRow/BrowserRow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,7 @@ export default class BrowserRow extends Component {
}

render() {
const {
className,
columns,
currentCol,
isUnique,
obj,
onPointerClick,
onPointerCmdClick,
order,
readOnlyFields,
row,
rowWidth,
selection,
selectRow,
setCopyableValue,
setCurrent,
setEditing,
setRelation,
onEditSelectedRow,
setContextMenu,
onFilterChange,
markRequiredFieldRow,
} = this.props;
const { className, columns, currentCol, isUnique, obj, onPointerClick, onPointerCmdClick, order, readOnlyFields, row, rowValue, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation, onEditSelectedRow, setContextMenu, onFilterChange, markRequiredFieldRow } = this.props;
dblythy marked this conversation as resolved.
Show resolved Hide resolved
const attributes = obj.attributes;
let requiredCols = [];
Object.entries(columns).reduce((acc, cur) => {
Expand Down Expand Up @@ -114,6 +92,7 @@ export default class BrowserRow extends Component {
className={className}
field={name}
row={row}
rowValue={rowValue}
col={j}
type={type}
readonly={isUnique || readOnlyFields.indexOf(name) > -1}
Expand Down
4 changes: 4 additions & 0 deletions src/components/ContextMenu/ContextMenu.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ const MenuSection = ({ level, items, path, setPath, hide }) => {
<li
key={`menu-section-${level}-${index}`}
className={styles.option}
style={item.disabled ? {'opacity': 0.5, cursor: 'not-allowed'} : {}}
onClick={() => {
if (item.disabled === true) {
return;
}
item.callback && item.callback();
hide();
}}
Expand Down
69 changes: 34 additions & 35 deletions src/dashboard/Data/Browser/BrowserTable.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export default class BrowserTable extends React.Component {
order={this.props.order}
readOnlyFields={READ_ONLY}
row={index}
rowValue={this.props.data[index]}
rowWidth={rowWidth}
selection={this.props.selection}
selectRow={this.props.selectRow}
Expand Down Expand Up @@ -279,41 +280,39 @@ export default class BrowserTable extends React.Component {

// Needed in order to force BrowserRow to update and re-render (and possibly update columns values),
// since the "obj" instance will only be updated when the update request is done.
const isEditingRow =
this.props.current && this.props.current.row === i && !!this.props.editing;
rows[index] = (
<BrowserRow
appId={this.props.appId}
key={index}
isEditing={isEditingRow}
className={this.props.className}
columns={this.props.columns}
schema={this.props.schema}
simplifiedSchema={this.props.simplifiedSchema}
filters={this.props.filters}
currentCol={currentCol}
isUnique={this.props.isUnique}
obj={obj}
onPointerClick={this.props.onPointerClick}
onPointerCmdClick={this.props.onPointerCmdClick}
onFilterChange={this.props.onFilterChange}
order={this.props.order}
readOnlyFields={READ_ONLY}
row={i}
rowWidth={rowWidth}
selection={this.props.selection}
selectRow={this.props.selectRow}
setCurrent={this.props.setCurrent}
setEditing={this.props.setEditing}
setRelation={this.props.setRelation}
setCopyableValue={this.props.setCopyableValue}
setContextMenu={this.props.setContextMenu}
onEditSelectedRow={this.props.onEditSelectedRow}
showNote={this.props.showNote}
onRefresh={this.props.onRefresh}
scripts={this.context.scripts}
/>
);
const isEditingRow = this.props.current && this.props.current.row === i && !!this.props.editing;
rows[index] = <BrowserRow
appId={this.props.appId}
key={index}
isEditing={isEditingRow}
className={this.props.className}
columns={this.props.columns}
schema={this.props.schema}
simplifiedSchema={this.props.simplifiedSchema}
filters={this.props.filters}
currentCol={currentCol}
isUnique={this.props.isUnique}
obj={obj}
onPointerClick={this.props.onPointerClick}
onPointerCmdClick={this.props.onPointerCmdClick}
onFilterChange={this.props.onFilterChange}
order={this.props.order}
readOnlyFields={READ_ONLY}
row={i}
rowValue={this.props.data[i]}
rowWidth={rowWidth}
selection={this.props.selection}
selectRow={this.props.selectRow}
setCurrent={this.props.setCurrent}
setEditing={this.props.setEditing}
setRelation={this.props.setRelation}
setCopyableValue={this.props.setCopyableValue}
setContextMenu={this.props.setContextMenu}
onEditSelectedRow={this.props.onEditSelectedRow}
showNote={this.props.showNote}
onRefresh={this.props.onRefresh}
scripts={this.context.scripts}
/>
}

if (this.props.editing) {
Expand Down