swaggerizer 0.0.0-stage → 1.0.1
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 +21 -0
- package/README.md +359 -2
- package/dist/admin.d.ts +35 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/catalogue.d.ts +22 -0
- package/dist/catalogue.d.ts.map +1 -0
- package/dist/chunks/scenario-DXI-TbMN.mjs +371 -0
- package/dist/chunks/scenario-DXI-TbMN.mjs.map +1 -0
- package/dist/chunks/scenario-_ZBu5fJX.js +391 -0
- package/dist/chunks/scenario-_ZBu5fJX.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2977 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +129 -0
- package/dist/config.js.map +1 -0
- package/dist/config.mjs +120 -0
- package/dist/config.mjs.map +1 -0
- package/dist/contract.d.ts +60 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/debug-menu.d.ts +20 -0
- package/dist/debug-menu.d.ts.map +1 -0
- package/dist/debug-menu.js +238 -0
- package/dist/debug-menu.js.map +1 -0
- package/dist/debug-menu.mjs +236 -0
- package/dist/debug-menu.mjs.map +1 -0
- package/dist/generate.d.ts +74 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2397 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2343 -0
- package/dist/index.mjs.map +1 -0
- package/dist/load-config.d.ts +15 -0
- package/dist/load-config.d.ts.map +1 -0
- package/dist/msw.d.ts +48 -0
- package/dist/msw.d.ts.map +1 -0
- package/dist/msw.js +324 -0
- package/dist/msw.js.map +1 -0
- package/dist/msw.mjs +317 -0
- package/dist/msw.mjs.map +1 -0
- package/dist/paths.d.ts +14 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/pull.d.ts +31 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/scenario.d.ts +86 -0
- package/dist/scenario.d.ts.map +1 -0
- package/dist/schema.d.ts +13 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/server.d.ts +83 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/spec.d.ts +36 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/ui/app.css +1119 -0
- package/dist/ui/app.js +806 -0
- package/dist/ui/index.html +77 -0
- package/dist/ui-assets.d.ts +9 -0
- package/dist/ui-assets.d.ts.map +1 -0
- package/package.json +99 -4
package/dist/ui/app.js
ADDED
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
/* swaggerizer admin page – plain browser JavaScript, no build step or dependencies. */
|
|
2
|
+
(function () {
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
var metaContent = (document.querySelector('meta[name="swaggerizer-base"]') || {}).content;
|
|
6
|
+
var BASE = (typeof metaContent === "string" && metaContent !== "__SWAGGERIZER_BASE__") ? metaContent : "/swaggerizer";
|
|
7
|
+
var STATE_POLL_MS = 3000;
|
|
8
|
+
var LOG_POLL_MS = 1500;
|
|
9
|
+
var LOG_KEEP = 100;
|
|
10
|
+
|
|
11
|
+
var ui = {
|
|
12
|
+
data: null,
|
|
13
|
+
dataSig: "",
|
|
14
|
+
filterText: "",
|
|
15
|
+
filterMethods: new Set(),
|
|
16
|
+
onlyModified: false,
|
|
17
|
+
openDrawers: new Set(),
|
|
18
|
+
tryUrls: new Map(),
|
|
19
|
+
tryResults: new Map(),
|
|
20
|
+
drafts: new Map(),
|
|
21
|
+
reportOpen: new Map(),
|
|
22
|
+
logSince: 0,
|
|
23
|
+
logEntries: [],
|
|
24
|
+
logClearedAt: 0,
|
|
25
|
+
freshLogIds: new Set()
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
var el = {
|
|
29
|
+
stats: document.getElementById("stats"),
|
|
30
|
+
conn: document.getElementById("conn"),
|
|
31
|
+
specs: document.getElementById("spec-list"),
|
|
32
|
+
methods: document.getElementById("method-filters"),
|
|
33
|
+
filterText: document.getElementById("filter-text"),
|
|
34
|
+
filterModified: document.getElementById("filter-modified"),
|
|
35
|
+
content: document.getElementById("content"),
|
|
36
|
+
log: document.getElementById("log"),
|
|
37
|
+
logEmpty: document.getElementById("log-empty"),
|
|
38
|
+
toasts: document.getElementById("toasts"),
|
|
39
|
+
reload: document.getElementById("reload-btn"),
|
|
40
|
+
resetAll: document.getElementById("reset-all-btn"),
|
|
41
|
+
clearLog: document.getElementById("clear-log-btn")
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/* ------------------------------------------------------------ helpers */
|
|
45
|
+
|
|
46
|
+
function h(tag, attrs) {
|
|
47
|
+
var node = document.createElement(tag);
|
|
48
|
+
if (attrs) {
|
|
49
|
+
Object.keys(attrs).forEach(function (key) {
|
|
50
|
+
var value = attrs[key];
|
|
51
|
+
if (value === undefined || value === null || value === false) return;
|
|
52
|
+
if (key === "class") node.className = value;
|
|
53
|
+
else if (key === "text") node.textContent = value;
|
|
54
|
+
else if (key.slice(0, 2) === "on") node.addEventListener(key.slice(2), value);
|
|
55
|
+
else if (key === "checked" || key === "value" || key === "hidden" || key === "open") node[key] = value;
|
|
56
|
+
else node.setAttribute(key, value === true ? "" : String(value));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
for (var i = 2; i < arguments.length; i++) append(node, arguments[i]);
|
|
60
|
+
return node;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function append(node, child) {
|
|
64
|
+
if (child === undefined || child === null || child === false) return;
|
|
65
|
+
if (Array.isArray(child)) return child.forEach(function (c) { append(node, c); });
|
|
66
|
+
node.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function api(path, body) {
|
|
70
|
+
var opts = body === undefined
|
|
71
|
+
? { headers: { Accept: "application/json" } }
|
|
72
|
+
: {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
75
|
+
body: JSON.stringify(body)
|
|
76
|
+
};
|
|
77
|
+
return fetch(BASE + path, opts).then(function (res) {
|
|
78
|
+
return res.json().catch(function () { return {}; }).then(function (json) {
|
|
79
|
+
setOnline(true);
|
|
80
|
+
if (!res.ok) throw new Error(json.error || "Request failed (" + res.status + ")");
|
|
81
|
+
return json;
|
|
82
|
+
});
|
|
83
|
+
}, function (err) {
|
|
84
|
+
setOnline(false);
|
|
85
|
+
throw err;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function setOnline(online) {
|
|
90
|
+
el.conn.classList.toggle("is-online", online);
|
|
91
|
+
el.conn.classList.toggle("is-offline", !online);
|
|
92
|
+
el.conn.querySelector(".conn-text").textContent = online ? "Live" : "Server unreachable";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function toast(message, isError) {
|
|
96
|
+
var node = h("div", { class: "toast" + (isError ? " is-error" : ""), text: message });
|
|
97
|
+
el.toasts.appendChild(node);
|
|
98
|
+
setTimeout(function () { node.remove(); }, isError ? 6000 : 2600);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function statusClass(code) {
|
|
102
|
+
var first = String(code).charAt(0);
|
|
103
|
+
return "status status-" + ("12345".indexOf(first) >= 0 ? first : "5");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function methodBadge(method) {
|
|
107
|
+
var m = String(method).toLowerCase();
|
|
108
|
+
return h("span", { class: "method method-" + m, text: m.toUpperCase() });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function pretty(value) {
|
|
112
|
+
if (value === undefined) return "(no body)";
|
|
113
|
+
if (typeof value === "string") return value;
|
|
114
|
+
try { return JSON.stringify(value, null, 2); } catch (e) { return String(value); }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function shellQuote(text) {
|
|
118
|
+
return "'" + String(text).replace(/'/g, "'\\''") + "'";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function timeOf(iso) {
|
|
122
|
+
var d = new Date(iso);
|
|
123
|
+
return isNaN(d) ? "" : d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function safeId(text) {
|
|
127
|
+
return String(text).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function opId(op) {
|
|
131
|
+
return "op-" + safeId(op.specId) + "-" + safeId(op.key);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Every selectable (code, example) pair for an operation – mirrors variantsOf() on the server. */
|
|
135
|
+
function variantsOf(op) {
|
|
136
|
+
var list = [];
|
|
137
|
+
op.responses.forEach(function (r) {
|
|
138
|
+
if (r.examples.length > 0) {
|
|
139
|
+
r.examples.forEach(function (ex) {
|
|
140
|
+
list.push({ code: r.code, example: ex.name, label: ex.name, summary: ex.summary, kind: "example", value: ex.value, response: r });
|
|
141
|
+
});
|
|
142
|
+
} else if (r.hasBody) {
|
|
143
|
+
list.push({ code: r.code, example: "generated", label: "generated", kind: "generated", value: r.generated, response: r });
|
|
144
|
+
} else {
|
|
145
|
+
list.push({ code: r.code, example: "empty", label: "no body", kind: "empty", value: undefined, response: r });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return list;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function selectedVariant(op, variants) {
|
|
152
|
+
variants = variants || variantsOf(op);
|
|
153
|
+
for (var i = 0; i < variants.length; i++) {
|
|
154
|
+
if (variants[i].code === op.selectedCode && variants[i].example === op.selectedExample) return variants[i];
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function sampleUrl(op) {
|
|
160
|
+
return op.route.replace(/\{[^}]+\}/g, "1");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function specOf(op) {
|
|
164
|
+
if (!ui.data) return null;
|
|
165
|
+
for (var i = 0; i < ui.data.specs.length; i++) if (ui.data.specs[i].id === op.specId) return ui.data.specs[i];
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function servingLabel(op, current) {
|
|
170
|
+
if (op.mode === "proxy") return "real API";
|
|
171
|
+
if (op.mode === "custom") return (op.custom ? op.custom.status : "") + " · custom";
|
|
172
|
+
return current ? current.code + " · " + current.label : op.selectedCode;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ------------------------------------------------------------ data flow */
|
|
176
|
+
|
|
177
|
+
function applyState(state) {
|
|
178
|
+
var sig = JSON.stringify(state);
|
|
179
|
+
if (sig === ui.dataSig) return;
|
|
180
|
+
ui.data = state;
|
|
181
|
+
ui.dataSig = sig;
|
|
182
|
+
render();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function refreshState() {
|
|
186
|
+
return api("/api/state").then(applyState);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function mergeOperations(updated) {
|
|
190
|
+
if (!ui.data) return;
|
|
191
|
+
updated.forEach(function (op) {
|
|
192
|
+
ui.data.operations = ui.data.operations.map(function (existing) {
|
|
193
|
+
return existing.key === op.key && existing.specId === op.specId ? op : existing;
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
ui.dataSig = "";
|
|
197
|
+
render();
|
|
198
|
+
refreshState().catch(function () {});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function select(op, body, message) {
|
|
202
|
+
body.method = op.method;
|
|
203
|
+
body.route = op.route;
|
|
204
|
+
return api("/api/select", body)
|
|
205
|
+
.then(function (res) {
|
|
206
|
+
mergeOperations(res.operations || []);
|
|
207
|
+
if (message) toast(op.method.toUpperCase() + " " + op.route + " → " + message);
|
|
208
|
+
})
|
|
209
|
+
.catch(function (err) {
|
|
210
|
+
toast(err.message, true);
|
|
211
|
+
refreshState().catch(function () {});
|
|
212
|
+
throw err;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function resetOne(op) {
|
|
217
|
+
ui.drafts.delete(opId(op));
|
|
218
|
+
return api("/api/reset", { method: op.method, route: op.route }).then(function (res) {
|
|
219
|
+
applyState(res.state);
|
|
220
|
+
toast(op.method.toUpperCase() + " " + op.route + " reset to default");
|
|
221
|
+
}, function (err) { toast(err.message, true); });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function withBusy(button, work) {
|
|
225
|
+
button.disabled = true;
|
|
226
|
+
return work().then(function () { button.disabled = false; }, function () { button.disabled = false; });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* ------------------------------------------------------------ rendering */
|
|
230
|
+
|
|
231
|
+
function isEditing() {
|
|
232
|
+
var a = document.activeElement;
|
|
233
|
+
return a && (a.tagName === "TEXTAREA" || (a.tagName === "INPUT" && a.closest && a.closest(".op")));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function render() {
|
|
237
|
+
if (!ui.data) return;
|
|
238
|
+
var focusedId = document.activeElement && document.activeElement.id;
|
|
239
|
+
renderStats();
|
|
240
|
+
renderSpecs();
|
|
241
|
+
renderMethodChips();
|
|
242
|
+
// Polling must not wipe out what someone is typing into a custom response or delay box.
|
|
243
|
+
if (isEditing()) ui.pendingOps = true;
|
|
244
|
+
else {
|
|
245
|
+
ui.pendingOps = false;
|
|
246
|
+
renderOperations();
|
|
247
|
+
}
|
|
248
|
+
if (focusedId) {
|
|
249
|
+
var again = document.getElementById(focusedId);
|
|
250
|
+
if (again && again !== document.activeElement) again.focus({ preventScroll: true });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function renderStats() {
|
|
255
|
+
var specs = ui.data.specs;
|
|
256
|
+
var ops = ui.data.operations;
|
|
257
|
+
var enabled = specs.filter(function (s) { return s.enabled && !s.error; }).length;
|
|
258
|
+
var active = ops.filter(function (o) { return o.active; });
|
|
259
|
+
var changed = active.filter(function (o) { return o.modified; }).length;
|
|
260
|
+
var breaking = (ui.data.changes || []).reduce(function (n, r) { return n + r.counts.breaking; }, 0);
|
|
261
|
+
el.stats.replaceChildren(
|
|
262
|
+
h("span", null, h("strong", { text: String(enabled) }), " / " + specs.length + " specs on"),
|
|
263
|
+
h("span", null, h("strong", { text: String(active.length) }), " endpoints served"),
|
|
264
|
+
h("span", null, h("strong", { text: String(changed) }), " switched"),
|
|
265
|
+
breaking ? h("span", { class: "status-5" }, h("strong", { text: String(breaking) }), " breaking change" + (breaking === 1 ? "" : "s")) : null
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function renderSpecs() {
|
|
270
|
+
var nodes = ui.data.specs.map(function (spec) {
|
|
271
|
+
var id = "spec-toggle-" + safeId(spec.id);
|
|
272
|
+
var meta = [];
|
|
273
|
+
if (spec.specVersion) meta.push(h("span", { class: "badge badge-accent", text: (spec.specVersion === "2.0" ? "Swagger " : "OpenAPI ") + spec.specVersion }));
|
|
274
|
+
if (spec.version) meta.push(h("span", { class: "badge", text: "v" + spec.version }));
|
|
275
|
+
meta.push(h("span", { class: "badge", text: spec.operationCount + " op" + (spec.operationCount === 1 ? "" : "s") }));
|
|
276
|
+
if (spec.warnings && spec.warnings.length) {
|
|
277
|
+
meta.push(h("span", { class: "badge badge-warn", title: spec.warnings.join("\n"), text: spec.warnings.length + " warning" + (spec.warnings.length === 1 ? "" : "s") }));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return h("li", { class: "spec" + (spec.enabled ? "" : " is-off") },
|
|
281
|
+
h("span", { class: "switch" },
|
|
282
|
+
h("input", {
|
|
283
|
+
type: "checkbox",
|
|
284
|
+
role: "switch",
|
|
285
|
+
id: id,
|
|
286
|
+
checked: spec.enabled,
|
|
287
|
+
"aria-label": (spec.enabled ? "Disable " : "Enable ") + spec.name,
|
|
288
|
+
onchange: function (e) {
|
|
289
|
+
var input = e.target;
|
|
290
|
+
input.disabled = true;
|
|
291
|
+
api("/api/spec", { id: spec.id, enabled: input.checked }).then(function (res) {
|
|
292
|
+
applyState(res.state);
|
|
293
|
+
toast(spec.name + (res.spec.enabled ? " enabled" : " disabled"));
|
|
294
|
+
}, function (err) {
|
|
295
|
+
toast(err.message, true);
|
|
296
|
+
input.checked = !input.checked;
|
|
297
|
+
}).then(function () { input.disabled = false; });
|
|
298
|
+
}
|
|
299
|
+
}),
|
|
300
|
+
h("span", { class: "switch-track", "aria-hidden": "true" })
|
|
301
|
+
),
|
|
302
|
+
h("div", { class: "spec-body" },
|
|
303
|
+
h("label", { class: "spec-title", for: id, text: spec.title || spec.name }),
|
|
304
|
+
h("div", { class: "spec-file", title: spec.file, text: spec.name }),
|
|
305
|
+
h("div", { class: "spec-meta" }, meta),
|
|
306
|
+
spec.error ? h("div", { class: "spec-error", text: "Failed to load: " + spec.error }) : null,
|
|
307
|
+
spec.enabled && spec.operationCount > 0
|
|
308
|
+
? h("div", { class: "spec-jump" },
|
|
309
|
+
h("button", {
|
|
310
|
+
type: "button",
|
|
311
|
+
class: "link-btn",
|
|
312
|
+
text: "Jump to endpoints",
|
|
313
|
+
onclick: function () {
|
|
314
|
+
var target = document.getElementById("group-" + safeId(spec.id));
|
|
315
|
+
if (target) target.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
316
|
+
}
|
|
317
|
+
}),
|
|
318
|
+
" · ",
|
|
319
|
+
h("a", { class: "link-btn", href: BASE + "/api/openapi/" + encodeURIComponent(spec.id) + ".json", target: "_blank", rel: "noopener", text: "OpenAPI JSON" }))
|
|
320
|
+
: null
|
|
321
|
+
)
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
if (nodes.length === 0) {
|
|
325
|
+
nodes = [h("li", { class: "group-note", text: "No OpenAPI or Swagger files found." })];
|
|
326
|
+
}
|
|
327
|
+
el.specs.replaceChildren.apply(el.specs, nodes);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function renderMethodChips() {
|
|
331
|
+
var methods = Array.from(new Set(ui.data.operations.map(function (o) { return o.method; }))).sort();
|
|
332
|
+
el.methods.replaceChildren.apply(el.methods, methods.map(function (m) {
|
|
333
|
+
return h("button", {
|
|
334
|
+
type: "button",
|
|
335
|
+
class: "chip",
|
|
336
|
+
id: "chip-" + m,
|
|
337
|
+
"aria-pressed": ui.filterMethods.has(m) ? "true" : "false",
|
|
338
|
+
text: m,
|
|
339
|
+
onclick: function () {
|
|
340
|
+
if (ui.filterMethods.has(m)) ui.filterMethods.delete(m); else ui.filterMethods.add(m);
|
|
341
|
+
render();
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function matchesFilters(op) {
|
|
348
|
+
if (ui.onlyModified && !op.modified) return false;
|
|
349
|
+
if (ui.filterMethods.size && !ui.filterMethods.has(op.method)) return false;
|
|
350
|
+
var q = ui.filterText.trim().toLowerCase();
|
|
351
|
+
if (!q) return true;
|
|
352
|
+
var haystack = [op.method, op.route, op.summary || "", op.description || "", op.operationId || "", op.specId, op.tags.join(" ")]
|
|
353
|
+
.concat(variantsOf(op).map(function (v) { return v.code + " " + v.label; }))
|
|
354
|
+
.join(" ")
|
|
355
|
+
.toLowerCase();
|
|
356
|
+
return haystack.indexOf(q) >= 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function renderChanges() {
|
|
360
|
+
var reports = (ui.data.changes || []).filter(function (r) { return r.changes.length; });
|
|
361
|
+
if (!reports.length) return null;
|
|
362
|
+
var breaking = reports.reduce(function (n, r) { return n + r.counts.breaking; }, 0);
|
|
363
|
+
var snapshot = reports.some(function (r) { return r.baseline === "snapshot"; });
|
|
364
|
+
return h("section", { class: "changes" + (breaking ? " is-breaking" : ""), "aria-label": "Contract changes" },
|
|
365
|
+
h("h2", { text: breaking ? "Breaking contract changes detected" : "Contract changes detected" }),
|
|
366
|
+
h("p", {
|
|
367
|
+
class: "changes-sub",
|
|
368
|
+
text: snapshot
|
|
369
|
+
? "The specs no longer match the generated client. Review these before running `swaggerizer generate`."
|
|
370
|
+
: "The specs changed since they were last loaded."
|
|
371
|
+
}),
|
|
372
|
+
reports.map(function (report) {
|
|
373
|
+
var title = (report.title || report.specId) +
|
|
374
|
+
(report.fromVersion && report.toVersion && report.fromVersion !== report.toVersion ? " (" + report.fromVersion + " → " + report.toVersion + ")" : "") +
|
|
375
|
+
" — " + report.counts.breaking + " breaking, " + report.counts.warning + " warning, " + report.counts.info + " info · " +
|
|
376
|
+
report.added.length + " new, " + report.removed.length + " removed, " + report.changed.length + " changed" +
|
|
377
|
+
(report.baseline === "snapshot" ? " (vs generated code)" : " (since last load)");
|
|
378
|
+
var reportKey = report.specId + ":" + report.baseline;
|
|
379
|
+
var isOpen = ui.reportOpen.has(reportKey) ? ui.reportOpen.get(reportKey) : report.counts.breaking > 0;
|
|
380
|
+
return h("details", {
|
|
381
|
+
class: "change-report",
|
|
382
|
+
open: isOpen,
|
|
383
|
+
ontoggle: function (e) { ui.reportOpen.set(reportKey, e.target.open); }
|
|
384
|
+
},
|
|
385
|
+
h("summary", { text: title }),
|
|
386
|
+
h("ul", { class: "change-list" }, report.changes.map(function (c) {
|
|
387
|
+
return h("li", null,
|
|
388
|
+
h("span", { class: "sev sev-" + c.severity, text: c.severity }),
|
|
389
|
+
h("span", { class: "change-op", text: c.operation }),
|
|
390
|
+
h("span", { text: c.message }),
|
|
391
|
+
c.location ? h("span", { class: "change-loc", text: c.location }) : null
|
|
392
|
+
);
|
|
393
|
+
}))
|
|
394
|
+
);
|
|
395
|
+
})
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function renderOperations() {
|
|
400
|
+
var groups = [];
|
|
401
|
+
var shown = 0;
|
|
402
|
+
var changes = renderChanges();
|
|
403
|
+
if (changes) groups.push(changes);
|
|
404
|
+
|
|
405
|
+
ui.data.specs.forEach(function (spec) {
|
|
406
|
+
var groupId = "group-" + safeId(spec.id);
|
|
407
|
+
var ops = ui.data.operations.filter(function (o) { return o.specId === spec.id; });
|
|
408
|
+
var visible = ops.filter(matchesFilters);
|
|
409
|
+
var body;
|
|
410
|
+
|
|
411
|
+
if (spec.error && ops.length === 0) {
|
|
412
|
+
body = h("div", { class: "group-note", text: "This file could not be parsed: " + spec.error });
|
|
413
|
+
} else if (!spec.enabled) {
|
|
414
|
+
body = h("div", { class: "group-note", text: "Disabled – its " + ops.length + " endpoint" + (ops.length === 1 ? " is" : "s are") + " not being served. Switch it on in the Specs panel." });
|
|
415
|
+
} else if (visible.length === 0) {
|
|
416
|
+
if (ops.length > 0) return;
|
|
417
|
+
body = h("div", { class: "group-note", text: "No operations are declared in this spec." });
|
|
418
|
+
} else {
|
|
419
|
+
shown += visible.length;
|
|
420
|
+
body = h("div", { class: "group-body" }, visible.map(renderOperation));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
groups.push(h("section", { class: "group", id: groupId, "aria-labelledby": groupId + "-title" },
|
|
424
|
+
h("div", { class: "group-head" },
|
|
425
|
+
h("h2", { id: groupId + "-title", text: spec.title || spec.name }),
|
|
426
|
+
h("span", { class: "group-file", text: spec.name + (spec.basePaths.length ? " · " + spec.basePaths.join(", ") : "") })
|
|
427
|
+
),
|
|
428
|
+
body
|
|
429
|
+
));
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (ui.data.specs.length === 0 || (shown === 0 && (ui.filterText || ui.filterMethods.size || ui.onlyModified))) {
|
|
433
|
+
groups.push(h("div", { class: "empty", text: ui.data.specs.length ? "No endpoints match the current filter." : "No specs loaded." }));
|
|
434
|
+
}
|
|
435
|
+
el.content.replaceChildren.apply(el.content, groups);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function modeButtons(op, id) {
|
|
439
|
+
var modes = [["mock", "Spec response"], ["custom", "Custom"], ["proxy", "Real API"]];
|
|
440
|
+
return h("div", { class: "modes", role: "group", "aria-label": "How to answer" }, modes.map(function (m) {
|
|
441
|
+
return h("button", {
|
|
442
|
+
type: "button",
|
|
443
|
+
id: id + "-mode-" + m[0],
|
|
444
|
+
"aria-pressed": op.mode === m[0] ? "true" : "false",
|
|
445
|
+
text: m[1],
|
|
446
|
+
onclick: function () {
|
|
447
|
+
if (op.mode === m[0]) return;
|
|
448
|
+
select(op, { mode: m[0] }, m[1].toLowerCase()).catch(function () {});
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function customEditor(op, id) {
|
|
455
|
+
var draft = ui.drafts.get(id) || {
|
|
456
|
+
status: String(op.custom ? op.custom.status : 200),
|
|
457
|
+
contentType: (op.custom && op.custom.contentType) || "",
|
|
458
|
+
body: op.custom && op.custom.body !== undefined ? pretty(op.custom.body) : ""
|
|
459
|
+
};
|
|
460
|
+
function keep(field) {
|
|
461
|
+
return function (e) { draft[field] = e.target.value; ui.drafts.set(id, draft); };
|
|
462
|
+
}
|
|
463
|
+
return h("form", {
|
|
464
|
+
class: "custom-editor",
|
|
465
|
+
onsubmit: function (e) {
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
var notJson = false;
|
|
468
|
+
if (draft.body.trim() && (!draft.contentType || /json/i.test(draft.contentType))) {
|
|
469
|
+
try { JSON.parse(draft.body); } catch (err) { notJson = true; }
|
|
470
|
+
}
|
|
471
|
+
if (notJson && draft.contentType) {
|
|
472
|
+
toast("The body is not valid JSON – fix it or change the content type", true);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (notJson) toast("The body is not valid JSON, so it will be sent as text/plain", true);
|
|
476
|
+
select(op, { mode: "custom", custom: { status: Number(draft.status), body: draft.body, contentType: draft.contentType || undefined } }, "custom " + draft.status)
|
|
477
|
+
.then(function () { ui.drafts.delete(id); render(); }, function () {});
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
h("div", { class: "custom-row" },
|
|
481
|
+
h("label", null, "Status", h("input", { type: "number", min: "100", max: "599", id: id + "-status", value: draft.status, oninput: keep("status") })),
|
|
482
|
+
h("label", null, "Content type", h("input", { type: "text", id: id + "-ctype", placeholder: "application/json", value: draft.contentType, oninput: keep("contentType") })),
|
|
483
|
+
h("button", { type: "submit", class: "btn btn-small", text: "Apply" })
|
|
484
|
+
),
|
|
485
|
+
h("label", { class: "sr-only", for: id + "-body", text: "Response body" }),
|
|
486
|
+
h("textarea", { id: id + "-body", spellcheck: "false", value: draft.body, placeholder: "{ \"message\": \"Anything you like\" }", oninput: keep("body") })
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function renderOperation(op) {
|
|
491
|
+
var id = opId(op);
|
|
492
|
+
var variants = variantsOf(op);
|
|
493
|
+
var current = selectedVariant(op, variants);
|
|
494
|
+
var drawerOpen = ui.openDrawers.has(id);
|
|
495
|
+
var spec = specOf(op);
|
|
496
|
+
|
|
497
|
+
var tags = [];
|
|
498
|
+
if (op.modified) tags.push(h("span", { class: "badge badge-accent", text: "switched" }));
|
|
499
|
+
if (op.deprecated) tags.push(h("span", { class: "badge badge-warn badge-deprecated", text: "deprecated" }));
|
|
500
|
+
if (op.shadowedBy) tags.push(h("span", { class: "badge badge-warn", title: "Another enabled spec already serves this method and route", text: "shadowed by " + op.shadowedBy }));
|
|
501
|
+
|
|
502
|
+
var meta = [];
|
|
503
|
+
if (op.operationId) meta.push(h("span", { class: "badge", title: "operationId – the generated client function name", text: op.operationId }));
|
|
504
|
+
op.tags.forEach(function (t) { meta.push(h("span", { class: "badge", text: "#" + t })); });
|
|
505
|
+
|
|
506
|
+
var body;
|
|
507
|
+
if (op.mode === "custom") {
|
|
508
|
+
body = customEditor(op, id);
|
|
509
|
+
} else if (op.mode === "proxy") {
|
|
510
|
+
body = h("p", { class: "proxy-note" }, "Requests are forwarded to ",
|
|
511
|
+
h("code", { text: spec && spec.proxyTarget ? spec.proxyTarget : "(no upstream – start with --proxy <url>)" }),
|
|
512
|
+
". In the browser, MSW lets them through to the real API.");
|
|
513
|
+
} else {
|
|
514
|
+
body = variants.length
|
|
515
|
+
? h("fieldset", { class: "variants" },
|
|
516
|
+
h("legend", { text: "Response" }),
|
|
517
|
+
variants.map(function (v, i) {
|
|
518
|
+
var inputId = id + "-v" + i;
|
|
519
|
+
var isDefault = v.code === op.defaultCode && v.example === op.defaultExample;
|
|
520
|
+
return h("label", { class: "variant", for: inputId, title: v.summary || v.response.description || "" },
|
|
521
|
+
h("input", {
|
|
522
|
+
type: "radio",
|
|
523
|
+
id: inputId,
|
|
524
|
+
name: id + "-variant",
|
|
525
|
+
checked: current === v,
|
|
526
|
+
onchange: function () { select(op, { code: v.code, example: v.example, mode: "mock" }, v.code + " · " + v.label).catch(function () {}); }
|
|
527
|
+
}),
|
|
528
|
+
h("span", { class: statusClass(v.code), text: v.code }),
|
|
529
|
+
h("span", { class: "variant-name", text: v.label }),
|
|
530
|
+
isDefault ? h("span", { class: "variant-default", text: "default" }) : null
|
|
531
|
+
);
|
|
532
|
+
})
|
|
533
|
+
)
|
|
534
|
+
: h("p", { class: "op-desc", text: "This operation declares no responses; swaggerizer answers 200 with an empty object." });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
var selection = op.mode === "custom"
|
|
538
|
+
? { method: op.method, route: op.route, mode: "custom" }
|
|
539
|
+
: op.mode === "proxy"
|
|
540
|
+
? { method: op.method, route: op.route, mode: "proxy" }
|
|
541
|
+
: { method: op.method, route: op.route, code: op.selectedCode, example: op.selectedExample };
|
|
542
|
+
if (op.delay) selection.delay = op.delay;
|
|
543
|
+
var link = location.origin + BASE + "/api/select?" + new URLSearchParams(selection).toString();
|
|
544
|
+
var curl = "curl -s -X POST " + shellQuote(location.origin + BASE + "/api/select") +
|
|
545
|
+
" -H 'Content-Type: application/json' -d " +
|
|
546
|
+
shellQuote(JSON.stringify(op.mode === "custom" ? Object.assign({}, selection, { custom: op.custom }) : selection));
|
|
547
|
+
|
|
548
|
+
var tryUrl = ui.tryUrls.has(id) ? ui.tryUrls.get(id) : sampleUrl(op);
|
|
549
|
+
var tryResult = ui.tryResults.get(id);
|
|
550
|
+
var preview = op.mode === "custom" ? (op.custom ? op.custom.body : undefined) : current ? current.value : undefined;
|
|
551
|
+
var previewStatus = op.mode === "custom" ? (op.custom ? String(op.custom.status) : "") : current ? current.code : "";
|
|
552
|
+
|
|
553
|
+
var drawer = h("div", { class: "drawer", id: id + "-drawer", hidden: !drawerOpen },
|
|
554
|
+
h("div", null,
|
|
555
|
+
h("div", { class: "drawer-label" }, "Will respond with",
|
|
556
|
+
op.mode === "proxy" ? h("span", { class: "badge", text: "real API" }) : h("span", { class: statusClass(previewStatus), text: previewStatus }),
|
|
557
|
+
op.mode === "mock" && current && current.response.contentTypes.length ? h("span", { class: "badge", text: current.response.contentTypes[0] }) : null,
|
|
558
|
+
op.mode === "mock" && current && current.kind === "generated" ? h("span", { class: "badge", title: "No example in the spec – synthesised from the schema. MSW mocks use orval's faker data instead.", text: "synthesised" }) : null
|
|
559
|
+
),
|
|
560
|
+
h("pre", { class: "code", text: op.mode === "proxy" ? "(whatever the real API returns)" : pretty(preview) })
|
|
561
|
+
),
|
|
562
|
+
h("div", null,
|
|
563
|
+
h("div", { class: "drawer-label", text: "Try it against the mock" }),
|
|
564
|
+
h("form", {
|
|
565
|
+
class: "try-row",
|
|
566
|
+
onsubmit: function (e) {
|
|
567
|
+
e.preventDefault();
|
|
568
|
+
sendTry(op, id);
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
h("label", { class: "sr-only", for: id + "-url", text: "Request path" }),
|
|
572
|
+
h("input", {
|
|
573
|
+
id: id + "-url",
|
|
574
|
+
value: tryUrl,
|
|
575
|
+
spellcheck: "false",
|
|
576
|
+
oninput: function (e) { ui.tryUrls.set(id, e.target.value); }
|
|
577
|
+
}),
|
|
578
|
+
h("button", { type: "submit", class: "btn btn-small", text: "Send " + op.method.toUpperCase() })
|
|
579
|
+
),
|
|
580
|
+
tryResult ? h("div", { class: "try-result" },
|
|
581
|
+
h("div", { class: "try-meta" },
|
|
582
|
+
tryResult.error ? h("span", { class: "status status-5", text: "Network error" }) : h("span", { class: statusClass(tryResult.status), text: String(tryResult.status) }),
|
|
583
|
+
h("span", { text: tryResult.ms + " ms" }),
|
|
584
|
+
tryResult.type ? h("span", { text: tryResult.type }) : null
|
|
585
|
+
),
|
|
586
|
+
h("pre", { class: "code", text: tryResult.error || tryResult.body || "(empty body)" })
|
|
587
|
+
) : null
|
|
588
|
+
)
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
return h("article", {
|
|
592
|
+
class: "op" + (op.modified ? " is-modified" : "") + (op.active ? "" : " is-inactive"),
|
|
593
|
+
id: id,
|
|
594
|
+
"aria-label": op.method.toUpperCase() + " " + op.route
|
|
595
|
+
},
|
|
596
|
+
h("div", { class: "op-head" },
|
|
597
|
+
methodBadge(op.method),
|
|
598
|
+
h("span", { class: "op-route", text: op.route }),
|
|
599
|
+
h("div", { class: "op-tags" }, tags)
|
|
600
|
+
),
|
|
601
|
+
op.summary || op.description ? h("p", { class: "op-desc", text: op.summary || op.description }) : null,
|
|
602
|
+
meta.length ? h("div", { class: "op-meta" }, meta) : null,
|
|
603
|
+
modeButtons(op, id),
|
|
604
|
+
body,
|
|
605
|
+
h("div", { class: "op-foot" },
|
|
606
|
+
h("span", { class: "serving" }, "Serving ", h("strong", { text: servingLabel(op, current) })),
|
|
607
|
+
h("label", { class: "delay-field", title: "Artificial latency before responding" }, "Delay",
|
|
608
|
+
h("input", {
|
|
609
|
+
type: "number",
|
|
610
|
+
min: "0",
|
|
611
|
+
step: "100",
|
|
612
|
+
id: id + "-delay",
|
|
613
|
+
value: op.delay ? String(op.delay) : "",
|
|
614
|
+
placeholder: "0",
|
|
615
|
+
onchange: function (e) {
|
|
616
|
+
var ms = Number(e.target.value) || 0;
|
|
617
|
+
e.target.blur();
|
|
618
|
+
select(op, { delay: ms }, ms ? ms + " ms delay" : "no delay").catch(function () {});
|
|
619
|
+
}
|
|
620
|
+
}), "ms"),
|
|
621
|
+
h("span", { class: "spacer" }),
|
|
622
|
+
h("button", {
|
|
623
|
+
type: "button",
|
|
624
|
+
class: "btn btn-small btn-ghost",
|
|
625
|
+
id: id + "-toggle",
|
|
626
|
+
"aria-expanded": drawerOpen ? "true" : "false",
|
|
627
|
+
"aria-controls": id + "-drawer",
|
|
628
|
+
text: drawerOpen ? "Hide preview" : "Preview & try",
|
|
629
|
+
onclick: function () {
|
|
630
|
+
if (ui.openDrawers.has(id)) ui.openDrawers.delete(id); else ui.openDrawers.add(id);
|
|
631
|
+
render();
|
|
632
|
+
}
|
|
633
|
+
}),
|
|
634
|
+
h("button", {
|
|
635
|
+
type: "button",
|
|
636
|
+
class: "btn btn-small btn-ghost",
|
|
637
|
+
id: id + "-link",
|
|
638
|
+
title: "Copy a GET link that switches to this response – open it in a browser or call it from a test",
|
|
639
|
+
text: "Copy link",
|
|
640
|
+
onclick: function () { copyText(link, "Switch link copied"); }
|
|
641
|
+
}),
|
|
642
|
+
h("button", {
|
|
643
|
+
type: "button",
|
|
644
|
+
class: "btn btn-small btn-ghost",
|
|
645
|
+
id: id + "-curl",
|
|
646
|
+
title: "Copy a curl command that switches to this response",
|
|
647
|
+
text: "Copy curl",
|
|
648
|
+
onclick: function () { copyText(curl, "curl command copied"); }
|
|
649
|
+
}),
|
|
650
|
+
op.modified ? h("button", {
|
|
651
|
+
type: "button",
|
|
652
|
+
class: "btn btn-small btn-ghost",
|
|
653
|
+
id: id + "-reset",
|
|
654
|
+
text: "Reset",
|
|
655
|
+
onclick: function () { resetOne(op); }
|
|
656
|
+
}) : null
|
|
657
|
+
),
|
|
658
|
+
drawer
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function sendTry(op, id) {
|
|
663
|
+
var path = (ui.tryUrls.get(id) || sampleUrl(op)).trim();
|
|
664
|
+
if (!path.startsWith("http://") && !path.startsWith("https://") && path.charAt(0) !== "/") {
|
|
665
|
+
path = "/" + path;
|
|
666
|
+
}
|
|
667
|
+
var started = performance.now();
|
|
668
|
+
var init = { method: op.method.toUpperCase(), headers: { Accept: "application/json" } };
|
|
669
|
+
if (["post", "put", "patch"].indexOf(op.method) >= 0) {
|
|
670
|
+
init.headers["Content-Type"] = "application/json";
|
|
671
|
+
init.body = "{}";
|
|
672
|
+
}
|
|
673
|
+
fetch(path, init).then(function (res) {
|
|
674
|
+
return res.text().then(function (text) {
|
|
675
|
+
var body = text;
|
|
676
|
+
try { body = JSON.stringify(JSON.parse(text), null, 2); } catch (e) { /* not JSON */ }
|
|
677
|
+
ui.tryResults.set(id, { status: res.status, body: body, type: res.headers.get("Content-Type"), ms: Math.round(performance.now() - started) });
|
|
678
|
+
});
|
|
679
|
+
}).catch(function (err) {
|
|
680
|
+
ui.tryResults.set(id, { error: err.message, ms: Math.round(performance.now() - started) });
|
|
681
|
+
}).then(function () {
|
|
682
|
+
render();
|
|
683
|
+
pollLog();
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function copyText(text, message) {
|
|
688
|
+
var done = function () { toast(message); };
|
|
689
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
690
|
+
navigator.clipboard.writeText(text).then(done, function () { fallbackCopy(text, done); });
|
|
691
|
+
} else {
|
|
692
|
+
fallbackCopy(text, done);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function fallbackCopy(text, done) {
|
|
697
|
+
var area = h("textarea", { class: "sr-only", readonly: true });
|
|
698
|
+
area.value = text;
|
|
699
|
+
document.body.appendChild(area);
|
|
700
|
+
area.select();
|
|
701
|
+
try { document.execCommand("copy"); done(); } catch (e) { toast("Copy failed – select the text manually", true); }
|
|
702
|
+
area.remove();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* ------------------------------------------------------------ request log */
|
|
706
|
+
|
|
707
|
+
function renderLog() {
|
|
708
|
+
var entries = ui.logEntries.slice().reverse();
|
|
709
|
+
el.logEmpty.hidden = entries.length > 0;
|
|
710
|
+
el.log.replaceChildren.apply(el.log, entries.map(function (entry) {
|
|
711
|
+
return h("li", { class: ui.freshLogIds.has(entry.id) ? "is-new" : "" },
|
|
712
|
+
methodBadge(entry.method),
|
|
713
|
+
h("span", { class: statusClass(entry.status), text: entry.status ? String(entry.status) : "→" }),
|
|
714
|
+
h("span", { class: "log-path", text: entry.path }),
|
|
715
|
+
h("span", { class: "log-meta" },
|
|
716
|
+
entry.source === "msw" ? h("span", { class: "log-source", title: "Answered by MSW in a browser or test", text: "msw" }) : null,
|
|
717
|
+
h("span", { text: timeOf(entry.time) }),
|
|
718
|
+
h("span", { text: entry.example === "empty" ? "no body" : entry.example }),
|
|
719
|
+
entry.ms !== undefined ? h("span", { text: entry.ms + " ms" }) : null,
|
|
720
|
+
entry.specId ? h("span", { text: entry.specId }) : null
|
|
721
|
+
)
|
|
722
|
+
);
|
|
723
|
+
}));
|
|
724
|
+
ui.freshLogIds.clear();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function pollLog() {
|
|
728
|
+
return api("/api/log?since=" + ui.logSince).then(function (res) {
|
|
729
|
+
var entries = res.entries || [];
|
|
730
|
+
if (!entries.length) return;
|
|
731
|
+
entries.forEach(function (e) {
|
|
732
|
+
ui.logSince = Math.max(ui.logSince, e.id);
|
|
733
|
+
if (e.id > ui.logClearedAt) {
|
|
734
|
+
ui.logEntries.push(e);
|
|
735
|
+
ui.freshLogIds.add(e.id);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
if (ui.logEntries.length > LOG_KEEP) ui.logEntries.splice(0, ui.logEntries.length - LOG_KEEP);
|
|
739
|
+
renderLog();
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/* ------------------------------------------------------------ wiring */
|
|
744
|
+
|
|
745
|
+
el.filterText.addEventListener("input", function (e) {
|
|
746
|
+
ui.filterText = e.target.value;
|
|
747
|
+
renderOperations();
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
el.filterModified.addEventListener("change", function (e) {
|
|
751
|
+
ui.onlyModified = e.target.checked;
|
|
752
|
+
renderOperations();
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
el.reload.addEventListener("click", function () {
|
|
756
|
+
withBusy(el.reload, function () {
|
|
757
|
+
return api("/api/reload", {}).then(function (state) {
|
|
758
|
+
applyState(state);
|
|
759
|
+
var failed = state.specs.filter(function (s) { return s.error; });
|
|
760
|
+
var breaking = (state.changes || []).reduce(function (n, r) { return n + r.counts.breaking; }, 0);
|
|
761
|
+
if (failed.length) toast(failed.length + " spec(s) failed to load – see the Specs panel", true);
|
|
762
|
+
else if (breaking) toast("Reloaded – " + breaking + " breaking contract change(s) detected", true);
|
|
763
|
+
else toast("Reloaded " + state.specs.length + " spec(s) from disk");
|
|
764
|
+
}, function (err) { toast(err.message, true); });
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
el.resetAll.addEventListener("click", function () {
|
|
769
|
+
withBusy(el.resetAll, function () {
|
|
770
|
+
ui.drafts.clear();
|
|
771
|
+
return api("/api/reset", {}).then(function (res) {
|
|
772
|
+
applyState(res.state);
|
|
773
|
+
toast("Reset " + res.reset + " endpoint(s) to their defaults");
|
|
774
|
+
}, function (err) { toast(err.message, true); });
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
el.clearLog.addEventListener("click", function () {
|
|
779
|
+
ui.logClearedAt = ui.logSince;
|
|
780
|
+
ui.logEntries = [];
|
|
781
|
+
renderLog();
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
refreshState()
|
|
785
|
+
.catch(function (err) {
|
|
786
|
+
el.content.replaceChildren(h("div", { class: "empty", text: "Could not reach swaggerizer: " + err.message }));
|
|
787
|
+
})
|
|
788
|
+
.then(pollLog)
|
|
789
|
+
.catch(function () {});
|
|
790
|
+
|
|
791
|
+
setInterval(function () {
|
|
792
|
+
if (document.hidden) return;
|
|
793
|
+
// Catch up on anything that arrived while a field was being edited.
|
|
794
|
+
if (ui.pendingOps && !isEditing()) render();
|
|
795
|
+
refreshState().catch(function () {});
|
|
796
|
+
}, STATE_POLL_MS);
|
|
797
|
+
|
|
798
|
+
setInterval(function () {
|
|
799
|
+
if (document.hidden) return;
|
|
800
|
+
pollLog().catch(function () {});
|
|
801
|
+
}, LOG_POLL_MS);
|
|
802
|
+
|
|
803
|
+
document.addEventListener("visibilitychange", function () {
|
|
804
|
+
if (!document.hidden) refreshState().catch(function () {});
|
|
805
|
+
});
|
|
806
|
+
})();
|