From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 8E53D71F8F for ; Mon, 14 Jun 2021 09:45:18 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7A84821DB2 for ; Mon, 14 Jun 2021 09:44:48 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 27E8321DA0 for ; Mon, 14 Jun 2021 09:44:47 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id DEABD42F9A for ; Mon, 14 Jun 2021 09:44:46 +0200 (CEST) Date: Mon, 14 Jun 2021 09:44:44 +0200 From: Stoiko Ivanov To: Dominik Csapak Cc: pmg-devel@lists.proxmox.com Message-ID: <20210614094444.7323af88@rosa.proxmox.com> In-Reply-To: <20210604124226.22208-1-d.csapak@proxmox.com> References: <20210604124226.22208-1-d.csapak@proxmox.com> X-Mailer: Claws Mail 3.17.3 (GTK+ 2.24.32; x86_64-pc-linux-gnu) MIME-Version: 1.0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.863 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pmg-devel] applied: [PATCH pmg-docs v2] build api-viewer from proxmox-widget-toolkit-dev X-BeenThere: pmg-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Mail Gateway development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 14 Jun 2021 07:45:18 -0000 On Fri, 4 Jun 2021 14:42:26 +0200 Dominik Csapak wrote: > build-depends on the new proxmox-widget-toolkit-dev package > > Signed-off-by: Dominik Csapak > --- > changes from v1: > * insert dev-dependency sorted > * use new 'api' of APIViewer (apiSchema/cliUsageRenderer) > * update path to APIViewer.js (previously APIVIEWER.js) > > Makefile | 10 +- > api-viewer/PMGAPI.js | 490 +------------------------------------------ > debian/control | 1 + > extractapi.pl | 2 +- > 4 files changed, 19 insertions(+), 484 deletions(-) > > diff --git a/Makefile b/Makefile > index 8b125df..eb4db5a 100644 > --- a/Makefile > +++ b/Makefile > @@ -53,6 +53,11 @@ GEN_SCRIPTS= \ > gen-pmg.conf.5-opts.pl \ > gen-user.conf.5-opts.pl > > +API_VIEWER_FILES= \ > + api-viewer/apidata.js \ > + api-viewer/PMGAPI.js \ > + /usr/share/javascript/proxmox-widget-toolkit-dev/APIViewer.js > + > API_VIEWER_SOURCES= \ > api-viewer/index.html \ > api-viewer/apidoc.js > @@ -139,8 +144,9 @@ pmg-admin-guide.epub: ${PMG_ADMIN_GUIDE_ADOCDEPENDS} > api-viewer/apidata.js: extractapi.pl > ./extractapi.pl >$@ > > -api-viewer/apidoc.js: api-viewer/apidata.js api-viewer/PMGAPI.js > - cat api-viewer/apidata.js api-viewer/PMGAPI.js >$@ > +api-viewer/apidoc.js: ${API_VIEWER_FILES} > + cat ${API_VIEWER_FILES} >$@.tmp > + mv $@.tmp $@ > > .PHONY: dinstall > dinstall: ${GEN_DEB} ${DOC_DEB} > diff --git a/api-viewer/PMGAPI.js b/api-viewer/PMGAPI.js > index 76fd77e..e5a6187 100644 > --- a/api-viewer/PMGAPI.js > +++ b/api-viewer/PMGAPI.js > @@ -1,482 +1,10 @@ > -// avoid errors when running without development tools > -if (!Ext.isDefined(Ext.global.console)) { > - var console = { > - dir: function() {}, > - log: function() {} > - }; > +var clicmdhash = { > + GET: 'get', > + POST: 'create', > + PUT: 'set', > + DELETE: 'delete' > +}; > + > +function cliUsageRenderer(method, path) { > + return ` CLI:pmgsh ${clicmdhash[method]} ${path}`; > } > - > -Ext.onReady(function() { > - > - Ext.define('pmg-param-schema', { > - extend: 'Ext.data.Model', > - fields: [ > - 'name', 'type', 'typetext', 'description', 'verbose_description', > - 'enum', 'minimum', 'maximum', 'minLength', 'maxLength', > - 'pattern', 'title', 'requires', 'format', 'default', > - 'disallow', 'extends', 'links', > - { > - name: 'optional', > - type: 'boolean' > - } > - ] > - }); > - > - var store = Ext.define('pmg-updated-treestore', { > - extend: 'Ext.data.TreeStore', > - model: Ext.define('pmg-api-doc', { > - extend: 'Ext.data.Model', > - fields: [ > - 'path', 'info', 'text', > - ] > - }), > - proxy: { > - type: 'memory', > - data: pmgapi > - }, > - sorters: [{ > - property: 'leaf', > - direction: 'ASC' > - }, { > - property: 'text', > - direction: 'ASC' > - }], > - filterer: 'bottomup', > - doFilter: function(node) { > - this.filterNodes(node, this.getFilters().getFilterFn(), true); > - }, > - > - filterNodes: function(node, filterFn, parentVisible) { > - var me = this, > - bottomUpFiltering = me.filterer === 'bottomup', > - match = filterFn(node) && parentVisible || (node.isRoot() && !me.getRootVisible()), > - childNodes = node.childNodes, > - len = childNodes && childNodes.length, i, matchingChildren; > - > - if (len) { > - for (i = 0; i < len; ++i) { > - matchingChildren = me.filterNodes(childNodes[i], filterFn, match || bottomUpFiltering) || matchingChildren; > - } > - if (bottomUpFiltering) { > - match = matchingChildren || match; > - } > - } > - > - node.set("visible", match, me._silentOptions); > - return match; > - }, > - > - }).create(); > - > - var render_description = function(value, metaData, record) { > - var pdef = record.data; > - > - value = pdef.verbose_description || value; > - > - // TODO: try to render asciidoc correctly > - > - metaData.style = 'white-space:pre-wrap;' > - > - return Ext.htmlEncode(value); > - }; > - > - var render_type = function(value, metaData, record) { > - var pdef = record.data; > - > - return pdef['enum'] ? 'enum' : (pdef.type || 'string'); > - }; > - > - var render_format = function(value, metaData, record) { > - var pdef = record.data; > - > - metaData.style = 'white-space:normal;' > - > - if (pdef.typetext) > - return Ext.htmlEncode(pdef.typetext); > - > - if (pdef['enum']) > - return pdef['enum'].join(' | '); > - > - if (pdef.format) > - return pdef.format; > - > - if (pdef.pattern) > - return Ext.htmlEncode(pdef.pattern); > - > - return ''; > - }; > - > - var render_docu = function(data) { > - var md = data.info; > - > - // console.dir(data); > - > - var items = []; > - > - var clicmdhash = { > - GET: 'get', > - POST: 'create', > - PUT: 'set', > - DELETE: 'delete' > - }; > - > - Ext.Array.each(['GET', 'POST', 'PUT', 'DELETE'], function(method) { > - var info = md[method]; > - if (info) { > - > - var usage = ""; > - > - usage += ""; > - usage += "
HTTP:   " + method + " /api2/json" + data.path + "
 
CLI:pmgsh " + clicmdhash[method] + " " + data.path + "
"; > - > - var sections = [ > - { > - title: 'Description', > - html: Ext.htmlEncode(info.description), > - bodyPadding: 10 > - }, > - { > - title: 'Usage', > - html: usage, > - bodyPadding: 10 > - } > - ]; > - > - if (info.parameters && info.parameters.properties) { > - > - var pstore = Ext.create('Ext.data.Store', { > - model: 'pmg-param-schema', > - proxy: { > - type: 'memory' > - }, > - groupField: 'optional', > - sorters: [ > - { > - property: 'name', > - direction: 'ASC' > - } > - ] > - }); > - > - Ext.Object.each(info.parameters.properties, function(name, pdef) { > - pdef.name = name; > - pstore.add(pdef); > - }); > - > - pstore.sort(); > - > - var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{ > - enableGroupingMenu: false, > - groupHeaderTpl: 'OptionalRequired' > - }); > - > - sections.push({ > - xtype: 'gridpanel', > - title: 'Parameters', > - features: [groupingFeature], > - store: pstore, > - viewConfig: { > - trackOver: false, > - stripeRows: true > - }, > - columns: [ > - { > - header: 'Name', > - dataIndex: 'name', > - flex: 1 > - }, > - { > - header: 'Type', > - dataIndex: 'type', > - renderer: render_type, > - flex: 1 > - }, > - { > - header: 'Default', > - dataIndex: 'default', > - flex: 1 > - }, > - { > - header: 'Format', > - dataIndex: 'type', > - renderer: render_format, > - flex: 2 > - }, > - { > - header: 'Description', > - dataIndex: 'description', > - renderer: render_description, > - flex: 6 > - } > - ] > - }); > - > - } > - > - if (info.returns) { > - > - var retinf = info.returns; > - var rtype = retinf.type; > - if (!rtype && retinf.items) > - rtype = 'array'; > - if (!rtype) > - rtype = 'object'; > - > - var rpstore = Ext.create('Ext.data.Store', { > - model: 'pmg-param-schema', > - proxy: { > - type: 'memory' > - }, > - groupField: 'optional', > - sorters: [ > - { > - property: 'name', > - direction: 'ASC' > - } > - ] > - }); > - > - var properties; > - if (rtype === 'array' && retinf.items.properties) { > - properties = retinf.items.properties; > - } > - > - if (rtype === 'object' && retinf.properties) { > - properties = retinf.properties; > - } > - > - Ext.Object.each(properties, function(name, pdef) { > - pdef.name = name; > - rpstore.add(pdef); > - }); > - > - rpstore.sort(); > - > - var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{ > - enableGroupingMenu: false, > - groupHeaderTpl: 'OptionalObligatory' > - }); > - var returnhtml; > - if (retinf.items) { > - returnhtml = '
items: ' + Ext.htmlEncode(JSON.stringify(retinf.items, null, 4)) + '
'; > - } > - > - if (retinf.properties) { > - returnhtml = returnhtml || ''; > - returnhtml += '
properties:' + Ext.htmlEncode(JSON.stringify(retinf.properties, null, 4)) + '
'; > - } > - > - var rawSection = Ext.create('Ext.panel.Panel', { > - bodyPadding: '0px 10px 10px 10px', > - html: returnhtml, > - hidden: true > - }); > - > - sections.push({ > - xtype: 'gridpanel', > - title: 'Returns: ' + rtype, > - features: [groupingFeature], > - store: rpstore, > - viewConfig: { > - trackOver: false, > - stripeRows: true > - }, > - columns: [ > - { > - header: 'Name', > - dataIndex: 'name', > - flex: 1 > - }, > - { > - header: 'Type', > - dataIndex: 'type', > - renderer: render_type, > - flex: 1 > - }, > - { > - header: 'Default', > - dataIndex: 'default', > - flex: 1 > - }, > - { > - header: 'Format', > - dataIndex: 'type', > - renderer: render_format, > - flex: 2 > - }, > - { > - header: 'Description', > - dataIndex: 'description', > - renderer: render_description, > - flex: 6 > - } > - ], > - bbar: [ > - { > - xtype: 'button', > - text: 'Show RAW', > - handler: function(btn) { > - rawSection.setVisible(!rawSection.isVisible()); > - btn.setText(rawSection.isVisible() ? 'Hide RAW' : 'Show RAW'); > - }} > - ] > - }); > - > - sections.push(rawSection); > - > - > - } > - > - var permhtml = ''; > - if (!info.permissions) { > - permhtml = "Root only."; > - } else { > - if (info.permissions.description) { > - permhtml += "
" + > - Ext.htmlEncode(info.permissions.description) + "
"; > - } > - > - if (info.permissions.user) { > - if (!info.permissions.description) { > - if (info.permissions.user === 'world') { > - permhtml += "Accessible without any authentication."; > - } else if (info.permissions.user === 'all') { > - permhtml += "Accessible by all authenticated users."; > - } else { > - permhtml += 'Onyl accessible by user "' + > - info.permissions.user + '"'; > - } > - } > - } else if (info.permissions.check) { > - permhtml += "
Check: " +
> -			    Ext.htmlEncode(Ext.JSON.encode(info.permissions.check))  + "
"; > - } else { > - permhtml += "Unknown systax!"; > - } > - } > - if (!info.allowtoken) { > - // PMG doesn't fully supports API token, and probably won't ever!? > - permhtml += "
This API endpoint is not available for API tokens." > - } > - > - sections.push({ > - title: 'Required permissions', > - bodyPadding: 10, > - html: permhtml > - }); > - > - > - items.push({ > - title: method, > - autoScroll: true, > - defaults: { > - border: false > - }, > - items: sections > - }); > - } > - }); > - > - var ct = Ext.getCmp('docview'); > - ct.setTitle("Path: " + data.path); > - ct.removeAll(true); > - ct.add(items); > - ct.setActiveTab(0); > - }; > - > - Ext.define('Ext.form.SearchField', { > - extend: 'Ext.form.field.Text', > - alias: 'widget.searchfield', > - > - emptyText: 'Search...', > - > - flex: 1, > - > - inputType: 'search', > - listeners: { > - 'change': function(){ > - > - var value = this.getValue(); > - if (!Ext.isEmpty(value)) { > - store.filter({ > - property: 'path', > - value: value, > - anyMatch: true > - }); > - } else { > - store.clearFilter(); > - } > - } > - } > - }); > - > - var tree = Ext.create('Ext.tree.Panel', { > - title: 'Resource Tree', > - tbar: [ > - { > - xtype: 'searchfield', > - } > - ], > - tools: [ > - { > - type: 'expand', > - tooltip: 'Expand all', > - tooltipType: 'title', > - callback: (tree) => tree.expandAll(), > - }, > - { > - type: 'collapse', > - tooltip: 'Collapse all', > - tooltipType: 'title', > - callback: (tree) => tree.collapseAll(), > - }, > - ], > - store: store, > - width: 200, > - region: 'west', > - split: true, > - margins: '5 0 5 5', > - rootVisible: false, > - listeners: { > - selectionchange: function(v, selections) { > - if (!selections[0]) > - return; > - var rec = selections[0]; > - render_docu(rec.data); > - location.hash = '#' + rec.data.path; > - } > - } > - }); > - > - Ext.create('Ext.container.Viewport', { > - layout: 'border', > - renderTo: Ext.getBody(), > - items: [ > - tree, > - { > - xtype: 'tabpanel', > - title: 'Documentation', > - id: 'docview', > - region: 'center', > - margins: '5 5 5 0', > - layout: 'fit', > - items: [] > - } > - ] > - }); > - > - var deepLink = function() { > - var path = window.location.hash.substring(1).replace(/\/\s*$/, '') > - var endpoint = store.findNode('path', path); > - > - if (endpoint) { > - tree.getSelectionModel().select(endpoint); > - tree.expandPath(endpoint.getPath()); > - render_docu(endpoint.data); > - } > - } > - window.onhashchange = deepLink; > - > - deepLink(); > - > -}); > diff --git a/debian/control b/debian/control > index 8d2ccde..5993180 100644 > --- a/debian/control > +++ b/debian/control > @@ -9,6 +9,7 @@ Build-Depends: asciidoc-dblatex, > imagemagick, > librsvg2-bin, > lintian, > + proxmox-widget-toolkit-dev, > source-highlight, > Standards-Version: 3.8.4 > > diff --git a/extractapi.pl b/extractapi.pl > index 283d8a0..9a7262d 100755 > --- a/extractapi.pl > +++ b/extractapi.pl > @@ -42,6 +42,6 @@ sub cleanup_tree { > > my $tree = cleanup_tree(PVE::RESTHandler::api_dump('PMG::API2')); > > -print "var pmgapi = " . to_json($tree, {pretty => 1, canonical => 1}) . ";\n\n"; > +print "var apiSchema = " . to_json($tree, {pretty => 1, canonical => 1}) . ";\n\n"; > > exit(0); applied - huge thanks!