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": [],
|
||||
"apidocs": true,
|
||||
"basicZoneEnabled": true,
|
||||
"multipleServer": false,
|
||||
"allowSettingTheme": true,
|
||||
|
||||
@ -348,6 +348,9 @@
|
||||
"label.annotation.everyone": "Visible to everyone",
|
||||
"label.anti.affinity": "Anti-affinity",
|
||||
"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.apikey": "API key",
|
||||
"label.app.cookie": "AppCookie",
|
||||
@ -1796,6 +1799,7 @@
|
||||
"label.replace.acl": "Replace ACL",
|
||||
"label.replace.acl.list": "Replace ACL list",
|
||||
"label.report.bug": "Ask a question or Report an issue",
|
||||
"label.request": "Request",
|
||||
"label.required": "Required",
|
||||
"label.requireshvm": "HVM",
|
||||
"label.requiresupgrade": "Requires upgrade",
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
import { UserLayout, BasicLayout, RouteView } from '@/layouts'
|
||||
import AutogenView from '@/views/AutogenView.vue'
|
||||
import IFramePlugin from '@/views/plugins/IFramePlugin.vue'
|
||||
import ApiDocsPlugin from '@/views/plugins/ApiDocsPlugin.vue'
|
||||
|
||||
import { shallowRef } from 'vue'
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -61,6 +61,7 @@ import {
|
||||
Tree,
|
||||
Calendar,
|
||||
Slider,
|
||||
Result,
|
||||
AutoComplete,
|
||||
Collapse,
|
||||
Space,
|
||||
@ -133,5 +134,6 @@ export default {
|
||||
app.use(Descriptions)
|
||||
app.use(Space)
|
||||
app.use(Statistic)
|
||||
app.use(Result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,7 +314,10 @@ const user = {
|
||||
const apiName = api.name
|
||||
apis[apiName] = {
|
||||
params: api.params,
|
||||
response: api.response
|
||||
response: api.response,
|
||||
isasync: api.isasync,
|
||||
since: api.since,
|
||||
description: api.description
|
||||
}
|
||||
}
|
||||
commit('SET_APIS', apis)
|
||||
|
||||
@ -471,6 +471,10 @@ a {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.ant-list-item.selected-item {
|
||||
background-color: @primary-color-light;
|
||||
}
|
||||
|
||||
.ant-select-arrow .anticon {
|
||||
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