swarm-tickets 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/LICENSE.txt +21 -0
- package/README.md +238 -0
- package/SKILL.md +235 -0
- package/backup-tickets.sh +39 -0
- package/package.json +54 -0
- package/setup.js +60 -0
- package/ticket-cli.js +120 -0
- package/ticket-server.js +327 -0
- package/ticket-tracker.html +755 -0
- package/tickets.example.json +74 -0
|
@@ -0,0 +1,755 @@
|
|
|
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
|
+
<title>Claude-flow Ticket Tracker</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
12
|
+
background: #1a1a1a;
|
|
13
|
+
color: #e0e0e0;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.container { max-width: 1200px; margin: 0 auto; }
|
|
18
|
+
h1 { margin-bottom: 10px; color: #00d4aa; }
|
|
19
|
+
|
|
20
|
+
.project-name {
|
|
21
|
+
color: #999;
|
|
22
|
+
font-size: 0.9em;
|
|
23
|
+
margin-bottom: 30px;
|
|
24
|
+
font-weight: normal;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.server-status {
|
|
28
|
+
padding: 10px 15px;
|
|
29
|
+
border-radius: 5px;
|
|
30
|
+
margin-bottom: 20px;
|
|
31
|
+
font-size: 0.9em;
|
|
32
|
+
}
|
|
33
|
+
.server-online { background: #2d4a2d; border-left: 4px solid #6bcf7f; }
|
|
34
|
+
.server-offline { background: #4a2d2d; border-left: 4px solid #ff6b6b; }
|
|
35
|
+
|
|
36
|
+
.tabs {
|
|
37
|
+
display: flex;
|
|
38
|
+
gap: 10px;
|
|
39
|
+
margin-bottom: 20px;
|
|
40
|
+
border-bottom: 2px solid #333;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.tab {
|
|
44
|
+
padding: 10px 20px;
|
|
45
|
+
background: #2a2a2a;
|
|
46
|
+
border: none;
|
|
47
|
+
color: #e0e0e0;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
border-radius: 5px 5px 0 0;
|
|
50
|
+
transition: all 0.3s;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tab:hover { background: #333; }
|
|
54
|
+
.tab.active { background: #00d4aa; color: #1a1a1a; }
|
|
55
|
+
|
|
56
|
+
.tab-content { display: none; }
|
|
57
|
+
.tab-content.active { display: block; }
|
|
58
|
+
|
|
59
|
+
.form-group { margin-bottom: 20px; }
|
|
60
|
+
|
|
61
|
+
label {
|
|
62
|
+
display: block;
|
|
63
|
+
margin-bottom: 5px;
|
|
64
|
+
color: #00d4aa;
|
|
65
|
+
font-weight: 500;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
input, textarea, select {
|
|
69
|
+
width: 100%;
|
|
70
|
+
padding: 10px;
|
|
71
|
+
background: #2a2a2a;
|
|
72
|
+
border: 1px solid #444;
|
|
73
|
+
color: #e0e0e0;
|
|
74
|
+
border-radius: 5px;
|
|
75
|
+
font-family: inherit;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
textarea {
|
|
79
|
+
resize: vertical;
|
|
80
|
+
font-family: 'Courier New', monospace;
|
|
81
|
+
min-height: 100px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
button {
|
|
85
|
+
background: #00d4aa;
|
|
86
|
+
color: #1a1a1a;
|
|
87
|
+
border: none;
|
|
88
|
+
padding: 12px 24px;
|
|
89
|
+
border-radius: 5px;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
transition: all 0.3s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
button:hover {
|
|
96
|
+
background: #00ffcc;
|
|
97
|
+
transform: translateY(-2px);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.ticket-list { display: grid; gap: 15px; }
|
|
101
|
+
|
|
102
|
+
.ticket-card {
|
|
103
|
+
background: #2a2a2a;
|
|
104
|
+
border: 1px solid #444;
|
|
105
|
+
border-radius: 8px;
|
|
106
|
+
padding: 20px;
|
|
107
|
+
transition: all 0.3s;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.ticket-card:hover {
|
|
111
|
+
border-color: #00d4aa;
|
|
112
|
+
box-shadow: 0 0 20px rgba(0, 212, 170, 0.1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.ticket-header {
|
|
116
|
+
display: flex;
|
|
117
|
+
justify-content: space-between;
|
|
118
|
+
align-items: start;
|
|
119
|
+
margin-bottom: 15px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.ticket-id {
|
|
123
|
+
font-weight: 700;
|
|
124
|
+
color: #00d4aa;
|
|
125
|
+
font-size: 1.1em;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.ticket-meta { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
129
|
+
|
|
130
|
+
.badge {
|
|
131
|
+
padding: 4px 12px;
|
|
132
|
+
border-radius: 12px;
|
|
133
|
+
font-size: 0.85em;
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.status-open { background: #ff6b6b; color: white; }
|
|
138
|
+
.status-in-progress { background: #ffd93d; color: #1a1a1a; }
|
|
139
|
+
.status-fixed { background: #6bcf7f; color: white; }
|
|
140
|
+
.status-closed { background: #666; color: white; }
|
|
141
|
+
|
|
142
|
+
.priority-critical { background: #ff3838; color: white; }
|
|
143
|
+
.priority-high { background: #ff9f43; color: white; }
|
|
144
|
+
.priority-medium { background: #ffd93d; color: #1a1a1a; }
|
|
145
|
+
.priority-low { background: #6bcf7f; color: white; }
|
|
146
|
+
|
|
147
|
+
.ticket-route {
|
|
148
|
+
color: #00d4aa;
|
|
149
|
+
font-family: 'Courier New', monospace;
|
|
150
|
+
margin-bottom: 10px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.error-section { margin: 10px 0; }
|
|
154
|
+
|
|
155
|
+
.error-section h4 {
|
|
156
|
+
color: #ff6b6b;
|
|
157
|
+
margin-bottom: 5px;
|
|
158
|
+
font-size: 0.9em;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.error-content {
|
|
162
|
+
background: #1a1a1a;
|
|
163
|
+
padding: 10px;
|
|
164
|
+
border-radius: 5px;
|
|
165
|
+
font-family: 'Courier New', monospace;
|
|
166
|
+
font-size: 0.85em;
|
|
167
|
+
overflow-x: auto;
|
|
168
|
+
white-space: pre-wrap;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.ticket-footer {
|
|
172
|
+
margin-top: 15px;
|
|
173
|
+
padding-top: 15px;
|
|
174
|
+
border-top: 1px solid #444;
|
|
175
|
+
font-size: 0.85em;
|
|
176
|
+
color: #999;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.filters {
|
|
180
|
+
display: flex;
|
|
181
|
+
gap: 15px;
|
|
182
|
+
margin-bottom: 20px;
|
|
183
|
+
flex-wrap: wrap;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.filter-group {
|
|
187
|
+
flex: 1;
|
|
188
|
+
min-width: 200px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.stats {
|
|
192
|
+
display: flex;
|
|
193
|
+
gap: 15px;
|
|
194
|
+
margin-bottom: 20px;
|
|
195
|
+
flex-wrap: wrap;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.stat-card {
|
|
199
|
+
flex: 1;
|
|
200
|
+
min-width: 150px;
|
|
201
|
+
background: #2a2a2a;
|
|
202
|
+
padding: 15px;
|
|
203
|
+
border-radius: 8px;
|
|
204
|
+
border: 1px solid #444;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.stat-value {
|
|
208
|
+
font-size: 2em;
|
|
209
|
+
font-weight: 700;
|
|
210
|
+
color: #00d4aa;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.stat-label {
|
|
214
|
+
color: #999;
|
|
215
|
+
font-size: 0.9em;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.quick-prompt-btn {
|
|
219
|
+
background: #6c5ce7;
|
|
220
|
+
color: white;
|
|
221
|
+
padding: 8px 16px;
|
|
222
|
+
font-size: 0.9em;
|
|
223
|
+
margin-top: 10px;
|
|
224
|
+
display: inline-block;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.quick-prompt-btn:hover {
|
|
228
|
+
background: #5f4fd1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.settings-section {
|
|
232
|
+
background: #2a2a2a;
|
|
233
|
+
padding: 20px;
|
|
234
|
+
border-radius: 8px;
|
|
235
|
+
margin-bottom: 20px;
|
|
236
|
+
border: 1px solid #444;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.settings-section h3 {
|
|
240
|
+
color: #00d4aa;
|
|
241
|
+
margin-bottom: 15px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.template-info {
|
|
245
|
+
background: #1a1a1a;
|
|
246
|
+
padding: 10px;
|
|
247
|
+
border-radius: 5px;
|
|
248
|
+
margin-top: 10px;
|
|
249
|
+
font-size: 0.9em;
|
|
250
|
+
color: #999;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.template-info code {
|
|
254
|
+
color: #00d4aa;
|
|
255
|
+
background: #2a2a2a;
|
|
256
|
+
padding: 2px 6px;
|
|
257
|
+
border-radius: 3px;
|
|
258
|
+
}
|
|
259
|
+
</style>
|
|
260
|
+
</head>
|
|
261
|
+
<body>
|
|
262
|
+
<div class="container">
|
|
263
|
+
<h1>🎫 Claude-flow Ticket Tracker</h1>
|
|
264
|
+
<div class="project-name" id="project-name">📁 Loading project...</div>
|
|
265
|
+
|
|
266
|
+
<div id="server-status" class="server-status"></div>
|
|
267
|
+
|
|
268
|
+
<div class="tabs">
|
|
269
|
+
<button class="tab active" onclick="switchTab('submit')">Submit Ticket</button>
|
|
270
|
+
<button class="tab" onclick="switchTab('view')">View Tickets</button>
|
|
271
|
+
<button class="tab" onclick="switchTab('settings')">Settings</button>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<div id="submit-tab" class="tab-content active">
|
|
275
|
+
<form id="ticket-form">
|
|
276
|
+
<div class="form-group">
|
|
277
|
+
<label for="route" id="route-form-label">Route/Webpage</label>
|
|
278
|
+
<input type="text" id="route" required placeholder="/dashboard/users">
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div class="form-group">
|
|
282
|
+
<label for="f12-errors" id="f12-form-label">F12 Console Errors</label>
|
|
283
|
+
<textarea id="f12-errors" placeholder="Paste console errors from browser DevTools..."></textarea>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<div class="form-group">
|
|
287
|
+
<label for="server-errors" id="server-form-label">Server Console Errors</label>
|
|
288
|
+
<textarea id="server-errors" placeholder="Paste server-side console errors..."></textarea>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div class="form-group">
|
|
292
|
+
<label for="description">Description (Optional)</label>
|
|
293
|
+
<textarea id="description" rows="3" placeholder="Additional context or steps to reproduce..."></textarea>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<div class="form-group">
|
|
297
|
+
<label for="status">Status</label>
|
|
298
|
+
<select id="status">
|
|
299
|
+
<option value="open">Open</option>
|
|
300
|
+
<option value="in-progress">In Progress</option>
|
|
301
|
+
<option value="fixed">Fixed</option>
|
|
302
|
+
<option value="closed">Closed</option>
|
|
303
|
+
</select>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<button type="submit">Create Ticket</button>
|
|
307
|
+
</form>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div id="view-tab" class="tab-content">
|
|
311
|
+
<div class="stats" id="stats"></div>
|
|
312
|
+
|
|
313
|
+
<div class="filters">
|
|
314
|
+
<div class="filter-group">
|
|
315
|
+
<label for="filter-status">Filter by Status</label>
|
|
316
|
+
<select id="filter-status" onchange="filterTickets()">
|
|
317
|
+
<option value="">All</option>
|
|
318
|
+
<option value="open">Open</option>
|
|
319
|
+
<option value="in-progress">In Progress</option>
|
|
320
|
+
<option value="fixed">Fixed</option>
|
|
321
|
+
<option value="closed">Closed</option>
|
|
322
|
+
</select>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<div class="filter-group">
|
|
326
|
+
<label for="filter-priority">Filter by Priority</label>
|
|
327
|
+
<select id="filter-priority" onchange="filterTickets()">
|
|
328
|
+
<option value="">All</option>
|
|
329
|
+
<option value="critical">Critical</option>
|
|
330
|
+
<option value="high">High</option>
|
|
331
|
+
<option value="medium">Medium</option>
|
|
332
|
+
<option value="low">Low</option>
|
|
333
|
+
</select>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<div class="filter-group">
|
|
337
|
+
<label for="search">Search</label>
|
|
338
|
+
<input type="text" id="search" placeholder="Search tickets..." oninput="filterTickets()">
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="ticket-list" id="ticket-list"></div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<div id="settings-tab" class="tab-content">
|
|
346
|
+
<div class="settings-section">
|
|
347
|
+
<h3>Quick Prompt Template</h3>
|
|
348
|
+
<p style="margin-bottom: 15px; color: #999;">Customize the prompt that gets copied when you click the Quick Prompt button on a ticket.</p>
|
|
349
|
+
|
|
350
|
+
<div class="form-group">
|
|
351
|
+
<label for="prompt-template">Template</label>
|
|
352
|
+
<textarea id="prompt-template" rows="5" placeholder="Please investigate and fix ticket {TICKET_ID}."></textarea>
|
|
353
|
+
<div class="template-info">
|
|
354
|
+
<strong>Available placeholders:</strong>
|
|
355
|
+
<ul style="margin-top: 5px; margin-left: 20px;">
|
|
356
|
+
<li><code>{TICKET_ID}</code> - The ticket's unique identifier</li>
|
|
357
|
+
</ul>
|
|
358
|
+
<p style="margin-top: 10px;">💡 Tip: Keep it simple and let Claude-flow ask clarifying questions as needed.</p>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
<button onclick="savePromptTemplate()">Save Template</button>
|
|
363
|
+
<button onclick="resetPromptTemplate()" style="background: #666; margin-left: 10px;">Reset to Default</button>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<div class="settings-section">
|
|
367
|
+
<h3>Project & Field Labels</h3>
|
|
368
|
+
<p style="margin-bottom: 15px; color: #999;">Customize how fields are displayed in your ticket tracker.</p>
|
|
369
|
+
|
|
370
|
+
<div class="form-group">
|
|
371
|
+
<label for="project-name-input">Project Name</label>
|
|
372
|
+
<input type="text" id="project-name-input" placeholder="My Awesome Project">
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<div class="form-group">
|
|
376
|
+
<label for="route-label">Location Field Label</label>
|
|
377
|
+
<input type="text" id="route-label" placeholder="Route/Webpage">
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<div class="form-group">
|
|
381
|
+
<label for="f12-label">Client Errors Field Label</label>
|
|
382
|
+
<input type="text" id="f12-label" placeholder="F12 Console Errors">
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<div class="form-group">
|
|
386
|
+
<label for="server-label">Server Errors Field Label</label>
|
|
387
|
+
<input type="text" id="server-label" placeholder="Server Console Errors">
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<button onclick="saveFieldLabels()">Save Labels</button>
|
|
391
|
+
<button onclick="resetFieldLabels()" style="background: #666; margin-left: 10px;">Reset to Default</button>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<script>
|
|
397
|
+
let tickets = [];
|
|
398
|
+
let useServer = false;
|
|
399
|
+
const API_BASE = 'http://localhost:3456/api';
|
|
400
|
+
const DEFAULT_PROMPT_TEMPLATE = 'Please investigate and fix ticket {TICKET_ID}.';
|
|
401
|
+
const DEFAULT_FIELD_LABELS = {
|
|
402
|
+
projectName: 'Ticket Tracker',
|
|
403
|
+
route: 'Route/Webpage',
|
|
404
|
+
f12: 'F12 Console Errors',
|
|
405
|
+
server: 'Server Console Errors'
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Load field labels from localStorage or use defaults
|
|
409
|
+
function getFieldLabels() {
|
|
410
|
+
const stored = localStorage.getItem('claudeflow-field-labels');
|
|
411
|
+
return stored ? JSON.parse(stored) : DEFAULT_FIELD_LABELS;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Update project name display
|
|
415
|
+
function updateProjectName() {
|
|
416
|
+
const labels = getFieldLabels();
|
|
417
|
+
document.getElementById('project-name').textContent = '📁 ' + labels.projectName;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Update all form labels
|
|
421
|
+
function updateFormLabels() {
|
|
422
|
+
const labels = getFieldLabels();
|
|
423
|
+
document.getElementById('route-form-label').textContent = labels.route;
|
|
424
|
+
document.getElementById('f12-form-label').textContent = labels.f12;
|
|
425
|
+
document.getElementById('server-form-label').textContent = labels.server;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Load prompt template from localStorage or use default
|
|
429
|
+
function getPromptTemplate() {
|
|
430
|
+
return localStorage.getItem('claudeflow-prompt-template') || DEFAULT_PROMPT_TEMPLATE;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Save prompt template to localStorage
|
|
434
|
+
function savePromptTemplate() {
|
|
435
|
+
const template = document.getElementById('prompt-template').value.trim();
|
|
436
|
+
if (!template) {
|
|
437
|
+
alert('Template cannot be empty!');
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
localStorage.setItem('claudeflow-prompt-template', template);
|
|
441
|
+
alert('✅ Prompt template saved!');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Reset prompt template to default
|
|
445
|
+
function resetPromptTemplate() {
|
|
446
|
+
if (confirm('Reset to default template?')) {
|
|
447
|
+
localStorage.removeItem('claudeflow-prompt-template');
|
|
448
|
+
document.getElementById('prompt-template').value = DEFAULT_PROMPT_TEMPLATE;
|
|
449
|
+
alert('✅ Template reset to default!');
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Save field labels to localStorage
|
|
454
|
+
function saveFieldLabels() {
|
|
455
|
+
const labels = {
|
|
456
|
+
projectName: document.getElementById('project-name-input').value.trim() || DEFAULT_FIELD_LABELS.projectName,
|
|
457
|
+
route: document.getElementById('route-label').value.trim() || DEFAULT_FIELD_LABELS.route,
|
|
458
|
+
f12: document.getElementById('f12-label').value.trim() || DEFAULT_FIELD_LABELS.f12,
|
|
459
|
+
server: document.getElementById('server-label').value.trim() || DEFAULT_FIELD_LABELS.server
|
|
460
|
+
};
|
|
461
|
+
localStorage.setItem('claudeflow-field-labels', JSON.stringify(labels));
|
|
462
|
+
updateProjectName();
|
|
463
|
+
updateFormLabels();
|
|
464
|
+
alert('✅ Field labels saved!');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Reset field labels to defaults
|
|
468
|
+
function resetFieldLabels() {
|
|
469
|
+
if (confirm('Reset all field labels to defaults?')) {
|
|
470
|
+
localStorage.removeItem('claudeflow-field-labels');
|
|
471
|
+
document.getElementById('project-name-input').value = DEFAULT_FIELD_LABELS.projectName;
|
|
472
|
+
document.getElementById('route-label').value = DEFAULT_FIELD_LABELS.route;
|
|
473
|
+
document.getElementById('f12-label').value = DEFAULT_FIELD_LABELS.f12;
|
|
474
|
+
document.getElementById('server-label').value = DEFAULT_FIELD_LABELS.server;
|
|
475
|
+
updateProjectName();
|
|
476
|
+
updateFormLabels();
|
|
477
|
+
alert('✅ Labels reset to defaults!');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Copy quick prompt for a ticket
|
|
482
|
+
function copyQuickPrompt(ticketId) {
|
|
483
|
+
const template = getPromptTemplate();
|
|
484
|
+
const prompt = template.replace(/{TICKET_ID}/g, ticketId);
|
|
485
|
+
|
|
486
|
+
navigator.clipboard.writeText(prompt).then(() => {
|
|
487
|
+
alert('✅ Prompt copied to clipboard!\n\n' + prompt);
|
|
488
|
+
}).catch(err => {
|
|
489
|
+
console.error('Failed to copy:', err);
|
|
490
|
+
alert('❌ Failed to copy to clipboard');
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Check if server is available
|
|
495
|
+
async function checkServer() {
|
|
496
|
+
try {
|
|
497
|
+
const response = await fetch(`${API_BASE}/tickets`);
|
|
498
|
+
useServer = response.ok;
|
|
499
|
+
updateServerStatus();
|
|
500
|
+
return useServer;
|
|
501
|
+
} catch {
|
|
502
|
+
useServer = false;
|
|
503
|
+
updateServerStatus();
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Update server status indicator
|
|
509
|
+
function updateServerStatus() {
|
|
510
|
+
const statusEl = document.getElementById('server-status');
|
|
511
|
+
if (useServer) {
|
|
512
|
+
statusEl.className = 'server-status server-online';
|
|
513
|
+
statusEl.textContent = '✅ Server connected - using tickets.json';
|
|
514
|
+
} else {
|
|
515
|
+
statusEl.className = 'server-status server-offline';
|
|
516
|
+
statusEl.textContent = '⚠️ Server offline - using localStorage';
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Load tickets from server or localStorage
|
|
521
|
+
async function loadTickets() {
|
|
522
|
+
await checkServer();
|
|
523
|
+
|
|
524
|
+
if (useServer) {
|
|
525
|
+
try {
|
|
526
|
+
const response = await fetch(`${API_BASE}/tickets`);
|
|
527
|
+
tickets = await response.json();
|
|
528
|
+
console.log('✅ Loaded', tickets.length, 'tickets from server');
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.error('❌ Failed to load from server:', error);
|
|
531
|
+
loadFromLocalStorage();
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
loadFromLocalStorage();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Load from localStorage
|
|
539
|
+
function loadFromLocalStorage() {
|
|
540
|
+
const stored = localStorage.getItem('claudeflow-tickets');
|
|
541
|
+
if (stored) {
|
|
542
|
+
tickets = JSON.parse(stored);
|
|
543
|
+
console.log('✅ Loaded', tickets.length, 'tickets from localStorage');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Save ticket to server or localStorage
|
|
548
|
+
async function saveTicket(ticket) {
|
|
549
|
+
if (useServer) {
|
|
550
|
+
try {
|
|
551
|
+
const response = await fetch(`${API_BASE}/tickets`, {
|
|
552
|
+
method: 'POST',
|
|
553
|
+
headers: { 'Content-Type': 'application/json' },
|
|
554
|
+
body: JSON.stringify(ticket)
|
|
555
|
+
});
|
|
556
|
+
const savedTicket = await response.json();
|
|
557
|
+
console.log('✅ Ticket saved to server:', savedTicket.id);
|
|
558
|
+
return savedTicket;
|
|
559
|
+
} catch (error) {
|
|
560
|
+
console.error('❌ Failed to save to server:', error);
|
|
561
|
+
return ticket;
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
tickets.push(ticket);
|
|
565
|
+
localStorage.setItem('claudeflow-tickets', JSON.stringify(tickets));
|
|
566
|
+
console.log('✅ Ticket saved to localStorage');
|
|
567
|
+
return ticket;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Switch between tabs
|
|
572
|
+
async function switchTab(tabName) {
|
|
573
|
+
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
|
574
|
+
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
|
575
|
+
|
|
576
|
+
event.target.classList.add('active');
|
|
577
|
+
document.getElementById(`${tabName}-tab`).classList.add('active');
|
|
578
|
+
|
|
579
|
+
if (tabName === 'view') {
|
|
580
|
+
await loadTickets();
|
|
581
|
+
renderTickets();
|
|
582
|
+
renderStats();
|
|
583
|
+
} else if (tabName === 'settings') {
|
|
584
|
+
// Load current template and field labels
|
|
585
|
+
document.getElementById('prompt-template').value = getPromptTemplate();
|
|
586
|
+
const labels = getFieldLabels();
|
|
587
|
+
document.getElementById('project-name-input').value = labels.projectName;
|
|
588
|
+
document.getElementById('route-label').value = labels.route;
|
|
589
|
+
document.getElementById('f12-label').value = labels.f12;
|
|
590
|
+
document.getElementById('server-label').value = labels.server;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Handle form submission
|
|
595
|
+
document.getElementById('ticket-form').addEventListener('submit', async function(e) {
|
|
596
|
+
e.preventDefault();
|
|
597
|
+
|
|
598
|
+
const ticket = {
|
|
599
|
+
route: document.getElementById('route').value,
|
|
600
|
+
f12Errors: document.getElementById('f12-errors').value,
|
|
601
|
+
serverErrors: document.getElementById('server-errors').value,
|
|
602
|
+
description: document.getElementById('description').value,
|
|
603
|
+
status: document.getElementById('status').value
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const savedTicket = await saveTicket(ticket);
|
|
607
|
+
|
|
608
|
+
// Clear form
|
|
609
|
+
this.reset();
|
|
610
|
+
|
|
611
|
+
alert('Ticket created: ' + savedTicket.id);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Render statistics
|
|
615
|
+
function renderStats() {
|
|
616
|
+
const stats = {
|
|
617
|
+
total: tickets.length,
|
|
618
|
+
open: tickets.filter(t => t.status === 'open').length,
|
|
619
|
+
inProgress: tickets.filter(t => t.status === 'in-progress').length,
|
|
620
|
+
fixed: tickets.filter(t => t.status === 'fixed').length
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const statsHTML = `
|
|
624
|
+
<div class="stat-card">
|
|
625
|
+
<div class="stat-value">${stats.total}</div>
|
|
626
|
+
<div class="stat-label">Total Tickets</div>
|
|
627
|
+
</div>
|
|
628
|
+
<div class="stat-card">
|
|
629
|
+
<div class="stat-value">${stats.open}</div>
|
|
630
|
+
<div class="stat-label">Open</div>
|
|
631
|
+
</div>
|
|
632
|
+
<div class="stat-card">
|
|
633
|
+
<div class="stat-value">${stats.inProgress}</div>
|
|
634
|
+
<div class="stat-label">In Progress</div>
|
|
635
|
+
</div>
|
|
636
|
+
<div class="stat-card">
|
|
637
|
+
<div class="stat-value">${stats.fixed}</div>
|
|
638
|
+
<div class="stat-label">Fixed</div>
|
|
639
|
+
</div>
|
|
640
|
+
`;
|
|
641
|
+
|
|
642
|
+
document.getElementById('stats').innerHTML = statsHTML;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Render tickets
|
|
646
|
+
function renderTickets(filteredTickets = null) {
|
|
647
|
+
const ticketsToRender = filteredTickets || tickets;
|
|
648
|
+
const container = document.getElementById('ticket-list');
|
|
649
|
+
const labels = getFieldLabels();
|
|
650
|
+
|
|
651
|
+
if (ticketsToRender.length === 0) {
|
|
652
|
+
container.innerHTML = '<p style="text-align: center; color: #999;">No tickets found.</p>';
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Sort by most recent first
|
|
657
|
+
const sorted = [...ticketsToRender].sort((a, b) =>
|
|
658
|
+
new Date(b.createdAt) - new Date(a.createdAt)
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
container.innerHTML = sorted.map(ticket => `
|
|
662
|
+
<div class="ticket-card">
|
|
663
|
+
<div class="ticket-header">
|
|
664
|
+
<div class="ticket-id">${ticket.id}</div>
|
|
665
|
+
<div class="ticket-meta">
|
|
666
|
+
<span class="badge status-${ticket.status}">${ticket.status.toUpperCase()}</span>
|
|
667
|
+
${ticket.priority ? `<span class="badge priority-${ticket.priority}">${ticket.priority.toUpperCase()}</span>` : ''}
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<div class="ticket-route">📍 ${ticket.route}</div>
|
|
672
|
+
|
|
673
|
+
${ticket.description ? `<p style="margin-bottom: 10px;">${ticket.description}</p>` : ''}
|
|
674
|
+
|
|
675
|
+
${ticket.f12Errors ? `
|
|
676
|
+
<div class="error-section">
|
|
677
|
+
<h4>🔴 ${labels.f12}</h4>
|
|
678
|
+
<div class="error-content">${ticket.f12Errors}</div>
|
|
679
|
+
</div>
|
|
680
|
+
` : ''}
|
|
681
|
+
|
|
682
|
+
${ticket.serverErrors ? `
|
|
683
|
+
<div class="error-section">
|
|
684
|
+
<h4>🖥️ ${labels.server}</h4>
|
|
685
|
+
<div class="error-content">${ticket.serverErrors}</div>
|
|
686
|
+
</div>
|
|
687
|
+
` : ''}
|
|
688
|
+
|
|
689
|
+
${ticket.swarmActions && ticket.swarmActions.length > 0 ? `
|
|
690
|
+
<div class="error-section">
|
|
691
|
+
<h4>🤖 Swarm Actions</h4>
|
|
692
|
+
<div class="error-content">${ticket.swarmActions.map(a => typeof a === 'string' ? a : JSON.stringify(a, null, 2)).join('\n\n')}</div>
|
|
693
|
+
</div>
|
|
694
|
+
` : ''}
|
|
695
|
+
|
|
696
|
+
${ticket.namespace ? `
|
|
697
|
+
<div style="margin-top: 10px;">
|
|
698
|
+
<strong>Namespace:</strong> <code>${ticket.namespace}</code>
|
|
699
|
+
</div>
|
|
700
|
+
` : ''}
|
|
701
|
+
|
|
702
|
+
<div class="ticket-footer">
|
|
703
|
+
<div>Created: ${new Date(ticket.createdAt).toLocaleString()}</div>
|
|
704
|
+
<div>Updated: ${new Date(ticket.updatedAt).toLocaleString()}</div>
|
|
705
|
+
${ticket.relatedTickets && ticket.relatedTickets.length > 0 ? `
|
|
706
|
+
<div style="margin-top: 10px;">
|
|
707
|
+
<strong>Related:</strong>
|
|
708
|
+
${ticket.relatedTickets.map(id => `<span style="color: #00d4aa; margin-right: 10px;">${id}</span>`).join('')}
|
|
709
|
+
</div>
|
|
710
|
+
` : ''}
|
|
711
|
+
</div>
|
|
712
|
+
|
|
713
|
+
<button class="quick-prompt-btn" onclick="copyQuickPrompt('${ticket.id}')">
|
|
714
|
+
📋 Quick Prompt
|
|
715
|
+
</button>
|
|
716
|
+
</div>
|
|
717
|
+
`).join('');
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Filter tickets
|
|
721
|
+
function filterTickets() {
|
|
722
|
+
const statusFilter = document.getElementById('filter-status').value;
|
|
723
|
+
const priorityFilter = document.getElementById('filter-priority').value;
|
|
724
|
+
const searchTerm = document.getElementById('search').value.toLowerCase();
|
|
725
|
+
|
|
726
|
+
let filtered = tickets;
|
|
727
|
+
|
|
728
|
+
if (statusFilter) {
|
|
729
|
+
filtered = filtered.filter(t => t.status === statusFilter);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (priorityFilter) {
|
|
733
|
+
filtered = filtered.filter(t => t.priority === priorityFilter);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (searchTerm) {
|
|
737
|
+
filtered = filtered.filter(t =>
|
|
738
|
+
t.id.toLowerCase().includes(searchTerm) ||
|
|
739
|
+
t.route.toLowerCase().includes(searchTerm) ||
|
|
740
|
+
(t.description && t.description.toLowerCase().includes(searchTerm)) ||
|
|
741
|
+
(t.f12Errors && t.f12Errors.toLowerCase().includes(searchTerm)) ||
|
|
742
|
+
(t.serverErrors && t.serverErrors.toLowerCase().includes(searchTerm))
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
renderTickets(filtered);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Initialize
|
|
750
|
+
updateProjectName();
|
|
751
|
+
updateFormLabels();
|
|
752
|
+
loadTickets();
|
|
753
|
+
</script>
|
|
754
|
+
</body>
|
|
755
|
+
</html>
|