agentdev-webui 1.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/lib/agent-api.js +530 -0
- package/lib/auth.js +127 -0
- package/lib/config.js +53 -0
- package/lib/database.js +762 -0
- package/lib/device-flow.js +257 -0
- package/lib/email.js +420 -0
- package/lib/encryption.js +112 -0
- package/lib/github.js +339 -0
- package/lib/history.js +143 -0
- package/lib/pwa.js +107 -0
- package/lib/redis-logs.js +226 -0
- package/lib/routes.js +680 -0
- package/migrations/000_create_database.sql +33 -0
- package/migrations/001_create_agentdev_schema.sql +135 -0
- package/migrations/001_create_agentdev_schema.sql.old +100 -0
- package/migrations/001_create_agentdev_schema_fixed.sql +135 -0
- package/migrations/002_add_github_token.sql +17 -0
- package/migrations/003_add_agent_logs_table.sql +23 -0
- package/migrations/004_remove_oauth_columns.sql +11 -0
- package/migrations/005_add_projects.sql +44 -0
- package/migrations/006_project_github_token.sql +7 -0
- package/migrations/007_project_repositories.sql +12 -0
- package/migrations/008_add_notifications.sql +20 -0
- package/migrations/009_unified_oauth.sql +153 -0
- package/migrations/README.md +97 -0
- package/package.json +37 -0
- package/public/css/styles.css +1140 -0
- package/public/device.html +384 -0
- package/public/docs.html +862 -0
- package/public/docs.md +697 -0
- package/public/favicon.svg +5 -0
- package/public/index.html +271 -0
- package/public/js/app.js +2379 -0
- package/public/login.html +224 -0
- package/public/profile.html +394 -0
- package/public/register.html +392 -0
- package/public/reset-password.html +349 -0
- package/public/verify-email.html +177 -0
- package/server.js +1450 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="theme-color" content="#1a1a2e">
|
|
7
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
8
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
9
|
+
<meta name="description" content="Authorize an Agent Dev agent">
|
|
10
|
+
<meta property="og:title" content="Authorize Agent - Agent Dev">
|
|
11
|
+
<meta property="og:description" content="Authorize an Agent Dev agent">
|
|
12
|
+
<meta property="og:type" content="website">
|
|
13
|
+
<meta property="og:image" content="/og-image.svg">
|
|
14
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
15
|
+
<meta name="twitter:title" content="Authorize Agent - Agent Dev">
|
|
16
|
+
<meta name="twitter:description" content="Authorize an Agent Dev agent">
|
|
17
|
+
<meta name="twitter:image" content="/og-image.svg">
|
|
18
|
+
<title>Authorize Agent - AgentDev</title>
|
|
19
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
20
|
+
<link rel="apple-touch-icon" href="/icon-192.png">
|
|
21
|
+
<link rel="manifest" href="/manifest.json">
|
|
22
|
+
<link rel="stylesheet" href="/css/styles.css">
|
|
23
|
+
<style>
|
|
24
|
+
.device-container {
|
|
25
|
+
max-width: 600px;
|
|
26
|
+
margin: 4rem auto;
|
|
27
|
+
padding: 0 1rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.device-card {
|
|
31
|
+
background: white;
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
padding: 2rem;
|
|
34
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.device-card h1 {
|
|
38
|
+
margin-top: 0;
|
|
39
|
+
color: #007bff;
|
|
40
|
+
text-align: center;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.code-input-group {
|
|
44
|
+
margin: 2rem 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.code-input-group input {
|
|
48
|
+
width: 100%;
|
|
49
|
+
padding: 1rem;
|
|
50
|
+
font-size: 1.5rem;
|
|
51
|
+
text-align: center;
|
|
52
|
+
text-transform: uppercase;
|
|
53
|
+
letter-spacing: 0.5rem;
|
|
54
|
+
border: 2px solid #ddd;
|
|
55
|
+
border-radius: 4px;
|
|
56
|
+
font-family: monospace;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.code-input-group input:focus {
|
|
60
|
+
outline: none;
|
|
61
|
+
border-color: #007bff;
|
|
62
|
+
box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.btn {
|
|
66
|
+
width: 100%;
|
|
67
|
+
background: #007bff;
|
|
68
|
+
color: white;
|
|
69
|
+
border: none;
|
|
70
|
+
padding: 1rem;
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
font-size: 1.1rem;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
margin-top: 1rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.btn:hover:not(:disabled) {
|
|
79
|
+
background: #0056b3;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.btn:disabled {
|
|
83
|
+
background: #ccc;
|
|
84
|
+
cursor: not-allowed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.btn-danger {
|
|
88
|
+
background: #dc3545;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.btn-danger:hover:not(:disabled) {
|
|
92
|
+
background: #c82333;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#device-details {
|
|
96
|
+
display: none;
|
|
97
|
+
margin-top: 2rem;
|
|
98
|
+
padding: 1.5rem;
|
|
99
|
+
background: #f8f9fa;
|
|
100
|
+
border-radius: 4px;
|
|
101
|
+
border-left: 4px solid #007bff;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#device-details h3 {
|
|
105
|
+
margin-top: 0;
|
|
106
|
+
color: #333;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#device-details dl {
|
|
110
|
+
margin: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#device-details dt {
|
|
114
|
+
font-weight: 600;
|
|
115
|
+
color: #666;
|
|
116
|
+
margin-top: 0.75rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#device-details dd {
|
|
120
|
+
margin: 0.25rem 0 0 0;
|
|
121
|
+
color: #333;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.capabilities {
|
|
125
|
+
display: flex;
|
|
126
|
+
gap: 0.5rem;
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
margin-top: 0.5rem;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.capability-badge {
|
|
132
|
+
background: #e7f3ff;
|
|
133
|
+
color: #004085;
|
|
134
|
+
padding: 0.25rem 0.75rem;
|
|
135
|
+
border-radius: 12px;
|
|
136
|
+
font-size: 0.875rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.error-message {
|
|
140
|
+
background: #f8d7da;
|
|
141
|
+
color: #721c24;
|
|
142
|
+
padding: 1rem;
|
|
143
|
+
border-radius: 4px;
|
|
144
|
+
margin-bottom: 1rem;
|
|
145
|
+
display: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.success-message {
|
|
149
|
+
background: #d4edda;
|
|
150
|
+
color: #155724;
|
|
151
|
+
padding: 1rem;
|
|
152
|
+
border-radius: 4px;
|
|
153
|
+
text-align: center;
|
|
154
|
+
display: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.success-message h2 {
|
|
158
|
+
margin-top: 0;
|
|
159
|
+
color: #155724;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.loading {
|
|
163
|
+
text-align: center;
|
|
164
|
+
color: #666;
|
|
165
|
+
margin-top: 1rem;
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
<div class="device-container">
|
|
171
|
+
<div class="device-card">
|
|
172
|
+
<h1>🔐 Authorize Agent</h1>
|
|
173
|
+
|
|
174
|
+
<div id="error-message" class="error-message"></div>
|
|
175
|
+
<div id="success-message" class="success-message" style="display: none;">
|
|
176
|
+
<h2>✅ Agent Approved!</h2>
|
|
177
|
+
<p>Your agent has been successfully authorized. You can close this window and return to your terminal.</p>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<form id="device-form">
|
|
181
|
+
<div class="code-input-group">
|
|
182
|
+
<label for="user-code" style="display: block; margin-bottom: 0.5rem; font-weight: 600;">Enter the code displayed in your terminal:</label>
|
|
183
|
+
<input
|
|
184
|
+
type="text"
|
|
185
|
+
id="user-code"
|
|
186
|
+
name="user_code"
|
|
187
|
+
placeholder="ABCD-EFGH"
|
|
188
|
+
pattern="[A-Z0-9]{4}-[A-Z0-9]{4}"
|
|
189
|
+
maxlength="9"
|
|
190
|
+
required
|
|
191
|
+
autocomplete="off"
|
|
192
|
+
>
|
|
193
|
+
</div>
|
|
194
|
+
<button type="submit" class="btn" id="submit-btn">Continue</button>
|
|
195
|
+
</form>
|
|
196
|
+
|
|
197
|
+
<div id="device-details">
|
|
198
|
+
<h3>Agent Details</h3>
|
|
199
|
+
<dl>
|
|
200
|
+
<dt>Name</dt>
|
|
201
|
+
<dd id="agent-name"></dd>
|
|
202
|
+
|
|
203
|
+
<dt>Capabilities</dt>
|
|
204
|
+
<dd>
|
|
205
|
+
<div class="capabilities">
|
|
206
|
+
<span class="capability-badge" id="agent-cpu"></span>
|
|
207
|
+
<span class="capability-badge" id="agent-memory"></span>
|
|
208
|
+
</div>
|
|
209
|
+
</dd>
|
|
210
|
+
|
|
211
|
+
<dt>Assign to Project</dt>
|
|
212
|
+
<dd>
|
|
213
|
+
<select id="project-select" style="width:100%;padding:0.5rem;font-size:1rem;border:2px solid #ddd;border-radius:4px;font-family:monospace;">
|
|
214
|
+
<option value="">Loading projects...</option>
|
|
215
|
+
</select>
|
|
216
|
+
</dd>
|
|
217
|
+
</dl>
|
|
218
|
+
|
|
219
|
+
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
|
|
220
|
+
<button id="approve-btn" class="btn" style="width: auto; flex: 1;">✓ Approve Agent</button>
|
|
221
|
+
<button id="deny-btn" class="btn btn-danger" style="width: auto; flex: 1;">✗ Deny</button>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<script>
|
|
228
|
+
let currentDeviceCode = null;
|
|
229
|
+
|
|
230
|
+
// Load projects for the selector
|
|
231
|
+
async function loadProjects() {
|
|
232
|
+
try {
|
|
233
|
+
const res = await fetch('/api/projects');
|
|
234
|
+
if (res.ok) {
|
|
235
|
+
const projects = await res.json();
|
|
236
|
+
const select = document.getElementById('project-select');
|
|
237
|
+
select.innerHTML = '';
|
|
238
|
+
if (projects.length === 0) {
|
|
239
|
+
select.innerHTML = '<option value="">No projects configured</option>';
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
projects.forEach(p => {
|
|
243
|
+
const opt = document.createElement('option');
|
|
244
|
+
opt.value = p.id;
|
|
245
|
+
opt.textContent = p.name + ' (' + p.github_org + ' #' + p.project_number + ')';
|
|
246
|
+
select.appendChild(opt);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.log('Could not load projects:', e);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
loadProjects();
|
|
254
|
+
|
|
255
|
+
// Handle code entry form
|
|
256
|
+
document.getElementById('device-form').addEventListener('submit', async (e) => {
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
|
|
259
|
+
const userCode = document.getElementById('user-code').value.toUpperCase().trim();
|
|
260
|
+
|
|
261
|
+
if (!/^[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(userCode)) {
|
|
262
|
+
showError('Invalid code format. Please enter a code like: ABCD-EFGH');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
document.getElementById('submit-btn').disabled = true;
|
|
267
|
+
document.getElementById('submit-btn').textContent = 'Checking...';
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const res = await fetch('/api/device/lookup', {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
headers: { 'Content-Type': 'application/json' },
|
|
273
|
+
body: JSON.stringify({ user_code: userCode })
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (res.ok) {
|
|
277
|
+
const data = await res.json();
|
|
278
|
+
currentDeviceCode = data.device_code;
|
|
279
|
+
showDeviceDetails(data);
|
|
280
|
+
} else {
|
|
281
|
+
const error = await res.json();
|
|
282
|
+
showError(error.error || 'Invalid or expired code');
|
|
283
|
+
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
showError('Network error: ' + error.message);
|
|
286
|
+
} finally {
|
|
287
|
+
document.getElementById('submit-btn').disabled = false;
|
|
288
|
+
document.getElementById('submit-btn').textContent = 'Continue';
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Show device details
|
|
293
|
+
function showDeviceDetails(data) {
|
|
294
|
+
document.getElementById('agent-name').textContent = data.agent_name || 'Unknown';
|
|
295
|
+
|
|
296
|
+
if (data.capabilities) {
|
|
297
|
+
document.getElementById('agent-cpu').textContent = `${data.capabilities.cpu_cores || '?'} CPU cores`;
|
|
298
|
+
document.getElementById('agent-memory').textContent = `${data.capabilities.memory_gb || '?'} GB RAM`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
document.getElementById('device-form').style.display = 'none';
|
|
302
|
+
document.getElementById('device-details').style.display = 'block';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Approve agent
|
|
306
|
+
document.getElementById('approve-btn').addEventListener('click', async () => {
|
|
307
|
+
document.getElementById('approve-btn').disabled = true;
|
|
308
|
+
document.getElementById('approve-btn').textContent = 'Approving...';
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const selectedProject = document.getElementById('project-select').value;
|
|
312
|
+
const res = await fetch('/api/device/approve', {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
headers: { 'Content-Type': 'application/json' },
|
|
315
|
+
body: JSON.stringify({
|
|
316
|
+
user_code: document.getElementById('user-code').value,
|
|
317
|
+
project_id: selectedProject ? parseInt(selectedProject) : null
|
|
318
|
+
})
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (res.ok) {
|
|
322
|
+
document.getElementById('device-details').style.display = 'none';
|
|
323
|
+
document.getElementById('success-message').style.display = 'block';
|
|
324
|
+
} else {
|
|
325
|
+
const error = await res.json();
|
|
326
|
+
showError(error.error || 'Failed to approve agent');
|
|
327
|
+
document.getElementById('approve-btn').disabled = false;
|
|
328
|
+
document.getElementById('approve-btn').textContent = '✓ Approve Agent';
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
showError('Network error: ' + error.message);
|
|
332
|
+
document.getElementById('approve-btn').disabled = false;
|
|
333
|
+
document.getElementById('approve-btn').textContent = '✓ Approve Agent';
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Deny agent
|
|
338
|
+
document.getElementById('deny-btn').addEventListener('click', async () => {
|
|
339
|
+
document.getElementById('deny-btn').disabled = true;
|
|
340
|
+
document.getElementById('deny-btn').textContent = 'Denying...';
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const res = await fetch('/api/device/deny', {
|
|
344
|
+
method: 'POST',
|
|
345
|
+
headers: { 'Content-Type': 'application/json' },
|
|
346
|
+
body: JSON.stringify({ user_code: document.getElementById('user-code').value })
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
if (res.ok) {
|
|
350
|
+
showError('Agent authorization denied');
|
|
351
|
+
setTimeout(() => {
|
|
352
|
+
window.location.reload();
|
|
353
|
+
}, 2000);
|
|
354
|
+
} else {
|
|
355
|
+
const error = await res.json();
|
|
356
|
+
showError(error.error || 'Failed to deny agent');
|
|
357
|
+
document.getElementById('deny-btn').disabled = false;
|
|
358
|
+
document.getElementById('deny-btn').textContent = '✗ Deny';
|
|
359
|
+
}
|
|
360
|
+
} catch (error) {
|
|
361
|
+
showError('Network error: ' + error.message);
|
|
362
|
+
document.getElementById('deny-btn').disabled = false;
|
|
363
|
+
document.getElementById('deny-btn').textContent = '✗ Deny';
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
function showError(message) {
|
|
368
|
+
const el = document.getElementById('error-message');
|
|
369
|
+
el.textContent = message;
|
|
370
|
+
el.style.display = 'block';
|
|
371
|
+
setTimeout(() => { el.style.display = 'none'; }, 5000);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Auto-format user code input
|
|
375
|
+
document.getElementById('user-code').addEventListener('input', (e) => {
|
|
376
|
+
let value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
|
|
377
|
+
if (value.length > 4) {
|
|
378
|
+
value = value.slice(0, 4) + '-' + value.slice(4, 8);
|
|
379
|
+
}
|
|
380
|
+
e.target.value = value;
|
|
381
|
+
});
|
|
382
|
+
</script>
|
|
383
|
+
</body>
|
|
384
|
+
</html>
|