]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/api-viewer/APIViewer.js
api viewer: eslint (auto) fixes + slight cleanup
[proxmox-widget-toolkit.git] / src / api-viewer / APIViewer.js
CommitLineData
4ae75df3 1Ext.onReady(function() {
4ae75df3
DC
2 Ext.define('pmx-param-schema', {
3 extend: 'Ext.data.Model',
52428d60 4 fields: [
4ae75df3
DC
5 'name', 'type', 'typetext', 'description', 'verbose_description',
6 'enum', 'minimum', 'maximum', 'minLength', 'maxLength',
7 'pattern', 'title', 'requires', 'format', 'default',
8 'disallow', 'extends', 'links',
9 {
10 name: 'optional',
52428d60
TL
11 type: 'boolean',
12 },
13 ],
4ae75df3
DC
14 });
15
52428d60 16 let store = Ext.define('pmx-updated-treestore', {
4ae75df3
DC
17 extend: 'Ext.data.TreeStore',
18 model: Ext.define('pmx-api-doc', {
19 extend: 'Ext.data.Model',
52428d60 20 fields: [
4ae75df3 21 'path', 'info', 'text',
52428d60 22 ],
4ae75df3 23 }),
52428d60
TL
24 proxy: {
25 type: 'memory',
26 data: pmxapi,
27 },
28 sorters: [{
29 property: 'leaf',
30 direction: 'ASC',
31 }, {
32 property: 'text',
33 direction: 'ASC',
4ae75df3
DC
34 }],
35 filterer: 'bottomup',
36 doFilter: function(node) {
37 this.filterNodes(node, this.getFilters().getFilterFn(), true);
38 },
39
40 filterNodes: function(node, filterFn, parentVisible) {
52428d60 41 let me = this,
4ae75df3
DC
42 bottomUpFiltering = me.filterer === 'bottomup',
43 match = filterFn(node) && parentVisible || (node.isRoot() && !me.getRootVisible()),
44 childNodes = node.childNodes,
45 len = childNodes && childNodes.length, i, matchingChildren;
46
47 if (len) {
48 for (i = 0; i < len; ++i) {
49 matchingChildren = me.filterNodes(childNodes[i], filterFn, match || bottomUpFiltering) || matchingChildren;
50 }
51 if (bottomUpFiltering) {
52 match = matchingChildren || match;
53 }
54 }
55
56 node.set("visible", match, me._silentOptions);
57 return match;
58 },
59
60 }).create();
61
52428d60
TL
62 let render_description = function(value, metaData, record) {
63 let pdef = record.data;
4ae75df3
DC
64
65 value = pdef.verbose_description || value;
66
67 // TODO: try to render asciidoc correctly
68
52428d60 69 metaData.style = 'white-space:pre-wrap;';
4ae75df3
DC
70
71 return Ext.htmlEncode(value);
72 };
73
52428d60
TL
74 let render_type = function(value, metaData, record) {
75 let pdef = record.data;
4ae75df3 76
52428d60 77 return pdef.enum ? 'enum' : pdef.type || 'string';
4ae75df3
DC
78 };
79
80 let render_simple_format = function(pdef, type_fallback) {
52428d60 81 if (pdef.typetext) {return pdef.typetext;}
4ae75df3 82
52428d60 83 if (pdef.enum) {return pdef.enum.join(' | ');}
4ae75df3 84
52428d60 85 if (pdef.format) {return pdef.format;}
4ae75df3 86
52428d60 87 if (pdef.pattern) {return pdef.pattern;}
4ae75df3 88
52428d60 89 if (pdef.type === 'boolean') {return `<true|false>`;}
4ae75df3 90
52428d60 91 if (type_fallback && pdef.type) {return `<${pdef.type}>`;}
4ae75df3
DC
92 };
93
94 let render_format = function(value, metaData, record) {
95 let pdef = record.data;
96
52428d60 97 metaData.style = 'white-space:normal;';
4ae75df3
DC
98
99 if (pdef.type === 'array' && pdef.items) {
100 let format = render_simple_format(pdef.items, true);
101 return `[${Ext.htmlEncode(format)}, ...]`;
102 }
103
104 return Ext.htmlEncode(render_simple_format(pdef) || '');
105 };
106
52428d60 107 let real_path = function(path) {
4ae75df3
DC
108 return path.replace(/^.*\/_upgrade_(\/)?/, "/");
109 };
110
52428d60 111 let permission_text = function(permission) {
4ae75df3
DC
112 let permhtml = "";
113
114 if (permission.user) {
115 if (!permission.description) {
116 if (permission.user === 'world') {
117 permhtml += "Accessible without any authentication.";
118 } else if (permission.user === 'all') {
119 permhtml += "Accessible by all authenticated users.";
120 } else {
121 permhtml += 'Onyl accessible by user "' +
122 permission.user + '"';
123 }
124 }
125 } else if (permission.check) {
126 permhtml += "<pre>Check: " +
52428d60 127 Ext.htmlEncode(Ext.JSON.encode(permission.check)) + "</pre>";
4ae75df3
DC
128 } else if (permission.userParam) {
129 permhtml += `<div>Check if user matches parameter '${permission.userParam}'`;
130 } else if (permission.or) {
131 permhtml += "<div>Or<div style='padding-left: 10px;'>";
132 Ext.Array.each(permission.or, function(sub_permission) {
133 permhtml += permission_text(sub_permission);
52428d60 134 });
4ae75df3
DC
135 permhtml += "</div></div>";
136 } else if (permission.and) {
137 permhtml += "<div>And<div style='padding-left: 10px;'>";
138 Ext.Array.each(permission.and, function(sub_permission) {
139 permhtml += permission_text(sub_permission);
52428d60 140 });
4ae75df3
DC
141 permhtml += "</div></div>";
142 } else {
143 //console.log(permission);
144 permhtml += "Unknown syntax!";
145 }
146
147 return permhtml;
148 };
149
52428d60
TL
150 let render_docu = function(data) {
151 let md = data.info;
4ae75df3
DC
152
153 // console.dir(data);
154
52428d60 155 let items = [];
4ae75df3 156
52428d60 157 let clicmdhash = {
4ae75df3
DC
158 GET: 'get',
159 POST: 'create',
160 PUT: 'set',
52428d60 161 DELETE: 'delete',
4ae75df3
DC
162 };
163
164 Ext.Array.each(['GET', 'POST', 'PUT', 'DELETE'], function(method) {
52428d60 165 let info = md[method];
4ae75df3 166 if (info) {
52428d60 167 let usage = "";
4ae75df3
DC
168
169 usage += "<table><tr><td>HTTP:&nbsp;&nbsp;&nbsp;</td><td>"
170 + method + " " + real_path("/api2/json" + data.path) + "</td></tr>";
171
172 if (typeof cliusage === 'function') {
173 usage += cliusage(method, real_path(data.path));
174 }
175
52428d60 176 let sections = [
4ae75df3
DC
177 {
178 title: 'Description',
179 html: Ext.htmlEncode(info.description),
52428d60 180 bodyPadding: 10,
4ae75df3
DC
181 },
182 {
183 title: 'Usage',
184 html: usage,
52428d60
TL
185 bodyPadding: 10,
186 },
4ae75df3
DC
187 ];
188
189 if (info.parameters && info.parameters.properties) {
52428d60 190 let pstore = Ext.create('Ext.data.Store', {
4ae75df3
DC
191 model: 'pmx-param-schema',
192 proxy: {
52428d60 193 type: 'memory',
4ae75df3
DC
194 },
195 groupField: 'optional',
196 sorters: [
197 {
198 property: 'name',
52428d60
TL
199 direction: 'ASC',
200 },
201 ],
4ae75df3
DC
202 });
203
204 Ext.Object.each(info.parameters.properties, function(name, pdef) {
205 pdef.name = name;
206 pstore.add(pdef);
207 });
208
209 pstore.sort();
210
52428d60 211 let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
4ae75df3 212 enableGroupingMenu: false,
52428d60 213 groupHeaderTpl: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Required</tpl>',
4ae75df3
DC
214 });
215
216 sections.push({
217 xtype: 'gridpanel',
218 title: 'Parameters',
219 features: [groupingFeature],
220 store: pstore,
221 viewConfig: {
222 trackOver: false,
52428d60 223 stripeRows: true,
4ae75df3
DC
224 },
225 columns: [
226 {
227 header: 'Name',
228 dataIndex: 'name',
52428d60 229 flex: 1,
4ae75df3
DC
230 },
231 {
232 header: 'Type',
233 dataIndex: 'type',
234 renderer: render_type,
52428d60 235 flex: 1,
4ae75df3
DC
236 },
237 {
238 header: 'Default',
239 dataIndex: 'default',
52428d60 240 flex: 1,
4ae75df3
DC
241 },
242 {
243 header: 'Format',
244 dataIndex: 'type',
245 renderer: render_format,
52428d60 246 flex: 2,
4ae75df3
DC
247 },
248 {
249 header: 'Description',
250 dataIndex: 'description',
251 renderer: render_description,
52428d60
TL
252 flex: 6,
253 },
254 ],
4ae75df3 255 });
4ae75df3
DC
256 }
257
258 if (info.returns) {
52428d60
TL
259 let retinf = info.returns;
260 let rtype = retinf.type;
261 if (!rtype && retinf.items) {rtype = 'array';}
262 if (!rtype) {rtype = 'object';}
4ae75df3 263
52428d60 264 let rpstore = Ext.create('Ext.data.Store', {
4ae75df3
DC
265 model: 'pmx-param-schema',
266 proxy: {
52428d60 267 type: 'memory',
4ae75df3
DC
268 },
269 groupField: 'optional',
270 sorters: [
271 {
272 property: 'name',
52428d60
TL
273 direction: 'ASC',
274 },
275 ],
4ae75df3
DC
276 });
277
52428d60 278 let properties;
4ae75df3
DC
279 if (rtype === 'array' && retinf.items.properties) {
280 properties = retinf.items.properties;
281 }
282
283 if (rtype === 'object' && retinf.properties) {
284 properties = retinf.properties;
285 }
286
287 Ext.Object.each(properties, function(name, pdef) {
288 pdef.name = name;
289 rpstore.add(pdef);
290 });
291
292 rpstore.sort();
293
52428d60 294 let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
4ae75df3 295 enableGroupingMenu: false,
52428d60 296 groupHeaderTpl: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Obligatory</tpl>',
4ae75df3 297 });
52428d60 298 let returnhtml;
4ae75df3
DC
299 if (retinf.items) {
300 returnhtml = '<pre>items: ' + Ext.htmlEncode(JSON.stringify(retinf.items, null, 4)) + '</pre>';
301 }
302
303 if (retinf.properties) {
304 returnhtml = returnhtml || '';
305 returnhtml += '<pre>properties:' + Ext.htmlEncode(JSON.stringify(retinf.properties, null, 4)) + '</pre>';
306 }
307
52428d60 308 let rawSection = Ext.create('Ext.panel.Panel', {
4ae75df3
DC
309 bodyPadding: '0px 10px 10px 10px',
310 html: returnhtml,
52428d60 311 hidden: true,
4ae75df3
DC
312 });
313
314 sections.push({
315 xtype: 'gridpanel',
316 title: 'Returns: ' + rtype,
317 features: [groupingFeature],
318 store: rpstore,
319 viewConfig: {
320 trackOver: false,
52428d60 321 stripeRows: true,
4ae75df3
DC
322 },
323 columns: [
324 {
325 header: 'Name',
326 dataIndex: 'name',
52428d60 327 flex: 1,
4ae75df3
DC
328 },
329 {
330 header: 'Type',
331 dataIndex: 'type',
332 renderer: render_type,
52428d60 333 flex: 1,
4ae75df3
DC
334 },
335 {
336 header: 'Default',
337 dataIndex: 'default',
52428d60 338 flex: 1,
4ae75df3
DC
339 },
340 {
341 header: 'Format',
342 dataIndex: 'type',
343 renderer: render_format,
52428d60 344 flex: 2,
4ae75df3
DC
345 },
346 {
347 header: 'Description',
348 dataIndex: 'description',
349 renderer: render_description,
52428d60
TL
350 flex: 6,
351 },
4ae75df3
DC
352 ],
353 bbar: [
354 {
355 xtype: 'button',
356 text: 'Show RAW',
357 handler: function(btn) {
358 rawSection.setVisible(!rawSection.isVisible());
359 btn.setText(rawSection.isVisible() ? 'Hide RAW' : 'Show RAW');
52428d60
TL
360 },
361},
362 ],
4ae75df3
DC
363 });
364
365 sections.push(rawSection);
4ae75df3
DC
366 }
367
368 if (!data.path.match(/\/_upgrade_/)) {
52428d60 369 let permhtml = '';
4ae75df3
DC
370
371 if (!info.permissions) {
372 permhtml = "Root only.";
373 } else {
374 if (info.permissions.description) {
375 permhtml += "<div style='white-space:pre-wrap;padding-bottom:10px;'>" +
376 Ext.htmlEncode(info.permissions.description) + "</div>";
377 }
378 permhtml += permission_text(info.permissions);
379 }
380
381 if (info.allowtoken !== undefined && !info.allowtoken) {
52428d60 382 permhtml += "<br />This API endpoint is not available for API tokens.";
4ae75df3
DC
383 }
384
385 sections.push({
386 title: 'Required permissions',
387 bodyPadding: 10,
52428d60 388 html: permhtml,
4ae75df3
DC
389 });
390 }
391
392 items.push({
393 title: method,
394 autoScroll: true,
395 defaults: {
52428d60 396 border: false,
4ae75df3 397 },
52428d60 398 items: sections,
4ae75df3
DC
399 });
400 }
401 });
402
52428d60
TL
403 let ct = Ext.getCmp('docview');
404 ct.setTitle("Path: " + real_path(data.path));
4ae75df3
DC
405 ct.removeAll(true);
406 ct.add(items);
407 ct.setActiveTab(0);
408 };
409
410 Ext.define('Ext.form.SearchField', {
411 extend: 'Ext.form.field.Text',
412 alias: 'widget.searchfield',
413
414 emptyText: 'Search...',
415
416 flex: 1,
417
418 inputType: 'search',
419 listeners: {
52428d60
TL
420 'change': function() {
421 let value = this.getValue();
4ae75df3
DC
422 if (!Ext.isEmpty(value)) {
423 store.filter({
424 property: 'path',
425 value: value,
52428d60 426 anyMatch: true,
4ae75df3
DC
427 });
428 } else {
429 store.clearFilter();
430 }
52428d60
TL
431 },
432 },
4ae75df3
DC
433 });
434
52428d60 435 let tree = Ext.create('Ext.tree.Panel', {
4ae75df3
DC
436 title: 'Resource Tree',
437 tbar: [
438 {
439 xtype: 'searchfield',
52428d60 440 },
4ae75df3
DC
441 ],
442 tools: [
443 {
444 type: 'expand',
445 tooltip: 'Expand all',
446 tooltipType: 'title',
447 callback: (tree) => tree.expandAll(),
448 },
449 {
450 type: 'collapse',
451 tooltip: 'Collapse all',
452 tooltipType: 'title',
453 callback: (tree) => tree.collapseAll(),
454 },
455 ],
456 store: store,
457 width: 200,
458 region: 'west',
459 split: true,
460 margins: '5 0 5 5',
461 rootVisible: false,
462 listeners: {
463 selectionchange: function(v, selections) {
52428d60
TL
464 if (!selections[0]) {return;}
465 let rec = selections[0];
4ae75df3
DC
466 render_docu(rec.data);
467 location.hash = '#' + rec.data.path;
52428d60
TL
468 },
469 },
4ae75df3
DC
470 });
471
472 Ext.create('Ext.container.Viewport', {
473 layout: 'border',
474 renderTo: Ext.getBody(),
475 items: [
476 tree,
477 {
478 xtype: 'tabpanel',
479 title: 'Documentation',
480 id: 'docview',
481 region: 'center',
482 margins: '5 5 5 0',
483 layout: 'fit',
52428d60
TL
484 items: [],
485 },
486 ],
4ae75df3
DC
487 });
488
52428d60
TL
489 let deepLink = function() {
490 let path = window.location.hash.substring(1).replace(/\/\s*$/, '');
491 let endpoint = store.findNode('path', path);
4ae75df3
DC
492
493 if (endpoint) {
494 tree.getSelectionModel().select(endpoint);
495 tree.expandPath(endpoint.getPath());
496 render_docu(endpoint.data);
497 }
52428d60 498 };
4ae75df3
DC
499 window.onhashchange = deepLink;
500
501 deepLink();
4ae75df3 502});