Skip to content
1 change: 1 addition & 0 deletions Parse-Dashboard/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ module.exports = function(config, options) {
<base href="${mountPath}"/>
<script>
PARSE_DASHBOARD_PATH = "${mountPath}";
PARSE_DASHBOARD_ENABLE_RESOURCE_CACHE = ${config.enableResourceCache ? 'true' : 'false'};
</script>
<title>Parse Dashboard</title>
</head>
Expand Down
3 changes: 2 additions & 1 deletion Parse-Dashboard/parse-dashboard-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"secondaryBackgroundColor": ""
}
],
"iconsFolder": "icons"
"iconsFolder": "icons",
"enableBrowserServiceWorker": false
}
46 changes: 46 additions & 0 deletions Parse-Dashboard/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const CACHE_NAME = 'dashboard-cache-v1';

self.addEventListener('install', () => {
self.skipWaiting();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

self.addEventListener('activate', event => {
event.waitUntil(
Promise.all([
self.clients.claim(),
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
])
);
});

self.addEventListener('fetch', event => {
const req = event.request;
if (req.destination === 'script' || req.destination === 'style' || req.url.includes('/bundles/')) {
event.respondWith(
caches.match(req).then(cached => {
return (
cached ||
fetch(req).then(resp => {
const resClone = resp.clone();
caches.open(CACHE_NAME).then(cache => cache.put(req, resClone));
return resp;
})
Comment thread
mtrezza marked this conversation as resolved.
);
})
);
}
});

self.addEventListener('message', event => {
if (event.data === 'unregister') {
self.registration.unregister();
}
});
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [Custom order in the filter popup](#custom-order-in-the-filter-popup)
- [Persistent Filters](#persistent-filters)
- [Scripts](#scripts)
- [Resource Cache](#resource-cache)
- [Running as Express Middleware](#running-as-express-middleware)
- [Deploying Parse Dashboard](#deploying-parse-dashboard)
- [Preparing for Deployment](#preparing-for-deployment)
Expand Down Expand Up @@ -510,6 +511,37 @@ Parse.Cloud.define('deleteAccount', async (req) => {

</details>

### Resource Cache

Parse Dashboard can cache its resources such as bundles in the browser, so that opening the dashboard in another tab does not reload the dashboard resources from the server but from the local browser cache. Caching only starts after login in the dashboard.

| Parameter | Type | Optional | Default | Example | Description |
|-----------------------|---------|----------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|
| `enableResourceCache` | Boolean | yes | `false` | `true` | Enables caching of dashboard resources in the browser for faster dashboard loading in additional browser tabs. |


Example configuration:

```javascript
const dashboard = new ParseDashboard({
enableResourceCache: true,
apps: [
{
serverURL: 'http://localhost:1337/parse',
appId: 'myAppId',
masterKey: 'myMasterKey',
appName: 'MyApp'
}
]
});
```

> [!Warning]
> This feature can make it more difficult to push dashboard updates to users. Enabling the resource cache will start a browser service worker that caches dashboard resources locally only once. As long as the service worker is running, it will prevent loading any dashboard updates from the server, even if the user reloads the browser tab. The service worker is automatically stopped, once the last dashboard browser tab is closed. On the opening of the first dashboard browser tab, a new service worker is started and the dashboard resources are loaded from the server.

> [!Note]
> For developers: during dashboard development, the resource cache should be disabled to ensure reloading the dashboard tab in the browser loads the new dashboard bundle with any changes you made in the source code. You can inspect the service worker in the developer tools of most browsers. For example in Google Chrome, go to *Developer Tools > Application tab > Service workers* to see whether the dashboard service worker is currently running and to debug it.

# Running as Express Middleware

Instead of starting Parse Dashboard with the CLI, you can also run it as an [express](https://github.com/expressjs/express) middleware.
Expand Down
2 changes: 2 additions & 0 deletions src/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import installDevTools from 'immutable-devtools';
import React from 'react';
import ReactDOM from 'react-dom';
import Dashboard from './Dashboard';
import registerServiceWorker from '../registerServiceWorker';

require('stylesheets/fonts.scss');
require('graphiql/graphiql.min.css');
installDevTools(Immutable);

const path = window.PARSE_DASHBOARD_PATH || '/';
ReactDOM.render(<Dashboard path={path} />, document.getElementById('browser_mount'));
registerServiceWorker();
66 changes: 66 additions & 0 deletions src/registerServiceWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
function registerServiceWorker() {

if (!window.PARSE_DASHBOARD_ENABLE_RESOURCE_CACHE) {
return;
}

if (!('serviceWorker' in navigator)) {
return;
}

const mountPath = (window.PARSE_DASHBOARD_PATH || '/').replace(/\/?$/, '/');
const swPath = `${mountPath}sw.js`;
const countKey = `pd-sw-tabs:${mountPath}`;

/**
* Registers the service worker at the specified path.
*/
const register = () => {
navigator.serviceWorker.register(swPath).catch(() => {});
};

/**
* Increments the count of open dashboard tabs in localStorage.
*/
const increment = () => {
let current = parseInt(localStorage.getItem(countKey) || '0', 10);
if (!navigator.serviceWorker.controller && current > 0) {
current = 0;
}
localStorage.setItem(countKey, String(current + 1));
};

/**
* Decrements the count of open dashboard tabs in localStorage.
*/
const decrement = () => {
const current = parseInt(localStorage.getItem(countKey) || '0', 10);
const next = Math.max(0, current - 1);
localStorage.setItem(countKey, String(next));
if (next === 0) {
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage('unregister');
}
navigator.serviceWorker.getRegistration(swPath).then(reg => {
if (reg) {
reg.unregister();
}
});
caches.keys().then(keys => keys.forEach(k => caches.delete(k)));
}
};

increment();

window.addEventListener('load', () => {
register();
});
window.addEventListener('beforeunload', () => {
decrement();
});
window.addEventListener('pagehide', () => {
decrement();
});
}

export default registerServiceWorker;
Loading