swagger-parser-mcp-server 2.3.3 → 3.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/README.md +81 -120
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +266 -102
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +2 -283
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +0 -108
- package/dist/schemas.js.map +1 -1
- package/dist/utils/swaggerCache.d.ts +0 -4
- package/dist/utils/swaggerCache.d.ts.map +1 -1
- package/dist/utils/swaggerCache.js +0 -8
- package/dist/utils/swaggerCache.js.map +1 -1
- package/package.json +3 -11
- package/dist/application/snapshot/createSnapshotRuntime.d.ts +0 -2
- package/dist/application/snapshot/createSnapshotRuntime.d.ts.map +0 -1
- package/dist/application/snapshot/createSnapshotRuntime.js +0 -2
- package/dist/application/snapshot/createSnapshotRuntime.js.map +0 -1
- package/dist/application/snapshot/snapshotCaptureService.d.ts +0 -90
- package/dist/application/snapshot/snapshotCaptureService.d.ts.map +0 -1
- package/dist/application/snapshot/snapshotCaptureService.js +0 -394
- package/dist/application/snapshot/snapshotCaptureService.js.map +0 -1
- package/dist/application/snapshot/snapshotRepository.d.ts +0 -77
- package/dist/application/snapshot/snapshotRepository.d.ts.map +0 -1
- package/dist/application/snapshot/snapshotRepository.js +0 -2
- package/dist/application/snapshot/snapshotRepository.js.map +0 -1
- package/dist/domain/canonical/canonicalSnapshot.d.ts +0 -61
- package/dist/domain/canonical/canonicalSnapshot.d.ts.map +0 -1
- package/dist/domain/canonical/canonicalSnapshot.js +0 -300
- package/dist/domain/canonical/canonicalSnapshot.js.map +0 -1
- package/dist/domain/contracts/runtimeEnvironmentContract.d.ts +0 -21
- package/dist/domain/contracts/runtimeEnvironmentContract.d.ts.map +0 -1
- package/dist/domain/contracts/runtimeEnvironmentContract.js +0 -50
- package/dist/domain/contracts/runtimeEnvironmentContract.js.map +0 -1
- package/dist/domain/contracts/snapshotDiffContract.d.ts +0 -270
- package/dist/domain/contracts/snapshotDiffContract.d.ts.map +0 -1
- package/dist/domain/contracts/snapshotDiffContract.js +0 -99
- package/dist/domain/contracts/snapshotDiffContract.js.map +0 -1
- package/dist/domain/diff/endpointDiffClassifier.d.ts +0 -78
- package/dist/domain/diff/endpointDiffClassifier.d.ts.map +0 -1
- package/dist/domain/diff/endpointDiffClassifier.js +0 -317
- package/dist/domain/diff/endpointDiffClassifier.js.map +0 -1
- package/dist/http.d.ts +0 -3
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -52
- package/dist/http.js.map +0 -1
- package/dist/infrastructure/postgres/migrationRunner.d.ts +0 -14
- package/dist/infrastructure/postgres/migrationRunner.d.ts.map +0 -1
- package/dist/infrastructure/postgres/migrationRunner.js +0 -161
- package/dist/infrastructure/postgres/migrationRunner.js.map +0 -1
- package/dist/infrastructure/postgres/migrations/001_snapshot_schema.sql +0 -29
- package/dist/infrastructure/postgres/migrations/002_snapshot_change_history.sql +0 -32
- package/dist/infrastructure/postgres/postgresSnapshotRepository.d.ts +0 -25
- package/dist/infrastructure/postgres/postgresSnapshotRepository.d.ts.map +0 -1
- package/dist/infrastructure/postgres/postgresSnapshotRepository.js +0 -323
- package/dist/infrastructure/postgres/postgresSnapshotRepository.js.map +0 -1
- package/dist/infrastructure/postgres/runMigrations.d.ts +0 -3
- package/dist/infrastructure/postgres/runMigrations.d.ts.map +0 -1
- package/dist/infrastructure/postgres/runMigrations.js +0 -33
- package/dist/infrastructure/postgres/runMigrations.js.map +0 -1
- package/dist/infrastructure/runtime/createSnapshotRuntime.d.ts +0 -17
- package/dist/infrastructure/runtime/createSnapshotRuntime.d.ts.map +0 -1
- package/dist/infrastructure/runtime/createSnapshotRuntime.js +0 -38
- package/dist/infrastructure/runtime/createSnapshotRuntime.js.map +0 -1
- package/dist/tests/canonicalSnapshot.test.d.ts +0 -2
- package/dist/tests/canonicalSnapshot.test.d.ts.map +0 -1
- package/dist/tests/canonicalSnapshot.test.js +0 -425
- package/dist/tests/canonicalSnapshot.test.js.map +0 -1
- package/dist/tests/endpointDiffClassifier.test.d.ts +0 -2
- package/dist/tests/endpointDiffClassifier.test.d.ts.map +0 -1
- package/dist/tests/endpointDiffClassifier.test.js +0 -633
- package/dist/tests/endpointDiffClassifier.test.js.map +0 -1
- package/dist/tests/httpSnapshotTransport.test.d.ts +0 -2
- package/dist/tests/httpSnapshotTransport.test.d.ts.map +0 -1
- package/dist/tests/httpSnapshotTransport.test.js +0 -356
- package/dist/tests/httpSnapshotTransport.test.js.map +0 -1
- package/dist/tests/indexLifecycle.test.d.ts +0 -2
- package/dist/tests/indexLifecycle.test.d.ts.map +0 -1
- package/dist/tests/indexLifecycle.test.js +0 -44
- package/dist/tests/indexLifecycle.test.js.map +0 -1
- package/dist/tests/mcpSnapshotTools.test.d.ts +0 -2
- package/dist/tests/mcpSnapshotTools.test.d.ts.map +0 -1
- package/dist/tests/mcpSnapshotTools.test.js +0 -316
- package/dist/tests/mcpSnapshotTools.test.js.map +0 -1
- package/dist/tests/postgresMigrationSmoke.test.d.ts +0 -2
- package/dist/tests/postgresMigrationSmoke.test.d.ts.map +0 -1
- package/dist/tests/postgresMigrationSmoke.test.js +0 -187
- package/dist/tests/postgresMigrationSmoke.test.js.map +0 -1
- package/dist/tests/realPostgresTestSchema.d.ts +0 -10
- package/dist/tests/realPostgresTestSchema.d.ts.map +0 -1
- package/dist/tests/realPostgresTestSchema.js +0 -73
- package/dist/tests/realPostgresTestSchema.js.map +0 -1
- package/dist/tests/snapshotCapturePipeline.test.d.ts +0 -2
- package/dist/tests/snapshotCapturePipeline.test.d.ts.map +0 -1
- package/dist/tests/snapshotCapturePipeline.test.js +0 -475
- package/dist/tests/snapshotCapturePipeline.test.js.map +0 -1
- package/dist/tests/snapshotDiffContract.test.d.ts +0 -2
- package/dist/tests/snapshotDiffContract.test.d.ts.map +0 -1
- package/dist/tests/snapshotDiffContract.test.js +0 -156
- package/dist/tests/snapshotDiffContract.test.js.map +0 -1
- package/dist/tests/snapshotPersistence.real.test.d.ts +0 -2
- package/dist/tests/snapshotPersistence.real.test.d.ts.map +0 -1
- package/dist/tests/snapshotPersistence.real.test.js +0 -310
- package/dist/tests/snapshotPersistence.real.test.js.map +0 -1
- package/dist/tests/webServerRuntime.test.d.ts +0 -2
- package/dist/tests/webServerRuntime.test.d.ts.map +0 -1
- package/dist/tests/webServerRuntime.test.js +0 -123
- package/dist/tests/webServerRuntime.test.js.map +0 -1
- package/dist/transport/createSnapshotToolRuntime.d.ts +0 -7
- package/dist/transport/createSnapshotToolRuntime.d.ts.map +0 -1
- package/dist/transport/createSnapshotToolRuntime.js +0 -40
- package/dist/transport/createSnapshotToolRuntime.js.map +0 -1
- package/dist/transport/httpSnapshotServer.d.ts +0 -11
- package/dist/transport/httpSnapshotServer.d.ts.map +0 -1
- package/dist/transport/httpSnapshotServer.js +0 -216
- package/dist/transport/httpSnapshotServer.js.map +0 -1
- package/dist/transport/mcpToolRouter.d.ts +0 -81
- package/dist/transport/mcpToolRouter.d.ts.map +0 -1
- package/dist/transport/mcpToolRouter.js +0 -416
- package/dist/transport/mcpToolRouter.js.map +0 -1
- package/dist/transport/webServerRuntime.d.ts +0 -17
- package/dist/transport/webServerRuntime.d.ts.map +0 -1
- package/dist/transport/webServerRuntime.js +0 -73
- package/dist/transport/webServerRuntime.js.map +0 -1
- package/dist/web/dashboard.css +0 -411
- package/dist/web/dashboard.html +0 -141
- package/dist/web/dashboard.js +0 -540
package/dist/web/dashboard.js
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
const API_BASE = '/api/snapshots';
|
|
2
|
-
const HISTORY_LIMIT = 50;
|
|
3
|
-
|
|
4
|
-
const elements = {
|
|
5
|
-
alert: document.getElementById('status-alert'),
|
|
6
|
-
sourceCaption: document.getElementById('source-caption'),
|
|
7
|
-
refreshButton: document.getElementById('refresh-snapshots-button'),
|
|
8
|
-
captureButton: document.getElementById('capture-button'),
|
|
9
|
-
compareForm: document.getElementById('compare-form'),
|
|
10
|
-
baselineSelect: document.getElementById('baseline-select'),
|
|
11
|
-
targetSelect: document.getElementById('target-select'),
|
|
12
|
-
snapshotList: document.getElementById('snapshot-list'),
|
|
13
|
-
metricTotal: document.getElementById('metric-total'),
|
|
14
|
-
metricBreaking: document.getElementById('metric-breaking'),
|
|
15
|
-
metricNonBreaking: document.getElementById('metric-non-breaking'),
|
|
16
|
-
metricInfo: document.getElementById('metric-info'),
|
|
17
|
-
diffSummaryLine: document.getElementById('diff-summary-line'),
|
|
18
|
-
changeTableBody: document.getElementById('change-table-body'),
|
|
19
|
-
historyCaption: document.getElementById('history-caption'),
|
|
20
|
-
historyFilterForm: document.getElementById('history-filter-form'),
|
|
21
|
-
historyPathInput: document.getElementById('history-path-input'),
|
|
22
|
-
historyMethodSelect: document.getElementById('history-method-select'),
|
|
23
|
-
historyClearButton: document.getElementById('history-clear-button'),
|
|
24
|
-
historyList: document.getElementById('history-list'),
|
|
25
|
-
detailHeading: document.getElementById('detail-heading'),
|
|
26
|
-
detailEvents: document.getElementById('detail-events'),
|
|
27
|
-
detailBefore: document.getElementById('detail-before'),
|
|
28
|
-
detailAfter: document.getElementById('detail-after'),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
!elements.alert ||
|
|
33
|
-
!elements.sourceCaption ||
|
|
34
|
-
!elements.refreshButton ||
|
|
35
|
-
!elements.captureButton ||
|
|
36
|
-
!elements.compareForm ||
|
|
37
|
-
!elements.baselineSelect ||
|
|
38
|
-
!elements.targetSelect ||
|
|
39
|
-
!elements.snapshotList ||
|
|
40
|
-
!elements.metricTotal ||
|
|
41
|
-
!elements.metricBreaking ||
|
|
42
|
-
!elements.metricNonBreaking ||
|
|
43
|
-
!elements.metricInfo ||
|
|
44
|
-
!elements.diffSummaryLine ||
|
|
45
|
-
!elements.changeTableBody ||
|
|
46
|
-
!elements.historyCaption ||
|
|
47
|
-
!elements.historyFilterForm ||
|
|
48
|
-
!elements.historyPathInput ||
|
|
49
|
-
!elements.historyMethodSelect ||
|
|
50
|
-
!elements.historyClearButton ||
|
|
51
|
-
!elements.historyList ||
|
|
52
|
-
!elements.detailHeading ||
|
|
53
|
-
!elements.detailEvents ||
|
|
54
|
-
!elements.detailBefore ||
|
|
55
|
-
!elements.detailAfter
|
|
56
|
-
) {
|
|
57
|
-
throw new Error('Dashboard markup is missing required elements.');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const state = {
|
|
61
|
-
projectKey: '',
|
|
62
|
-
sourceUrl: '',
|
|
63
|
-
snapshots: [],
|
|
64
|
-
diffResult: null,
|
|
65
|
-
historyEvents: [],
|
|
66
|
-
historyFilter: {
|
|
67
|
-
path: null,
|
|
68
|
-
method: null,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
function formatTimestamp(isoDate) {
|
|
73
|
-
const parsed = new Date(isoDate);
|
|
74
|
-
return Number.isNaN(parsed.getTime()) ? isoDate : parsed.toLocaleString();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function setAlert(message, kind = 'info') {
|
|
78
|
-
if (!message) {
|
|
79
|
-
elements.alert.hidden = true;
|
|
80
|
-
elements.alert.textContent = '';
|
|
81
|
-
elements.alert.classList.remove('error');
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
elements.alert.hidden = false;
|
|
86
|
-
elements.alert.textContent = message;
|
|
87
|
-
elements.alert.classList.toggle('error', kind === 'error');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function requestJson(path, options) {
|
|
91
|
-
const response = await fetch(path, options);
|
|
92
|
-
let payload = null;
|
|
93
|
-
try {
|
|
94
|
-
payload = await response.json();
|
|
95
|
-
} catch {
|
|
96
|
-
payload = null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
const errorMessage =
|
|
101
|
-
payload && typeof payload.error === 'string' ? payload.error : `Request failed: ${response.status}`;
|
|
102
|
-
throw new Error(errorMessage);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return payload;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function renderSnapshotSelects() {
|
|
109
|
-
const previousBaseline = elements.baselineSelect.value;
|
|
110
|
-
const previousTarget = elements.targetSelect.value;
|
|
111
|
-
const options = state.snapshots
|
|
112
|
-
.map(snapshot => `<option value="${snapshot.id}">${formatTimestamp(snapshot.capturedAt)} · ${snapshot.id}</option>`)
|
|
113
|
-
.join('');
|
|
114
|
-
|
|
115
|
-
elements.baselineSelect.innerHTML = options;
|
|
116
|
-
elements.targetSelect.innerHTML = options;
|
|
117
|
-
|
|
118
|
-
const hasPreviousBaseline = state.snapshots.some(snapshot => snapshot.id === previousBaseline);
|
|
119
|
-
const hasPreviousTarget = state.snapshots.some(snapshot => snapshot.id === previousTarget);
|
|
120
|
-
|
|
121
|
-
if (hasPreviousBaseline) {
|
|
122
|
-
elements.baselineSelect.value = previousBaseline;
|
|
123
|
-
}
|
|
124
|
-
if (hasPreviousTarget) {
|
|
125
|
-
elements.targetSelect.value = previousTarget;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!elements.baselineSelect.value && state.snapshots.length >= 2) {
|
|
129
|
-
elements.baselineSelect.value = state.snapshots[1].id;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!elements.targetSelect.value && state.snapshots.length >= 1) {
|
|
133
|
-
elements.targetSelect.value = state.snapshots[0].id;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (elements.baselineSelect.value === elements.targetSelect.value && state.snapshots.length >= 2) {
|
|
137
|
-
elements.baselineSelect.value = state.snapshots[1].id;
|
|
138
|
-
elements.targetSelect.value = state.snapshots[0].id;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function renderSnapshotList() {
|
|
143
|
-
if (state.snapshots.length === 0) {
|
|
144
|
-
elements.snapshotList.innerHTML = '<p class="caption">No snapshots captured yet.</p>';
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const list = document.createElement('div');
|
|
149
|
-
list.className = 'snapshot-list';
|
|
150
|
-
|
|
151
|
-
for (const snapshot of state.snapshots) {
|
|
152
|
-
const button = document.createElement('button');
|
|
153
|
-
button.type = 'button';
|
|
154
|
-
button.className = 'snapshot-item';
|
|
155
|
-
button.innerHTML = `
|
|
156
|
-
<time datetime="${snapshot.capturedAt}">${formatTimestamp(snapshot.capturedAt)}</time>
|
|
157
|
-
<strong>${snapshot.id}</strong>
|
|
158
|
-
`;
|
|
159
|
-
button.addEventListener('click', () => {
|
|
160
|
-
elements.targetSelect.value = snapshot.id;
|
|
161
|
-
if (elements.baselineSelect.value === snapshot.id && state.snapshots.length > 1) {
|
|
162
|
-
const fallback = state.snapshots.find(item => item.id !== snapshot.id);
|
|
163
|
-
if (fallback) {
|
|
164
|
-
elements.baselineSelect.value = fallback.id;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
setAlert(`Target snapshot selected: ${snapshot.id}`);
|
|
168
|
-
});
|
|
169
|
-
list.appendChild(button);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
elements.snapshotList.innerHTML = '';
|
|
173
|
-
elements.snapshotList.appendChild(list);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function renderChangeHistory() {
|
|
177
|
-
elements.historyPathInput.value = state.historyFilter.path ?? '';
|
|
178
|
-
elements.historyMethodSelect.value = state.historyFilter.method ?? '';
|
|
179
|
-
|
|
180
|
-
if (state.historyFilter.path && state.historyFilter.method) {
|
|
181
|
-
elements.historyCaption.textContent = `Timeline for ${state.historyFilter.method.toUpperCase()} ${state.historyFilter.path}`;
|
|
182
|
-
} else {
|
|
183
|
-
elements.historyCaption.textContent = 'Latest endpoint changes.';
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (state.historyEvents.length === 0) {
|
|
187
|
-
elements.historyList.innerHTML = '<p class="caption">No change history events.</p>';
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const list = document.createElement('div');
|
|
192
|
-
list.className = 'history-list';
|
|
193
|
-
|
|
194
|
-
for (const event of state.historyEvents) {
|
|
195
|
-
const button = document.createElement('button');
|
|
196
|
-
button.type = 'button';
|
|
197
|
-
button.className = 'history-item';
|
|
198
|
-
button.innerHTML = `
|
|
199
|
-
<div class="history-item-top">
|
|
200
|
-
<strong>${event.method.toUpperCase()} ${event.path}</strong>
|
|
201
|
-
<span class="${classificationClassName(event.classification)}">${event.classification}</span>
|
|
202
|
-
</div>
|
|
203
|
-
<div class="history-item-meta">
|
|
204
|
-
<span>${event.changeReason}</span>
|
|
205
|
-
<time datetime="${event.changedAt}">${formatTimestamp(event.changedAt)}</time>
|
|
206
|
-
</div>
|
|
207
|
-
<div class="caption">${event.baselineSnapshotId} -> ${event.targetSnapshotId}</div>
|
|
208
|
-
`;
|
|
209
|
-
button.addEventListener('click', () => {
|
|
210
|
-
setLoading(true);
|
|
211
|
-
void fetchSnapshots()
|
|
212
|
-
.then(() => {
|
|
213
|
-
elements.baselineSelect.value = event.baselineSnapshotId;
|
|
214
|
-
elements.targetSelect.value = event.targetSnapshotId;
|
|
215
|
-
return compareSnapshots();
|
|
216
|
-
})
|
|
217
|
-
.then(() => loadEndpointDetail(event.path, event.method))
|
|
218
|
-
.then(() => {
|
|
219
|
-
setAlert(`History event loaded: ${event.method.toUpperCase()} ${event.path}`);
|
|
220
|
-
})
|
|
221
|
-
.catch(error => {
|
|
222
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
223
|
-
setAlert(message, 'error');
|
|
224
|
-
})
|
|
225
|
-
.finally(() => {
|
|
226
|
-
setLoading(false);
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
list.appendChild(button);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
elements.historyList.innerHTML = '';
|
|
233
|
-
elements.historyList.appendChild(list);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function classificationClassName(value) {
|
|
237
|
-
if (value === 'breaking') {
|
|
238
|
-
return 'classification breaking';
|
|
239
|
-
}
|
|
240
|
-
if (value === 'non-breaking') {
|
|
241
|
-
return 'classification non-breaking';
|
|
242
|
-
}
|
|
243
|
-
return 'classification info';
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function resetDetail() {
|
|
247
|
-
elements.detailHeading.textContent = 'Pick one endpoint from the diff table.';
|
|
248
|
-
elements.detailEvents.innerHTML = '';
|
|
249
|
-
elements.detailBefore.textContent = 'No selection';
|
|
250
|
-
elements.detailAfter.textContent = 'No selection';
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function renderDiffSummary() {
|
|
254
|
-
if (!state.diffResult) {
|
|
255
|
-
elements.metricTotal.textContent = '-';
|
|
256
|
-
elements.metricBreaking.textContent = '-';
|
|
257
|
-
elements.metricNonBreaking.textContent = '-';
|
|
258
|
-
elements.metricInfo.textContent = '-';
|
|
259
|
-
elements.diffSummaryLine.textContent = 'No diff selected.';
|
|
260
|
-
elements.changeTableBody.innerHTML = '<tr><td colspan="3" class="caption">No change rows.</td></tr>';
|
|
261
|
-
resetDetail();
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const { summary, counts, changes, warnings } = state.diffResult;
|
|
266
|
-
elements.metricTotal.textContent = String(counts.total);
|
|
267
|
-
elements.metricBreaking.textContent = String(counts.breaking);
|
|
268
|
-
elements.metricNonBreaking.textContent = String(counts.nonBreaking);
|
|
269
|
-
elements.metricInfo.textContent = String(counts.info);
|
|
270
|
-
elements.diffSummaryLine.textContent = `Compared ${summary.baselineSnapshotId} -> ${summary.targetSnapshotId} at ${formatTimestamp(
|
|
271
|
-
summary.comparedAt
|
|
272
|
-
)}`;
|
|
273
|
-
|
|
274
|
-
if (warnings.length > 0) {
|
|
275
|
-
setAlert(warnings.map(warning => warning.message).join(' | '));
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (changes.length === 0) {
|
|
279
|
-
elements.changeTableBody.innerHTML = '<tr><td colspan="3" class="caption">No endpoint changes.</td></tr>';
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
elements.changeTableBody.innerHTML = '';
|
|
284
|
-
for (const change of changes) {
|
|
285
|
-
const row = document.createElement('tr');
|
|
286
|
-
const endpointCell = document.createElement('td');
|
|
287
|
-
const endpointButton = document.createElement('button');
|
|
288
|
-
endpointButton.type = 'button';
|
|
289
|
-
endpointButton.className = 'button secondary';
|
|
290
|
-
endpointButton.textContent = `${change.method.toUpperCase()} ${change.url}`;
|
|
291
|
-
endpointButton.addEventListener('click', () => {
|
|
292
|
-
void loadEndpointDetail(change.url, change.method);
|
|
293
|
-
});
|
|
294
|
-
endpointCell.appendChild(endpointButton);
|
|
295
|
-
|
|
296
|
-
const classificationCell = document.createElement('td');
|
|
297
|
-
classificationCell.className = classificationClassName(change.classification);
|
|
298
|
-
classificationCell.textContent = change.classification;
|
|
299
|
-
|
|
300
|
-
const reasonCell = document.createElement('td');
|
|
301
|
-
reasonCell.textContent = change.changeReason;
|
|
302
|
-
|
|
303
|
-
row.appendChild(endpointCell);
|
|
304
|
-
row.appendChild(classificationCell);
|
|
305
|
-
row.appendChild(reasonCell);
|
|
306
|
-
elements.changeTableBody.appendChild(row);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function renderEndpointDetail(detail) {
|
|
311
|
-
elements.detailHeading.textContent = `${detail.method.toUpperCase()} ${detail.path} · ${detail.changeReason ?? 'no_change'}`;
|
|
312
|
-
elements.detailEvents.innerHTML = '';
|
|
313
|
-
|
|
314
|
-
if (detail.details.length === 0) {
|
|
315
|
-
const empty = document.createElement('p');
|
|
316
|
-
empty.className = 'caption';
|
|
317
|
-
empty.textContent = 'No detail events.';
|
|
318
|
-
elements.detailEvents.appendChild(empty);
|
|
319
|
-
} else {
|
|
320
|
-
for (const event of detail.details) {
|
|
321
|
-
const item = document.createElement('div');
|
|
322
|
-
item.className = 'event-row';
|
|
323
|
-
item.innerHTML = `
|
|
324
|
-
<strong class="${classificationClassName(event.classification)}">${event.classification}</strong>
|
|
325
|
-
<div>${event.message}</div>
|
|
326
|
-
`;
|
|
327
|
-
elements.detailEvents.appendChild(item);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
elements.detailBefore.textContent = JSON.stringify(detail.before, null, 2) ?? 'null';
|
|
332
|
-
elements.detailAfter.textContent = JSON.stringify(detail.after, null, 2) ?? 'null';
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async function fetchSnapshots() {
|
|
336
|
-
const result = await requestJson(`${API_BASE}?limit=200`);
|
|
337
|
-
state.projectKey = result.projectKey;
|
|
338
|
-
state.sourceUrl = result.sourceUrl;
|
|
339
|
-
state.snapshots = result.snapshots;
|
|
340
|
-
elements.sourceCaption.textContent = `${result.projectKey} · ${result.sourceUrl}`;
|
|
341
|
-
renderSnapshotSelects();
|
|
342
|
-
renderSnapshotList();
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function normalizeHistoryFilter(filter = {}) {
|
|
346
|
-
const path = typeof filter.path === 'string' && filter.path.trim().length > 0 ? filter.path.trim() : undefined;
|
|
347
|
-
const method =
|
|
348
|
-
typeof filter.method === 'string' && filter.method.trim().length > 0
|
|
349
|
-
? filter.method.trim().toLowerCase()
|
|
350
|
-
: undefined;
|
|
351
|
-
|
|
352
|
-
if (!path && !method) {
|
|
353
|
-
return {};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
path,
|
|
358
|
-
method,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function readHistoryFilterInputs() {
|
|
363
|
-
return normalizeHistoryFilter({
|
|
364
|
-
path: elements.historyPathInput.value,
|
|
365
|
-
method: elements.historyMethodSelect.value,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
async function loadChangeHistory(filter = {}) {
|
|
370
|
-
const normalizedFilter = normalizeHistoryFilter(filter);
|
|
371
|
-
const params = new URLSearchParams({
|
|
372
|
-
limit: String(HISTORY_LIMIT),
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
if (normalizedFilter.path) {
|
|
376
|
-
params.set('path', normalizedFilter.path);
|
|
377
|
-
}
|
|
378
|
-
if (normalizedFilter.method) {
|
|
379
|
-
params.set('method', normalizedFilter.method);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const result = await requestJson(`${API_BASE}/change-history?${params.toString()}`);
|
|
383
|
-
state.historyEvents = result.events;
|
|
384
|
-
state.historyFilter = result.filter;
|
|
385
|
-
renderChangeHistory();
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
async function compareSnapshots() {
|
|
389
|
-
const baselineSnapshotId = elements.baselineSelect.value;
|
|
390
|
-
const targetSnapshotId = elements.targetSelect.value;
|
|
391
|
-
|
|
392
|
-
if (!baselineSnapshotId || !targetSnapshotId) {
|
|
393
|
-
setAlert('Choose baseline and target snapshot first.', 'error');
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (baselineSnapshotId === targetSnapshotId) {
|
|
398
|
-
setAlert('Baseline and target must be different.', 'error');
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const diffResult = await requestJson(
|
|
403
|
-
`${API_BASE}/diff?baselineSnapshotId=${encodeURIComponent(baselineSnapshotId)}&targetSnapshotId=${encodeURIComponent(
|
|
404
|
-
targetSnapshotId
|
|
405
|
-
)}`
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
state.diffResult = diffResult;
|
|
409
|
-
renderDiffSummary();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async function loadEndpointDetail(endpointPath, endpointMethod) {
|
|
413
|
-
const baselineSnapshotId = elements.baselineSelect.value;
|
|
414
|
-
const targetSnapshotId = elements.targetSelect.value;
|
|
415
|
-
const detail = await requestJson(
|
|
416
|
-
`${API_BASE}/endpoint-detail?baselineSnapshotId=${encodeURIComponent(
|
|
417
|
-
baselineSnapshotId
|
|
418
|
-
)}&targetSnapshotId=${encodeURIComponent(targetSnapshotId)}&path=${encodeURIComponent(
|
|
419
|
-
endpointPath
|
|
420
|
-
)}&method=${encodeURIComponent(endpointMethod)}`
|
|
421
|
-
);
|
|
422
|
-
await loadChangeHistory({
|
|
423
|
-
path: endpointPath,
|
|
424
|
-
method: endpointMethod,
|
|
425
|
-
});
|
|
426
|
-
renderEndpointDetail(detail);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
async function captureSnapshot() {
|
|
430
|
-
const capturedDiff = await requestJson(`${API_BASE}/capture`, { method: 'POST' });
|
|
431
|
-
state.diffResult = capturedDiff;
|
|
432
|
-
await fetchSnapshots();
|
|
433
|
-
await loadChangeHistory({
|
|
434
|
-
path: state.historyFilter.path ?? undefined,
|
|
435
|
-
method: state.historyFilter.method ?? undefined,
|
|
436
|
-
});
|
|
437
|
-
if (capturedDiff.summary?.baselineSnapshotId) {
|
|
438
|
-
elements.baselineSelect.value = capturedDiff.summary.baselineSnapshotId;
|
|
439
|
-
}
|
|
440
|
-
if (capturedDiff.summary?.targetSnapshotId) {
|
|
441
|
-
elements.targetSelect.value = capturedDiff.summary.targetSnapshotId;
|
|
442
|
-
}
|
|
443
|
-
renderDiffSummary();
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function setLoading(isLoading) {
|
|
447
|
-
elements.refreshButton.disabled = isLoading;
|
|
448
|
-
elements.captureButton.disabled = isLoading;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
async function loadInitialData() {
|
|
452
|
-
setLoading(true);
|
|
453
|
-
try {
|
|
454
|
-
await fetchSnapshots();
|
|
455
|
-
await loadChangeHistory();
|
|
456
|
-
if (state.snapshots.length >= 2) {
|
|
457
|
-
await compareSnapshots();
|
|
458
|
-
} else {
|
|
459
|
-
renderDiffSummary();
|
|
460
|
-
}
|
|
461
|
-
setAlert('Dashboard loaded.');
|
|
462
|
-
} catch (error) {
|
|
463
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
464
|
-
setAlert(message, 'error');
|
|
465
|
-
renderDiffSummary();
|
|
466
|
-
} finally {
|
|
467
|
-
setLoading(false);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
elements.refreshButton.addEventListener('click', () => {
|
|
472
|
-
void loadInitialData();
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
elements.captureButton.addEventListener('click', () => {
|
|
476
|
-
setLoading(true);
|
|
477
|
-
void captureSnapshot()
|
|
478
|
-
.then(() => {
|
|
479
|
-
setAlert('Snapshot captured successfully.');
|
|
480
|
-
})
|
|
481
|
-
.catch(error => {
|
|
482
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
483
|
-
setAlert(message, 'error');
|
|
484
|
-
})
|
|
485
|
-
.finally(() => {
|
|
486
|
-
setLoading(false);
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
elements.compareForm.addEventListener('submit', event => {
|
|
491
|
-
event.preventDefault();
|
|
492
|
-
setLoading(true);
|
|
493
|
-
void compareSnapshots()
|
|
494
|
-
.then(() => {
|
|
495
|
-
setAlert('Diff updated.');
|
|
496
|
-
resetDetail();
|
|
497
|
-
})
|
|
498
|
-
.catch(error => {
|
|
499
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
500
|
-
setAlert(message, 'error');
|
|
501
|
-
})
|
|
502
|
-
.finally(() => {
|
|
503
|
-
setLoading(false);
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
elements.historyFilterForm.addEventListener('submit', event => {
|
|
508
|
-
event.preventDefault();
|
|
509
|
-
setLoading(true);
|
|
510
|
-
void loadChangeHistory(readHistoryFilterInputs())
|
|
511
|
-
.then(() => {
|
|
512
|
-
setAlert('Change history updated.');
|
|
513
|
-
})
|
|
514
|
-
.catch(error => {
|
|
515
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
516
|
-
setAlert(message, 'error');
|
|
517
|
-
})
|
|
518
|
-
.finally(() => {
|
|
519
|
-
setLoading(false);
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
elements.historyClearButton.addEventListener('click', () => {
|
|
524
|
-
elements.historyPathInput.value = '';
|
|
525
|
-
elements.historyMethodSelect.value = '';
|
|
526
|
-
setLoading(true);
|
|
527
|
-
void loadChangeHistory()
|
|
528
|
-
.then(() => {
|
|
529
|
-
setAlert('Change history filter cleared.');
|
|
530
|
-
})
|
|
531
|
-
.catch(error => {
|
|
532
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
533
|
-
setAlert(message, 'error');
|
|
534
|
-
})
|
|
535
|
-
.finally(() => {
|
|
536
|
-
setLoading(false);
|
|
537
|
-
});
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
void loadInitialData();
|