@polderlabs/bizar-dash 3.0.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/dist/assets/index-B5X9g8B4.css +1 -0
- package/dist/assets/index-LqQuSp9d.js +388 -0
- package/dist/assets/index-LqQuSp9d.js.map +1 -0
- package/dist/index.html +18 -0
- package/package.json +67 -0
- package/src/cli.mjs +228 -0
- package/src/server/agents-store.mjs +190 -0
- package/src/server/api.mjs +913 -0
- package/src/server/browser.mjs +40 -0
- package/src/server/diagnostics-store.mjs +138 -0
- package/src/server/mods-loader.mjs +361 -0
- package/src/server/projects-store.mjs +198 -0
- package/src/server/providers-store.mjs +183 -0
- package/src/server/schedules-runner.mjs +150 -0
- package/src/server/schedules-store.mjs +233 -0
- package/src/server/search-store.mjs +120 -0
- package/src/server/server.mjs +388 -0
- package/src/server/state.mjs +357 -0
- package/src/server/tailscale-store.mjs +113 -0
- package/src/server/tasks-store.mjs +275 -0
- package/src/server/tui.mjs +844 -0
- package/src/server/watcher.mjs +81 -0
- package/src/web/App.tsx +316 -0
- package/src/web/components/Button.tsx +55 -0
- package/src/web/components/Card.tsx +40 -0
- package/src/web/components/EmptyState.tsx +30 -0
- package/src/web/components/Modal.tsx +137 -0
- package/src/web/components/SearchModal.tsx +185 -0
- package/src/web/components/Spinner.tsx +19 -0
- package/src/web/components/StatusBadge.tsx +25 -0
- package/src/web/components/Tag.tsx +28 -0
- package/src/web/components/Toast.tsx +142 -0
- package/src/web/components/Topbar.tsx +203 -0
- package/src/web/index.html +17 -0
- package/src/web/lib/api.ts +71 -0
- package/src/web/lib/markdown.tsx +59 -0
- package/src/web/lib/types.ts +388 -0
- package/src/web/lib/utils.ts +79 -0
- package/src/web/lib/ws.ts +132 -0
- package/src/web/main.tsx +12 -0
- package/src/web/styles/main.css +3148 -0
- package/src/web/views/Agents.tsx +406 -0
- package/src/web/views/Chat.tsx +527 -0
- package/src/web/views/Config.tsx +683 -0
- package/src/web/views/Mods.tsx +350 -0
- package/src/web/views/Overview.tsx +350 -0
- package/src/web/views/Plans.tsx +667 -0
- package/src/web/views/Schedules.tsx +299 -0
- package/src/web/views/Settings.tsx +571 -0
- package/src/web/views/Tasks.tsx +761 -0
- package/templates/mod/FORMAT.md +76 -0
- package/templates/mod/hello-mod/README.md +19 -0
- package/templates/mod/hello-mod/agents/greeter.md +8 -0
- package/templates/mod/hello-mod/commands/hello.md +6 -0
- package/templates/mod/hello-mod/mod.json +20 -0
- package/templates/mod/hello-mod/routes/ping.mjs +9 -0
- package/templates/mod/hello-mod/views/HelloView.tsx +10 -0
- package/tsconfig.json +23 -0
- package/vite.config.ts +24 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
// src/views/Settings.tsx — v3 settings: theme colors, UI layout, defaults, Tailscale, service.
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Sliders,
|
|
5
|
+
Save,
|
|
6
|
+
RefreshCw,
|
|
7
|
+
Sun,
|
|
8
|
+
Moon,
|
|
9
|
+
Monitor,
|
|
10
|
+
Info,
|
|
11
|
+
Globe,
|
|
12
|
+
Palette,
|
|
13
|
+
Layout as LayoutIcon,
|
|
14
|
+
Server as ServerIcon,
|
|
15
|
+
RotateCcw,
|
|
16
|
+
Plug,
|
|
17
|
+
} from 'lucide-react';
|
|
18
|
+
import { Button } from '../components/Button';
|
|
19
|
+
import { Card, CardTitle, CardMeta } from '../components/Card';
|
|
20
|
+
import { useToast } from '../components/Toast';
|
|
21
|
+
import { api } from '../lib/api';
|
|
22
|
+
import { cn } from '../lib/utils';
|
|
23
|
+
import {
|
|
24
|
+
applyTheme,
|
|
25
|
+
applyThemeTokens,
|
|
26
|
+
type Settings,
|
|
27
|
+
type SettingsResponse,
|
|
28
|
+
type Snapshot,
|
|
29
|
+
type ThemeName,
|
|
30
|
+
type TailscaleStatus,
|
|
31
|
+
} from '../lib/types';
|
|
32
|
+
|
|
33
|
+
type Props = {
|
|
34
|
+
snapshot: Snapshot;
|
|
35
|
+
settings: Settings;
|
|
36
|
+
activeTab: string;
|
|
37
|
+
setActiveTab: (id: string) => void;
|
|
38
|
+
refreshSnapshot: () => Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const THEMES: { id: ThemeName; label: string; Icon: typeof Sun }[] = [
|
|
42
|
+
{ id: 'dark', label: 'Dark', Icon: Moon },
|
|
43
|
+
{ id: 'light', label: 'Light', Icon: Sun },
|
|
44
|
+
{ id: 'system', label: 'System', Icon: Monitor },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const FONT_FAMILIES = [
|
|
48
|
+
'Inter',
|
|
49
|
+
'system-ui',
|
|
50
|
+
'Segoe UI',
|
|
51
|
+
'Roboto',
|
|
52
|
+
'JetBrains Mono',
|
|
53
|
+
'SF Mono',
|
|
54
|
+
'Cascadia Code',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const LAYOUTS = [
|
|
58
|
+
{ id: 'topnav', label: 'Top nav' },
|
|
59
|
+
{ id: 'sidebar', label: 'Sidebar' },
|
|
60
|
+
{ id: 'both', label: 'Both' },
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export function SettingsView({ settings: initial, refreshSnapshot }: Props) {
|
|
64
|
+
const toast = useToast();
|
|
65
|
+
const [settings, setSettings] = useState<Settings>(initial);
|
|
66
|
+
const [dirty, setDirty] = useState(false);
|
|
67
|
+
const [saving, setSaving] = useState(false);
|
|
68
|
+
const [tailscale, setTailscale] = useState<TailscaleStatus | null>(null);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
setSettings(initial);
|
|
72
|
+
setDirty(false);
|
|
73
|
+
if (initial.theme) applyThemeTokens(initial.theme);
|
|
74
|
+
}, [initial]);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
api.get<TailscaleStatus>('/tailscale/status').then(setTailscale).catch(() => undefined);
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
const patchTheme = (patch: Partial<Settings['theme']>) => {
|
|
81
|
+
setSettings((cur) => {
|
|
82
|
+
const next = { ...cur, theme: { ...cur.theme, ...patch } };
|
|
83
|
+
applyThemeTokens(next.theme);
|
|
84
|
+
return next;
|
|
85
|
+
});
|
|
86
|
+
setDirty(true);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const patchUi = (patch: Partial<Settings['ui']>) => {
|
|
90
|
+
setSettings((cur) => ({ ...cur, ui: { ...cur.ui, ...patch } }));
|
|
91
|
+
setDirty(true);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const patchTop = <K extends keyof Settings>(key: K, value: Settings[K]) => {
|
|
95
|
+
setSettings((cur) => ({ ...cur, [key]: value }));
|
|
96
|
+
setDirty(true);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const onSave = async () => {
|
|
100
|
+
setSaving(true);
|
|
101
|
+
try {
|
|
102
|
+
const r = await api.put<SettingsResponse>('/settings', settings);
|
|
103
|
+
setSettings(r.data);
|
|
104
|
+
setDirty(false);
|
|
105
|
+
applyTheme(r.data.theme);
|
|
106
|
+
applyThemeTokens(r.data.theme);
|
|
107
|
+
toast.success('Settings saved.');
|
|
108
|
+
await refreshSnapshot();
|
|
109
|
+
} catch (err) {
|
|
110
|
+
toast.error(`Save failed: ${(err as Error).message}`);
|
|
111
|
+
} finally {
|
|
112
|
+
setSaving(false);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const onReload = async () => {
|
|
117
|
+
try {
|
|
118
|
+
const r = await api.get<SettingsResponse>('/settings');
|
|
119
|
+
setSettings(r.data);
|
|
120
|
+
setDirty(false);
|
|
121
|
+
applyTheme(r.data.theme);
|
|
122
|
+
applyThemeTokens(r.data.theme);
|
|
123
|
+
toast.info('Settings reloaded.', 1500);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
toast.error(`Reload failed: ${(err as Error).message}`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const onReset = async () => {
|
|
130
|
+
if (!confirm('Reset all settings to defaults?')) return;
|
|
131
|
+
try {
|
|
132
|
+
const r = await api.post<SettingsResponse>('/settings/reset');
|
|
133
|
+
setSettings(r.data);
|
|
134
|
+
setDirty(false);
|
|
135
|
+
applyTheme(r.data.theme);
|
|
136
|
+
applyThemeTokens(r.data.theme);
|
|
137
|
+
toast.success('Settings reset.');
|
|
138
|
+
await refreshSnapshot();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
toast.error(`Reset failed: ${(err as Error).message}`);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const onTailscaleToggle = async () => {
|
|
145
|
+
try {
|
|
146
|
+
if (tailscale?.settings.enabled) {
|
|
147
|
+
await api.post('/tailscale/disable');
|
|
148
|
+
toast.success('Tailscale serve disabled.');
|
|
149
|
+
} else {
|
|
150
|
+
await api.post('/tailscale/enable', {
|
|
151
|
+
port: tailscale?.settings.port || 4321,
|
|
152
|
+
https: tailscale?.settings.https !== false,
|
|
153
|
+
hostname: tailscale?.settings.hostname || '',
|
|
154
|
+
});
|
|
155
|
+
toast.success('Tailscale serve enabled.');
|
|
156
|
+
}
|
|
157
|
+
const r = await api.get<TailscaleStatus>('/tailscale/status');
|
|
158
|
+
setTailscale(r);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
toast.error(`Tailscale failed: ${(err as Error).message}`);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const about = settings.about || {
|
|
165
|
+
version: '3.0.0',
|
|
166
|
+
homepage: 'https://github.com/DrB0rk/BizarHarness',
|
|
167
|
+
license: 'MIT',
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<div className="view view-settings">
|
|
172
|
+
<header className="view-header">
|
|
173
|
+
<div className="view-header-text">
|
|
174
|
+
<h2 className="view-title">
|
|
175
|
+
<Sliders size={18} /> Settings
|
|
176
|
+
</h2>
|
|
177
|
+
<p className="view-subtitle">
|
|
178
|
+
Personal preferences. Changes are saved to{' '}
|
|
179
|
+
<code>~/.config/bizar/settings.json</code>.
|
|
180
|
+
</p>
|
|
181
|
+
</div>
|
|
182
|
+
<div className="view-actions">
|
|
183
|
+
<Button variant="ghost" size="sm" onClick={onReset}>
|
|
184
|
+
<RotateCcw size={14} /> Reset
|
|
185
|
+
</Button>
|
|
186
|
+
<Button variant="secondary" size="sm" onClick={onReload}>
|
|
187
|
+
<RefreshCw size={14} /> Reload
|
|
188
|
+
</Button>
|
|
189
|
+
<Button
|
|
190
|
+
variant="primary"
|
|
191
|
+
size="sm"
|
|
192
|
+
disabled={!dirty || saving}
|
|
193
|
+
onClick={onSave}
|
|
194
|
+
>
|
|
195
|
+
{saving ? <span className="btn-spinner" /> : <Save size={14} />}
|
|
196
|
+
{saving ? 'Saving…' : 'Save'}
|
|
197
|
+
</Button>
|
|
198
|
+
</div>
|
|
199
|
+
</header>
|
|
200
|
+
|
|
201
|
+
<div className="settings-grid">
|
|
202
|
+
<Card>
|
|
203
|
+
<CardTitle><Palette size={14} /> Theme</CardTitle>
|
|
204
|
+
<CardMeta>Mode, accent, and colors. Live preview as you tweak.</CardMeta>
|
|
205
|
+
|
|
206
|
+
<div className="field">
|
|
207
|
+
<label className="field-label">Mode</label>
|
|
208
|
+
<div className="theme-row">
|
|
209
|
+
{THEMES.map(({ id, label, Icon }) => {
|
|
210
|
+
const active = settings.theme.mode === id;
|
|
211
|
+
return (
|
|
212
|
+
<button
|
|
213
|
+
key={id}
|
|
214
|
+
type="button"
|
|
215
|
+
className={cn('theme-card', active && 'theme-card-active')}
|
|
216
|
+
onClick={() => patchTheme({ mode: id })}
|
|
217
|
+
>
|
|
218
|
+
<Icon size={16} />
|
|
219
|
+
<span className="theme-card-label">{label}</span>
|
|
220
|
+
<span
|
|
221
|
+
className={cn(
|
|
222
|
+
'theme-card-swatch',
|
|
223
|
+
`theme-card-swatch-${id}`,
|
|
224
|
+
)}
|
|
225
|
+
/>
|
|
226
|
+
</button>
|
|
227
|
+
);
|
|
228
|
+
})}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div className="theme-colors">
|
|
233
|
+
<div className="field">
|
|
234
|
+
<label className="field-label">Accent</label>
|
|
235
|
+
<div className="color-row">
|
|
236
|
+
<input
|
|
237
|
+
type="color"
|
|
238
|
+
className="input color-input"
|
|
239
|
+
value={settings.theme.accent}
|
|
240
|
+
onChange={(e) => patchTheme({ accent: e.target.value })}
|
|
241
|
+
/>
|
|
242
|
+
<input
|
|
243
|
+
type="text"
|
|
244
|
+
className="input"
|
|
245
|
+
value={settings.theme.accent}
|
|
246
|
+
onChange={(e) => patchTheme({ accent: e.target.value })}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
<div className="field">
|
|
251
|
+
<label className="field-label">Success</label>
|
|
252
|
+
<div className="color-row">
|
|
253
|
+
<input
|
|
254
|
+
type="color"
|
|
255
|
+
className="input color-input"
|
|
256
|
+
value={settings.theme.success}
|
|
257
|
+
onChange={(e) => patchTheme({ success: e.target.value })}
|
|
258
|
+
/>
|
|
259
|
+
<input
|
|
260
|
+
type="text"
|
|
261
|
+
className="input"
|
|
262
|
+
value={settings.theme.success}
|
|
263
|
+
onChange={(e) => patchTheme({ success: e.target.value })}
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div className="field">
|
|
268
|
+
<label className="field-label">Warning</label>
|
|
269
|
+
<div className="color-row">
|
|
270
|
+
<input
|
|
271
|
+
type="color"
|
|
272
|
+
className="input color-input"
|
|
273
|
+
value={settings.theme.warning}
|
|
274
|
+
onChange={(e) => patchTheme({ warning: e.target.value })}
|
|
275
|
+
/>
|
|
276
|
+
<input
|
|
277
|
+
type="text"
|
|
278
|
+
className="input"
|
|
279
|
+
value={settings.theme.warning}
|
|
280
|
+
onChange={(e) => patchTheme({ warning: e.target.value })}
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
<div className="field">
|
|
285
|
+
<label className="field-label">Error</label>
|
|
286
|
+
<div className="color-row">
|
|
287
|
+
<input
|
|
288
|
+
type="color"
|
|
289
|
+
className="input color-input"
|
|
290
|
+
value={settings.theme.error}
|
|
291
|
+
onChange={(e) => patchTheme({ error: e.target.value })}
|
|
292
|
+
/>
|
|
293
|
+
<input
|
|
294
|
+
type="text"
|
|
295
|
+
className="input"
|
|
296
|
+
value={settings.theme.error}
|
|
297
|
+
onChange={(e) => patchTheme({ error: e.target.value })}
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
<div className="field">
|
|
302
|
+
<label className="field-label">Info</label>
|
|
303
|
+
<div className="color-row">
|
|
304
|
+
<input
|
|
305
|
+
type="color"
|
|
306
|
+
className="input color-input"
|
|
307
|
+
value={settings.theme.info}
|
|
308
|
+
onChange={(e) => patchTheme({ info: e.target.value })}
|
|
309
|
+
/>
|
|
310
|
+
<input
|
|
311
|
+
type="text"
|
|
312
|
+
className="input"
|
|
313
|
+
value={settings.theme.info}
|
|
314
|
+
onChange={(e) => patchTheme({ info: e.target.value })}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<div className="field">
|
|
321
|
+
<label className="field-label">Font family</label>
|
|
322
|
+
<select
|
|
323
|
+
className="select"
|
|
324
|
+
value={settings.theme.fontFamily}
|
|
325
|
+
onChange={(e) => patchTheme({ fontFamily: e.target.value })}
|
|
326
|
+
>
|
|
327
|
+
{FONT_FAMILIES.map((f) => (
|
|
328
|
+
<option key={f} value={f}>{f}</option>
|
|
329
|
+
))}
|
|
330
|
+
</select>
|
|
331
|
+
</div>
|
|
332
|
+
<div className="field">
|
|
333
|
+
<label className="field-label">Font size: {settings.theme.fontSize}px</label>
|
|
334
|
+
<input
|
|
335
|
+
type="range"
|
|
336
|
+
min={12}
|
|
337
|
+
max={20}
|
|
338
|
+
value={settings.theme.fontSize}
|
|
339
|
+
onChange={(e) => patchTheme({ fontSize: Number(e.target.value) })}
|
|
340
|
+
/>
|
|
341
|
+
</div>
|
|
342
|
+
<label className="checkbox-row">
|
|
343
|
+
<input
|
|
344
|
+
type="checkbox"
|
|
345
|
+
checked={settings.theme.compactMode}
|
|
346
|
+
onChange={(e) => patchTheme({ compactMode: e.target.checked })}
|
|
347
|
+
/>
|
|
348
|
+
<span>Compact mode (denser UI)</span>
|
|
349
|
+
</label>
|
|
350
|
+
<label className="checkbox-row">
|
|
351
|
+
<input
|
|
352
|
+
type="checkbox"
|
|
353
|
+
checked={settings.theme.animations}
|
|
354
|
+
onChange={(e) => patchTheme({ animations: e.target.checked })}
|
|
355
|
+
/>
|
|
356
|
+
<span>Enable animations</span>
|
|
357
|
+
</label>
|
|
358
|
+
</Card>
|
|
359
|
+
|
|
360
|
+
<Card>
|
|
361
|
+
<CardTitle><LayoutIcon size={14} /> UI layout</CardTitle>
|
|
362
|
+
<CardMeta>Choose how the dashboard's navigation is presented.</CardMeta>
|
|
363
|
+
<div className="layout-row">
|
|
364
|
+
{LAYOUTS.map((l) => (
|
|
365
|
+
<button
|
|
366
|
+
key={l.id}
|
|
367
|
+
type="button"
|
|
368
|
+
className={cn('layout-card', settings.ui.layout === l.id && 'layout-card-active')}
|
|
369
|
+
onClick={() => patchUi({ layout: l.id })}
|
|
370
|
+
>
|
|
371
|
+
<span className="layout-card-label">{l.label}</span>
|
|
372
|
+
</button>
|
|
373
|
+
))}
|
|
374
|
+
</div>
|
|
375
|
+
<label className="checkbox-row">
|
|
376
|
+
<input
|
|
377
|
+
type="checkbox"
|
|
378
|
+
checked={settings.ui.showHeader}
|
|
379
|
+
onChange={(e) => patchUi({ showHeader: e.target.checked })}
|
|
380
|
+
/>
|
|
381
|
+
<span>Show header</span>
|
|
382
|
+
</label>
|
|
383
|
+
<label className="checkbox-row">
|
|
384
|
+
<input
|
|
385
|
+
type="checkbox"
|
|
386
|
+
checked={settings.ui.showStatusBar}
|
|
387
|
+
onChange={(e) => patchUi({ showStatusBar: e.target.checked })}
|
|
388
|
+
/>
|
|
389
|
+
<span>Show status bar</span>
|
|
390
|
+
</label>
|
|
391
|
+
<div className="field">
|
|
392
|
+
<label className="field-label">Default tab</label>
|
|
393
|
+
<select
|
|
394
|
+
className="select"
|
|
395
|
+
value={settings.ui.defaultTab}
|
|
396
|
+
onChange={(e) => patchUi({ defaultTab: e.target.value })}
|
|
397
|
+
>
|
|
398
|
+
<option value="overview">Overview</option>
|
|
399
|
+
<option value="chat">Chat</option>
|
|
400
|
+
<option value="agents">Agents</option>
|
|
401
|
+
<option value="plans">Plans</option>
|
|
402
|
+
<option value="projects">Projects</option>
|
|
403
|
+
<option value="tasks">Tasks</option>
|
|
404
|
+
<option value="config">Config</option>
|
|
405
|
+
<option value="settings">Settings</option>
|
|
406
|
+
<option value="mods">Mods</option>
|
|
407
|
+
<option value="schedules">Schedules</option>
|
|
408
|
+
</select>
|
|
409
|
+
</div>
|
|
410
|
+
</Card>
|
|
411
|
+
|
|
412
|
+
<Card>
|
|
413
|
+
<CardTitle>General</CardTitle>
|
|
414
|
+
<CardMeta>Default agent + model override.</CardMeta>
|
|
415
|
+
<div className="field">
|
|
416
|
+
<label className="field-label" htmlFor="set-default-agent">Default agent</label>
|
|
417
|
+
<input
|
|
418
|
+
id="set-default-agent"
|
|
419
|
+
className="input"
|
|
420
|
+
type="text"
|
|
421
|
+
placeholder="e.g. odin"
|
|
422
|
+
value={settings.defaultAgent || ''}
|
|
423
|
+
onChange={(e) => patchTop('defaultAgent', e.target.value)}
|
|
424
|
+
/>
|
|
425
|
+
</div>
|
|
426
|
+
<div className="field">
|
|
427
|
+
<label className="field-label" htmlFor="set-default-model">Model override</label>
|
|
428
|
+
<input
|
|
429
|
+
id="set-default-model"
|
|
430
|
+
className="input"
|
|
431
|
+
type="text"
|
|
432
|
+
placeholder="(leave empty for provider default)"
|
|
433
|
+
value={settings.defaultModel || ''}
|
|
434
|
+
onChange={(e) => patchTop('defaultModel', e.target.value)}
|
|
435
|
+
/>
|
|
436
|
+
</div>
|
|
437
|
+
</Card>
|
|
438
|
+
|
|
439
|
+
<Card>
|
|
440
|
+
<CardTitle><ServerIcon size={14} /> Service</CardTitle>
|
|
441
|
+
<CardMeta>Background daemon that runs schedules.</CardMeta>
|
|
442
|
+
{tailscale ? (
|
|
443
|
+
<div className="service-card">
|
|
444
|
+
<p>
|
|
445
|
+
Status: <strong>{tailscale?.settings.enabled ? 'enabled' : 'disabled'}</strong>
|
|
446
|
+
{' '}· Tailscale installed: <strong>{tailscale.installed ? 'yes' : 'no'}</strong>
|
|
447
|
+
{' '}· authenticated: <strong>{tailscale.authenticated ? 'yes' : 'no'}</strong>
|
|
448
|
+
</p>
|
|
449
|
+
<p className="muted">
|
|
450
|
+
Use <code>bizar service start</code> / <code>bizar service stop</code> in
|
|
451
|
+
your terminal to control the daemon.
|
|
452
|
+
</p>
|
|
453
|
+
</div>
|
|
454
|
+
) : (
|
|
455
|
+
<p className="muted">Loading service status…</p>
|
|
456
|
+
)}
|
|
457
|
+
</Card>
|
|
458
|
+
|
|
459
|
+
<Card>
|
|
460
|
+
<CardTitle><Plug size={14} /> Tailscale serve</CardTitle>
|
|
461
|
+
<CardMeta>Expose the dashboard over your Tailscale network.</CardMeta>
|
|
462
|
+
{tailscale ? (
|
|
463
|
+
<>
|
|
464
|
+
<p>
|
|
465
|
+
Installed: <strong>{tailscale.installed ? 'yes' : 'no'}</strong>{' '}
|
|
466
|
+
{tailscale.version && <span className="muted">({tailscale.version})</span>}
|
|
467
|
+
</p>
|
|
468
|
+
<p>
|
|
469
|
+
Authenticated: <strong>{tailscale.authenticated ? 'yes' : 'no'}</strong>
|
|
470
|
+
</p>
|
|
471
|
+
<p>
|
|
472
|
+
Serve enabled: <strong>{tailscale.settings.enabled ? 'yes' : 'no'}</strong>
|
|
473
|
+
</p>
|
|
474
|
+
<div className="task-form-row">
|
|
475
|
+
<div className="task-form-field">
|
|
476
|
+
<label className="field-label">Port</label>
|
|
477
|
+
<input
|
|
478
|
+
type="number"
|
|
479
|
+
className="input"
|
|
480
|
+
defaultValue={tailscale.settings.port}
|
|
481
|
+
onChange={(e) => {
|
|
482
|
+
// Update local state via patchUi is unrelated; just inform
|
|
483
|
+
}}
|
|
484
|
+
/>
|
|
485
|
+
</div>
|
|
486
|
+
<div className="task-form-field">
|
|
487
|
+
<label className="field-label">Use HTTPS</label>
|
|
488
|
+
<input
|
|
489
|
+
type="checkbox"
|
|
490
|
+
defaultChecked={tailscale.settings.https === true}
|
|
491
|
+
/>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
<Button variant="primary" onClick={onTailscaleToggle}>
|
|
495
|
+
{tailscale.settings.enabled ? 'Disable serve' : 'Enable serve'}
|
|
496
|
+
</Button>
|
|
497
|
+
</>
|
|
498
|
+
) : (
|
|
499
|
+
<p className="muted">Loading Tailscale status…</p>
|
|
500
|
+
)}
|
|
501
|
+
</Card>
|
|
502
|
+
|
|
503
|
+
<Card>
|
|
504
|
+
<CardTitle>Notifications</CardTitle>
|
|
505
|
+
<CardMeta>Toast triggers inside the dashboard.</CardMeta>
|
|
506
|
+
<label className="checkbox-row">
|
|
507
|
+
<input
|
|
508
|
+
type="checkbox"
|
|
509
|
+
checked={!!settings.notifications.onAgentComplete}
|
|
510
|
+
onChange={(e) =>
|
|
511
|
+
setSettings((cur) => ({
|
|
512
|
+
...cur,
|
|
513
|
+
notifications: { ...cur.notifications, onAgentComplete: e.target.checked },
|
|
514
|
+
}))
|
|
515
|
+
}
|
|
516
|
+
/>
|
|
517
|
+
<span>Notify when an agent invocation completes</span>
|
|
518
|
+
</label>
|
|
519
|
+
<label className="checkbox-row">
|
|
520
|
+
<input
|
|
521
|
+
type="checkbox"
|
|
522
|
+
checked={!!settings.notifications.onPlanApproval}
|
|
523
|
+
onChange={(e) =>
|
|
524
|
+
setSettings((cur) => ({
|
|
525
|
+
...cur,
|
|
526
|
+
notifications: { ...cur.notifications, onPlanApproval: e.target.checked },
|
|
527
|
+
}))
|
|
528
|
+
}
|
|
529
|
+
/>
|
|
530
|
+
<span>Notify when a plan needs approval</span>
|
|
531
|
+
</label>
|
|
532
|
+
</Card>
|
|
533
|
+
|
|
534
|
+
<Card>
|
|
535
|
+
<CardTitle><Globe size={14} /> Dashboard</CardTitle>
|
|
536
|
+
<CardMeta>Controls how <code>bizar</code> starts up.</CardMeta>
|
|
537
|
+
<label className="checkbox-row">
|
|
538
|
+
<input
|
|
539
|
+
type="checkbox"
|
|
540
|
+
checked={settings.dashboard.autoLaunchWeb !== false}
|
|
541
|
+
onChange={(e) =>
|
|
542
|
+
setSettings((cur) => ({
|
|
543
|
+
...cur,
|
|
544
|
+
dashboard: { ...cur.dashboard, autoLaunchWeb: e.target.checked },
|
|
545
|
+
}))
|
|
546
|
+
}
|
|
547
|
+
/>
|
|
548
|
+
<span>Auto-launch web UI alongside TUI</span>
|
|
549
|
+
</label>
|
|
550
|
+
</Card>
|
|
551
|
+
</div>
|
|
552
|
+
|
|
553
|
+
<Card>
|
|
554
|
+
<CardTitle><Info size={14} /> About</CardTitle>
|
|
555
|
+
<CardMeta>Build metadata.</CardMeta>
|
|
556
|
+
<dl className="about-table">
|
|
557
|
+
<dt>Version</dt>
|
|
558
|
+
<dd className="mono">{about.version}</dd>
|
|
559
|
+
<dt>Homepage</dt>
|
|
560
|
+
<dd>
|
|
561
|
+
<a href={about.homepage} target="_blank" rel="noopener noreferrer">
|
|
562
|
+
{about.homepage}
|
|
563
|
+
</a>
|
|
564
|
+
</dd>
|
|
565
|
+
<dt>License</dt>
|
|
566
|
+
<dd>{about.license}</dd>
|
|
567
|
+
</dl>
|
|
568
|
+
</Card>
|
|
569
|
+
</div>
|
|
570
|
+
);
|
|
571
|
+
}
|