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