remote-coder 0.4.1__py3-none-any.whl
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.
- app/__init__.py +3 -0
- app/admin/__init__.py +0 -0
- app/admin/advanced_settings.py +88 -0
- app/admin/database_browser.py +301 -0
- app/admin/router.py +528 -0
- app/admin/static/i18n.js +401 -0
- app/admin/static/icons/advanced.svg +8 -0
- app/admin/static/icons/database.svg +5 -0
- app/admin/static/icons/download.svg +3 -0
- app/admin/static/icons/home.svg +4 -0
- app/admin/static/icons/logs.svg +3 -0
- app/admin/static/icons/projects.svg +5 -0
- app/admin/static/summary.js +73 -0
- app/admin/templates/admin.html +511 -0
- app/admin/templates/advanced.html +635 -0
- app/admin/templates/database.html +880 -0
- app/admin/templates/logs.html +686 -0
- app/admin/templates/projects.html +878 -0
- app/ai/__init__.py +0 -0
- app/ai/base.py +129 -0
- app/ai/claude.py +20 -0
- app/ai/codex.py +34 -0
- app/ai/factory.py +27 -0
- app/ai/gemini.py +20 -0
- app/ai/model_catalog.py +47 -0
- app/ai/usage.py +134 -0
- app/cli.py +238 -0
- app/config.py +130 -0
- app/git/__init__.py +0 -0
- app/git/ai_commit.py +88 -0
- app/git/branch_naming.py +21 -0
- app/git/commit_message.py +279 -0
- app/git/service.py +669 -0
- app/jobs/__init__.py +0 -0
- app/jobs/manager.py +770 -0
- app/jobs/schemas.py +116 -0
- app/jobs/store.py +334 -0
- app/main.py +265 -0
- app/models.py +20 -0
- app/monitoring/__init__.py +10 -0
- app/monitoring/code.py +161 -0
- app/monitoring/events.py +33 -0
- app/monitoring/git.py +103 -0
- app/monitoring/log_buffer.py +245 -0
- app/monitoring/memory.py +19 -0
- app/monitoring/model.py +598 -0
- app/projects/__init__.py +19 -0
- app/projects/registry.py +384 -0
- app/security/__init__.py +0 -0
- app/security/auth.py +19 -0
- app/system_startup.py +34 -0
- app/telegram/__init__.py +0 -0
- app/telegram/bot_instances.py +67 -0
- app/telegram/commands/__init__.py +64 -0
- app/telegram/commands/base.py +222 -0
- app/telegram/commands/branch.py +366 -0
- app/telegram/commands/clear_stop.py +221 -0
- app/telegram/commands/fix.py +219 -0
- app/telegram/commands/model.py +93 -0
- app/telegram/commands/monitor.py +185 -0
- app/telegram/commands/registry.py +110 -0
- app/telegram/commands/status.py +243 -0
- app/telegram/commands/system.py +201 -0
- app/telegram/confirmations.py +36 -0
- app/telegram/conversation.py +789 -0
- app/telegram/i18n.py +742 -0
- app/telegram/model_preferences.py +53 -0
- app/telegram/notifier.py +387 -0
- app/telegram/parser.py +267 -0
- app/telegram/webhook.py +988 -0
- app/telegram/webhook_registration.py +172 -0
- app/tunnel.py +104 -0
- remote_coder-0.4.1.dist-info/METADATA +520 -0
- remote_coder-0.4.1.dist-info/RECORD +78 -0
- remote_coder-0.4.1.dist-info/WHEEL +5 -0
- remote_coder-0.4.1.dist-info/entry_points.txt +2 -0
- remote_coder-0.4.1.dist-info/licenses/LICENSE +201 -0
- remote_coder-0.4.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,686 @@
|
|
|
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" />
|
|
6
|
+
<title data-i18n="title.logs">Remote AI Coder - Server Logs</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0f1419;
|
|
10
|
+
--bg-elevated: #1a2332;
|
|
11
|
+
--surface: #243044;
|
|
12
|
+
--border: rgba(255, 255, 255, 0.08);
|
|
13
|
+
--text: #e8edf4;
|
|
14
|
+
--text-muted: #8b9cb3;
|
|
15
|
+
--accent: #3d9cf5;
|
|
16
|
+
--accent-dim: rgba(61, 156, 245, 0.15);
|
|
17
|
+
--success: #34c759;
|
|
18
|
+
--success-bg: rgba(52, 199, 89, 0.12);
|
|
19
|
+
--danger: #ff453a;
|
|
20
|
+
--danger-bg: rgba(255, 69, 58, 0.12);
|
|
21
|
+
--warning: #ffcc00;
|
|
22
|
+
--radius: 12px;
|
|
23
|
+
--radius-sm: 8px;
|
|
24
|
+
--shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
|
|
25
|
+
--font: "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
26
|
+
--focus: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
margin: 0;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
font-family: var(--font);
|
|
35
|
+
font-size: 15px;
|
|
36
|
+
line-height: 1.5;
|
|
37
|
+
color: var(--text);
|
|
38
|
+
background: radial-gradient(ellipse 120% 80% at 50% -20%, #1e3a5f 0%, var(--bg) 55%);
|
|
39
|
+
padding: 1.25rem 1rem 2.5rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.wrap { max-width: 72rem; margin: 0 auto; }
|
|
43
|
+
|
|
44
|
+
.app-header {
|
|
45
|
+
margin-bottom: 1.5rem;
|
|
46
|
+
padding: 1.25rem 1.35rem;
|
|
47
|
+
background: linear-gradient(135deg, var(--bg-elevated) 0%, var(--surface) 100%);
|
|
48
|
+
border: 1px solid var(--border);
|
|
49
|
+
border-radius: var(--radius);
|
|
50
|
+
box-shadow: var(--shadow);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.app-header h1 {
|
|
54
|
+
margin: 0 0 0.35rem;
|
|
55
|
+
font-size: 1.5rem;
|
|
56
|
+
font-weight: 700;
|
|
57
|
+
letter-spacing: -0.02em;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.app-header .tagline {
|
|
61
|
+
margin: 0;
|
|
62
|
+
color: var(--text-muted);
|
|
63
|
+
font-size: 0.9rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.localhost-pill {
|
|
67
|
+
display: inline-flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: 0.35rem;
|
|
70
|
+
margin-top: 0.75rem;
|
|
71
|
+
padding: 0.35rem 0.65rem;
|
|
72
|
+
font-size: 0.8rem;
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
color: var(--warning);
|
|
75
|
+
background: rgba(255, 204, 0, 0.1);
|
|
76
|
+
border: 1px solid rgba(255, 204, 0, 0.25);
|
|
77
|
+
border-radius: 999px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.header-row {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
align-items: flex-start;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
gap: 1rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.header-icon-nav {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
gap: 0.35rem;
|
|
92
|
+
align-items: center;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header-icon-link {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
width: 2.5rem;
|
|
100
|
+
height: 2.5rem;
|
|
101
|
+
border-radius: var(--radius-sm);
|
|
102
|
+
border: 1px solid var(--border);
|
|
103
|
+
background: rgba(0, 0, 0, 0.15);
|
|
104
|
+
transition: background 0.15s, border-color 0.15s;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.header-icon-link:hover {
|
|
108
|
+
background: #2d3d54;
|
|
109
|
+
border-color: rgba(255, 255, 255, 0.12);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.header-icon-link:focus-visible {
|
|
113
|
+
outline: none;
|
|
114
|
+
box-shadow: var(--focus);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.header-icon-link.is-current {
|
|
118
|
+
border-color: rgba(61, 156, 245, 0.45);
|
|
119
|
+
background: var(--accent-dim);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.header-icon-link img {
|
|
123
|
+
display: block;
|
|
124
|
+
width: 1.35rem;
|
|
125
|
+
height: 1.35rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#global-msg {
|
|
129
|
+
margin-bottom: 1rem;
|
|
130
|
+
padding: 0.65rem 1rem;
|
|
131
|
+
border-radius: var(--radius-sm);
|
|
132
|
+
font-size: 0.9rem;
|
|
133
|
+
display: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#global-msg.is-visible { display: block; }
|
|
137
|
+
|
|
138
|
+
#global-msg.msg-err {
|
|
139
|
+
color: #ffb4b0;
|
|
140
|
+
background: var(--danger-bg);
|
|
141
|
+
border: 1px solid rgba(255, 69, 58, 0.35);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
section.card {
|
|
145
|
+
margin-bottom: 1.25rem;
|
|
146
|
+
padding: 1.25rem 1.35rem;
|
|
147
|
+
background: var(--bg-elevated);
|
|
148
|
+
border: 1px solid var(--border);
|
|
149
|
+
border-radius: var(--radius);
|
|
150
|
+
box-shadow: var(--shadow);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
section.card h2 {
|
|
154
|
+
margin: 0 0 0.5rem;
|
|
155
|
+
font-size: 1.05rem;
|
|
156
|
+
font-weight: 700;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
section.card > .lead {
|
|
160
|
+
margin: 0 0 1rem;
|
|
161
|
+
color: var(--text-muted);
|
|
162
|
+
font-size: 0.88rem;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.toolbar {
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-wrap: wrap;
|
|
168
|
+
gap: 0.65rem 0.75rem;
|
|
169
|
+
align-items: flex-end;
|
|
170
|
+
margin-bottom: 0.85rem;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.field-inline {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
gap: 0.25rem;
|
|
177
|
+
min-width: 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.field-inline label {
|
|
181
|
+
font-size: 0.72rem;
|
|
182
|
+
font-weight: 600;
|
|
183
|
+
text-transform: uppercase;
|
|
184
|
+
letter-spacing: 0.04em;
|
|
185
|
+
color: var(--text-muted);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.field-inline input,
|
|
189
|
+
.field-inline select {
|
|
190
|
+
font-family: inherit;
|
|
191
|
+
font-size: 0.88rem;
|
|
192
|
+
padding: 0.45rem 0.55rem;
|
|
193
|
+
color: var(--text);
|
|
194
|
+
background: var(--bg);
|
|
195
|
+
border: 1px solid var(--border);
|
|
196
|
+
border-radius: var(--radius-sm);
|
|
197
|
+
min-width: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.field-inline input:focus,
|
|
201
|
+
.field-inline select:focus {
|
|
202
|
+
outline: none;
|
|
203
|
+
border-color: var(--accent);
|
|
204
|
+
box-shadow: 0 0 0 3px var(--accent-dim);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.field-inline.narrow { width: 7rem; }
|
|
208
|
+
.field-inline.wide { flex: 1 1 10rem; max-width: 16rem; }
|
|
209
|
+
|
|
210
|
+
.checks {
|
|
211
|
+
display: flex;
|
|
212
|
+
flex-wrap: wrap;
|
|
213
|
+
gap: 0.65rem 1rem;
|
|
214
|
+
align-items: center;
|
|
215
|
+
font-size: 0.88rem;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.checks label {
|
|
220
|
+
display: inline-flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
gap: 0.35rem;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.checks input { width: auto; margin: 0; }
|
|
227
|
+
|
|
228
|
+
button {
|
|
229
|
+
font-family: inherit;
|
|
230
|
+
font-size: 0.82rem;
|
|
231
|
+
font-weight: 600;
|
|
232
|
+
padding: 0.45rem 0.75rem;
|
|
233
|
+
border-radius: var(--radius-sm);
|
|
234
|
+
border: 1px solid var(--border);
|
|
235
|
+
background: var(--surface);
|
|
236
|
+
color: var(--text);
|
|
237
|
+
cursor: pointer;
|
|
238
|
+
transition: background 0.15s, border-color 0.15s, opacity 0.15s;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
button:hover:not(:disabled) {
|
|
242
|
+
background: #2d3d54;
|
|
243
|
+
border-color: rgba(255, 255, 255, 0.12);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
button:focus-visible {
|
|
247
|
+
outline: none;
|
|
248
|
+
box-shadow: var(--focus);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
button.btn-primary {
|
|
252
|
+
background: var(--accent);
|
|
253
|
+
border-color: transparent;
|
|
254
|
+
color: #0a1628;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
button.btn-primary:hover:not(:disabled) {
|
|
258
|
+
background: #5aadf7;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.console-wrap {
|
|
262
|
+
border: 1px solid var(--border);
|
|
263
|
+
border-radius: var(--radius-sm);
|
|
264
|
+
background: #0a0e14;
|
|
265
|
+
overflow: hidden;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.console-meta {
|
|
269
|
+
display: flex;
|
|
270
|
+
flex-wrap: wrap;
|
|
271
|
+
justify-content: space-between;
|
|
272
|
+
gap: 0.5rem;
|
|
273
|
+
padding: 0.45rem 0.65rem;
|
|
274
|
+
font-size: 0.75rem;
|
|
275
|
+
color: var(--text-muted);
|
|
276
|
+
background: rgba(0, 0, 0, 0.35);
|
|
277
|
+
border-bottom: 1px solid var(--border);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#console {
|
|
281
|
+
margin: 0;
|
|
282
|
+
padding: 0.65rem 0.75rem;
|
|
283
|
+
min-height: 18rem;
|
|
284
|
+
max-height: min(70vh, 36rem);
|
|
285
|
+
overflow-y: auto;
|
|
286
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
287
|
+
font-size: 0.78rem;
|
|
288
|
+
line-height: 1.45;
|
|
289
|
+
white-space: pre-wrap;
|
|
290
|
+
word-break: break-word;
|
|
291
|
+
color: #c8d4e0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.log-line { margin: 0 0 0.2rem; }
|
|
295
|
+
.log-line .ts { color: #6b7c93; }
|
|
296
|
+
.log-line .lvl { font-weight: 700; margin: 0 0.35rem; }
|
|
297
|
+
.log-line .lvl-DEBUG { color: #8b9cb3; }
|
|
298
|
+
.log-line .lvl-INFO { color: #7ec8ff; }
|
|
299
|
+
.log-line .lvl-WARNING { color: var(--warning); }
|
|
300
|
+
.log-line .lvl-ERROR, .log-line .lvl-CRITICAL { color: #ff8a80; }
|
|
301
|
+
.log-line .name { color: #9ad59a; }
|
|
302
|
+
.log-line .msg { color: #e8edf4; }
|
|
303
|
+
.log-tags {
|
|
304
|
+
display: inline-flex;
|
|
305
|
+
flex-wrap: wrap;
|
|
306
|
+
gap: 0.25rem;
|
|
307
|
+
align-items: center;
|
|
308
|
+
margin-left: 0.25rem;
|
|
309
|
+
vertical-align: middle;
|
|
310
|
+
}
|
|
311
|
+
.tag {
|
|
312
|
+
display: inline-block;
|
|
313
|
+
font-size: 0.65rem;
|
|
314
|
+
font-weight: 600;
|
|
315
|
+
padding: 0.12rem 0.4rem;
|
|
316
|
+
border-radius: 4px;
|
|
317
|
+
border: 1px solid rgba(61, 156, 245, 0.35);
|
|
318
|
+
background: var(--accent-dim);
|
|
319
|
+
color: var(--accent);
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
user-select: none;
|
|
322
|
+
}
|
|
323
|
+
.tag:hover { filter: brightness(1.12); }
|
|
324
|
+
.log-exc {
|
|
325
|
+
margin: 0.15rem 0 0.35rem 1rem;
|
|
326
|
+
padding: 0.35rem 0.5rem;
|
|
327
|
+
border-left: 2px solid rgba(255, 69, 58, 0.45);
|
|
328
|
+
color: #ffb4b0;
|
|
329
|
+
white-space: pre-wrap;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.summary-grid {
|
|
333
|
+
display: grid;
|
|
334
|
+
grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
|
|
335
|
+
gap: 0.75rem;
|
|
336
|
+
margin-bottom: 1.25rem;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.stat-card {
|
|
340
|
+
padding: 1rem 1.1rem;
|
|
341
|
+
background: var(--bg-elevated);
|
|
342
|
+
border: 1px solid var(--border);
|
|
343
|
+
border-radius: var(--radius-sm);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.stat-card .label {
|
|
347
|
+
margin: 0;
|
|
348
|
+
font-size: 0.72rem;
|
|
349
|
+
font-weight: 600;
|
|
350
|
+
text-transform: uppercase;
|
|
351
|
+
letter-spacing: 0.06em;
|
|
352
|
+
color: var(--text-muted);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.stat-card .value {
|
|
356
|
+
margin: 0.25rem 0 0;
|
|
357
|
+
font-size: 1.35rem;
|
|
358
|
+
font-weight: 700;
|
|
359
|
+
color: var(--text);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.stat-card .sub {
|
|
363
|
+
margin: 0.2rem 0 0;
|
|
364
|
+
font-size: 0.78rem;
|
|
365
|
+
color: var(--text-muted);
|
|
366
|
+
word-break: break-all;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
@media (max-width: 640px) {
|
|
370
|
+
.app-header h1 { font-size: 1.25rem; }
|
|
371
|
+
}
|
|
372
|
+
</style>
|
|
373
|
+
</head>
|
|
374
|
+
<body>
|
|
375
|
+
<div class="wrap">
|
|
376
|
+
<header class="app-header">
|
|
377
|
+
<div class="header-row">
|
|
378
|
+
<div>
|
|
379
|
+
<h1 data-i18n="logs.h1">Server logs</h1>
|
|
380
|
+
<p class="tagline" data-i18n-html="logs.tagline">Recent logs collected by the <code>app</code> package logger. Auto-refresh shows them near real time.</p>
|
|
381
|
+
</div>
|
|
382
|
+
<nav class="header-icon-nav" aria-label="Page navigation" data-i18n-aria-label="nav.aria">
|
|
383
|
+
<a href="/" class="header-icon-link" title="Admin home" aria-label="Admin home" data-i18n-title="nav.home" data-i18n-aria-label="nav.home">
|
|
384
|
+
<img src="/admin-static/icons/home.svg" width="22" height="22" alt="" />
|
|
385
|
+
</a>
|
|
386
|
+
<a href="/projects" class="header-icon-link" title="Projects" aria-label="Projects" data-i18n-title="nav.projects" data-i18n-aria-label="nav.projects">
|
|
387
|
+
<img src="/admin-static/icons/projects.svg" width="22" height="22" alt="" />
|
|
388
|
+
</a>
|
|
389
|
+
<a href="/advanced" class="header-icon-link" title="Advanced settings" aria-label="Advanced settings" data-i18n-title="nav.advanced" data-i18n-aria-label="nav.advanced">
|
|
390
|
+
<img src="/admin-static/icons/advanced.svg" width="22" height="22" alt="" />
|
|
391
|
+
</a>
|
|
392
|
+
<a href="/logs" class="header-icon-link is-current" title="Server logs" aria-label="Server logs" data-i18n-title="nav.logs" data-i18n-aria-label="nav.logs">
|
|
393
|
+
<img src="/admin-static/icons/logs.svg" width="22" height="22" alt="" />
|
|
394
|
+
</a>
|
|
395
|
+
<a href="/database" class="header-icon-link" title="Data browser" aria-label="Data browser" data-i18n-title="nav.database" data-i18n-aria-label="nav.database">
|
|
396
|
+
<img src="/admin-static/icons/database.svg" width="22" height="22" alt="" />
|
|
397
|
+
</a>
|
|
398
|
+
</nav>
|
|
399
|
+
</div>
|
|
400
|
+
<span class="localhost-pill" aria-hidden="true" data-i18n="common.localOnly">Local only</span>
|
|
401
|
+
<p class="tagline" style="margin-top:0.65rem" data-i18n-html="common.localhostNote">This page is only available from <strong>127.0.0.1</strong>.</p>
|
|
402
|
+
</header>
|
|
403
|
+
|
|
404
|
+
<div id="global-msg" role="status" aria-live="polite"></div>
|
|
405
|
+
|
|
406
|
+
<div class="summary-grid" id="summary-grid" aria-label="Summary" data-i18n-aria-label="common.summaryAria"></div>
|
|
407
|
+
|
|
408
|
+
<section class="card" aria-labelledby="logs-heading">
|
|
409
|
+
<h2 id="logs-heading" data-i18n="logs.console">Console</h2>
|
|
410
|
+
<p class="lead" data-i18n-html="logs.lead">Level is a <strong>minimum</strong>. Search partially matches message and exception text. Click a badge to fill that filter.</p>
|
|
411
|
+
|
|
412
|
+
<div class="toolbar">
|
|
413
|
+
<div class="field-inline narrow">
|
|
414
|
+
<label for="f-level" data-i18n="logs.level">Level</label>
|
|
415
|
+
<select id="f-level" aria-label="Minimum log level" data-i18n-aria-label="logs.levelAria">
|
|
416
|
+
<option value="" data-i18n="common.all">All</option>
|
|
417
|
+
<option value="DEBUG">DEBUG</option>
|
|
418
|
+
<option value="INFO">INFO</option>
|
|
419
|
+
<option value="WARNING">WARNING</option>
|
|
420
|
+
<option value="ERROR">ERROR</option>
|
|
421
|
+
<option value="CRITICAL">CRITICAL</option>
|
|
422
|
+
</select>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="field-inline narrow">
|
|
425
|
+
<label for="f-category" data-i18n="logs.category">Category</label>
|
|
426
|
+
<select id="f-category" aria-label="Event category" data-i18n-aria-label="logs.categoryAria">
|
|
427
|
+
<option value="" data-i18n="common.all">All</option>
|
|
428
|
+
<option value="telegram.inbound">telegram.inbound</option>
|
|
429
|
+
<option value="telegram.outbound">telegram.outbound</option>
|
|
430
|
+
<option value="telegram.command">telegram.command</option>
|
|
431
|
+
<option value="auth.reject">auth.reject</option>
|
|
432
|
+
<option value="job.lifecycle">job.lifecycle</option>
|
|
433
|
+
<option value="git.operation">git.operation</option>
|
|
434
|
+
<option value="ai.runner">ai.runner</option>
|
|
435
|
+
</select>
|
|
436
|
+
</div>
|
|
437
|
+
<div class="field-inline narrow">
|
|
438
|
+
<label for="f-chat-id">chat_id</label>
|
|
439
|
+
<input id="f-chat-id" type="text" inputmode="numeric" placeholder="e.g. 123" data-i18n-placeholder="logs.phChatId" autocomplete="off" />
|
|
440
|
+
</div>
|
|
441
|
+
<div class="field-inline narrow">
|
|
442
|
+
<label for="f-user-id">user_id</label>
|
|
443
|
+
<input id="f-user-id" type="text" inputmode="numeric" placeholder="e.g. 456" data-i18n-placeholder="logs.phUserId" autocomplete="off" />
|
|
444
|
+
</div>
|
|
445
|
+
<div class="field-inline wide">
|
|
446
|
+
<label for="f-job-id">job_id</label>
|
|
447
|
+
<input id="f-job-id" type="text" placeholder="e.g. job_…" data-i18n-placeholder="logs.phJobId" autocomplete="off" />
|
|
448
|
+
</div>
|
|
449
|
+
<div class="field-inline wide">
|
|
450
|
+
<label for="f-project">project</label>
|
|
451
|
+
<input id="f-project" type="text" placeholder="Project name" data-i18n-placeholder="logs.phProject" autocomplete="off" />
|
|
452
|
+
</div>
|
|
453
|
+
<div class="field-inline wide">
|
|
454
|
+
<label for="f-logger" data-i18n="logs.logger">Logger (partial match)</label>
|
|
455
|
+
<input id="f-logger" type="text" placeholder="e.g. app.telegram" data-i18n-placeholder="logs.phLogger" autocomplete="off" />
|
|
456
|
+
</div>
|
|
457
|
+
<div class="field-inline wide">
|
|
458
|
+
<label for="f-q" data-i18n="common.search">Search</label>
|
|
459
|
+
<input id="f-q" type="text" placeholder="Message/exception" data-i18n-placeholder="logs.phSearch" autocomplete="off" />
|
|
460
|
+
</div>
|
|
461
|
+
<div class="field-inline narrow">
|
|
462
|
+
<label for="f-interval" data-i18n="logs.refresh">Refresh (s)</label>
|
|
463
|
+
<select id="f-interval" aria-label="Auto-refresh interval" data-i18n-aria-label="logs.refreshAria">
|
|
464
|
+
<option value="0" data-i18n="common.off">Off</option>
|
|
465
|
+
<option value="1">1</option>
|
|
466
|
+
<option value="2" selected>2</option>
|
|
467
|
+
<option value="5">5</option>
|
|
468
|
+
</select>
|
|
469
|
+
</div>
|
|
470
|
+
<button type="button" class="btn-primary" id="btn-apply" data-i18n="logs.applyFilter">Apply filters</button>
|
|
471
|
+
<button type="button" id="btn-clear-view" data-i18n="logs.clearView">Clear view</button>
|
|
472
|
+
</div>
|
|
473
|
+
|
|
474
|
+
<div class="checks">
|
|
475
|
+
<label><input type="checkbox" id="chk-stick" checked /> <span data-i18n="logs.stickBottom">Scroll to bottom</span></label>
|
|
476
|
+
</div>
|
|
477
|
+
|
|
478
|
+
<div class="console-wrap" style="margin-top:0.75rem">
|
|
479
|
+
<div class="console-meta">
|
|
480
|
+
<span id="meta-count" data-i18n="logs.loadWaiting">Waiting to load</span>
|
|
481
|
+
<span id="meta-max">max_id: —</span>
|
|
482
|
+
</div>
|
|
483
|
+
<div id="console" role="log" aria-live="polite" aria-relevant="additions"></div>
|
|
484
|
+
</div>
|
|
485
|
+
</section>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<script src="/admin-static/i18n.js"></script>
|
|
489
|
+
<script src="/admin-static/summary.js"></script>
|
|
490
|
+
<script>
|
|
491
|
+
const $ = (s) => document.querySelector(s);
|
|
492
|
+
const globalMsg = $("#global-msg");
|
|
493
|
+
const consoleEl = $("#console");
|
|
494
|
+
const seen = new Set();
|
|
495
|
+
let lastMaxId = 0;
|
|
496
|
+
let pollTimer = null;
|
|
497
|
+
|
|
498
|
+
function setGlobalMsg(kind, text) {
|
|
499
|
+
if (!text) {
|
|
500
|
+
globalMsg.textContent = "";
|
|
501
|
+
globalMsg.className = "";
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
globalMsg.textContent = text;
|
|
505
|
+
globalMsg.className =
|
|
506
|
+
"is-visible " + (kind === "err" ? "msg-err" : "msg-info");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function escapeHtml(s) {
|
|
510
|
+
return String(s).replace(/[&<>"']/g, (c) => ({
|
|
511
|
+
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
512
|
+
}[c]));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function buildQuery(extra) {
|
|
516
|
+
const params = new URLSearchParams();
|
|
517
|
+
const level = $("#f-level").value;
|
|
518
|
+
const category = $("#f-category").value.trim();
|
|
519
|
+
const chatId = $("#f-chat-id").value.trim();
|
|
520
|
+
const userId = $("#f-user-id").value.trim();
|
|
521
|
+
const jobId = $("#f-job-id").value.trim();
|
|
522
|
+
const project = $("#f-project").value.trim();
|
|
523
|
+
const logger = $("#f-logger").value.trim();
|
|
524
|
+
const q = $("#f-q").value.trim();
|
|
525
|
+
if (level) params.set("level", level);
|
|
526
|
+
if (category) params.set("category", category);
|
|
527
|
+
if (chatId) params.set("chat_id", chatId);
|
|
528
|
+
if (userId) params.set("user_id", userId);
|
|
529
|
+
if (jobId) params.set("job_id", jobId);
|
|
530
|
+
if (project) params.set("project", project);
|
|
531
|
+
if (logger) params.set("logger", logger);
|
|
532
|
+
if (q) params.set("q", q);
|
|
533
|
+
params.set("limit", "400");
|
|
534
|
+
if (extra && typeof extra.after_id === "number" && extra.after_id > 0) {
|
|
535
|
+
params.set("after_id", String(extra.after_id));
|
|
536
|
+
}
|
|
537
|
+
return params.toString();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function tagSpan(filter, value, label) {
|
|
541
|
+
const v = String(value);
|
|
542
|
+
return (
|
|
543
|
+
'<span class="tag" role="button" tabindex="0" data-filter="' +
|
|
544
|
+
escapeHtml(filter) +
|
|
545
|
+
'" data-value="' +
|
|
546
|
+
escapeHtml(v) +
|
|
547
|
+
'">' +
|
|
548
|
+
escapeHtml(label) +
|
|
549
|
+
"</span>"
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function formatLine(e) {
|
|
554
|
+
const lvl = escapeHtml(e.level || "");
|
|
555
|
+
const ts = escapeHtml(e.created_at || "");
|
|
556
|
+
const name = escapeHtml(e.logger || "");
|
|
557
|
+
const msg = escapeHtml(e.message || "");
|
|
558
|
+
let badges = "";
|
|
559
|
+
if (e.category) badges += tagSpan("category", e.category, e.category);
|
|
560
|
+
if (e.chat_id != null && e.chat_id !== "") badges += tagSpan("chat_id", e.chat_id, "chat:" + e.chat_id);
|
|
561
|
+
if (e.user_id != null && e.user_id !== "") badges += tagSpan("user_id", e.user_id, "user:" + e.user_id);
|
|
562
|
+
if (e.job_id) badges += tagSpan("job_id", e.job_id, "job:" + e.job_id);
|
|
563
|
+
if (e.project) badges += tagSpan("project", e.project, "proj:" + e.project);
|
|
564
|
+
let html = '<div class="log-line" data-id="' + escapeHtml(String(e.id)) + '">';
|
|
565
|
+
html += '<span class="ts">' + ts + "</span>";
|
|
566
|
+
html += '<span class="lvl lvl-' + lvl + '">' + lvl + "</span>";
|
|
567
|
+
if (badges) html += '<span class="log-tags">' + badges + "</span>";
|
|
568
|
+
html += '<span class="name">' + name + "</span>";
|
|
569
|
+
html += '<span class="msg"> — ' + msg + "</span></div>";
|
|
570
|
+
if (e.exception) {
|
|
571
|
+
html += '<pre class="log-exc">' + escapeHtml(e.exception) + "</pre>";
|
|
572
|
+
}
|
|
573
|
+
return html;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function scrollToBottom() {
|
|
577
|
+
if (!$("#chk-stick").checked) return;
|
|
578
|
+
consoleEl.scrollTop = consoleEl.scrollHeight;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function fetchLogs(extra) {
|
|
582
|
+
const qs = buildQuery(extra || {});
|
|
583
|
+
const r = await fetch("/api/logs?" + qs);
|
|
584
|
+
const raw = await r.text();
|
|
585
|
+
if (!r.ok) {
|
|
586
|
+
let detail = raw || "Request failed (" + r.status + ")";
|
|
587
|
+
try {
|
|
588
|
+
const j = JSON.parse(raw);
|
|
589
|
+
if (j && typeof j.detail === "string") detail = j.detail;
|
|
590
|
+
} catch (_) { /* ignore */ }
|
|
591
|
+
throw new Error(detail);
|
|
592
|
+
}
|
|
593
|
+
return JSON.parse(raw);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function applyEntries(entries, append) {
|
|
597
|
+
if (!append) {
|
|
598
|
+
consoleEl.innerHTML = "";
|
|
599
|
+
seen.clear();
|
|
600
|
+
}
|
|
601
|
+
for (const e of entries) {
|
|
602
|
+
const id = Number(e.id);
|
|
603
|
+
if (seen.has(id)) continue;
|
|
604
|
+
seen.add(id);
|
|
605
|
+
consoleEl.insertAdjacentHTML("beforeend", formatLine(e));
|
|
606
|
+
}
|
|
607
|
+
$("#meta-count").textContent = i18n.t("logs.shownLines", { n: seen.size });
|
|
608
|
+
scrollToBottom();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async function loadFull() {
|
|
612
|
+
setGlobalMsg("", "");
|
|
613
|
+
const d = await fetchLogs({});
|
|
614
|
+
lastMaxId = Number(d.max_id) || 0;
|
|
615
|
+
$("#meta-max").textContent = "max_id: " + (lastMaxId || "—");
|
|
616
|
+
applyEntries(d.entries || [], false);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function pollTail() {
|
|
620
|
+
try {
|
|
621
|
+
const d = await fetchLogs({ after_id: lastMaxId > 0 ? lastMaxId : undefined });
|
|
622
|
+
lastMaxId = Number(d.max_id) || lastMaxId;
|
|
623
|
+
$("#meta-max").textContent = "max_id: " + lastMaxId;
|
|
624
|
+
if ((d.entries || []).length) {
|
|
625
|
+
applyEntries(d.entries, true);
|
|
626
|
+
$("#meta-count").textContent = i18n.t("logs.shownLines", { n: seen.size });
|
|
627
|
+
}
|
|
628
|
+
} catch (e) {
|
|
629
|
+
setGlobalMsg("err", String(e));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function restartPoll() {
|
|
634
|
+
if (pollTimer) {
|
|
635
|
+
clearInterval(pollTimer);
|
|
636
|
+
pollTimer = null;
|
|
637
|
+
}
|
|
638
|
+
const sec = Number($("#f-interval").value);
|
|
639
|
+
if (sec > 0) {
|
|
640
|
+
pollTimer = setInterval(pollTail, sec * 1000);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async function applyFiltersFromUi() {
|
|
645
|
+
try {
|
|
646
|
+
await loadFull();
|
|
647
|
+
restartPoll();
|
|
648
|
+
} catch (e) {
|
|
649
|
+
setGlobalMsg("err", String(e));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
$("#btn-apply").addEventListener("click", () => {
|
|
654
|
+
applyFiltersFromUi();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
consoleEl.addEventListener("click", (ev) => {
|
|
658
|
+
const t = ev.target.closest(".tag[data-filter]");
|
|
659
|
+
if (!t) return;
|
|
660
|
+
const kind = t.getAttribute("data-filter");
|
|
661
|
+
const val = t.getAttribute("data-value") || "";
|
|
662
|
+
if (kind === "category") $("#f-category").value = val;
|
|
663
|
+
else if (kind === "chat_id") $("#f-chat-id").value = val;
|
|
664
|
+
else if (kind === "user_id") $("#f-user-id").value = val;
|
|
665
|
+
else if (kind === "job_id") $("#f-job-id").value = val;
|
|
666
|
+
else if (kind === "project") $("#f-project").value = val;
|
|
667
|
+
applyFiltersFromUi();
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
$("#btn-clear-view").addEventListener("click", () => {
|
|
671
|
+
consoleEl.innerHTML = "";
|
|
672
|
+
seen.clear();
|
|
673
|
+
lastMaxId = 0;
|
|
674
|
+
$("#meta-count").textContent = i18n.t("logs.shownLines", { n: 0 });
|
|
675
|
+
$("#meta-max").textContent = "max_id: —";
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
$("#f-interval").addEventListener("change", restartPoll);
|
|
679
|
+
|
|
680
|
+
(async function init() {
|
|
681
|
+
adminSummary.loadSummaryGrid();
|
|
682
|
+
await applyFiltersFromUi();
|
|
683
|
+
})();
|
|
684
|
+
</script>
|
|
685
|
+
</body>
|
|
686
|
+
</html>
|