mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	ui: add new API docs tab (#9409)
* ui: add new API docs tab This introduces a new API docs table which is enabled by default but the admin can disable it via config.json. This uses the discovered APIs for logged in user/account to show them the APIs accessible to them and generates dynamic API docs based on them which are searchable. Also introduces some common auto-completed API groups that are available to most roles. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> * Update ui/src/views/plugins/ApiDocsPlugin.vue * Update ui/src/views/plugins/ApiDocsPlugin.vue * Update ui/src/views/plugins/ApiDocsPlugin.vue * Update ui/src/views/plugins/ApiDocsPlugin.vue * Update ui/src/views/plugins/ApiDocsPlugin.vue * fix performance issues Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> * Update ui/src/views/plugins/ApiDocsPlugin.vue Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com> * Update ui/public/locales/en.json Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com> * address Suresh's feedback Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> * filter example/options as we type Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> * Address Joao's comments Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> --------- Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
This commit is contained in:
		
							parent
							
								
									56c661c1df
								
							
						
					
					
						commit
						f24fb20e6b
					
				
							
								
								
									
										1
									
								
								ui/public/config.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								ui/public/config.json
									
									
									
									
										vendored
									
									
								
							| @ -92,6 +92,7 @@ | |||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   "plugins": [], |   "plugins": [], | ||||||
|  |   "apidocs": true, | ||||||
|   "basicZoneEnabled": true, |   "basicZoneEnabled": true, | ||||||
|   "multipleServer": false, |   "multipleServer": false, | ||||||
|   "allowSettingTheme": true, |   "allowSettingTheme": true, | ||||||
|  | |||||||
| @ -348,6 +348,9 @@ | |||||||
| "label.annotation.everyone": "Visible to everyone", | "label.annotation.everyone": "Visible to everyone", | ||||||
| "label.anti.affinity": "Anti-affinity", | "label.anti.affinity": "Anti-affinity", | ||||||
| "label.anti.affinity.group": "Anti-affinity group", | "label.anti.affinity.group": "Anti-affinity group", | ||||||
|  | "label.api.docs": "API Docs", | ||||||
|  | "label.api.docs.description": "For information about how the APIs work, and tips on how to use them, click here to see the Developer's Guide.", | ||||||
|  | "label.api.docs.count": "APIs available for your account", | ||||||
| "label.api.version": "API version", | "label.api.version": "API version", | ||||||
| "label.apikey": "API key", | "label.apikey": "API key", | ||||||
| "label.app.cookie": "AppCookie", | "label.app.cookie": "AppCookie", | ||||||
| @ -1796,6 +1799,7 @@ | |||||||
| "label.replace.acl": "Replace ACL", | "label.replace.acl": "Replace ACL", | ||||||
| "label.replace.acl.list": "Replace ACL list", | "label.replace.acl.list": "Replace ACL list", | ||||||
| "label.report.bug": "Ask a question or Report an issue", | "label.report.bug": "Ask a question or Report an issue", | ||||||
|  | "label.request": "Request", | ||||||
| "label.required": "Required", | "label.required": "Required", | ||||||
| "label.requireshvm": "HVM", | "label.requireshvm": "HVM", | ||||||
| "label.requiresupgrade": "Requires upgrade", | "label.requiresupgrade": "Requires upgrade", | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ | |||||||
| import { UserLayout, BasicLayout, RouteView } from '@/layouts' | import { UserLayout, BasicLayout, RouteView } from '@/layouts' | ||||||
| import AutogenView from '@/views/AutogenView.vue' | import AutogenView from '@/views/AutogenView.vue' | ||||||
| import IFramePlugin from '@/views/plugins/IFramePlugin.vue' | import IFramePlugin from '@/views/plugins/IFramePlugin.vue' | ||||||
|  | import ApiDocsPlugin from '@/views/plugins/ApiDocsPlugin.vue' | ||||||
| 
 | 
 | ||||||
| import { shallowRef } from 'vue' | import { shallowRef } from 'vue' | ||||||
| import { vueProps } from '@/vue-app' | import { vueProps } from '@/vue-app' | ||||||
| @ -275,6 +276,16 @@ export function asyncRouterMap () { | |||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   const apidocs = vueProps.$config.apidocs | ||||||
|  |   if (apidocs !== false) { | ||||||
|  |     routerMap[0].children.push({ | ||||||
|  |       path: '/apidocs/', | ||||||
|  |       name: 'apidocs', | ||||||
|  |       component: shallowRef(ApiDocsPlugin), | ||||||
|  |       meta: { title: 'label.api.docs', icon: 'read-outlined' } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   return routerMap |   return routerMap | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -61,6 +61,7 @@ import { | |||||||
|   Tree, |   Tree, | ||||||
|   Calendar, |   Calendar, | ||||||
|   Slider, |   Slider, | ||||||
|  |   Result, | ||||||
|   AutoComplete, |   AutoComplete, | ||||||
|   Collapse, |   Collapse, | ||||||
|   Space, |   Space, | ||||||
| @ -133,5 +134,6 @@ export default { | |||||||
|     app.use(Descriptions) |     app.use(Descriptions) | ||||||
|     app.use(Space) |     app.use(Space) | ||||||
|     app.use(Statistic) |     app.use(Statistic) | ||||||
|  |     app.use(Result) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -314,7 +314,10 @@ const user = { | |||||||
|               const apiName = api.name |               const apiName = api.name | ||||||
|               apis[apiName] = { |               apis[apiName] = { | ||||||
|                 params: api.params, |                 params: api.params, | ||||||
|                 response: api.response |                 response: api.response, | ||||||
|  |                 isasync: api.isasync, | ||||||
|  |                 since: api.since, | ||||||
|  |                 description: api.description | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|             commit('SET_APIS', apis) |             commit('SET_APIS', apis) | ||||||
|  | |||||||
| @ -471,6 +471,10 @@ a { | |||||||
|   width: auto; |   width: auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .ant-list-item.selected-item { | ||||||
|  |   background-color: @primary-color-light; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .ant-select-arrow .anticon { | .ant-select-arrow .anticon { | ||||||
|   vertical-align: top; |   vertical-align: top; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										222
									
								
								ui/src/views/plugins/ApiDocsPlugin.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								ui/src/views/plugins/ApiDocsPlugin.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,222 @@ | |||||||
|  | // Licensed to the Apache Software Foundation (ASF) under one | ||||||
|  | // or more contributor license agreements.  See the NOTICE file | ||||||
|  | // distributed with this work for additional information | ||||||
|  | // regarding copyright ownership.  The ASF licenses this file | ||||||
|  | // to you under the Apache License, Version 2.0 (the | ||||||
|  | // "License"); you may not use this file except in compliance | ||||||
|  | // with the License.  You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, | ||||||
|  | // software distributed under the License is distributed on an | ||||||
|  | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||||
|  | // KIND, either express or implied.  See the License for the | ||||||
|  | // specific language governing permissions and limitations | ||||||
|  | // under the License. | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <resource-layout> | ||||||
|  |       <template #left> | ||||||
|  |         <a-card :bordered="false"> | ||||||
|  |           <a-auto-complete | ||||||
|  |             v-model:value="query" | ||||||
|  |             :options="options.filter(value => value.value.toLowerCase().includes(query.toLowerCase()))" | ||||||
|  |             style="width: 100%" | ||||||
|  |             > | ||||||
|  |             <a-input-search | ||||||
|  |                 size="default" | ||||||
|  |                 :placeholder="$t('label.search')" | ||||||
|  |                 v-model:value="query" | ||||||
|  |                 allow-clear | ||||||
|  |                 enter-button | ||||||
|  |                 > | ||||||
|  |                 <template #prefix><search-outlined /></template> | ||||||
|  |             </a-input-search> | ||||||
|  |           </a-auto-complete> | ||||||
|  |           <a-list style="margin-top: 12px; height:580px; overflow-y: scroll;" size="small" :data-source="Object.keys($store.getters.apis).sort()"> | ||||||
|  |             <template #renderItem="{ item }"> | ||||||
|  |               <a> | ||||||
|  |               <a-list-item | ||||||
|  |                 v-if="item.toLowerCase().includes(query.toLowerCase())" | ||||||
|  |                 @click="showApi(item)" | ||||||
|  |                 style="padding-left: 12px" | ||||||
|  |                 :class="selectedApi === item ? 'selected-item' : ''"> | ||||||
|  |                 {{ item }} <a-tag v-if="$store.getters.apis[item].isasync" color="blue">async</a-tag> | ||||||
|  |               </a-list-item> | ||||||
|  |               </a> | ||||||
|  |             </template> | ||||||
|  |           </a-list> | ||||||
|  |           <a-divider style="margin-bottom: 12px" /> | ||||||
|  |           <span>{{ Object.keys($store.getters.apis).length }} {{ $t('label.api.docs.count') }}</span> | ||||||
|  |         </a-card> | ||||||
|  |       </template> | ||||||
|  |       <template #right> | ||||||
|  |         <a-card | ||||||
|  |           class="spin-content" | ||||||
|  |           :bordered="true" | ||||||
|  |           style="width: 100%; overflow-x: auto"> | ||||||
|  |           <span v-if="selectedApi && selectedApi in $store.getters.apis"> | ||||||
|  |             <h2>{{ selectedApi }} | ||||||
|  |               <a-tag v-if="$store.getters.apis[selectedApi].isasync" color="blue">Asynchronous API</a-tag> | ||||||
|  |               <a-tag v-if="$store.getters.apis[selectedApi].since">Since {{ $store.getters.apis[selectedApi].since }}</a-tag> | ||||||
|  |               <tooltip-button | ||||||
|  |                 tooltipPlacement="right" | ||||||
|  |                 :tooltip="$t('label.copy') + ' ' + selectedApi" | ||||||
|  |                 icon="CopyOutlined" | ||||||
|  |                 type="outlined" | ||||||
|  |                 size="small" | ||||||
|  |                 @onClick="$message.success($t('label.copied.clipboard'))" | ||||||
|  |                 :copyResource="selectedApi" /> | ||||||
|  |             </h2> | ||||||
|  |             <p>{{ $store.getters.apis[selectedApi].description }}</p> | ||||||
|  |             <h3>{{ $t('label.request') }} {{ $t('label.params') }}:</h3> | ||||||
|  |             <a-table | ||||||
|  |               :columns="[{title: $t('label.name'), dataIndex: 'name'}, {title: $t('label.required'), dataIndex: 'required'}, {title: $t('label.type'), dataIndex: 'type'}, {title: $t('label.description'), dataIndex: 'description'}]" | ||||||
|  |               :data-source="selectedParams" | ||||||
|  |               :pagination="false" | ||||||
|  |               size="small"> | ||||||
|  |               <template #bodyCell="{text, column, record}"> | ||||||
|  |                 <a-tag v-if="record.since && column.dataIndex === 'description'">Since {{ record.since }}</a-tag> | ||||||
|  |                 <span v-if="record.required === true"><strong>{{ text }}</strong></span> | ||||||
|  |                 <span v-else>{{ text }}</span> | ||||||
|  |               </template> | ||||||
|  |             </a-table> | ||||||
|  |             <br/> | ||||||
|  |             <h3>{{ $t('label.response') }} {{ $t('label.params') }}:</h3> | ||||||
|  |             <a-table | ||||||
|  |               :columns="[{title: $t('label.name'), dataIndex: 'name'}, {title: $t('label.type'), dataIndex: 'type'}, {title: $t('label.description'), dataIndex: 'description'}]" | ||||||
|  |               :data-source="selectedResponse" | ||||||
|  |               :pagination="false" | ||||||
|  |               size="small" /> | ||||||
|  |           </span> | ||||||
|  |           <span v-else> | ||||||
|  |             <a-alert | ||||||
|  |               :message="$t('label.api.docs')" | ||||||
|  |               type="info" | ||||||
|  |               show-icon | ||||||
|  |               banner> | ||||||
|  |               <template #description> | ||||||
|  |                 <a href="https://docs.cloudstack.apache.org/en/latest/developersguide/dev.html" target="_blank">{{ $t('label.api.docs.description') }}</a> | ||||||
|  |               </template> | ||||||
|  |             </a-alert> | ||||||
|  |             <a-result | ||||||
|  |               status="success" | ||||||
|  |               :title="$t('label.download') + ' CloudStack CloudMonkey CLI'" | ||||||
|  |               sub-title="For API automation and orchestration" | ||||||
|  |             > | ||||||
|  |               <template #extra> | ||||||
|  |                 <a-button type="primary"><a href="https://github.com/apache/cloudstack-cloudmonkey/releases" target="_blank">{{ $t('label.download') }} CLI</a></a-button> | ||||||
|  |                 <a-button><a href="https://github.com/apache/cloudstack-cloudmonkey/wiki/Usage" target="_blank">{{ $t('label.open.documentation') }} (CLI)</a></a-button> | ||||||
|  |                 <br/> | ||||||
|  |                 <br/> | ||||||
|  |                 <div v-if="showKeys"> | ||||||
|  |                   <key-outlined /> | ||||||
|  |                   <strong> | ||||||
|  |                     {{ $t('label.apikey') }} | ||||||
|  |                     <tooltip-button | ||||||
|  |                       tooltipPlacement="right" | ||||||
|  |                       :tooltip="$t('label.copy') + ' ' + $t('label.apikey')" | ||||||
|  |                       icon="CopyOutlined" | ||||||
|  |                       type="dashed" | ||||||
|  |                       size="small" | ||||||
|  |                       @onClick="$message.success($t('label.copied.clipboard'))" | ||||||
|  |                       :copyResource="userkeys.apikey" /> | ||||||
|  |                   </strong> | ||||||
|  |                   <div> | ||||||
|  |                     {{ userkeys.apikey.substring(0, 20) }}... | ||||||
|  |                   </div> | ||||||
|  |                   <br/> | ||||||
|  |                   <lock-outlined /> | ||||||
|  |                   <strong> | ||||||
|  |                     {{ $t('label.secretkey') }} | ||||||
|  |                     <tooltip-button | ||||||
|  |                       tooltipPlacement="right" | ||||||
|  |                       :tooltip="$t('label.copy') + ' ' + $t('label.secretkey')" | ||||||
|  |                       icon="CopyOutlined" | ||||||
|  |                       type="dashed" | ||||||
|  |                       size="small" | ||||||
|  |                       @onClick="$message.success($t('label.copied.clipboard'))" | ||||||
|  |                       :copyResource="userkeys.secretkey" /> | ||||||
|  |                   </strong> | ||||||
|  |                   <div> | ||||||
|  |                     {{ userkeys.secretkey.substring(0, 20) }}... | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </template> | ||||||
|  |             </a-result> | ||||||
|  |           </span> | ||||||
|  |         </a-card> | ||||||
|  |       </template> | ||||||
|  |     </resource-layout> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import { api } from '@/api' | ||||||
|  | 
 | ||||||
|  | import ResourceLayout from '@/layouts/ResourceLayout' | ||||||
|  | import TooltipButton from '@/components/widgets/TooltipButton' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   name: 'ApiDocsPlugin', | ||||||
|  |   components: { | ||||||
|  |     ResourceLayout, | ||||||
|  |     TooltipButton | ||||||
|  |   }, | ||||||
|  |   data () { | ||||||
|  |     return { | ||||||
|  |       query: '', | ||||||
|  |       selectedApi: '', | ||||||
|  |       selectedParams: [], | ||||||
|  |       selectedResponse: [], | ||||||
|  |       showKeys: false, | ||||||
|  |       userkeys: {}, | ||||||
|  |       options: [ | ||||||
|  |         { value: 'VirtualMachine', label: 'Instance' }, | ||||||
|  |         { value: 'Kubernetes', label: 'Kubernetes' }, | ||||||
|  |         { value: 'Volume', label: 'Volume' }, | ||||||
|  |         { value: 'Snapshot', label: 'Snapshot' }, | ||||||
|  |         { value: 'Backup', label: 'Backup' }, | ||||||
|  |         { value: 'Network', label: 'Network' }, | ||||||
|  |         { value: 'IpAddress', label: 'IP Address' }, | ||||||
|  |         { value: 'VPN', label: 'VPN' }, | ||||||
|  |         { value: 'VPC', label: 'VPC' }, | ||||||
|  |         { value: 'NetworkACL', label: 'Network ACL' }, | ||||||
|  |         { value: 'SecurityGroup', label: 'Security Group' }, | ||||||
|  |         { value: 'Template', label: 'Template' }, | ||||||
|  |         { value: 'ISO', label: 'ISO' }, | ||||||
|  |         { value: 'SSH', label: 'SSH' }, | ||||||
|  |         { value: 'Project', label: 'Project' }, | ||||||
|  |         { value: 'Account', label: 'Account' }, | ||||||
|  |         { value: 'User', label: 'User' }, | ||||||
|  |         { value: 'Event', label: 'Event' }, | ||||||
|  |         { value: 'Offering', label: 'Offering' }, | ||||||
|  |         { value: 'Zone', label: 'Zone' } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   created () { | ||||||
|  |     if (!('getUserKeys' in this.$store.getters.apis)) { | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |     api('getUserKeys', { id: this.$store.getters.userInfo.id }).then(json => { | ||||||
|  |       this.userkeys = json.getuserkeysresponse.userkeys | ||||||
|  |       if (this.userkeys && this.userkeys.secretkey) { | ||||||
|  |         this.showKeys = true | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     showApi (api) { | ||||||
|  |       this.selectedApi = api | ||||||
|  |       this.selectedParams = this.$store.getters.apis[api].params | ||||||
|  |         .sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)) | ||||||
|  |         .sort((a, b) => (a.required > b.required) ? -1 : ((b.required > a.required) ? 1 : 0)) | ||||||
|  |         .filter(value => Object.keys(value).length > 0) | ||||||
|  |       this.selectedResponse = this.$store.getters.apis[api].response.filter(value => Object.keys(value).length > 0) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user