Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [Limitations](#limitations)
- [CSV Export](#csv-export)
- [Views](#views)
- [View Table](#view-table)
- [Pointer](#pointer)
- [Link](#link)
- [Contributing](#contributing)

# Getting Started
Expand Down Expand Up @@ -1253,12 +1256,76 @@ This feature allows you to change how a pointer is represented in the browser. B
This feature will take either selected rows or all rows of an individual class and saves them to a CSV file, which is then downloaded. CSV headers are added to the top of the file matching the column names.

> ⚠️ There is currently a 10,000 row limit when exporting all data. If more than 10,000 rows are present in the class, the CSV file will only contain 10,000 rows.

## Views

▶️ *Core > Views*

Views are saved queries that display aggregated data from your classes. Create a view by providing a name, selecting a class and defining an aggregation pipeline. Optionally enable the object counter to show how many items match the view. Saved views appear in the sidebar, where you can select, edit, or delete them.

> [!Caution]
> Values are generally rendered without sanitization in the resulting data table. If rendered values come from user input or untrusted data, make sure to remove potentially dangerous HTML or JavaScript, to prevent an attacker from injecting malicious code, to exploit vulnerabilities like Cross-Site Scripting (XSS).

### View Table

When designing the aggregation pipeline, consider that some values are rendered specially in the output table.

#### Pointer

Parse Object pointers are automatically displayed as links to the target object.

Example:

```json
{ "__type": "Pointer", "className": "_User", "objectId": "xWMyZ4YEGZ" }
```

#### Link

Links are rendered as hyperlinks that open in a new browser tab.

Example:

```json
{
"__type": "Link",
"url": "https://example.com",
"text": "Link"
}
```

Set `isRelativeUrl: true` when linking to another dashboard page, in which case the base URL for the relative URL will be `<PROTOCOL>://<HOST>/<MOUNT_PATH>/apps/<APP_NAME>/`. The key `isRelativeUrl` is optional and `false` by default.

Example:

```json
{
"__type": "Link",
"url": "browser/_Installation",
"isRelativeUrl": true,
"text": "Link"
}
```

A query part of the URL can be easily added using the `urlQuery` key which will automatically escape the query string.

Example:

```json
{
"__type": "Link",
"url": "browser/_Installation",
"urlQuery": "filters=[{\"field\":\"objectId\",\"constraint\":\"eq\",\"compareTo\":\"xWMyZ4YEGZ\",\"class\":\"_Installation\"}]",
"isRelativeUrl": true,
"text": "Link"
}
```

In the example above, the query string will be escaped and added to the url, resulting in the complete URL:

```js
"browser/_Installation?filters=%5B%7B%22field%22%3A%22objectId%22%2C%22constraint%22%3A%22eq%22%2C%22compareTo%22%3A%22xWMyZ4YEGZ%22%2C%22class%22%3A%22_Installation%22%7D%5D"
```

# Contributing

Expand Down
40 changes: 39 additions & 1 deletion src/dashboard/Data/Views/Views.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,13 @@ class Views extends TableView {
if (text === undefined) {
text = '';
} else if (text && typeof text === 'object') {
text = text.__type === 'Date' && text.iso ? text.iso : JSON.stringify(text);
if (text.__type === 'Date' && text.iso) {
text = text.iso;
} else if (text.__type === 'Link' && text.text) {
text = text.text;
} else {
text = JSON.stringify(text);
}
}
text = String(text);
if (typeof document !== 'undefined') {
Expand Down Expand Up @@ -166,6 +172,8 @@ class Views extends TableView {
type = 'File';
} else if (val.__type === 'GeoPoint') {
type = 'GeoPoint';
} else if (val.__type === 'Link') {
type = 'Link';
} else {
type = 'Object';
}
Expand Down Expand Up @@ -285,6 +293,8 @@ class Views extends TableView {
type = 'File';
} else if (value.__type === 'GeoPoint') {
type = 'GeoPoint';
} else if (value.__type === 'Link') {
type = 'Link';
} else {
type = 'Object';
}
Expand All @@ -306,6 +316,34 @@ class Views extends TableView {
content = JSON.stringify(value);
} else if (type === 'Date') {
content = value && value.iso ? value.iso : String(value);
} else if (type === 'Link') {
// Sanitize URL
let url = value.url;
if (
url.match(/javascript/i) ||
url.match(/<script/i)
) {
url = '#';
} else {
url = value.isRelativeUrl
? `apps/${this.context.slug}/${url}${value.query ? `?${new URLSearchParams(value.urlQuery).toString()}` : ''}`
: url;
}
// Sanitize text
let text = value.text;
if (
text.match(/javascript/i) ||
text.match(/<script/i) ||
!text ||
text.trim() === ''
) {
text = 'Link';
}
content = (
<a href={url} target="_blank" rel="noreferrer">
{text}
</a>
);
} else if (value === undefined) {
content = '';
} else {
Expand Down
Loading