]>
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
],
244 enableTextSelection
: true,
255 renderer
: render_type
,
260 dataIndex
: 'default',
266 renderer
: render_format
,
270 header
: 'Description',
271 dataIndex
: 'description',
272 renderer
: render_description
,
280 let retinf
= info
.returns
;
281 let rtype
= retinf
.type
;
282 if (!rtype
&& retinf
.items
) {rtype
= 'array';}
283 if (!rtype
) {rtype
= 'object';}
285 let rpstore
= Ext
.create('Ext.data.Store', {
286 model
: 'pmx-param-schema',
290 groupField
: 'optional',
300 if (rtype
=== 'array' && retinf
.items
.properties
) {
301 properties
= retinf
.items
.properties
;
304 if (rtype
=== 'object' && retinf
.properties
) {
305 properties
= retinf
.properties
;
308 Ext
.Object
.each(properties
, function(name
, pdef
) {
315 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
316 enableGroupingMenu
: false,
317 groupHeaderTpl
: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Obligatory</tpl>',
321 returnhtml
= '<pre>items: ' + Ext
.htmlEncode(JSON
.stringify(retinf
.items
, null, 4)) + '</pre>';
324 if (retinf
.properties
) {
325 returnhtml
= returnhtml
|| '';
326 returnhtml
+= '<pre>properties:' + Ext
.htmlEncode(JSON
.stringify(retinf
.properties
, null, 4)) + '</pre>';
329 let rawSection
= Ext
.create('Ext.panel.Panel', {
330 bodyPadding
: '0px 10px 10px 10px',
337 title
: 'Returns: ' + rtype
,
338 features
: [groupingFeature
],
343 enableTextSelection
: true,
354 renderer
: render_type
,
359 dataIndex
: 'default',
365 renderer
: render_format
,
369 header
: 'Description',
370 dataIndex
: 'description',
371 renderer
: render_description
,
379 handler: function(btn
) {
380 rawSection
.setVisible(!rawSection
.isVisible());
381 btn
.setText(rawSection
.isVisible() ? 'Hide RAW' : 'Show RAW');
387 sections
.push(rawSection
);
390 if (!data
.path
.match(/\/_upgrade_/)) {
393 if (!info
.permissions
) {
394 permhtml
= "Root only.";
396 if (info
.permissions
.description
) {
397 permhtml
+= "<div style='white-space:pre-wrap;padding-bottom:10px;'>" +
398 Ext
.htmlEncode(info
.permissions
.description
) + "</div>";
400 permhtml
+= permission_text(info
.permissions
);
403 if (info
.allowtoken
!== undefined && !info
.allowtoken
) {
404 permhtml
+= "<br />This API endpoint is not available for API tokens.";
408 title
: 'Required permissions',
425 let ct
= Ext
.getCmp('docview');
426 ct
.setTitle("Path: " + real_path(data
.path
));
432 Ext
.define('Ext.form.SearchField', {
433 extend
: 'Ext.form.field.Text',
434 alias
: 'widget.searchfield',
436 emptyText
: 'Search...',
442 'change': function() {
443 let value
= this.getValue();
444 if (!Ext
.isEmpty(value
)) {
457 let treePanel
= Ext
.create('Ext.tree.Panel', {
458 title
: 'Resource Tree',
461 xtype
: 'searchfield',
467 tooltip
: 'Expand all',
468 tooltipType
: 'title',
469 callback
: tree
=> tree
.expandAll(),
473 tooltip
: 'Collapse all',
474 tooltipType
: 'title',
475 callback
: tree
=> tree
.collapseAll(),
485 selectionchange: function(v
, selections
) {
486 if (!selections
[0]) {return;}
487 let rec
= selections
[0];
488 render_docu(rec
.data
);
489 location
.hash
= '#' + rec
.data
.path
;
494 Ext
.create('Ext.container.Viewport', {
496 renderTo
: Ext
.getBody(),
501 title
: 'Documentation',
511 let deepLink = function() {
512 let path
= window
.location
.hash
.substring(1).replace(/\/\s*$/, '');
513 let endpoint
= store
.findNode('path', path
);
516 treePanel
.getSelectionModel().select(endpoint
);
517 treePanel
.expandPath(endpoint
.getPath());
518 render_docu(endpoint
.data
);
521 window
.onhashchange
= deepLink
;