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,224 @@
|
|
|
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, maximum-scale=1, user-scalable=no">
|
|
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="Sign in to Agent Dev">
|
|
10
|
+
<meta property="og:title" content="Login - Agent Dev">
|
|
11
|
+
<meta property="og:description" content="Sign in to Agent Dev">
|
|
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="Login - Agent Dev">
|
|
16
|
+
<meta name="twitter:description" content="Sign in to Agent Dev">
|
|
17
|
+
<meta name="twitter:image" content="/og-image.svg">
|
|
18
|
+
<title>Login - Agent Dev</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
|
+
.login-container {
|
|
25
|
+
min-height: 100vh;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
padding: 20px;
|
|
30
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.login-box {
|
|
34
|
+
background: rgba(30, 30, 50, 0.95);
|
|
35
|
+
border-radius: 16px;
|
|
36
|
+
padding: 40px;
|
|
37
|
+
width: 100%;
|
|
38
|
+
max-width: 400px;
|
|
39
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
40
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.login-logo {
|
|
44
|
+
text-align: center;
|
|
45
|
+
margin-bottom: 30px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.login-logo h1 {
|
|
49
|
+
color: #fff;
|
|
50
|
+
font-size: 28px;
|
|
51
|
+
margin: 0;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.login-logo p {
|
|
56
|
+
color: #888;
|
|
57
|
+
margin: 8px 0 0;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.login-form .form-group {
|
|
62
|
+
margin-bottom: 20px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.login-form label {
|
|
66
|
+
display: block;
|
|
67
|
+
color: #aaa;
|
|
68
|
+
font-size: 13px;
|
|
69
|
+
margin-bottom: 8px;
|
|
70
|
+
text-transform: uppercase;
|
|
71
|
+
letter-spacing: 0.5px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.login-form input {
|
|
75
|
+
width: 100%;
|
|
76
|
+
padding: 14px 16px;
|
|
77
|
+
background: rgba(255, 255, 255, 0.05);
|
|
78
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
79
|
+
border-radius: 8px;
|
|
80
|
+
color: #fff;
|
|
81
|
+
font-size: 15px;
|
|
82
|
+
transition: all 0.2s;
|
|
83
|
+
box-sizing: border-box;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.login-form input:focus {
|
|
87
|
+
outline: none;
|
|
88
|
+
border-color: #4ade80;
|
|
89
|
+
background: rgba(255, 255, 255, 0.08);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.login-form input::placeholder {
|
|
93
|
+
color: #666;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.login-btn {
|
|
97
|
+
width: 100%;
|
|
98
|
+
padding: 14px;
|
|
99
|
+
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
|
|
100
|
+
border: none;
|
|
101
|
+
border-radius: 8px;
|
|
102
|
+
color: #000;
|
|
103
|
+
font-size: 15px;
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
transition: all 0.2s;
|
|
107
|
+
margin-top: 10px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.login-btn:hover {
|
|
111
|
+
transform: translateY(-2px);
|
|
112
|
+
box-shadow: 0 8px 25px rgba(74, 222, 128, 0.3);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.login-btn:active {
|
|
116
|
+
transform: translateY(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.login-btn:disabled {
|
|
120
|
+
background: #444;
|
|
121
|
+
cursor: not-allowed;
|
|
122
|
+
transform: none;
|
|
123
|
+
box-shadow: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.login-error {
|
|
127
|
+
background: rgba(239, 68, 68, 0.1);
|
|
128
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
129
|
+
color: #ef4444;
|
|
130
|
+
padding: 12px 16px;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
margin-bottom: 20px;
|
|
133
|
+
font-size: 14px;
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.login-error.visible {
|
|
138
|
+
display: block;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
141
|
+
</head>
|
|
142
|
+
<body>
|
|
143
|
+
<div class="login-container">
|
|
144
|
+
<div class="login-box">
|
|
145
|
+
<div class="login-logo">
|
|
146
|
+
<img src="/favicon.svg" width="48" height="48" alt="Agent Dev" style="margin-bottom: 12px;">
|
|
147
|
+
<h1>Agent Dev</h1>
|
|
148
|
+
<p>Multi-agent workflow dashboard</p>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="login-error" id="loginError"></div>
|
|
152
|
+
|
|
153
|
+
<form class="login-form" id="loginForm" onsubmit="handleLogin(event)">
|
|
154
|
+
<div class="form-group">
|
|
155
|
+
<label for="username">Email</label>
|
|
156
|
+
<input type="email" id="username" name="username" placeholder="Enter your email" required autofocus>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="form-group">
|
|
160
|
+
<label for="password">Password</label>
|
|
161
|
+
<input type="password" id="password" name="password" placeholder="Enter your password" required>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<button type="submit" class="login-btn" id="loginBtn">Sign In</button>
|
|
165
|
+
</form>
|
|
166
|
+
|
|
167
|
+
<div style="text-align: center; margin-top: 24px; padding-top: 24px; border-top: 1px solid rgba(255, 255, 255, 0.1);">
|
|
168
|
+
<p style="color: rgba(255, 255, 255, 0.6); font-size: 14px; margin: 0 0 8px 0;">
|
|
169
|
+
Don't have an account? <a href="/register" style="color: #ff6b6b; text-decoration: none; font-weight: 500;">Sign up</a>
|
|
170
|
+
</p>
|
|
171
|
+
<p style="color: rgba(255, 255, 255, 0.6); font-size: 14px; margin: 0 0 8px 0;">
|
|
172
|
+
<a href="/reset-password" style="color: rgba(255, 255, 255, 0.5); text-decoration: none;">Forgot password?</a>
|
|
173
|
+
</p>
|
|
174
|
+
<p style="color: rgba(255, 255, 255, 0.6); font-size: 14px; margin: 0;">
|
|
175
|
+
<a href="/docs" style="color: #4ade80; text-decoration: none; font-weight: 500;">
|
|
176
|
+
<svg viewBox="0 0 24 24" width="14" height="14" style="display: inline-block; vertical-align: middle; margin-right: 4px;">
|
|
177
|
+
<path fill="currentColor" d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>
|
|
178
|
+
</svg>
|
|
179
|
+
Documentation
|
|
180
|
+
</a>
|
|
181
|
+
</p>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<script>
|
|
187
|
+
async function handleLogin(e) {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
|
|
190
|
+
const username = document.getElementById('username').value;
|
|
191
|
+
const password = document.getElementById('password').value;
|
|
192
|
+
const loginBtn = document.getElementById('loginBtn');
|
|
193
|
+
const loginError = document.getElementById('loginError');
|
|
194
|
+
|
|
195
|
+
loginBtn.disabled = true;
|
|
196
|
+
loginBtn.textContent = 'Signing in...';
|
|
197
|
+
loginError.classList.remove('visible');
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const res = await fetch('/api/login', {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: { 'Content-Type': 'application/json' },
|
|
203
|
+
body: JSON.stringify({ username, password })
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const data = await res.json();
|
|
207
|
+
|
|
208
|
+
if (data.success) {
|
|
209
|
+
window.location.href = '/';
|
|
210
|
+
} else {
|
|
211
|
+
loginError.textContent = data.error || 'Invalid credentials';
|
|
212
|
+
loginError.classList.add('visible');
|
|
213
|
+
}
|
|
214
|
+
} catch (err) {
|
|
215
|
+
loginError.textContent = 'Connection error. Please try again.';
|
|
216
|
+
loginError.classList.add('visible');
|
|
217
|
+
} finally {
|
|
218
|
+
loginBtn.disabled = false;
|
|
219
|
+
loginBtn.textContent = 'Sign In';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
@@ -0,0 +1,394 @@
|
|
|
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="Agent Dev profile settings">
|
|
10
|
+
<meta property="og:title" content="Profile - Agent Dev">
|
|
11
|
+
<meta property="og:description" content="Agent Dev profile settings">
|
|
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="Profile - Agent Dev">
|
|
16
|
+
<meta name="twitter:description" content="Agent Dev profile settings">
|
|
17
|
+
<meta name="twitter:image" content="/og-image.svg">
|
|
18
|
+
<title>Profile - 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
|
+
/* Override dashboard styles for profile page */
|
|
25
|
+
body {
|
|
26
|
+
overflow: auto !important;
|
|
27
|
+
overflow-y: scroll !important;
|
|
28
|
+
display: block !important;
|
|
29
|
+
min-height: auto !important;
|
|
30
|
+
height: auto !important;
|
|
31
|
+
flex-direction: initial !important;
|
|
32
|
+
flex: initial !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
html {
|
|
36
|
+
overflow: auto !important;
|
|
37
|
+
overflow-y: scroll !important;
|
|
38
|
+
height: auto !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.profile-container {
|
|
42
|
+
max-width: 800px;
|
|
43
|
+
margin: 2rem auto;
|
|
44
|
+
padding: 0 1rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.profile-section {
|
|
48
|
+
background: white;
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
padding: 2rem;
|
|
51
|
+
margin-bottom: 2rem;
|
|
52
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.profile-section h2 {
|
|
56
|
+
margin-top: 0;
|
|
57
|
+
border-bottom: 2px solid #007bff;
|
|
58
|
+
padding-bottom: 0.5rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.form-group {
|
|
62
|
+
margin-bottom: 1.5rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.form-group label {
|
|
66
|
+
display: block;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
margin-bottom: 0.5rem;
|
|
69
|
+
color: #333;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.form-group input, .form-group select {
|
|
73
|
+
width: 100%;
|
|
74
|
+
padding: 0.75rem;
|
|
75
|
+
border: 1px solid #ddd;
|
|
76
|
+
border-radius: 4px;
|
|
77
|
+
font-size: 1rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.form-group input:focus {
|
|
81
|
+
outline: none;
|
|
82
|
+
border-color: #007bff;
|
|
83
|
+
box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.btn {
|
|
87
|
+
background: #007bff;
|
|
88
|
+
color: white;
|
|
89
|
+
border: none;
|
|
90
|
+
padding: 0.75rem 1.5rem;
|
|
91
|
+
border-radius: 4px;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
font-size: 1rem;
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.btn:hover {
|
|
98
|
+
background: #0056b3;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.btn-secondary {
|
|
102
|
+
background: #6c757d;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.btn-secondary:hover {
|
|
106
|
+
background: #545b62;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.help-text {
|
|
110
|
+
font-size: 0.875rem;
|
|
111
|
+
color: #666;
|
|
112
|
+
margin-top: 0.25rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
details {
|
|
116
|
+
margin-top: 1rem;
|
|
117
|
+
padding: 1rem;
|
|
118
|
+
background: #f8f9fa;
|
|
119
|
+
border-radius: 4px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
summary {
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
color: #007bff;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
details ol {
|
|
129
|
+
margin-top: 0.5rem;
|
|
130
|
+
padding-left: 1.5rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
details li {
|
|
134
|
+
margin-bottom: 0.5rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.success-message {
|
|
138
|
+
background: #d4edda;
|
|
139
|
+
color: #155724;
|
|
140
|
+
padding: 1rem;
|
|
141
|
+
border-radius: 4px;
|
|
142
|
+
margin-bottom: 1rem;
|
|
143
|
+
display: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.error-message {
|
|
147
|
+
background: #f8d7da;
|
|
148
|
+
color: #721c24;
|
|
149
|
+
padding: 1rem;
|
|
150
|
+
border-radius: 4px;
|
|
151
|
+
margin-bottom: 1rem;
|
|
152
|
+
display: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.agents-list {
|
|
156
|
+
list-style: none;
|
|
157
|
+
padding: 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.agent-item {
|
|
161
|
+
display: flex;
|
|
162
|
+
justify-content: space-between;
|
|
163
|
+
align-items: center;
|
|
164
|
+
padding: 1rem;
|
|
165
|
+
background: #f8f9fa;
|
|
166
|
+
border-radius: 4px;
|
|
167
|
+
margin-bottom: 0.5rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.agent-status {
|
|
171
|
+
display: inline-block;
|
|
172
|
+
padding: 0.25rem 0.75rem;
|
|
173
|
+
border-radius: 12px;
|
|
174
|
+
font-size: 0.875rem;
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.agent-status.online { background: #d4edda; color: #155724; }
|
|
179
|
+
.agent-status.idle { background: #fff3cd; color: #856404; }
|
|
180
|
+
.agent-status.busy { background: #cce5ff; color: #004085; }
|
|
181
|
+
.agent-status.offline { background: #f8d7da; color: #721c24; }
|
|
182
|
+
|
|
183
|
+
.nav-link {
|
|
184
|
+
color: #007bff;
|
|
185
|
+
text-decoration: none;
|
|
186
|
+
margin-right: 1rem;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.nav-link:hover {
|
|
190
|
+
text-decoration: underline;
|
|
191
|
+
}
|
|
192
|
+
</style>
|
|
193
|
+
</head>
|
|
194
|
+
<body>
|
|
195
|
+
<div class="profile-container">
|
|
196
|
+
<nav style="margin-bottom: 2rem;">
|
|
197
|
+
<a href="/" class="nav-link">← Back to Dashboard</a>
|
|
198
|
+
<a href="#" onclick="logout()" class="nav-link">Logout</a>
|
|
199
|
+
</nav>
|
|
200
|
+
|
|
201
|
+
<div id="success-message" class="success-message"></div>
|
|
202
|
+
<div id="error-message" class="error-message"></div>
|
|
203
|
+
|
|
204
|
+
<!-- GitHub Personal Access Token Section -->
|
|
205
|
+
<div class="profile-section">
|
|
206
|
+
<h2>GitHub Personal Access Token</h2>
|
|
207
|
+
<p>Configure your GitHub Personal Access Token for creating tickets and managing GitHub operations.</p>
|
|
208
|
+
|
|
209
|
+
<form id="token-form">
|
|
210
|
+
<div class="form-group">
|
|
211
|
+
<label for="github-token">Personal Access Token</label>
|
|
212
|
+
<input type="password" id="github-token" name="github_token" placeholder="ghp_xxxxxxxxxxxx">
|
|
213
|
+
<div class="help-text">Your token is encrypted before storage and never exposed</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<button type="submit" class="btn">Save GitHub Token</button>
|
|
217
|
+
</form>
|
|
218
|
+
|
|
219
|
+
<details>
|
|
220
|
+
<summary>How to create a GitHub Personal Access Token</summary>
|
|
221
|
+
<ol>
|
|
222
|
+
<li>Go to <a href="https://github.com/settings/tokens/new" target="_blank">GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)</a></li>
|
|
223
|
+
<li>Click "Generate new token (classic)"</li>
|
|
224
|
+
<li>Set <strong>Note:</strong> AgentDev Token</li>
|
|
225
|
+
<li>Set <strong>Expiration:</strong> No expiration (or your preference)</li>
|
|
226
|
+
<li>Select the following <strong>scopes:</strong>
|
|
227
|
+
<ul>
|
|
228
|
+
<li><code>repo</code> - Full control of private repositories</li>
|
|
229
|
+
<li><code>read:org</code> - Read org and team membership</li>
|
|
230
|
+
<li><code>project</code> - Full control of projects</li>
|
|
231
|
+
</ul>
|
|
232
|
+
</li>
|
|
233
|
+
<li>Click "Generate token"</li>
|
|
234
|
+
<li>Copy the token (starts with <code>ghp_</code>) and paste it above</li>
|
|
235
|
+
<li><strong>Important:</strong> Save the token securely - GitHub won't show it again</li>
|
|
236
|
+
</ol>
|
|
237
|
+
</details>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<!-- Agent Limits Section -->
|
|
241
|
+
<div class="profile-section">
|
|
242
|
+
<h2>Agent Limits</h2>
|
|
243
|
+
<form id="limits-form">
|
|
244
|
+
<div class="form-group">
|
|
245
|
+
<label for="max-agents">Maximum Concurrent Agents</label>
|
|
246
|
+
<input type="number" id="max-agents" name="max_agents" value="3" min="1" max="10">
|
|
247
|
+
<div class="help-text">How many agents can run simultaneously (1-10)</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<button type="submit" class="btn">Save Limits</button>
|
|
251
|
+
</form>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<!-- Registered Agents Section -->
|
|
255
|
+
<div class="profile-section">
|
|
256
|
+
<h2>Registered Agents</h2>
|
|
257
|
+
<ul id="agents-list" class="agents-list">
|
|
258
|
+
<li style="text-align: center; color: #999;">Loading agents...</li>
|
|
259
|
+
</ul>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<script>
|
|
264
|
+
// Load user profile
|
|
265
|
+
async function loadProfile() {
|
|
266
|
+
try {
|
|
267
|
+
const res = await fetch('/api/profile');
|
|
268
|
+
if (res.ok) {
|
|
269
|
+
const data = await res.json();
|
|
270
|
+
|
|
271
|
+
// Populate limits form
|
|
272
|
+
document.getElementById('max-agents').value = data.max_agents || 3;
|
|
273
|
+
|
|
274
|
+
// Load agents
|
|
275
|
+
loadAgents();
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error('Failed to load profile:', error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Load registered agents
|
|
283
|
+
async function loadAgents() {
|
|
284
|
+
try {
|
|
285
|
+
const res = await fetch('/api/agents');
|
|
286
|
+
if (res.ok) {
|
|
287
|
+
const agents = await res.json();
|
|
288
|
+
const list = document.getElementById('agents-list');
|
|
289
|
+
|
|
290
|
+
if (agents.length === 0) {
|
|
291
|
+
list.innerHTML = '<li style="text-align: center; color: #999;">No agents registered yet</li>';
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
list.innerHTML = agents.map(agent => `
|
|
296
|
+
<li class="agent-item">
|
|
297
|
+
<div>
|
|
298
|
+
<strong>${agent.name}</strong>
|
|
299
|
+
${agent.hostname ? `<div style="font-size: 0.875rem; color: #666;">${agent.hostname}</div>` : ''}
|
|
300
|
+
${agent.last_heartbeat ? `<div style="font-size: 0.75rem; color: #999;">Last seen: ${new Date(agent.last_heartbeat).toLocaleString()}</div>` : ''}
|
|
301
|
+
</div>
|
|
302
|
+
<span class="agent-status ${agent.status}">${agent.status}</span>
|
|
303
|
+
</li>
|
|
304
|
+
`).join('');
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error('Failed to load agents:', error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Save GitHub token
|
|
312
|
+
document.getElementById('token-form').addEventListener('submit', async (e) => {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
|
|
315
|
+
const githubToken = document.getElementById('github-token').value;
|
|
316
|
+
|
|
317
|
+
if (!githubToken || !githubToken.trim()) {
|
|
318
|
+
showError('Please enter a GitHub token');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const res = await fetch('/api/profile/token', {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
headers: { 'Content-Type': 'application/json' },
|
|
326
|
+
body: JSON.stringify({ github_token: githubToken })
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (res.ok) {
|
|
330
|
+
showSuccess('GitHub token saved successfully!');
|
|
331
|
+
// Clear token field for security
|
|
332
|
+
document.getElementById('github-token').value = '';
|
|
333
|
+
} else {
|
|
334
|
+
const error = await res.json();
|
|
335
|
+
showError(error.error || 'Failed to save GitHub token');
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
showError('Network error: ' + error.message);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Save limits
|
|
343
|
+
document.getElementById('limits-form').addEventListener('submit', async (e) => {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
|
|
346
|
+
const formData = {
|
|
347
|
+
max_agents: parseInt(document.getElementById('max-agents').value)
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const res = await fetch('/api/profile/limits', {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: { 'Content-Type': 'application/json' },
|
|
354
|
+
body: JSON.stringify(formData)
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (res.ok) {
|
|
358
|
+
showSuccess('Limits saved successfully!');
|
|
359
|
+
} else {
|
|
360
|
+
const error = await res.json();
|
|
361
|
+
showError(error.error || 'Failed to save limits');
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
showError('Network error: ' + error.message);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
function showSuccess(message) {
|
|
369
|
+
const el = document.getElementById('success-message');
|
|
370
|
+
el.textContent = message;
|
|
371
|
+
el.style.display = 'block';
|
|
372
|
+
setTimeout(() => { el.style.display = 'none'; }, 5000);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function showError(message) {
|
|
376
|
+
const el = document.getElementById('error-message');
|
|
377
|
+
el.textContent = message;
|
|
378
|
+
el.style.display = 'block';
|
|
379
|
+
setTimeout(() => { el.style.display = 'none'; }, 5000);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function logout() {
|
|
383
|
+
await fetch('/api/logout', { method: 'POST' });
|
|
384
|
+
window.location.href = '/login';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Load profile on page load
|
|
388
|
+
loadProfile();
|
|
389
|
+
|
|
390
|
+
// Refresh agents every 30 seconds
|
|
391
|
+
setInterval(loadAgents, 30000);
|
|
392
|
+
</script>
|
|
393
|
+
</body>
|
|
394
|
+
</html>
|