create-ekka-desktop-app 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 +137 -0
- package/bin/cli.js +72 -0
- package/package.json +23 -0
- package/template/branding/app.json +6 -0
- package/template/branding/icon.icns +0 -0
- package/template/eslint.config.js +98 -0
- package/template/index.html +29 -0
- package/template/package.json +40 -0
- package/template/src/app/App.tsx +24 -0
- package/template/src/demo/DemoApp.tsx +260 -0
- package/template/src/demo/components/Banner.tsx +82 -0
- package/template/src/demo/components/EmptyState.tsx +61 -0
- package/template/src/demo/components/InfoPopover.tsx +171 -0
- package/template/src/demo/components/InfoTooltip.tsx +76 -0
- package/template/src/demo/components/LearnMore.tsx +98 -0
- package/template/src/demo/components/NodeCredentialsOnboarding.tsx +219 -0
- package/template/src/demo/components/SetupWizard.tsx +48 -0
- package/template/src/demo/components/StatusBadge.tsx +83 -0
- package/template/src/demo/components/index.ts +10 -0
- package/template/src/demo/hooks/index.ts +6 -0
- package/template/src/demo/hooks/useAuditEvents.ts +30 -0
- package/template/src/demo/layout/Shell.tsx +110 -0
- package/template/src/demo/layout/Sidebar.tsx +192 -0
- package/template/src/demo/pages/AuditLogPage.tsx +235 -0
- package/template/src/demo/pages/DocGenPage.tsx +874 -0
- package/template/src/demo/pages/HomeSetupPage.tsx +182 -0
- package/template/src/demo/pages/LoginPage.tsx +192 -0
- package/template/src/demo/pages/PathPermissionsPage.tsx +873 -0
- package/template/src/demo/pages/RunnerPage.tsx +445 -0
- package/template/src/demo/pages/SystemPage.tsx +557 -0
- package/template/src/demo/pages/VaultPage.tsx +805 -0
- package/template/src/ekka/__tests__/demo-backend.test.ts +187 -0
- package/template/src/ekka/audit/index.ts +7 -0
- package/template/src/ekka/audit/store.ts +68 -0
- package/template/src/ekka/audit/types.ts +22 -0
- package/template/src/ekka/auth/client.ts +212 -0
- package/template/src/ekka/auth/index.ts +30 -0
- package/template/src/ekka/auth/storage.ts +114 -0
- package/template/src/ekka/auth/types.ts +67 -0
- package/template/src/ekka/backend/demo.ts +151 -0
- package/template/src/ekka/backend/interface.ts +36 -0
- package/template/src/ekka/config.ts +48 -0
- package/template/src/ekka/constants.ts +143 -0
- package/template/src/ekka/errors.ts +54 -0
- package/template/src/ekka/index.ts +516 -0
- package/template/src/ekka/internal/backend.ts +156 -0
- package/template/src/ekka/internal/index.ts +7 -0
- package/template/src/ekka/ops/auth.ts +29 -0
- package/template/src/ekka/ops/debug.ts +68 -0
- package/template/src/ekka/ops/home.ts +101 -0
- package/template/src/ekka/ops/index.ts +16 -0
- package/template/src/ekka/ops/nodeCredentials.ts +131 -0
- package/template/src/ekka/ops/nodeSession.ts +145 -0
- package/template/src/ekka/ops/paths.ts +183 -0
- package/template/src/ekka/ops/runner.ts +86 -0
- package/template/src/ekka/ops/runtime.ts +31 -0
- package/template/src/ekka/ops/setup.ts +47 -0
- package/template/src/ekka/ops/vault.ts +459 -0
- package/template/src/ekka/ops/workflowRuns.ts +116 -0
- package/template/src/ekka/types.ts +82 -0
- package/template/src/ekka/utils/idempotency.ts +14 -0
- package/template/src/ekka/utils/index.ts +7 -0
- package/template/src/ekka/utils/time.ts +77 -0
- package/template/src/main.tsx +12 -0
- package/template/src/vite-env.d.ts +12 -0
- package/template/src-tauri/Cargo.toml +41 -0
- package/template/src-tauri/build.rs +3 -0
- package/template/src-tauri/capabilities/default.json +11 -0
- package/template/src-tauri/icons/icon.icns +0 -0
- package/template/src-tauri/icons/icon.png +0 -0
- package/template/src-tauri/resources/ekka-engine-bootstrap +0 -0
- package/template/src-tauri/src/bootstrap.rs +37 -0
- package/template/src-tauri/src/commands.rs +1215 -0
- package/template/src-tauri/src/device_secret.rs +111 -0
- package/template/src-tauri/src/engine_process.rs +538 -0
- package/template/src-tauri/src/grants.rs +129 -0
- package/template/src-tauri/src/handlers/home.rs +65 -0
- package/template/src-tauri/src/handlers/mod.rs +7 -0
- package/template/src-tauri/src/handlers/paths.rs +128 -0
- package/template/src-tauri/src/handlers/vault.rs +680 -0
- package/template/src-tauri/src/main.rs +243 -0
- package/template/src-tauri/src/node_auth.rs +858 -0
- package/template/src-tauri/src/node_credentials.rs +541 -0
- package/template/src-tauri/src/node_runner.rs +882 -0
- package/template/src-tauri/src/node_vault_crypto.rs +113 -0
- package/template/src-tauri/src/node_vault_store.rs +267 -0
- package/template/src-tauri/src/ops/auth.rs +50 -0
- package/template/src-tauri/src/ops/home.rs +251 -0
- package/template/src-tauri/src/ops/mod.rs +7 -0
- package/template/src-tauri/src/ops/runtime.rs +21 -0
- package/template/src-tauri/src/state.rs +639 -0
- package/template/src-tauri/src/types.rs +84 -0
- package/template/src-tauri/tauri.conf.json +41 -0
- package/template/tsconfig.json +26 -0
- package/template/tsconfig.tsbuildinfo +1 -0
- package/template/vite.config.ts +34 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Page
|
|
3
|
+
*
|
|
4
|
+
* Shows cryptographic identity, security status, and system configuration.
|
|
5
|
+
* Designed to inspire confidence in the security architecture.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect, useRef, type CSSProperties, type ReactElement } from 'react';
|
|
9
|
+
import { ekka, advanced, type HomeStatus, type RuntimeInfo, type RunnerStatus } from '../../ekka';
|
|
10
|
+
|
|
11
|
+
interface SystemPageProps {
|
|
12
|
+
darkMode: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SystemInfo {
|
|
16
|
+
runtime: RuntimeInfo | null;
|
|
17
|
+
homeStatus: HomeStatus | null;
|
|
18
|
+
user: {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
name: string | null;
|
|
22
|
+
tenantId: string;
|
|
23
|
+
tenantName: string | null;
|
|
24
|
+
} | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function timeAgo(iso: string | null): string {
|
|
28
|
+
if (!iso) return '—';
|
|
29
|
+
try {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const then = new Date(iso).getTime();
|
|
32
|
+
const diffSec = Math.floor((now - then) / 1000);
|
|
33
|
+
if (diffSec < 0) return 'just now';
|
|
34
|
+
if (diffSec < 60) return `${diffSec}s ago`;
|
|
35
|
+
if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m ago`;
|
|
36
|
+
if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h ago`;
|
|
37
|
+
return `${Math.floor(diffSec / 86400)}d ago`;
|
|
38
|
+
} catch {
|
|
39
|
+
return '—';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function shortId(id: string | null): string {
|
|
44
|
+
if (!id) return '—';
|
|
45
|
+
return id.length > 12 ? `${id.slice(0, 12)}...` : id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function SystemPage({ darkMode }: SystemPageProps): ReactElement {
|
|
49
|
+
const [loading, setLoading] = useState(true);
|
|
50
|
+
const [info, setInfo] = useState<SystemInfo>({
|
|
51
|
+
runtime: null,
|
|
52
|
+
homeStatus: null,
|
|
53
|
+
user: null,
|
|
54
|
+
});
|
|
55
|
+
const [runnerStatus, setRunnerStatus] = useState<RunnerStatus | null>(null);
|
|
56
|
+
const pollingRef = useRef<number | null>(null);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
void loadSystemInfo();
|
|
60
|
+
void loadRunnerStatus();
|
|
61
|
+
|
|
62
|
+
// Poll runner status every 2 seconds
|
|
63
|
+
pollingRef.current = window.setInterval(() => {
|
|
64
|
+
void loadRunnerStatus();
|
|
65
|
+
}, 2000);
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
if (pollingRef.current) {
|
|
69
|
+
clearInterval(pollingRef.current);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
async function loadRunnerStatus(): Promise<void> {
|
|
75
|
+
try {
|
|
76
|
+
const status = await advanced.runner.status();
|
|
77
|
+
setRunnerStatus(status);
|
|
78
|
+
} catch {
|
|
79
|
+
// Silently ignore - runner status is optional
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function loadSystemInfo(): Promise<void> {
|
|
84
|
+
try {
|
|
85
|
+
const [runtime, homeStatus] = await Promise.all([
|
|
86
|
+
advanced.runtime.info().catch(() => null),
|
|
87
|
+
advanced.home.status().catch(() => null),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const currentUser = ekka.auth.user();
|
|
91
|
+
const user = currentUser
|
|
92
|
+
? {
|
|
93
|
+
id: currentUser.id,
|
|
94
|
+
email: currentUser.email,
|
|
95
|
+
name: currentUser.name,
|
|
96
|
+
tenantId: currentUser.company?.id || 'default',
|
|
97
|
+
tenantName: currentUser.company?.name || null,
|
|
98
|
+
}
|
|
99
|
+
: null;
|
|
100
|
+
|
|
101
|
+
setInfo({ runtime, homeStatus, user });
|
|
102
|
+
} finally {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const colors = {
|
|
108
|
+
text: darkMode ? '#ffffff' : '#1d1d1f',
|
|
109
|
+
textMuted: darkMode ? '#98989d' : '#6e6e73',
|
|
110
|
+
textDim: darkMode ? '#636366' : '#aeaeb2',
|
|
111
|
+
bg: darkMode ? '#2c2c2e' : '#fafafa',
|
|
112
|
+
bgAlt: darkMode ? '#1c1c1e' : '#ffffff',
|
|
113
|
+
border: darkMode ? '#3a3a3c' : '#e5e5e5',
|
|
114
|
+
accent: darkMode ? '#0a84ff' : '#007aff',
|
|
115
|
+
green: darkMode ? '#30d158' : '#34c759',
|
|
116
|
+
orange: darkMode ? '#ff9f0a' : '#ff9500',
|
|
117
|
+
red: darkMode ? '#ff453a' : '#ff3b30',
|
|
118
|
+
purple: darkMode ? '#bf5af2' : '#af52de',
|
|
119
|
+
mono: darkMode ? '#98989d' : '#6e6e73',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const styles: Record<string, CSSProperties> = {
|
|
123
|
+
container: {
|
|
124
|
+
width: '100%',
|
|
125
|
+
},
|
|
126
|
+
header: {
|
|
127
|
+
marginBottom: '32px',
|
|
128
|
+
},
|
|
129
|
+
title: {
|
|
130
|
+
fontSize: '28px',
|
|
131
|
+
fontWeight: 700,
|
|
132
|
+
color: colors.text,
|
|
133
|
+
marginBottom: '8px',
|
|
134
|
+
letterSpacing: '-0.02em',
|
|
135
|
+
},
|
|
136
|
+
subtitle: {
|
|
137
|
+
fontSize: '14px',
|
|
138
|
+
color: colors.textMuted,
|
|
139
|
+
lineHeight: 1.5,
|
|
140
|
+
},
|
|
141
|
+
section: {
|
|
142
|
+
marginBottom: '24px',
|
|
143
|
+
},
|
|
144
|
+
sectionHeader: {
|
|
145
|
+
display: 'flex',
|
|
146
|
+
alignItems: 'center',
|
|
147
|
+
gap: '8px',
|
|
148
|
+
marginBottom: '12px',
|
|
149
|
+
},
|
|
150
|
+
sectionTitle: {
|
|
151
|
+
fontSize: '11px',
|
|
152
|
+
fontWeight: 600,
|
|
153
|
+
color: colors.textMuted,
|
|
154
|
+
textTransform: 'uppercase' as const,
|
|
155
|
+
letterSpacing: '0.05em',
|
|
156
|
+
},
|
|
157
|
+
sectionLine: {
|
|
158
|
+
flex: 1,
|
|
159
|
+
height: '1px',
|
|
160
|
+
background: colors.border,
|
|
161
|
+
},
|
|
162
|
+
card: {
|
|
163
|
+
background: colors.bg,
|
|
164
|
+
border: `1px solid ${colors.border}`,
|
|
165
|
+
borderRadius: '12px',
|
|
166
|
+
overflow: 'hidden',
|
|
167
|
+
},
|
|
168
|
+
row: {
|
|
169
|
+
display: 'flex',
|
|
170
|
+
justifyContent: 'space-between',
|
|
171
|
+
alignItems: 'flex-start',
|
|
172
|
+
padding: '14px 16px',
|
|
173
|
+
borderBottom: `1px solid ${colors.border}`,
|
|
174
|
+
gap: '16px',
|
|
175
|
+
},
|
|
176
|
+
rowLast: {
|
|
177
|
+
display: 'flex',
|
|
178
|
+
justifyContent: 'space-between',
|
|
179
|
+
alignItems: 'flex-start',
|
|
180
|
+
padding: '14px 16px',
|
|
181
|
+
gap: '16px',
|
|
182
|
+
},
|
|
183
|
+
label: {
|
|
184
|
+
fontSize: '13px',
|
|
185
|
+
color: colors.textMuted,
|
|
186
|
+
fontWeight: 500,
|
|
187
|
+
minWidth: '120px',
|
|
188
|
+
flexShrink: 0,
|
|
189
|
+
},
|
|
190
|
+
value: {
|
|
191
|
+
fontSize: '13px',
|
|
192
|
+
color: colors.text,
|
|
193
|
+
fontFamily: 'SF Mono, Monaco, Consolas, monospace',
|
|
194
|
+
wordBreak: 'break-all' as const,
|
|
195
|
+
textAlign: 'right' as const,
|
|
196
|
+
},
|
|
197
|
+
valueMuted: {
|
|
198
|
+
fontSize: '13px',
|
|
199
|
+
color: colors.textDim,
|
|
200
|
+
fontStyle: 'italic' as const,
|
|
201
|
+
textAlign: 'right' as const,
|
|
202
|
+
},
|
|
203
|
+
badge: {
|
|
204
|
+
display: 'inline-flex',
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
gap: '6px',
|
|
207
|
+
padding: '4px 10px',
|
|
208
|
+
borderRadius: '6px',
|
|
209
|
+
fontSize: '12px',
|
|
210
|
+
fontWeight: 600,
|
|
211
|
+
},
|
|
212
|
+
badgeGreen: {
|
|
213
|
+
background: darkMode ? 'rgba(48, 209, 88, 0.15)' : 'rgba(52, 199, 89, 0.12)',
|
|
214
|
+
color: colors.green,
|
|
215
|
+
},
|
|
216
|
+
badgeOrange: {
|
|
217
|
+
background: darkMode ? 'rgba(255, 159, 10, 0.15)' : 'rgba(255, 149, 0, 0.12)',
|
|
218
|
+
color: colors.orange,
|
|
219
|
+
},
|
|
220
|
+
badgePurple: {
|
|
221
|
+
background: darkMode ? 'rgba(191, 90, 242, 0.15)' : 'rgba(175, 82, 222, 0.12)',
|
|
222
|
+
color: colors.purple,
|
|
223
|
+
},
|
|
224
|
+
badgeBlue: {
|
|
225
|
+
background: darkMode ? 'rgba(10, 132, 255, 0.15)' : 'rgba(0, 122, 255, 0.12)',
|
|
226
|
+
color: colors.accent,
|
|
227
|
+
},
|
|
228
|
+
dot: {
|
|
229
|
+
width: '6px',
|
|
230
|
+
height: '6px',
|
|
231
|
+
borderRadius: '50%',
|
|
232
|
+
background: 'currentColor',
|
|
233
|
+
},
|
|
234
|
+
securityBanner: {
|
|
235
|
+
display: 'flex',
|
|
236
|
+
alignItems: 'center',
|
|
237
|
+
gap: '12px',
|
|
238
|
+
padding: '16px',
|
|
239
|
+
background: darkMode ? 'rgba(48, 209, 88, 0.08)' : 'rgba(52, 199, 89, 0.06)',
|
|
240
|
+
borderRadius: '12px',
|
|
241
|
+
marginBottom: '24px',
|
|
242
|
+
},
|
|
243
|
+
securityIcon: {
|
|
244
|
+
width: '40px',
|
|
245
|
+
height: '40px',
|
|
246
|
+
borderRadius: '10px',
|
|
247
|
+
background: darkMode ? 'rgba(48, 209, 88, 0.15)' : 'rgba(52, 199, 89, 0.12)',
|
|
248
|
+
display: 'flex',
|
|
249
|
+
alignItems: 'center',
|
|
250
|
+
justifyContent: 'center',
|
|
251
|
+
color: colors.green,
|
|
252
|
+
},
|
|
253
|
+
securityText: {
|
|
254
|
+
flex: 1,
|
|
255
|
+
},
|
|
256
|
+
securityTitle: {
|
|
257
|
+
fontSize: '14px',
|
|
258
|
+
fontWeight: 600,
|
|
259
|
+
color: colors.text,
|
|
260
|
+
marginBottom: '2px',
|
|
261
|
+
},
|
|
262
|
+
securitySubtitle: {
|
|
263
|
+
fontSize: '12px',
|
|
264
|
+
color: colors.textMuted,
|
|
265
|
+
},
|
|
266
|
+
loadingContainer: {
|
|
267
|
+
display: 'flex',
|
|
268
|
+
alignItems: 'center',
|
|
269
|
+
justifyContent: 'center',
|
|
270
|
+
padding: '60px 20px',
|
|
271
|
+
color: colors.textMuted,
|
|
272
|
+
fontSize: '14px',
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
if (loading) {
|
|
277
|
+
return (
|
|
278
|
+
<div style={styles.container}>
|
|
279
|
+
<header style={styles.header}>
|
|
280
|
+
<h1 style={styles.title}>System</h1>
|
|
281
|
+
</header>
|
|
282
|
+
<div style={styles.loadingContainer}>Loading system information...</div>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const isSecure = info.homeStatus?.state === 'HOME_GRANTED';
|
|
288
|
+
const mode = advanced.internal.mode();
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<div style={styles.container}>
|
|
292
|
+
<header style={styles.header}>
|
|
293
|
+
<h1 style={styles.title}>System</h1>
|
|
294
|
+
<p style={styles.subtitle}>
|
|
295
|
+
Cryptographic identity, security status, and runtime configuration
|
|
296
|
+
</p>
|
|
297
|
+
</header>
|
|
298
|
+
|
|
299
|
+
{/* Security Status Banner */}
|
|
300
|
+
<div
|
|
301
|
+
style={{
|
|
302
|
+
...styles.securityBanner,
|
|
303
|
+
background: isSecure
|
|
304
|
+
? darkMode
|
|
305
|
+
? 'rgba(48, 209, 88, 0.08)'
|
|
306
|
+
: 'rgba(52, 199, 89, 0.06)'
|
|
307
|
+
: darkMode
|
|
308
|
+
? 'rgba(255, 159, 10, 0.08)'
|
|
309
|
+
: 'rgba(255, 149, 0, 0.06)',
|
|
310
|
+
}}
|
|
311
|
+
>
|
|
312
|
+
<div
|
|
313
|
+
style={{
|
|
314
|
+
...styles.securityIcon,
|
|
315
|
+
background: isSecure
|
|
316
|
+
? darkMode
|
|
317
|
+
? 'rgba(48, 209, 88, 0.15)'
|
|
318
|
+
: 'rgba(52, 199, 89, 0.12)'
|
|
319
|
+
: darkMode
|
|
320
|
+
? 'rgba(255, 159, 10, 0.15)'
|
|
321
|
+
: 'rgba(255, 149, 0, 0.12)',
|
|
322
|
+
color: isSecure ? colors.green : colors.orange,
|
|
323
|
+
}}
|
|
324
|
+
>
|
|
325
|
+
<ShieldIcon />
|
|
326
|
+
</div>
|
|
327
|
+
<div style={styles.securityText}>
|
|
328
|
+
<div style={styles.securityTitle}>
|
|
329
|
+
{isSecure ? 'Secure Session Active' : 'Setup Required'}
|
|
330
|
+
</div>
|
|
331
|
+
<div style={styles.securitySubtitle}>
|
|
332
|
+
{isSecure
|
|
333
|
+
? 'Ed25519 signed grants verified · Home directory protected'
|
|
334
|
+
: 'Home grant required to complete security initialization'}
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<span
|
|
338
|
+
style={{
|
|
339
|
+
...styles.badge,
|
|
340
|
+
...(isSecure ? styles.badgeGreen : styles.badgeOrange),
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
<span style={styles.dot} />
|
|
344
|
+
{isSecure ? 'Protected' : 'Pending'}
|
|
345
|
+
</span>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{/* Identity Section */}
|
|
349
|
+
<div style={styles.section}>
|
|
350
|
+
<div style={styles.sectionHeader}>
|
|
351
|
+
<span style={styles.sectionTitle}>Identity</span>
|
|
352
|
+
<div style={styles.sectionLine} />
|
|
353
|
+
</div>
|
|
354
|
+
<div style={styles.card}>
|
|
355
|
+
<div style={styles.row}>
|
|
356
|
+
<span style={styles.label}>User ID</span>
|
|
357
|
+
<span style={styles.value}>{info.user?.id || '—'}</span>
|
|
358
|
+
</div>
|
|
359
|
+
<div style={styles.row}>
|
|
360
|
+
<span style={styles.label}>Email</span>
|
|
361
|
+
<span style={styles.value}>{info.user?.email || '—'}</span>
|
|
362
|
+
</div>
|
|
363
|
+
<div style={styles.row}>
|
|
364
|
+
<span style={styles.label}>Tenant ID</span>
|
|
365
|
+
<span style={styles.value}>{info.user?.tenantId || '—'}</span>
|
|
366
|
+
</div>
|
|
367
|
+
<div style={styles.rowLast}>
|
|
368
|
+
<span style={styles.label}>Organization</span>
|
|
369
|
+
<span style={info.user?.tenantName ? styles.value : styles.valueMuted}>
|
|
370
|
+
{info.user?.tenantName || 'Not set'}
|
|
371
|
+
</span>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
{/* Runtime Section */}
|
|
377
|
+
<div style={styles.section}>
|
|
378
|
+
<div style={styles.sectionHeader}>
|
|
379
|
+
<span style={styles.sectionTitle}>Runtime</span>
|
|
380
|
+
<div style={styles.sectionLine} />
|
|
381
|
+
</div>
|
|
382
|
+
<div style={styles.card}>
|
|
383
|
+
<div style={styles.row}>
|
|
384
|
+
<span style={styles.label}>Environment</span>
|
|
385
|
+
<span style={{ ...styles.badge, ...styles.badgeBlue }}>
|
|
386
|
+
{info.runtime?.runtime === 'tauri' ? 'Desktop (Tauri)' : 'Web Browser'}
|
|
387
|
+
</span>
|
|
388
|
+
</div>
|
|
389
|
+
<div style={styles.row}>
|
|
390
|
+
<span style={styles.label}>Backend Mode</span>
|
|
391
|
+
<span style={{ ...styles.badge, ...styles.badgePurple }}>
|
|
392
|
+
{mode === 'engine' ? 'Engine' : 'Demo'}
|
|
393
|
+
</span>
|
|
394
|
+
</div>
|
|
395
|
+
<div style={styles.row}>
|
|
396
|
+
<span style={styles.label}>Engine Present</span>
|
|
397
|
+
<span style={styles.value}>{info.runtime?.engine_present ? 'Yes' : 'No'}</span>
|
|
398
|
+
</div>
|
|
399
|
+
<div style={styles.rowLast}>
|
|
400
|
+
<span style={styles.label}>Client Version</span>
|
|
401
|
+
<span style={styles.value}>0.2.0</span>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
{/* Home Directory Section */}
|
|
407
|
+
<div style={styles.section}>
|
|
408
|
+
<div style={styles.sectionHeader}>
|
|
409
|
+
<span style={styles.sectionTitle}>Home Directory</span>
|
|
410
|
+
<div style={styles.sectionLine} />
|
|
411
|
+
</div>
|
|
412
|
+
<div style={styles.card}>
|
|
413
|
+
<div style={styles.row}>
|
|
414
|
+
<span style={styles.label}>Path</span>
|
|
415
|
+
<span style={styles.value}>{info.homeStatus?.homePath || '—'}</span>
|
|
416
|
+
</div>
|
|
417
|
+
<div style={styles.row}>
|
|
418
|
+
<span style={styles.label}>State</span>
|
|
419
|
+
<span
|
|
420
|
+
style={{
|
|
421
|
+
...styles.badge,
|
|
422
|
+
...(info.homeStatus?.state === 'HOME_GRANTED'
|
|
423
|
+
? styles.badgeGreen
|
|
424
|
+
: styles.badgeOrange),
|
|
425
|
+
}}
|
|
426
|
+
>
|
|
427
|
+
{formatHomeState(info.homeStatus?.state)}
|
|
428
|
+
</span>
|
|
429
|
+
</div>
|
|
430
|
+
<div style={styles.rowLast}>
|
|
431
|
+
<span style={styles.label}>Grant Present</span>
|
|
432
|
+
<span style={styles.value}>{info.homeStatus?.grantPresent ? 'Yes' : 'No'}</span>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
{/* Security Section */}
|
|
438
|
+
<div style={styles.section}>
|
|
439
|
+
<div style={styles.sectionHeader}>
|
|
440
|
+
<span style={styles.sectionTitle}>Security</span>
|
|
441
|
+
<div style={styles.sectionLine} />
|
|
442
|
+
</div>
|
|
443
|
+
<div style={styles.card}>
|
|
444
|
+
<div style={styles.row}>
|
|
445
|
+
<span style={styles.label}>Signing Algorithm</span>
|
|
446
|
+
<span style={styles.value}>Ed25519</span>
|
|
447
|
+
</div>
|
|
448
|
+
<div style={styles.row}>
|
|
449
|
+
<span style={styles.label}>Canonicalization</span>
|
|
450
|
+
<span style={styles.value}>JCS (RFC 8785)</span>
|
|
451
|
+
</div>
|
|
452
|
+
<div style={styles.row}>
|
|
453
|
+
<span style={styles.label}>Grant Schema</span>
|
|
454
|
+
<span style={styles.value}>ekka.grant.v1</span>
|
|
455
|
+
</div>
|
|
456
|
+
<div style={styles.rowLast}>
|
|
457
|
+
<span style={styles.label}>Storage Layout</span>
|
|
458
|
+
<span style={styles.value}>v1</span>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
{/* Runner Status Section */}
|
|
464
|
+
<div style={styles.section}>
|
|
465
|
+
<div style={styles.sectionHeader}>
|
|
466
|
+
<span style={styles.sectionTitle}>Runner Status</span>
|
|
467
|
+
<div style={styles.sectionLine} />
|
|
468
|
+
<span style={{ fontSize: '10px', color: colors.textDim, marginLeft: '8px' }}>
|
|
469
|
+
Auto-refresh: 2s
|
|
470
|
+
</span>
|
|
471
|
+
</div>
|
|
472
|
+
<div style={styles.card}>
|
|
473
|
+
<div style={styles.row}>
|
|
474
|
+
<span style={styles.label}>Runner Enabled</span>
|
|
475
|
+
<span style={styles.value}>{runnerStatus?.enabled ? 'Yes' : 'No'}</span>
|
|
476
|
+
</div>
|
|
477
|
+
<div style={styles.row}>
|
|
478
|
+
<span style={styles.label}>Runner State</span>
|
|
479
|
+
<span
|
|
480
|
+
style={{
|
|
481
|
+
...styles.badge,
|
|
482
|
+
...(runnerStatus?.state === 'running'
|
|
483
|
+
? styles.badgeGreen
|
|
484
|
+
: runnerStatus?.state === 'error'
|
|
485
|
+
? { background: darkMode ? 'rgba(255, 69, 58, 0.15)' : 'rgba(255, 59, 48, 0.12)', color: colors.red }
|
|
486
|
+
: styles.badgeOrange),
|
|
487
|
+
}}
|
|
488
|
+
>
|
|
489
|
+
<span style={styles.dot} />
|
|
490
|
+
{runnerStatus?.state === 'running' ? 'Running' : runnerStatus?.state === 'error' ? 'Error' : 'Stopped'}
|
|
491
|
+
</span>
|
|
492
|
+
</div>
|
|
493
|
+
<div style={styles.row}>
|
|
494
|
+
<span style={styles.label}>Runner ID</span>
|
|
495
|
+
<span style={styles.value}>{shortId(runnerStatus?.runnerId ?? null)}</span>
|
|
496
|
+
</div>
|
|
497
|
+
<div style={styles.row}>
|
|
498
|
+
<span style={styles.label}>Engine URL</span>
|
|
499
|
+
<span style={runnerStatus?.engineUrl ? styles.value : styles.valueMuted}>
|
|
500
|
+
{runnerStatus?.engineUrl || 'Not configured'}
|
|
501
|
+
</span>
|
|
502
|
+
</div>
|
|
503
|
+
<div style={styles.row}>
|
|
504
|
+
<span style={styles.label}>Last Poll</span>
|
|
505
|
+
<span style={runnerStatus?.lastPollAt ? styles.value : styles.valueMuted}>
|
|
506
|
+
{timeAgo(runnerStatus?.lastPollAt ?? null)}
|
|
507
|
+
</span>
|
|
508
|
+
</div>
|
|
509
|
+
<div style={styles.row}>
|
|
510
|
+
<span style={styles.label}>Last Claim</span>
|
|
511
|
+
<span style={runnerStatus?.lastClaimAt ? styles.value : styles.valueMuted}>
|
|
512
|
+
{runnerStatus?.lastClaimAt
|
|
513
|
+
? `${timeAgo(runnerStatus.lastClaimAt)} · ${shortId(runnerStatus.lastTaskId ?? null)}`
|
|
514
|
+
: '—'}
|
|
515
|
+
</span>
|
|
516
|
+
</div>
|
|
517
|
+
<div style={styles.row}>
|
|
518
|
+
<span style={styles.label}>Last Complete</span>
|
|
519
|
+
<span style={runnerStatus?.lastCompleteAt ? styles.value : styles.valueMuted}>
|
|
520
|
+
{runnerStatus?.lastCompleteAt
|
|
521
|
+
? `${timeAgo(runnerStatus.lastCompleteAt)} · ${shortId(runnerStatus.lastTaskId ?? null)}`
|
|
522
|
+
: '—'}
|
|
523
|
+
</span>
|
|
524
|
+
</div>
|
|
525
|
+
<div style={styles.rowLast}>
|
|
526
|
+
<span style={styles.label}>Last Error</span>
|
|
527
|
+
<span style={runnerStatus?.lastError ? { ...styles.value, color: colors.red } : styles.valueMuted}>
|
|
528
|
+
{runnerStatus?.lastError || '—'}
|
|
529
|
+
</span>
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function formatHomeState(state: string | undefined): string {
|
|
538
|
+
switch (state) {
|
|
539
|
+
case 'HOME_GRANTED':
|
|
540
|
+
return 'Granted';
|
|
541
|
+
case 'AUTHENTICATED_NO_HOME_GRANT':
|
|
542
|
+
return 'Awaiting Grant';
|
|
543
|
+
case 'BOOTSTRAP_PRE_LOGIN':
|
|
544
|
+
return 'Pre-Login';
|
|
545
|
+
default:
|
|
546
|
+
return 'Unknown';
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function ShieldIcon(): ReactElement {
|
|
551
|
+
return (
|
|
552
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
553
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
554
|
+
<path d="M9 12l2 2 4-4" />
|
|
555
|
+
</svg>
|
|
556
|
+
);
|
|
557
|
+
}
|