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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [View Table](#view-table)
- [Pointer](#pointer)
- [Link](#link)
- [Image](#image)
- [Contributing](#contributing)

# Getting Started
Expand Down Expand Up @@ -1362,6 +1363,25 @@ In the example above, the query string will be escaped and added to the url, res
> [!Note]
> For security reasons, the link `<a>` tag contains the `rel="noreferrer"` attribute, which prevents the target website to know the referring website which in this case is the Parse Dashboard URL. That attribute is widely supported across modern browsers, but if in doubt check your browser's compatibility.

#### Image

Images are rendered directly in the output table with an `<img>` tag. The content mode is always "scale to fit", meaning that if the image file is 100x50px and the specified dimensions are 50x50px, it would display as 50x25px, since it's scaled maintaining aspect ratio.

Example:

```json
{
"__type": "Image",
"url": "https://example.com/image.png",
"width": "50",
"height": "50",
"alt": "Image"
}
```

> [!Warning]
> The URL will be directly invoked by the browser when trying to display the image. For security reasons, make sure you either control the full URL, including the image file name, or sanitize the URL before returning it to the dashboard. URLs containing `javascript:` or `<script` will be blocked automatically and replaced with a placeholder.

# Contributing

We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Dashboard guide](CONTRIBUTING.md).
Expand Down
39 changes: 39 additions & 0 deletions src/dashboard/Data/Views/Views.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ class Views extends TableView {
type = 'GeoPoint';
} else if (val.__type === 'Link') {
type = 'Link';
} else if (val.__type === 'Image') {
type = 'Image';
} else {
type = 'Object';
}
Expand Down Expand Up @@ -379,6 +381,8 @@ class Views extends TableView {
type = 'GeoPoint';
} else if (value.__type === 'Link') {
type = 'Link';
} else if (value.__type === 'Image') {
type = 'Image';
} else {
type = 'Object';
}
Expand Down Expand Up @@ -428,6 +432,41 @@ class Views extends TableView {
{text}
</a>
);
} else if (type === 'Image') {
// Sanitize URL
let url = value.url;
if (
!url ||
url.match(/javascript/i) ||
url.match(/<script/i)
) {
url = '#';
}

// Parse dimensions, ensuring they are positive numbers
const width = value.width && parseInt(value.width, 10) > 0 ? parseInt(value.width, 10) : null;
const height = value.height && parseInt(value.height, 10) > 0 ? parseInt(value.height, 10) : null;

// Create style object for scale-to-fit behavior
const imgStyle = {
maxWidth: width ? `${width}px` : '100%',
maxHeight: height ? `${height}px` : '100%',
objectFit: 'contain', // This ensures scale-to-fit behavior maintaining aspect ratio
display: 'block'
};

content = (
<img
src={url}
alt={value.alt || 'Image'}
style={imgStyle}
onError={(e) => {
if (e.target && e.target.style) {
e.target.style.display = 'none';
}
}}
/>
);
} else if (value === undefined) {
content = '';
} else {
Expand Down
Loading