lobsterboard 0.1.0
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/CHANGELOG.md +95 -0
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/lobsterboard.css +347 -0
- package/dist/lobsterboard.esm.js +1195 -0
- package/dist/lobsterboard.esm.js.map +1 -0
- package/dist/lobsterboard.esm.min.js +8 -0
- package/dist/lobsterboard.esm.min.js.map +1 -0
- package/dist/lobsterboard.umd.js +1219 -0
- package/dist/lobsterboard.umd.js.map +1 -0
- package/dist/lobsterboard.umd.min.js +8 -0
- package/dist/lobsterboard.umd.min.js.map +1 -0
- package/package.json +67 -0
- package/src/builder.js +723 -0
- package/src/index.js +53 -0
- package/src/widgets.js +435 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LobsterBoard - Dashboard Builder Library
|
|
3
|
+
*
|
|
4
|
+
* A library for building and generating dashboard configurations
|
|
5
|
+
* with customizable widgets.
|
|
6
|
+
*
|
|
7
|
+
* @module lobsterboard
|
|
8
|
+
* @example
|
|
9
|
+
* // ESM
|
|
10
|
+
* import { WIDGETS, generateDashboardHtml, generateDashboardCss } from 'lobsterboard';
|
|
11
|
+
*
|
|
12
|
+
* // CommonJS
|
|
13
|
+
* const { WIDGETS, generateDashboardHtml } = require('lobsterboard');
|
|
14
|
+
*
|
|
15
|
+
* // Browser (UMD)
|
|
16
|
+
* <script src="https://unpkg.com/lobsterboard"></script>
|
|
17
|
+
* const { WIDGETS } = LobsterBoard;
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Widget definitions
|
|
21
|
+
export {
|
|
22
|
+
WIDGETS,
|
|
23
|
+
getWidgetCategories,
|
|
24
|
+
getWidget,
|
|
25
|
+
getWidgetTypes
|
|
26
|
+
} from './widgets.js';
|
|
27
|
+
|
|
28
|
+
// Builder utilities
|
|
29
|
+
export {
|
|
30
|
+
escapeHtml,
|
|
31
|
+
processWidgetHtml,
|
|
32
|
+
generateDashboardCss,
|
|
33
|
+
generateEditJs,
|
|
34
|
+
generateWidgetHtml,
|
|
35
|
+
generateWidgetJs,
|
|
36
|
+
generateDashboardHtml,
|
|
37
|
+
generateDashboardJs,
|
|
38
|
+
generateReadme
|
|
39
|
+
} from './builder.js';
|
|
40
|
+
|
|
41
|
+
// Re-export defaults for convenience
|
|
42
|
+
import { WIDGETS } from './widgets.js';
|
|
43
|
+
import builder from './builder.js';
|
|
44
|
+
|
|
45
|
+
// Version (will be replaced during build)
|
|
46
|
+
export const VERSION = '0.1.0';
|
|
47
|
+
|
|
48
|
+
// Default export for convenience
|
|
49
|
+
export default {
|
|
50
|
+
VERSION,
|
|
51
|
+
WIDGETS,
|
|
52
|
+
...builder
|
|
53
|
+
};
|
package/src/widgets.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LobsterBoard - Widget Definitions
|
|
3
|
+
* Each widget defines its default size, properties, and generated code
|
|
4
|
+
*
|
|
5
|
+
* @module lobsterboard/widgets
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const WIDGETS = {
|
|
9
|
+
// ─────────────────────────────────────────────
|
|
10
|
+
// SMALL CARDS (KPI style)
|
|
11
|
+
// ─────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
'weather': {
|
|
14
|
+
name: 'Local Weather',
|
|
15
|
+
icon: '🌡️',
|
|
16
|
+
category: 'small',
|
|
17
|
+
description: 'Shows current weather for a single location using wttr.in (no API key needed).',
|
|
18
|
+
defaultWidth: 200,
|
|
19
|
+
defaultHeight: 120,
|
|
20
|
+
hasApiKey: false,
|
|
21
|
+
properties: {
|
|
22
|
+
title: 'Local Weather',
|
|
23
|
+
location: 'Atlanta',
|
|
24
|
+
units: 'F',
|
|
25
|
+
refreshInterval: 600
|
|
26
|
+
},
|
|
27
|
+
preview: `<div style="text-align:center;padding:8px;">
|
|
28
|
+
<div style="font-size:24px;">72°F</div>
|
|
29
|
+
<div style="font-size:11px;color:#8b949e;">Atlanta</div>
|
|
30
|
+
</div>`,
|
|
31
|
+
generateHtml: (props) => `
|
|
32
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
33
|
+
<div class="dash-card-head">
|
|
34
|
+
<span class="dash-card-title">🌡️ ${props.title || 'Local Weather'}</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="dash-card-body" style="display:flex;align-items:center;justify-content:center;gap:10px;">
|
|
37
|
+
<span id="${props.id}-icon" style="font-size:24px;">🌡️</span>
|
|
38
|
+
<div>
|
|
39
|
+
<div class="kpi-value blue" id="${props.id}-value">—</div>
|
|
40
|
+
<div class="kpi-label" id="${props.id}-label">${props.location || 'Location'}</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>`,
|
|
44
|
+
generateJs: (props) => `
|
|
45
|
+
// Weather Widget: ${props.id} (uses free wttr.in API - no key needed)
|
|
46
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
47
|
+
try {
|
|
48
|
+
const location = encodeURIComponent('${props.location || 'Atlanta'}');
|
|
49
|
+
const res = await fetch('https://wttr.in/' + location + '?format=j1');
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
const current = data.current_condition[0];
|
|
52
|
+
const temp = '${props.units}' === 'C' ? current.temp_C : current.temp_F;
|
|
53
|
+
const unit = '${props.units}' === 'C' ? '°C' : '°F';
|
|
54
|
+
document.getElementById('${props.id}-value').textContent = temp + unit;
|
|
55
|
+
document.getElementById('${props.id}-label').textContent = current.weatherDesc[0].value;
|
|
56
|
+
const code = parseInt(current.weatherCode);
|
|
57
|
+
let icon = '🌡️';
|
|
58
|
+
if (code === 113) icon = '☀️';
|
|
59
|
+
else if (code === 116 || code === 119) icon = '⛅';
|
|
60
|
+
else if (code >= 176 && code <= 359) icon = '🌧️';
|
|
61
|
+
else if (code >= 368 && code <= 395) icon = '❄️';
|
|
62
|
+
document.getElementById('${props.id}-icon').textContent = icon;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('Weather widget error:', e);
|
|
65
|
+
document.getElementById('${props.id}-value').textContent = '—';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
69
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 600) * 1000});
|
|
70
|
+
`
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
'clock': {
|
|
74
|
+
name: 'Clock',
|
|
75
|
+
icon: '🕐',
|
|
76
|
+
category: 'small',
|
|
77
|
+
description: 'Simple digital clock. Supports 12h or 24h format.',
|
|
78
|
+
defaultWidth: 200,
|
|
79
|
+
defaultHeight: 120,
|
|
80
|
+
hasApiKey: false,
|
|
81
|
+
properties: {
|
|
82
|
+
title: 'Clock',
|
|
83
|
+
timezone: 'local',
|
|
84
|
+
format24h: false
|
|
85
|
+
},
|
|
86
|
+
preview: `<div style="text-align:center;padding:8px;">
|
|
87
|
+
<div style="font-size:24px;">3:45 PM</div>
|
|
88
|
+
<div style="font-size:11px;color:#8b949e;">Wed, Feb 5</div>
|
|
89
|
+
</div>`,
|
|
90
|
+
generateHtml: (props) => `
|
|
91
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
92
|
+
<div class="dash-card-head">
|
|
93
|
+
<span class="dash-card-title">🕐 ${props.title || 'Clock'}</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="dash-card-body" style="display:flex;flex-direction:column;align-items:center;justify-content:center;">
|
|
96
|
+
<div class="kpi-value" id="${props.id}-time">—</div>
|
|
97
|
+
<div class="kpi-label" id="${props.id}-date">—</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>`,
|
|
100
|
+
generateJs: (props) => `
|
|
101
|
+
// Clock Widget: ${props.id}
|
|
102
|
+
function updateClock_${props.id.replace(/-/g, '_')}() {
|
|
103
|
+
const now = new Date();
|
|
104
|
+
const timeEl = document.getElementById('${props.id}-time');
|
|
105
|
+
const dateEl = document.getElementById('${props.id}-date');
|
|
106
|
+
const opts = { hour: 'numeric', minute: '2-digit', hour12: ${!props.format24h} };
|
|
107
|
+
timeEl.textContent = now.toLocaleTimeString('en-US', opts);
|
|
108
|
+
dateEl.textContent = now.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
|
|
109
|
+
}
|
|
110
|
+
updateClock_${props.id.replace(/-/g, '_')}();
|
|
111
|
+
setInterval(updateClock_${props.id.replace(/-/g, '_')}, 1000);
|
|
112
|
+
`
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
'auth-status': {
|
|
116
|
+
name: 'Auth Status',
|
|
117
|
+
icon: '🔐',
|
|
118
|
+
category: 'small',
|
|
119
|
+
description: 'Shows if OpenClaw is using Anthropic Max subscription (green) or API key fallback (yellow).',
|
|
120
|
+
defaultWidth: 180,
|
|
121
|
+
defaultHeight: 100,
|
|
122
|
+
hasApiKey: true,
|
|
123
|
+
apiKeyName: 'OPENCLAW_API',
|
|
124
|
+
properties: {
|
|
125
|
+
title: 'Auth Type',
|
|
126
|
+
endpoint: '/api/status',
|
|
127
|
+
refreshInterval: 30
|
|
128
|
+
},
|
|
129
|
+
preview: `<div style="text-align:center;padding:8px;">
|
|
130
|
+
<div style="width:10px;height:10px;background:#3fb950;border-radius:50%;margin:0 auto 4px;"></div>
|
|
131
|
+
<div style="font-size:13px;">OAuth</div>
|
|
132
|
+
<div style="font-size:11px;color:#8b949e;">Auth</div>
|
|
133
|
+
</div>`,
|
|
134
|
+
generateHtml: (props) => `
|
|
135
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
136
|
+
<div class="dash-card-head">
|
|
137
|
+
<span class="dash-card-title">🔐 ${props.title || 'Auth Type'}</span>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="dash-card-body" style="display:flex;align-items:center;justify-content:center;gap:10px;">
|
|
140
|
+
<div class="kpi-indicator" id="${props.id}-dot"></div>
|
|
141
|
+
<div class="kpi-value" id="${props.id}-value">—</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>`,
|
|
144
|
+
generateJs: (props) => `
|
|
145
|
+
// Auth Status Widget: ${props.id}
|
|
146
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch('${props.endpoint || '/api/status'}');
|
|
149
|
+
const json = await res.json();
|
|
150
|
+
const data = json.data || json;
|
|
151
|
+
const dot = document.getElementById('${props.id}-dot');
|
|
152
|
+
const val = document.getElementById('${props.id}-value');
|
|
153
|
+
val.textContent = data.authMode === 'oauth' ? 'Subscription' : 'API';
|
|
154
|
+
dot.className = 'kpi-indicator ' + (data.authMode === 'oauth' ? 'green' : 'yellow');
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.error('Auth status widget error:', e);
|
|
157
|
+
document.getElementById('${props.id}-value').textContent = '—';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
161
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 30) * 1000});
|
|
162
|
+
`
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
'session-count': {
|
|
166
|
+
name: 'Active Sessions',
|
|
167
|
+
icon: '💬',
|
|
168
|
+
category: 'small',
|
|
169
|
+
description: 'Shows count of active OpenClaw sessions.',
|
|
170
|
+
defaultWidth: 160,
|
|
171
|
+
defaultHeight: 100,
|
|
172
|
+
hasApiKey: true,
|
|
173
|
+
apiKeyName: 'OPENCLAW_API',
|
|
174
|
+
properties: {
|
|
175
|
+
title: 'Sessions',
|
|
176
|
+
endpoint: '/api/sessions',
|
|
177
|
+
refreshInterval: 30
|
|
178
|
+
},
|
|
179
|
+
preview: `<div style="text-align:center;padding:8px;">
|
|
180
|
+
<div style="font-size:28px;color:#58a6ff;">3</div>
|
|
181
|
+
<div style="font-size:11px;color:#8b949e;">Active</div>
|
|
182
|
+
</div>`,
|
|
183
|
+
generateHtml: (props) => `
|
|
184
|
+
<div class="kpi-card kpi-sm" id="widget-${props.id}">
|
|
185
|
+
<div class="kpi-icon">💬</div>
|
|
186
|
+
<div class="kpi-data">
|
|
187
|
+
<div class="kpi-value blue" id="${props.id}-count">—</div>
|
|
188
|
+
<div class="kpi-label">Active</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>`,
|
|
191
|
+
generateJs: (props) => `
|
|
192
|
+
// Session Count Widget: ${props.id}
|
|
193
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
194
|
+
try {
|
|
195
|
+
const res = await fetch('${props.endpoint || '/api/sessions'}');
|
|
196
|
+
const json = await res.json();
|
|
197
|
+
const data = json.data || json;
|
|
198
|
+
document.getElementById('${props.id}-count').textContent = data.active || data.length || 0;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
document.getElementById('${props.id}-count').textContent = '—';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
204
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 30) * 1000});
|
|
205
|
+
`
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// ─────────────────────────────────────────────
|
|
209
|
+
// LARGE CARDS (Content)
|
|
210
|
+
// ─────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
'activity-list': {
|
|
213
|
+
name: 'Activity List',
|
|
214
|
+
icon: '📋',
|
|
215
|
+
category: 'large',
|
|
216
|
+
description: 'Shows recent OpenClaw activity from /api/activity endpoint.',
|
|
217
|
+
defaultWidth: 400,
|
|
218
|
+
defaultHeight: 300,
|
|
219
|
+
hasApiKey: true,
|
|
220
|
+
apiKeyName: 'OPENCLAW_API',
|
|
221
|
+
properties: {
|
|
222
|
+
title: 'Today',
|
|
223
|
+
endpoint: '/api/activity',
|
|
224
|
+
maxItems: 10,
|
|
225
|
+
refreshInterval: 60
|
|
226
|
+
},
|
|
227
|
+
preview: `<div style="padding:4px;font-size:11px;color:#8b949e;">
|
|
228
|
+
<div>• Meeting at 2pm</div>
|
|
229
|
+
<div>• Review PR #42</div>
|
|
230
|
+
<div>• Deploy v1.2</div>
|
|
231
|
+
</div>`,
|
|
232
|
+
generateHtml: (props) => `
|
|
233
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
234
|
+
<div class="dash-card-head">
|
|
235
|
+
<span class="dash-card-title">📋 ${props.title || 'Today'}</span>
|
|
236
|
+
<span class="dash-card-badge" id="${props.id}-badge">—</span>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="dash-card-body compact-list" id="${props.id}-list">
|
|
239
|
+
<div class="list-item">• Team standup at 10am</div>
|
|
240
|
+
<div class="list-item">• Review PR #42</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>`,
|
|
243
|
+
generateJs: (props) => `
|
|
244
|
+
// Activity List Widget: ${props.id}
|
|
245
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
246
|
+
try {
|
|
247
|
+
const res = await fetch('${props.endpoint || '/api/activity'}');
|
|
248
|
+
const json = await res.json();
|
|
249
|
+
const data = json.data || json;
|
|
250
|
+
const list = document.getElementById('${props.id}-list');
|
|
251
|
+
const badge = document.getElementById('${props.id}-badge');
|
|
252
|
+
const items = data.items || [];
|
|
253
|
+
list.innerHTML = items.slice(0, ${props.maxItems || 10}).map(item =>
|
|
254
|
+
'<div class="list-item">' + item.text + '</div>'
|
|
255
|
+
).join('');
|
|
256
|
+
badge.textContent = items.length + ' items';
|
|
257
|
+
} catch (e) {
|
|
258
|
+
console.error('Activity list widget error:', e);
|
|
259
|
+
document.getElementById('${props.id}-list').innerHTML = '<div class="list-item">—</div>';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
263
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 60) * 1000});
|
|
264
|
+
`
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
'cron-jobs': {
|
|
268
|
+
name: 'Cron Jobs',
|
|
269
|
+
icon: '⏰',
|
|
270
|
+
category: 'large',
|
|
271
|
+
description: 'Lists scheduled cron jobs from OpenClaw /api/cron endpoint.',
|
|
272
|
+
defaultWidth: 400,
|
|
273
|
+
defaultHeight: 250,
|
|
274
|
+
hasApiKey: true,
|
|
275
|
+
apiKeyName: 'OPENCLAW_API',
|
|
276
|
+
properties: {
|
|
277
|
+
title: 'Cron',
|
|
278
|
+
endpoint: '/api/cron',
|
|
279
|
+
refreshInterval: 30
|
|
280
|
+
},
|
|
281
|
+
preview: `<div style="padding:4px;font-size:11px;color:#8b949e;">
|
|
282
|
+
<div>⏰ Daily backup - 2am</div>
|
|
283
|
+
<div>⏰ Sync data - */5 *</div>
|
|
284
|
+
</div>`,
|
|
285
|
+
generateHtml: (props) => `
|
|
286
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
287
|
+
<div class="dash-card-head">
|
|
288
|
+
<span class="dash-card-title">⏰ ${props.title || 'Cron'}</span>
|
|
289
|
+
<span class="dash-card-badge" id="${props.id}-badge">—</span>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="dash-card-body" id="${props.id}-list">
|
|
292
|
+
<div class="cron-item"><span class="cron-name">Daily backup</span><span class="cron-next">2:00 AM</span></div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>`,
|
|
295
|
+
generateJs: (props) => `
|
|
296
|
+
// Cron Jobs Widget: ${props.id}
|
|
297
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
298
|
+
try {
|
|
299
|
+
const res = await fetch('${props.endpoint || '/api/cron'}');
|
|
300
|
+
const json = await res.json();
|
|
301
|
+
const data = json.data || json;
|
|
302
|
+
const list = document.getElementById('${props.id}-list');
|
|
303
|
+
const badge = document.getElementById('${props.id}-badge');
|
|
304
|
+
const jobs = data.jobs || [];
|
|
305
|
+
list.innerHTML = jobs.map(job =>
|
|
306
|
+
'<div class="cron-item"><span class="cron-name">' + job.name + '</span><span class="cron-next">' + job.next + '</span></div>'
|
|
307
|
+
).join('');
|
|
308
|
+
badge.textContent = jobs.length + ' jobs';
|
|
309
|
+
} catch (e) {
|
|
310
|
+
console.error('Cron jobs widget error:', e);
|
|
311
|
+
document.getElementById('${props.id}-list').innerHTML = '<div class="cron-item"><span class="cron-name">—</span></div>';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
315
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 30) * 1000});
|
|
316
|
+
`
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
'system-log': {
|
|
320
|
+
name: 'System Log',
|
|
321
|
+
icon: '🔧',
|
|
322
|
+
category: 'large',
|
|
323
|
+
description: 'Shows recent system logs from OpenClaw /api/logs endpoint.',
|
|
324
|
+
defaultWidth: 500,
|
|
325
|
+
defaultHeight: 400,
|
|
326
|
+
hasApiKey: true,
|
|
327
|
+
apiKeyName: 'OPENCLAW_API',
|
|
328
|
+
properties: {
|
|
329
|
+
title: 'System Log',
|
|
330
|
+
endpoint: '/api/logs',
|
|
331
|
+
maxLines: 50,
|
|
332
|
+
refreshInterval: 10
|
|
333
|
+
},
|
|
334
|
+
preview: `<div style="padding:4px;font-size:10px;font-family:monospace;color:#8b949e;">
|
|
335
|
+
<div>[INFO] System started</div>
|
|
336
|
+
<div>[DEBUG] Loading config</div>
|
|
337
|
+
</div>`,
|
|
338
|
+
generateHtml: (props) => `
|
|
339
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
340
|
+
<div class="dash-card-head">
|
|
341
|
+
<span class="dash-card-title">🔧 ${props.title || 'System Log'}</span>
|
|
342
|
+
<span class="dash-card-badge" id="${props.id}-badge">—</span>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="dash-card-body compact-list syslog-scroll" id="${props.id}-log">
|
|
345
|
+
<div class="log-line">[INFO] System started successfully</div>
|
|
346
|
+
</div>
|
|
347
|
+
</div>`,
|
|
348
|
+
generateJs: (props) => `
|
|
349
|
+
// System Log Widget: ${props.id}
|
|
350
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
351
|
+
try {
|
|
352
|
+
const res = await fetch('${props.endpoint || '/api/logs'}');
|
|
353
|
+
const json = await res.json();
|
|
354
|
+
const data = json.data || json;
|
|
355
|
+
const log = document.getElementById('${props.id}-log');
|
|
356
|
+
const badge = document.getElementById('${props.id}-badge');
|
|
357
|
+
const lines = data.lines || [];
|
|
358
|
+
log.innerHTML = lines.slice(-${props.maxLines || 50}).map(line =>
|
|
359
|
+
'<div class="log-line">' + line + '</div>'
|
|
360
|
+
).join('');
|
|
361
|
+
badge.textContent = lines.length + ' lines';
|
|
362
|
+
log.scrollTop = log.scrollHeight;
|
|
363
|
+
} catch (e) {
|
|
364
|
+
console.error('System log widget error:', e);
|
|
365
|
+
document.getElementById('${props.id}-log').innerHTML = '<div class="log-line">—</div>';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
369
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 10) * 1000});
|
|
370
|
+
`
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
// ─────────────────────────────────────────────
|
|
374
|
+
// BARS
|
|
375
|
+
// ─────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
'topbar': {
|
|
378
|
+
name: 'Top Nav Bar',
|
|
379
|
+
icon: '🔝',
|
|
380
|
+
category: 'bar',
|
|
381
|
+
description: 'Navigation bar with clock, weather, and system stats.',
|
|
382
|
+
defaultWidth: 1920,
|
|
383
|
+
defaultHeight: 48,
|
|
384
|
+
hasApiKey: false,
|
|
385
|
+
properties: {
|
|
386
|
+
title: 'OpenClaw',
|
|
387
|
+
links: 'Dashboard,Activity,Settings'
|
|
388
|
+
},
|
|
389
|
+
preview: `<div style="background:#161b22;padding:8px;font-size:11px;display:flex;gap:12px;">
|
|
390
|
+
<span>🤖 OpenClaw</span>
|
|
391
|
+
<span style="color:#58a6ff;">Dashboard</span>
|
|
392
|
+
</div>`,
|
|
393
|
+
generateHtml: (props) => `
|
|
394
|
+
<nav class="topbar" id="widget-${props.id}">
|
|
395
|
+
<div class="topbar-left">
|
|
396
|
+
<span class="topbar-brand">🤖 ${props.title || 'OpenClaw'}</span>
|
|
397
|
+
${(props.links || 'Dashboard').split(',').map((link, i) =>
|
|
398
|
+
`<a href="#" class="topbar-link${i === 0 ? ' active' : ''}">${link.trim()}</a>`
|
|
399
|
+
).join('')}
|
|
400
|
+
</div>
|
|
401
|
+
<div class="topbar-right">
|
|
402
|
+
<span class="topbar-meta" id="${props.id}-refresh">—</span>
|
|
403
|
+
<button class="topbar-refresh" onclick="location.reload()" title="Refresh">↻</button>
|
|
404
|
+
</div>
|
|
405
|
+
</nav>`,
|
|
406
|
+
generateJs: (props) => `
|
|
407
|
+
// Top Bar Widget: ${props.id}
|
|
408
|
+
document.getElementById('${props.id}-refresh').textContent =
|
|
409
|
+
new Date().toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
|
410
|
+
`
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Helper to get widget categories
|
|
415
|
+
export function getWidgetCategories() {
|
|
416
|
+
const categories = {};
|
|
417
|
+
for (const [key, widget] of Object.entries(WIDGETS)) {
|
|
418
|
+
const cat = widget.category || 'other';
|
|
419
|
+
if (!categories[cat]) categories[cat] = [];
|
|
420
|
+
categories[cat].push({ key, ...widget });
|
|
421
|
+
}
|
|
422
|
+
return categories;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Helper to get widget by type
|
|
426
|
+
export function getWidget(type) {
|
|
427
|
+
return WIDGETS[type] || null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Helper to list all widget types
|
|
431
|
+
export function getWidgetTypes() {
|
|
432
|
+
return Object.keys(WIDGETS);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export default WIDGETS;
|