lobsterboard 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/lobsterboard.css +1 -1
- package/dist/lobsterboard.esm.js +1 -1
- package/dist/lobsterboard.esm.min.js +1 -1
- package/dist/lobsterboard.umd.js +1 -1
- package/dist/lobsterboard.umd.min.js +1 -1
- package/js/builder.js +12 -2
- package/package.json +1 -1
- package/server.cjs +13 -2
- package/templates/full-screen-focus-on-openclaw-systems/config.json +5 -4
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ A self-hosted, drag-and-drop dashboard builder with 50 widgets, a template galle
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
+

|
|
10
|
+
|
|
9
11
|
## Quick Start
|
|
10
12
|
|
|
11
13
|
```bash
|
|
@@ -25,6 +27,8 @@ node server.cjs
|
|
|
25
27
|
|
|
26
28
|
Open **http://localhost:8080** → press **Ctrl+E** to enter edit mode → drag widgets from the sidebar → click **💾 Save**.
|
|
27
29
|
|
|
30
|
+

|
|
31
|
+
|
|
28
32
|
## Features
|
|
29
33
|
|
|
30
34
|
- **Drag-and-drop editor** — visual layout with 20px snap grid, resize handles, property panel
|
|
@@ -47,8 +51,12 @@ Widget settings are edited in the right-hand panel during edit mode. All configu
|
|
|
47
51
|
|
|
48
52
|
## Template Gallery
|
|
49
53
|
|
|
54
|
+

|
|
55
|
+
|
|
50
56
|
LobsterBoard includes a built-in template system for sharing and reusing dashboard layouts.
|
|
51
57
|
|
|
58
|
+

|
|
59
|
+
|
|
52
60
|
- **Export** your current dashboard as a template (auto-captures a screenshot preview)
|
|
53
61
|
- **Browse** the template gallery to discover pre-built layouts
|
|
54
62
|
- **Import** templates in two modes:
|
|
@@ -56,6 +64,8 @@ LobsterBoard includes a built-in template system for sharing and reusing dashboa
|
|
|
56
64
|
- **Merge** — append the template's widgets below your existing layout
|
|
57
65
|
- Templates are stored in the `templates/` directory and can be shared as folders
|
|
58
66
|
|
|
67
|
+

|
|
68
|
+
|
|
59
69
|
## Widgets
|
|
60
70
|
|
|
61
71
|
### 🖥️ System Monitoring
|
package/dist/lobsterboard.css
CHANGED
package/dist/lobsterboard.esm.js
CHANGED
package/dist/lobsterboard.umd.js
CHANGED
package/js/builder.js
CHANGED
|
@@ -143,7 +143,7 @@ function executeWidgetScripts() {
|
|
|
143
143
|
state.widgets.forEach(widget => {
|
|
144
144
|
const template = WIDGETS[widget.type];
|
|
145
145
|
if (!template || !template.generateJs) return;
|
|
146
|
-
const props = { ...widget.properties, id: 'preview-' + widget.id };
|
|
146
|
+
const props = sanitizeProps({ ...widget.properties, id: 'preview-' + widget.id });
|
|
147
147
|
try {
|
|
148
148
|
const js = template.generateJs(props);
|
|
149
149
|
new Function(js)();
|
|
@@ -2436,12 +2436,22 @@ function generateEditJs() {
|
|
|
2436
2436
|
`;
|
|
2437
2437
|
}
|
|
2438
2438
|
|
|
2439
|
+
function sanitizeProps(props) {
|
|
2440
|
+
const safe = { ...props };
|
|
2441
|
+
for (const key of Object.keys(safe)) {
|
|
2442
|
+
if (typeof safe[key] === 'string') {
|
|
2443
|
+
safe[key] = safe[key].replace(/[`$\\]/g, '\\$&').replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
return safe;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2439
2449
|
function generateDashboardJs() {
|
|
2440
2450
|
const widgetJs = state.widgets.map(widget => {
|
|
2441
2451
|
const template = WIDGETS[widget.type];
|
|
2442
2452
|
if (!template || !template.generateJs) return '';
|
|
2443
2453
|
|
|
2444
|
-
const props = { ...widget.properties, id: widget.id };
|
|
2454
|
+
const props = sanitizeProps({ ...widget.properties, id: widget.id });
|
|
2445
2455
|
return template.generateJs(props);
|
|
2446
2456
|
}).join('\n\n');
|
|
2447
2457
|
|
package/package.json
CHANGED
package/server.cjs
CHANGED
|
@@ -1276,7 +1276,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
1276
1276
|
|
|
1277
1277
|
const sensitiveKeys = ['apiKey', 'api_key', 'token', 'secret', 'password'];
|
|
1278
1278
|
const privateIpRegex = /^https?:\/\/(10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|localhost|127\.0\.0\.1)/i;
|
|
1279
|
-
|
|
1279
|
+
// URLs that may contain private auth tokens — strip them in templates
|
|
1280
|
+
const privateUrlKeys = ['icalUrl'];
|
|
1281
|
+
const privateUrlPatterns = [/[?&/]private[-_]?[a-f0-9]/i, /caldav\.icloud\.com/i, /\/private\//i];
|
|
1280
1282
|
|
|
1281
1283
|
function stripSensitive(props) {
|
|
1282
1284
|
if (!props || typeof props !== 'object') return props;
|
|
@@ -1289,7 +1291,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
1289
1291
|
} else if ((key === 'url' || key === 'endpoint') && typeof result[key] === 'string' && privateIpRegex.test(result[key])) {
|
|
1290
1292
|
result[key] = 'http://your-server:port/path';
|
|
1291
1293
|
stripped = true;
|
|
1292
|
-
} else if (
|
|
1294
|
+
} else if (privateUrlKeys.includes(key) && typeof result[key] === 'string') {
|
|
1295
|
+
// Always strip private calendar/feed URLs — they contain auth tokens
|
|
1296
|
+
if (result[key] && (result[key].length > 0)) {
|
|
1297
|
+
const hasPrivateToken = privateUrlPatterns.some(p => p.test(result[key]));
|
|
1298
|
+
if (hasPrivateToken) {
|
|
1299
|
+
result[key] = '';
|
|
1300
|
+
stripped = true;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
} else if (typeof result[key] === 'object' && result[key] !== null) {
|
|
1293
1304
|
const inner = stripSensitive(result[key]);
|
|
1294
1305
|
result[key] = inner.result;
|
|
1295
1306
|
if (inner.stripped) stripped = true;
|
|
@@ -269,10 +269,11 @@
|
|
|
269
269
|
"height": 200,
|
|
270
270
|
"properties": {
|
|
271
271
|
"title": "Calendar",
|
|
272
|
-
"icalUrl": "
|
|
272
|
+
"icalUrl": "",
|
|
273
273
|
"maxEvents": 5,
|
|
274
274
|
"refreshInterval": 300
|
|
275
|
-
}
|
|
275
|
+
},
|
|
276
|
+
"_templateNote": "\u26a0\ufe0f Configure this widget's settings after import"
|
|
276
277
|
},
|
|
277
278
|
{
|
|
278
279
|
"id": "widget-48",
|
|
@@ -300,7 +301,7 @@
|
|
|
300
301
|
"apiKey": "YOUR_API_KEY_HERE",
|
|
301
302
|
"repo": "LobsterBoard"
|
|
302
303
|
},
|
|
303
|
-
"_templateNote": "
|
|
304
|
+
"_templateNote": "\u26a0\ufe0f Configure this widget's settings after import"
|
|
304
305
|
},
|
|
305
306
|
{
|
|
306
307
|
"id": "widget-50",
|
|
@@ -329,7 +330,7 @@
|
|
|
329
330
|
"apiKeyNote": "Get a free key at finnhub.io/register",
|
|
330
331
|
"refreshInterval": 60
|
|
331
332
|
},
|
|
332
|
-
"_templateNote": "
|
|
333
|
+
"_templateNote": "\u26a0\ufe0f Configure this widget's settings after import"
|
|
333
334
|
}
|
|
334
335
|
]
|
|
335
336
|
}
|