]>
git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/api-viewer/APIViewer.js
3 Ext
.onReady(function() {
4 Ext
.define('pmx-param-schema', {
5 extend
: 'Ext.data.Model',
7 'name', 'type', 'typetext', 'description', 'verbose_description',
8 'enum', 'minimum', 'maximum', 'minLength', 'maxLength',
9 'pattern', 'title', 'requires', 'format', 'default',
10 'disallow', 'extends', 'links',
18 let store
= Ext
.define('pmx-updated-treestore', {
19 extend
: 'Ext.data.TreeStore',
20 model
: Ext
.define('pmx-api-doc', {
21 extend
: 'Ext.data.Model',
23 'path', 'info', 'text',
38 doFilter: function(node
) {
39 this.filterNodes(node
, this.getFilters().getFilterFn(), true);
42 filterNodes: function(node
, filterFn
, parentVisible
) {
45 let match
= filterFn(node
) && (parentVisible
|| (node
.isRoot() && !me
.getRootVisible()));
47 if (node
.childNodes
&& node
.childNodes
.length
) {
48 let bottomUpFiltering
= me
.filterer
=== 'bottomup';
50 for (const child
of node
.childNodes
) {
51 childMatch
= me
.filterNodes(child
, filterFn
, match
|| bottomUpFiltering
) || childMatch
;
53 if (bottomUpFiltering
) {
54 match
= childMatch
|| match
;
58 node
.set("visible", match
, me
._silentOptions
);
64 let render_description = function(value
, metaData
, record
) {
65 let pdef
= record
.data
;
67 value
= pdef
.verbose_description
|| value
;
69 // TODO: try to render asciidoc correctly
71 metaData
.style
= 'white-space:pre-wrap;';
73 return Ext
.htmlEncode(value
);
76 let render_type = function(value
, metaData
, record
) {
77 let pdef
= record
.data
;
79 return pdef
.enum ? 'enum' : pdef
.type
|| 'string';
82 const renderFormatString = function(obj
) {
83 if (!Ext
.isObject(obj
)) {
88 Object
.entries(obj
).forEach(function([name
, param
]) {
89 let list
= param
.optional
? optional
: mandatory
;
90 let str
= param
.default_key
? `[${name}=]` : `${name}=`;
93 } else if (param
.enum) {
94 str
+= `(${param.enum?.join(' | ')})`;
96 str
+= `<${param.format_description || param.pattern || param.type}>`;
100 return mandatory
.join(", ") + ' ' + optional
.map(each
=> `[,${each}]`).join(' ');
103 let render_simple_format = function(pdef
, type_fallback
) {
105 return pdef
.typetext
;
108 return pdef
.enum.join(' | ');
111 return renderFormatString(pdef
.format
);
116 if (pdef
.type
=== 'boolean') {
117 return `<true|false>`;
119 if (type_fallback
&& pdef
.type
) {
120 return `<${pdef.type}>`;
122 if (pdef
.minimum
|| pdef
.maximum
) {
123 return `${pdef.minimum || 'N'} - ${pdef.maximum || 'N'}`;
128 let render_format = function(value
, metaData
, record
) {
129 let pdef
= record
.data
;
131 metaData
.style
= 'white-space:normal;';
133 if (pdef
.type
=== 'array' && pdef
.items
) {
134 let format
= render_simple_format(pdef
.items
, true);
135 return `[${Ext.htmlEncode(format)}, ...]`;
138 return Ext
.htmlEncode(render_simple_format(pdef
));
141 let real_path = function(path
) {
142 if (!path
.match(/^[/]/)) {
145 return path
.replace(/^.*\/_upgrade_(\/)?/, "/");
148 let permission_text = function(permission
) {
151 if (permission
.user
) {
152 if (!permission
.description
) {
153 if (permission
.user
=== 'world') {
154 permhtml
+= "Accessible without any authentication.";
155 } else if (permission
.user
=== 'all') {
156 permhtml
+= "Accessible by all authenticated users.";
158 permhtml
+= `Only accessible by user "${permission.user}"`;
161 } else if (permission
.check
) {
162 permhtml
+= `<pre>Check: ${Ext.htmlEncode(JSON.stringify(permission.check))}</pre>`;
163 } else if (permission
.userParam
) {
164 permhtml
+= `<div>Check if user matches parameter '${permission.userParam}'`;
165 } else if (permission
.or
) {
166 permhtml
+= "<div>Or<div style='padding-left: 10px;'>";
167 permhtml
+= permission
.or
.map(v
=> permission_text(v
)).join('');
168 permhtml
+= "</div></div>";
169 } else if (permission
.and
) {
170 permhtml
+= "<div>And<div style='padding-left: 10px;'>";
171 permhtml
+= permission
.and
.map(v
=> permission_text(v
)).join('');
172 permhtml
+= "</div></div>";
174 permhtml
+= "Unknown syntax!";
180 let render_docu = function(data
) {
185 Ext
.Array
.each(['GET', 'POST', 'PUT', 'DELETE'], function(method
) {
186 let info
= md
[method
];
188 let endpoint
= real_path(data
.path
);
189 let usage
= `<table><tr><td>HTTP: </td><td>`;
190 usage
+= `${method} /api2/json${endpoint}</td></tr>`;
192 if (typeof cliUsageRenderer
=== 'function') {
193 usage
+= cliUsageRenderer(method
, endpoint
); // eslint-disable-line no-undef
198 title
: 'Description',
199 html
: Ext
.htmlEncode(info
.description
),
209 if (info
.parameters
&& info
.parameters
.properties
) {
210 let pstore
= Ext
.create('Ext.data.Store', {
211 model
: 'pmx-param-schema',
215 groupField
: 'optional',
224 Ext
.Object
.each(info
.parameters
.properties
, function(name
, pdef
) {
231 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
232 enableGroupingMenu
: false,
233 groupHeaderTpl
: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Required</tpl>',
239 features
: [groupingFeature
],
254 renderer
: render_type
,
259 dataIndex
: 'default',
265 renderer
: render_format
,
269 header
: 'Description',
270 dataIndex
: 'description',
271 renderer
: render_description
,
279 let retinf
= info
.returns
;
280 let rtype
= retinf
.type
;
281 if (!rtype
&& retinf
.items
) {rtype
= 'array';}
282 if (!rtype
) {rtype
= 'object';}
284 let rpstore
= Ext
.create('Ext.data.Store', {
285 model
: 'pmx-param-schema',
289 groupField
: 'optional',
299 if (rtype
=== 'array' && retinf
.items
.properties
) {
300 properties
= retinf
.items
.properties
;
303 if (rtype
=== 'object' && retinf
.properties
) {
304 properties
= retinf
.properties
;
307 Ext
.Object
.each(properties
, function(name
, pdef
) {
314 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
315 enableGroupingMenu
: false,
316 groupHeaderTpl
: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Obligatory</tpl>',
320 returnhtml
= '<pre>items: ' + Ext
.htmlEncode(JSON
.stringify(retinf
.items
, null, 4)) + '</pre>';
323 if (retinf
.properties
) {
324 returnhtml
= returnhtml
|| '';
325 returnhtml
+= '<pre>properties:' + Ext
.htmlEncode(JSON
.stringify(retinf
.properties
, null, 4)) + '</pre>';
328 let rawSection
= Ext
.create('Ext.panel.Panel', {
329 bodyPadding
: '0px 10px 10px 10px',
336 title
: 'Returns: ' + rtype
,
337 features
: [groupingFeature
],
352 renderer
: render_type
,
357 dataIndex
: 'default',
363 renderer
: render_format
,
367 header
: 'Description',
368 dataIndex
: 'description',
369 renderer
: render_description
,
377 handler: function(btn
) {
378 rawSection
.setVisible(!rawSection
.isVisible());
379 btn
.setText(rawSection
.isVisible() ? 'Hide RAW' : 'Show RAW');
385 sections
.push(rawSection
);
388 if (!data
.path
.match(/\/_upgrade_/)) {
391 if (!info
.permissions
) {
392 permhtml
= "Root only.";
394 if (info
.permissions
.description
) {
395 permhtml
+= "<div style='white-space:pre-wrap;padding-bottom:10px;'>" +
396 Ext
.htmlEncode(info
.permissions
.description
) + "</div>";
398 permhtml
+= permission_text(info
.permissions
);
401 if (info
.allowtoken
!== undefined && !info
.allowtoken
) {
402 permhtml
+= "<br />This API endpoint is not available for API tokens.";
406 title
: 'Required permissions',
423 let ct
= Ext
.getCmp('docview');
424 ct
.setTitle("Path: " + real_path(data
.path
));
430 Ext
.define('Ext.form.SearchField', {
431 extend
: 'Ext.form.field.Text',
432 alias
: 'widget.searchfield',
434 emptyText
: 'Search...',
440 'change': function() {
441 let value
= this.getValue();
442 if (!Ext
.isEmpty(value
)) {
455 let treePanel
= Ext
.create('Ext.tree.Panel', {
456 title
: 'Resource Tree',
459 xtype
: 'searchfield',
465 tooltip
: 'Expand all',
466 tooltipType
: 'title',
467 callback
: tree
=> tree
.expandAll(),
471 tooltip
: 'Collapse all',
472 tooltipType
: 'title',
473 callback
: tree
=> tree
.collapseAll(),
483 selectionchange: function(v
, selections
) {
484 if (!selections
[0]) {return;}
485 let rec
= selections
[0];
486 render_docu(rec
.data
);
487 location
.hash
= '#' + rec
.data
.path
;
492 Ext
.create('Ext.container.Viewport', {
494 renderTo
: Ext
.getBody(),
499 title
: 'Documentation',
509 let deepLink = function() {
510 let path
= window
.location
.hash
.substring(1).replace(/\/\s*$/, '');
511 let endpoint
= store
.findNode('path', path
);
514 treePanel
.getSelectionModel().select(endpoint
);
515 treePanel
.expandPath(endpoint
.getPath());
516 render_docu(endpoint
.data
);
519 window
.onhashchange
= deepLink
;