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 CHANGED
@@ -6,6 +6,8 @@ A self-hosted, drag-and-drop dashboard builder with 50 widgets, a template galle
6
6
 
7
7
  ![LobsterBoard](lobsterboard-logo-final.png)
8
8
 
9
+ ![LobsterBoard Dashboard](lobsterboard-screenshot.jpg)
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
+ ![Edit Mode](lobsterboard-editor.jpg)
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
+ ![Template Gallery](lobsterboard-templates.jpg)
55
+
50
56
  LobsterBoard includes a built-in template system for sharing and reusing dashboard layouts.
51
57
 
58
+ ![Template Import](lobsterboard-template-detail.jpg)
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
+ ![Dashboard Example](lobsterboard-dashboard-2.jpg)
68
+
59
69
  ## Widgets
60
70
 
61
71
  ### 🖥️ System Monitoring
@@ -1,4 +1,4 @@
1
- /* LobsterBoard v0.2.0 - Dashboard Styles */
1
+ /* LobsterBoard v0.2.2 - Dashboard Styles */
2
2
  /* LobsterBoard Dashboard - Generated Styles */
3
3
 
4
4
  :root {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.2.0
2
+ * LobsterBoard v0.2.2
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.2.0
2
+ * LobsterBoard v0.2.2
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.2.0
2
+ * LobsterBoard v0.2.2
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.2.0
2
+ * LobsterBoard v0.2.2
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobsterboard",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Self-hosted drag-and-drop dashboard builder with 50 widgets, template gallery, and custom pages. Works standalone or with OpenClaw.",
5
5
  "keywords": [
6
6
  "dashboard",
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
- const keepUrlKeys = ['icalUrl', 'feedUrl'];
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 (!keepUrlKeys.includes(key) && typeof result[key] === 'object' && result[key] !== null) {
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": "https://calendar.google.com/calendar/ical/curbob%40gmail.com/private-da3070618dc7bf05f1515cccd39f09e8/basic.ics",
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": "⚠️ Configure this widget's settings after import"
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": "⚠️ Configure this widget's settings after import"
333
+ "_templateNote": "\u26a0\ufe0f Configure this widget's settings after import"
333
334
  }
334
335
  ]
335
336
  }