bugpatrol 0.1.4 → 0.1.5
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/dist/bin.js +2947 -613
- package/package.json +1 -1
- package/src/ui/app.js +83 -0
- package/src/ui/index.html +1 -0
- package/src/ui/style.css +23 -0
package/package.json
CHANGED
package/src/ui/app.js
CHANGED
|
@@ -20,6 +20,7 @@ const state = {
|
|
|
20
20
|
sessions: [],
|
|
21
21
|
selectedSessionId: null,
|
|
22
22
|
sessionDetail: null,
|
|
23
|
+
flow: null,
|
|
23
24
|
appmap: null,
|
|
24
25
|
selectedScreenId: null,
|
|
25
26
|
routines: [],
|
|
@@ -103,6 +104,15 @@ async function refresh({ keepSelection = true } = {}) {
|
|
|
103
104
|
state.sessionDetail = null;
|
|
104
105
|
}
|
|
105
106
|
}
|
|
107
|
+
if (state.view === 'flow') {
|
|
108
|
+
state.sessions = await getJson('/api/sessions');
|
|
109
|
+
if (!state.sessions.some((session) => session.id === state.selectedSessionId)) {
|
|
110
|
+
state.selectedSessionId = state.sessions[0]?.id ?? null;
|
|
111
|
+
}
|
|
112
|
+
state.flow = state.selectedSessionId
|
|
113
|
+
? await getJson(`/api/flow/${encodeURIComponent(state.selectedSessionId)}`).catch(() => null)
|
|
114
|
+
: null;
|
|
115
|
+
}
|
|
106
116
|
if (state.view === 'screens') {
|
|
107
117
|
state.appmap = await getJson('/api/appmap');
|
|
108
118
|
state.issues = await getJson('/api/issues');
|
|
@@ -143,6 +153,7 @@ function render() {
|
|
|
143
153
|
overview: renderOverview,
|
|
144
154
|
issues: renderIssues,
|
|
145
155
|
activity: renderActivity,
|
|
156
|
+
flow: renderFlow,
|
|
146
157
|
screens: renderScreens,
|
|
147
158
|
memory: renderMemory,
|
|
148
159
|
checks: renderRuns,
|
|
@@ -1159,6 +1170,78 @@ function renderEventRow(event) {
|
|
|
1159
1170
|
]);
|
|
1160
1171
|
}
|
|
1161
1172
|
|
|
1173
|
+
// The flow view is what a human reads when triaging: the agent's actions, the
|
|
1174
|
+
// failed requests the app reported, and the backend logs that followed, merged
|
|
1175
|
+
// in time order. A log close behind a failure is marked correlated.
|
|
1176
|
+
function renderFlow() {
|
|
1177
|
+
const list = el('section', { class: 'card panel' }, [
|
|
1178
|
+
title('Flow'),
|
|
1179
|
+
...state.sessions.map((session) =>
|
|
1180
|
+
el(
|
|
1181
|
+
'button',
|
|
1182
|
+
{
|
|
1183
|
+
class: `list-row ${session.id === state.selectedSessionId ? 'active' : ''}`,
|
|
1184
|
+
onclick: async () => {
|
|
1185
|
+
state.selectedSessionId = session.id;
|
|
1186
|
+
await refresh();
|
|
1187
|
+
},
|
|
1188
|
+
},
|
|
1189
|
+
[
|
|
1190
|
+
roleIcon(session.role),
|
|
1191
|
+
el('span', { class: 'grow' }, [
|
|
1192
|
+
el('strong', { text: firstSentence(session.summary) || `${capital(session.role)} session` }),
|
|
1193
|
+
el('small', {
|
|
1194
|
+
class: 'muted',
|
|
1195
|
+
text: `${capital(session.role)} · ${relativeTime(session.startedAt)} · ${duration(session)}`,
|
|
1196
|
+
}),
|
|
1197
|
+
]),
|
|
1198
|
+
],
|
|
1199
|
+
),
|
|
1200
|
+
),
|
|
1201
|
+
]);
|
|
1202
|
+
const flow = state.flow;
|
|
1203
|
+
if (!flow) {
|
|
1204
|
+
return el('div', { class: 'master-detail' }, [
|
|
1205
|
+
list,
|
|
1206
|
+
el('section', {
|
|
1207
|
+
class: 'card panel empty',
|
|
1208
|
+
text: 'No flow for this session. Add a `logs:` source to bugpatrol.yml and run a patrol.',
|
|
1209
|
+
}),
|
|
1210
|
+
]);
|
|
1211
|
+
}
|
|
1212
|
+
const sources = flow.sources.length
|
|
1213
|
+
? flow.sources.map((source) =>
|
|
1214
|
+
el('div', {
|
|
1215
|
+
text: `${source.name} (${source.kind}): ${source.collected} line${source.collected === 1 ? '' : 's'}`,
|
|
1216
|
+
}),
|
|
1217
|
+
)
|
|
1218
|
+
: [
|
|
1219
|
+
el('div', {
|
|
1220
|
+
text: 'No log sources configured. Add `logs:` to bugpatrol.yml to see backend logs beside each action.',
|
|
1221
|
+
}),
|
|
1222
|
+
];
|
|
1223
|
+
return el('div', { class: 'master-detail' }, [
|
|
1224
|
+
list,
|
|
1225
|
+
el('section', { class: 'card panel' }, [
|
|
1226
|
+
title('Flow', `${capital(flow.role)} · ${relativeTime(flow.startedAt)}`),
|
|
1227
|
+
el('div', { class: 'muted session-usage' }, sources),
|
|
1228
|
+
flow.entries.length
|
|
1229
|
+
? el('div', { class: 'event-list' }, flow.entries.map(renderFlowRow))
|
|
1230
|
+
: el('div', { class: 'muted', text: 'This session recorded nothing.' }),
|
|
1231
|
+
]),
|
|
1232
|
+
]);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function renderFlowRow(entry) {
|
|
1236
|
+
return el('div', { class: `event-row flow-row flow-${entry.kind}${entry.correlated ? ' correlated' : ''}` }, [
|
|
1237
|
+
el('time', { text: new Date(entry.at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }),
|
|
1238
|
+
el('span', { class: 'flow-kind', text: entry.kind }),
|
|
1239
|
+
el('span', { class: 'grow truncate', text: entry.summary }),
|
|
1240
|
+
entry.level ? el('small', { class: 'muted', text: entry.level }) : null,
|
|
1241
|
+
entry.screenshot ? image(entry.screenshot, entry.summary, 'event-thumb') : null,
|
|
1242
|
+
]);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1162
1245
|
async function openScreen(id) {
|
|
1163
1246
|
state.view = 'screens';
|
|
1164
1247
|
state.selectedScreenId = id;
|
package/src/ui/index.html
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<button type="button" data-view="overview" class="active">Overview</button>
|
|
17
17
|
<button type="button" data-view="issues">Issues</button>
|
|
18
18
|
<button type="button" data-view="activity">Activity</button>
|
|
19
|
+
<button type="button" data-view="flow">Flow</button>
|
|
19
20
|
<button type="button" data-view="screens">Screens</button>
|
|
20
21
|
<button type="button" data-view="memory">Memory</button>
|
|
21
22
|
<button type="button" data-view="checks">Checks</button>
|
package/src/ui/style.css
CHANGED
|
@@ -749,6 +749,29 @@ h1 {
|
|
|
749
749
|
font-size: 0.75rem;
|
|
750
750
|
white-space: nowrap;
|
|
751
751
|
}
|
|
752
|
+
.flow-row {
|
|
753
|
+
gap: 0.6rem;
|
|
754
|
+
}
|
|
755
|
+
.flow-row.correlated {
|
|
756
|
+
background: color-mix(in srgb, #b93038 12%, transparent);
|
|
757
|
+
}
|
|
758
|
+
.flow-kind {
|
|
759
|
+
flex: none;
|
|
760
|
+
width: 4.2rem;
|
|
761
|
+
font-size: 0.72rem;
|
|
762
|
+
text-transform: uppercase;
|
|
763
|
+
letter-spacing: 0.04em;
|
|
764
|
+
color: var(--muted);
|
|
765
|
+
}
|
|
766
|
+
.flow-action .flow-kind {
|
|
767
|
+
color: #3b6ea5;
|
|
768
|
+
}
|
|
769
|
+
.flow-request .flow-kind {
|
|
770
|
+
color: #ae551c;
|
|
771
|
+
}
|
|
772
|
+
.flow-log .flow-kind {
|
|
773
|
+
color: #4f7a4f;
|
|
774
|
+
}
|
|
752
775
|
.event-row details {
|
|
753
776
|
flex: none;
|
|
754
777
|
font-size: 0.75rem;
|