]>
git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/api-viewer/APIViewer.js
2b04b8caf822ecb21e3c75ff2744410a16e57eea
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}>`;
125 let render_format = function(value
, metaData
, record
) {
126 let pdef
= record
.data
;
128 metaData
.style
= 'white-space:normal;';
130 if (pdef
.type
=== 'array' && pdef
.items
) {
131 let format
= render_simple_format(pdef
.items
, true);
132 return `[${Ext.htmlEncode(format)}, ...]`;
135 return Ext
.htmlEncode(render_simple_format(pdef
));
138 let real_path = function(path
) {
139 if (!path
.match(/^[/]/)) {
142 return path
.replace(/^.*\/_upgrade_(\/)?/, "/");
145 let permission_text = function(permission
) {
148 if (permission
.user
) {
149 if (!permission
.description
) {
150 if (permission
.user
=== 'world') {
151 permhtml
+= "Accessible without any authentication.";
152 } else if (permission
.user
=== 'all') {
153 permhtml
+= "Accessible by all authenticated users.";
155 permhtml
+= `Only accessible by user "${permission.user}"`;
158 } else if (permission
.check
) {
159 permhtml
+= `<pre>Check: ${Ext.htmlEncode(JSON.stringify(permission.check))}</pre>`;
160 } else if (permission
.userParam
) {
161 permhtml
+= `<div>Check if user matches parameter '${permission.userParam}'`;
162 } else if (permission
.or
) {
163 permhtml
+= "<div>Or<div style='padding-left: 10px;'>";
164 permhtml
+= permission
.or
.map(v
=> permission_text(v
)).join('');
165 permhtml
+= "</div></div>";
166 } else if (permission
.and
) {
167 permhtml
+= "<div>And<div style='padding-left: 10px;'>";
168 permhtml
+= permission
.and
.map(v
=> permission_text(v
)).join('');
169 permhtml
+= "</div></div>";
171 permhtml
+= "Unknown syntax!";
177 let render_docu = function(data
) {
182 Ext
.Array
.each(['GET', 'POST', 'PUT', 'DELETE'], function(method
) {
183 let info
= md
[method
];
185 let endpoint
= real_path(data
.path
);
186 let usage
= `<table><tr><td>HTTP: </td><td>`;
187 usage
+= `${method} /api2/json${endpoint}</td></tr>`;
189 if (typeof cliUsageRenderer
=== 'function') {
190 usage
+= cliUsageRenderer(method
, endpoint
); // eslint-disable-line no-undef
195 title
: 'Description',
196 html
: Ext
.htmlEncode(info
.description
),
206 if (info
.parameters
&& info
.parameters
.properties
) {
207 let pstore
= Ext
.create('Ext.data.Store', {
208 model
: 'pmx-param-schema',
212 groupField
: 'optional',
221 Ext
.Object
.each(info
.parameters
.properties
, function(name
, pdef
) {
228 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
229 enableGroupingMenu
: false,
230 groupHeaderTpl
: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Required</tpl>',
236 features
: [groupingFeature
],
251 renderer
: render_type
,
256 dataIndex
: 'default',
262 renderer
: render_format
,
266 header
: 'Description',
267 dataIndex
: 'description',
268 renderer
: render_description
,
276 let retinf
= info
.returns
;
277 let rtype
= retinf
.type
;
278 if (!rtype
&& retinf
.items
) {rtype
= 'array';}
279 if (!rtype
) {rtype
= 'object';}
281 let rpstore
= Ext
.create('Ext.data.Store', {
282 model
: 'pmx-param-schema',
286 groupField
: 'optional',
296 if (rtype
=== 'array' && retinf
.items
.properties
) {
297 properties
= retinf
.items
.properties
;
300 if (rtype
=== 'object' && retinf
.properties
) {
301 properties
= retinf
.properties
;
304 Ext
.Object
.each(properties
, function(name
, pdef
) {
311 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
312 enableGroupingMenu
: false,
313 groupHeaderTpl
: '<tpl if="groupValue">Optional</tpl><tpl if="!groupValue">Obligatory</tpl>',
317 returnhtml
= '<pre>items: ' + Ext
.htmlEncode(JSON
.stringify(retinf
.items
, null, 4)) + '</pre>';
320 if (retinf
.properties
) {
321 returnhtml
= returnhtml
|| '';
322 returnhtml
+= '<pre>properties:' + Ext
.htmlEncode(JSON
.stringify(retinf
.properties
, null, 4)) + '</pre>';
325 let rawSection
= Ext
.create('Ext.panel.Panel', {
326 bodyPadding
: '0px 10px 10px 10px',
333 title
: 'Returns: ' + rtype
,
334 features
: [groupingFeature
],
349 renderer
: render_type
,
354 dataIndex
: 'default',
360 renderer
: render_format
,
364 header
: 'Description',
365 dataIndex
: 'description',
366 renderer
: render_description
,
374 handler: function(btn
) {
375 rawSection
.setVisible(!rawSection
.isVisible());
376 btn
.setText(rawSection
.isVisible() ? 'Hide RAW' : 'Show RAW');
382 sections
.push(rawSection
);
385 if (!data
.path
.match(/\/_upgrade_/)) {
388 if (!info
.permissions
) {
389 permhtml
= "Root only.";
391 if (info
.permissions
.description
) {
392 permhtml
+= "<div style='white-space:pre-wrap;padding-bottom:10px;'>" +
393 Ext
.htmlEncode(info
.permissions
.description
) + "</div>";
395 permhtml
+= permission_text(info
.permissions
);
398 if (info
.allowtoken
!== undefined && !info
.allowtoken
) {
399 permhtml
+= "<br />This API endpoint is not available for API tokens.";
403 title
: 'Required permissions',
420 let ct
= Ext
.getCmp('docview');
421 ct
.setTitle("Path: " + real_path(data
.path
));
427 Ext
.define('Ext.form.SearchField', {
428 extend
: 'Ext.form.field.Text',
429 alias
: 'widget.searchfield',
431 emptyText
: 'Search...',
437 'change': function() {
438 let value
= this.getValue();
439 if (!Ext
.isEmpty(value
)) {
452 let treePanel
= Ext
.create('Ext.tree.Panel', {
453 title
: 'Resource Tree',
456 xtype
: 'searchfield',
462 tooltip
: 'Expand all',
463 tooltipType
: 'title',
464 callback
: tree
=> tree
.expandAll(),
468 tooltip
: 'Collapse all',
469 tooltipType
: 'title',
470 callback
: tree
=> tree
.collapseAll(),
480 selectionchange: function(v
, selections
) {
481 if (!selections
[0]) {return;}
482 let rec
= selections
[0];
483 render_docu(rec
.data
);
484 location
.hash
= '#' + rec
.data
.path
;
489 Ext
.create('Ext.container.Viewport', {
491 renderTo
: Ext
.getBody(),
496 title
: 'Documentation',
506 let deepLink = function() {
507 let path
= window
.location
.hash
.substring(1).replace(/\/\s*$/, '');
508 let endpoint
= store
.findNode('path', path
);
511 treePanel
.getSelectionModel().select(endpoint
);
512 treePanel
.expandPath(endpoint
.getPath());
513 render_docu(endpoint
.data
);
516 window
.onhashchange
= deepLink
;