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,226 @@
|
|
|
1
|
+
const Redis = require('ioredis');
|
|
2
|
+
const config = require('./config');
|
|
3
|
+
|
|
4
|
+
// Two Redis clients: one for pub/sub, one for regular commands
|
|
5
|
+
let pubClient = null;
|
|
6
|
+
let subClient = null;
|
|
7
|
+
let subscribers = new Map(); // channel -> Set of callback functions
|
|
8
|
+
|
|
9
|
+
function connect() {
|
|
10
|
+
if (pubClient && subClient) {
|
|
11
|
+
return { pubClient, subClient };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const redisConfig = {
|
|
15
|
+
host: config.REDIS_HOST || 'localhost',
|
|
16
|
+
port: config.REDIS_PORT || 6379,
|
|
17
|
+
db: config.REDIS_DB || 3,
|
|
18
|
+
retryStrategy: (times) => {
|
|
19
|
+
const delay = Math.min(times * 50, 2000);
|
|
20
|
+
return delay;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
pubClient = new Redis(redisConfig);
|
|
25
|
+
subClient = new Redis(redisConfig);
|
|
26
|
+
|
|
27
|
+
pubClient.on('error', (err) => console.error('Redis Pub Client Error:', err));
|
|
28
|
+
subClient.on('error', (err) => console.error('Redis Sub Client Error:', err));
|
|
29
|
+
|
|
30
|
+
// Handle incoming messages
|
|
31
|
+
subClient.on('message', (channel, message) => {
|
|
32
|
+
const callbacks = subscribers.get(channel);
|
|
33
|
+
if (callbacks) {
|
|
34
|
+
try {
|
|
35
|
+
const data = JSON.parse(message);
|
|
36
|
+
callbacks.forEach(cb => cb(data));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error('Error parsing Redis message:', e);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Handle pattern messages (for wildcard subscriptions)
|
|
44
|
+
subClient.on('pmessage', (pattern, channel, message) => {
|
|
45
|
+
const callbacks = subscribers.get(pattern);
|
|
46
|
+
if (callbacks) {
|
|
47
|
+
try {
|
|
48
|
+
const data = JSON.parse(message);
|
|
49
|
+
// Extract agentId from channel (agentdev:logs:{agentId})
|
|
50
|
+
const agentId = data.agent || channel.replace('agentdev:logs:', '');
|
|
51
|
+
callbacks.forEach(cb => cb(agentId, data));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error('Error parsing Redis pmessage:', e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return { pubClient, subClient };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Publish a log message to Redis channel
|
|
63
|
+
* @param {string} agentId - Agent identifier
|
|
64
|
+
* @param {object} logData - Log data { content, level, timestamp }
|
|
65
|
+
*/
|
|
66
|
+
async function publishLog(agentId, logData) {
|
|
67
|
+
const { pubClient } = connect();
|
|
68
|
+
const channel = `agentdev:logs:${agentId}`;
|
|
69
|
+
const message = JSON.stringify({
|
|
70
|
+
agent: agentId,
|
|
71
|
+
timestamp: logData.timestamp || new Date().toISOString(),
|
|
72
|
+
level: logData.level || 'INFO',
|
|
73
|
+
content: logData.content
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await pubClient.publish(channel, message);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Subscribe to agent logs
|
|
81
|
+
* @param {string} agentId - Agent identifier (or '*' for all agents)
|
|
82
|
+
* @param {function} callback - Callback function(data, channel)
|
|
83
|
+
*/
|
|
84
|
+
async function subscribeToLogs(agentId, callback) {
|
|
85
|
+
const { subClient } = connect();
|
|
86
|
+
|
|
87
|
+
if (agentId === '*') {
|
|
88
|
+
// Subscribe to all agent logs with pattern
|
|
89
|
+
const pattern = 'agentdev:logs:*';
|
|
90
|
+
if (!subscribers.has(pattern)) {
|
|
91
|
+
subscribers.set(pattern, new Set());
|
|
92
|
+
await subClient.psubscribe(pattern);
|
|
93
|
+
}
|
|
94
|
+
subscribers.get(pattern).add(callback);
|
|
95
|
+
return pattern;
|
|
96
|
+
} else {
|
|
97
|
+
// Subscribe to specific agent
|
|
98
|
+
const channel = `agentdev:logs:${agentId}`;
|
|
99
|
+
if (!subscribers.has(channel)) {
|
|
100
|
+
subscribers.set(channel, new Set());
|
|
101
|
+
await subClient.subscribe(channel);
|
|
102
|
+
}
|
|
103
|
+
subscribers.get(channel).add(callback);
|
|
104
|
+
return channel;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Unsubscribe from agent logs
|
|
110
|
+
* @param {string} channelOrPattern - Channel or pattern returned from subscribeToLogs
|
|
111
|
+
* @param {function} callback - Callback function to remove
|
|
112
|
+
*/
|
|
113
|
+
async function unsubscribeFromLogs(channelOrPattern, callback) {
|
|
114
|
+
const { subClient } = connect();
|
|
115
|
+
const callbacks = subscribers.get(channelOrPattern);
|
|
116
|
+
|
|
117
|
+
if (callbacks) {
|
|
118
|
+
callbacks.delete(callback);
|
|
119
|
+
|
|
120
|
+
// If no more callbacks, unsubscribe from channel
|
|
121
|
+
if (callbacks.size === 0) {
|
|
122
|
+
subscribers.delete(channelOrPattern);
|
|
123
|
+
if (channelOrPattern.includes('*')) {
|
|
124
|
+
await subClient.punsubscribe(channelOrPattern);
|
|
125
|
+
} else {
|
|
126
|
+
await subClient.unsubscribe(channelOrPattern);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Publish agent status update
|
|
134
|
+
* @param {object} statusData - Status data { agents: [...], history: [...] }
|
|
135
|
+
*/
|
|
136
|
+
async function publishAgentStatus(statusData) {
|
|
137
|
+
const { pubClient } = connect();
|
|
138
|
+
const channel = 'agentdev:agents:status';
|
|
139
|
+
const message = JSON.stringify(statusData);
|
|
140
|
+
await pubClient.publish(channel, message);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Subscribe to agent status updates
|
|
145
|
+
* @param {function} callback - Callback function(data)
|
|
146
|
+
*/
|
|
147
|
+
async function subscribeToAgentStatus(callback) {
|
|
148
|
+
const { subClient } = connect();
|
|
149
|
+
const channel = 'agentdev:agents:status';
|
|
150
|
+
|
|
151
|
+
if (!subscribers.has(channel)) {
|
|
152
|
+
subscribers.set(channel, new Set());
|
|
153
|
+
await subClient.subscribe(channel);
|
|
154
|
+
}
|
|
155
|
+
subscribers.get(channel).add(callback);
|
|
156
|
+
return channel;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Store value in Redis with optional TTL
|
|
161
|
+
* @param {string} key - Redis key
|
|
162
|
+
* @param {string|object} value - Value to store (objects are JSON stringified)
|
|
163
|
+
* @param {number} ttl - Time to live in seconds (optional)
|
|
164
|
+
*/
|
|
165
|
+
async function set(key, value, ttl) {
|
|
166
|
+
const { pubClient } = connect();
|
|
167
|
+
const stringValue = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
168
|
+
|
|
169
|
+
if (ttl) {
|
|
170
|
+
await pubClient.setex(key, ttl, stringValue);
|
|
171
|
+
} else {
|
|
172
|
+
await pubClient.set(key, stringValue);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get value from Redis
|
|
178
|
+
* @param {string} key - Redis key
|
|
179
|
+
* @returns {string|object|null} - Value (auto-parsed if JSON)
|
|
180
|
+
*/
|
|
181
|
+
async function get(key) {
|
|
182
|
+
const { pubClient } = connect();
|
|
183
|
+
const value = await pubClient.get(key);
|
|
184
|
+
|
|
185
|
+
if (!value) return null;
|
|
186
|
+
|
|
187
|
+
// Try to parse as JSON
|
|
188
|
+
try {
|
|
189
|
+
return JSON.parse(value);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Delete key from Redis
|
|
197
|
+
* @param {string} key - Redis key
|
|
198
|
+
*/
|
|
199
|
+
async function del(key) {
|
|
200
|
+
const { pubClient } = connect();
|
|
201
|
+
await pubClient.del(key);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Close Redis connections
|
|
206
|
+
*/
|
|
207
|
+
function disconnect() {
|
|
208
|
+
if (pubClient) pubClient.quit();
|
|
209
|
+
if (subClient) subClient.quit();
|
|
210
|
+
pubClient = null;
|
|
211
|
+
subClient = null;
|
|
212
|
+
subscribers.clear();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
connect,
|
|
217
|
+
disconnect,
|
|
218
|
+
publishLog,
|
|
219
|
+
subscribeToLogs,
|
|
220
|
+
unsubscribeFromLogs,
|
|
221
|
+
publishAgentStatus,
|
|
222
|
+
subscribeToAgentStatus,
|
|
223
|
+
set,
|
|
224
|
+
get,
|
|
225
|
+
del
|
|
226
|
+
};
|