Copilot commented on code in PR #13225: URL: https://github.com/apache/cloudstack/pull/13225#discussion_r3311029380
########## ui/src/components/view/ApiKeyPairsTab.vue: ########## @@ -0,0 +1,456 @@ +// 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> + <a-spin :spinning="fetchLoading"> + <a-button + v-if="'registerUserKeys' in $store.getters.apis" + type="dashed" + style="width: 100%; margin-bottom: 15px" + @click="onShowAddKeyPair()"> + <template #icon><plus-outlined /></template> + {{ $t('label.register.api.key') }} + </a-button> + <a-button + v-if="this.selectedRowKeys.length > 0 && ('deleteUserKeys' in $store.getters.apis)" + type="primary" + danger + style="width: 100%; margin-bottom: 15px" + @click="bulkActionConfirmation()"> + <template #icon><delete-outlined /></template> + {{ $t('label.action.bulk.delete.api.keys') }} + </a-button> + <a-table + size="small" + style="overflow-y: auto" + :columns="columns" + :dataSource="keypairs" + :rowKey="item => item.id" + :rowSelection="rowSelection()" + :pagination="false" > + <template #name="{ record }"> + <div> + <router-link :to="{ path: '/keypair/' + record.id }" > + {{ record.name }} + </router-link> + </div> + </template> + <template #apikey="{ record }"> + <strong> + <tooltip-button + tooltipPlacement="right" + :tooltip="$t('label.copy')" + icon="CopyOutlined" + type="dashed" + size="small" + @onClick="$message.success($t('label.copied.clipboard'))" + :copyResource="record.apikey" /> + </strong> + <div> + {{ record.apikey.substring(0, 20) }}... + </div> + </template> + + <template #secretkey="{ record }"> + <strong> + <tooltip-button + tooltipPlacement="right" + :tooltip="$t('label.copy')" + icon="CopyOutlined" + type="dashed" + size="small" + @onClick="$message.success($t('label.copied.clipboard'))" + :copyResource="record.secretkey" /> + </strong> + <div> + {{ record.secretkey.substring(0, 20) }}... + </div> + </template> + + <template #startdate="{ record }"> + <div> {{ $toLocaleDate(record.startdate) }} </div> + </template> + + <template #enddate="{ record }"> + <div> {{ $toLocaleDate(record.enddate)}} </div> + </template> + + <template #created="{ record }"> + <div> {{ $toLocaleDate(record.created) }} </div> + </template> + + </a-table> + <a-divider/> + <a-pagination + class="row-element pagination" + size="small" + :current="page" + :pageSize="pageSize" + :total="totalKeypairs" + :showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`" + :pageSizeOptions="['10', '20', '40', '80', '100']" + @change="changePage" + @showSizeChange="changePageSize" + showSizeChanger> + <template #buildOptionText="props"> + <span>{{ props.value }} / {{ $t('label.page') }}</span> + </template> + </a-pagination> + </a-spin> + <bulk-action-view + v-if="(showConfirmationAction || showGroupActionModal)" + :showConfirmationAction="showConfirmationAction" + :showGroupActionModal="showGroupActionModal" + :items="keypairs" + :selectedRowKeys="selectedRowKeys" + :selectedItems="selectedItems" + :columns="columns" + :selectedColumns="selectedColumns" + action="eraseKeypairs" + :loading="loading" + :message="bulkDeleteMessage" + @group-action="eraseKeypairs" + @handle-cancel="handleCancelBulk" + @close-modal="closeModalBulk" /> + <generate-api-key-pair + :showAddKeyPair="showAddKeyPair" + :resource="resource" + @fetch-data="fetchData" + @refresh-data="handleRefreshData" + @close-modal="closeModalAddKeyPair" /> + </div> +</template> +<script> +import { getAPI, postAPI } from '@/api' +import Status from '@/components/widgets/Status' +import TooltipButton from '@/components/widgets/TooltipButton' +import BulkActionView from '@/components/view/BulkActionView.vue' +import eventBus from '@/config/eventBus' +import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue' +import GenerateApiKeyPair from '@/views/iam/GenerateApiKeyPair.vue' + +export default { + name: 'ApiKeyPairsTab', + components: { + OwnershipSelection, + Status, + TooltipButton, + BulkActionView, + GenerateApiKeyPair + }, Review Comment: `OwnershipSelection` and `Status` are imported and registered as components but never used in the template. Please drop the unused imports/components to avoid lint warnings and reduce bundle size. ########## ui/src/components/view/ApiKeyPairsTab.vue: ########## @@ -0,0 +1,456 @@ +// 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> + <a-spin :spinning="fetchLoading"> + <a-button + v-if="'registerUserKeys' in $store.getters.apis" + type="dashed" + style="width: 100%; margin-bottom: 15px" + @click="onShowAddKeyPair()"> + <template #icon><plus-outlined /></template> + {{ $t('label.register.api.key') }} + </a-button> + <a-button + v-if="this.selectedRowKeys.length > 0 && ('deleteUserKeys' in $store.getters.apis)" + type="primary" + danger + style="width: 100%; margin-bottom: 15px" + @click="bulkActionConfirmation()"> + <template #icon><delete-outlined /></template> + {{ $t('label.action.bulk.delete.api.keys') }} + </a-button> + <a-table + size="small" + style="overflow-y: auto" + :columns="columns" + :dataSource="keypairs" + :rowKey="item => item.id" + :rowSelection="rowSelection()" + :pagination="false" > + <template #name="{ record }"> + <div> + <router-link :to="{ path: '/keypair/' + record.id }" > + {{ record.name }} + </router-link> + </div> + </template> + <template #apikey="{ record }"> + <strong> + <tooltip-button + tooltipPlacement="right" + :tooltip="$t('label.copy')" + icon="CopyOutlined" + type="dashed" + size="small" + @onClick="$message.success($t('label.copied.clipboard'))" + :copyResource="record.apikey" /> + </strong> + <div> + {{ record.apikey.substring(0, 20) }}... + </div> + </template> + + <template #secretkey="{ record }"> + <strong> + <tooltip-button + tooltipPlacement="right" + :tooltip="$t('label.copy')" + icon="CopyOutlined" + type="dashed" + size="small" + @onClick="$message.success($t('label.copied.clipboard'))" + :copyResource="record.secretkey" /> + </strong> + <div> + {{ record.secretkey.substring(0, 20) }}... + </div> + </template> + + <template #startdate="{ record }"> + <div> {{ $toLocaleDate(record.startdate) }} </div> + </template> + + <template #enddate="{ record }"> + <div> {{ $toLocaleDate(record.enddate)}} </div> + </template> + + <template #created="{ record }"> + <div> {{ $toLocaleDate(record.created) }} </div> + </template> + + </a-table> + <a-divider/> + <a-pagination + class="row-element pagination" + size="small" + :current="page" + :pageSize="pageSize" + :total="totalKeypairs" + :showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`" + :pageSizeOptions="['10', '20', '40', '80', '100']" + @change="changePage" + @showSizeChange="changePageSize" + showSizeChanger> + <template #buildOptionText="props"> + <span>{{ props.value }} / {{ $t('label.page') }}</span> + </template> + </a-pagination> + </a-spin> + <bulk-action-view + v-if="(showConfirmationAction || showGroupActionModal)" + :showConfirmationAction="showConfirmationAction" + :showGroupActionModal="showGroupActionModal" + :items="keypairs" + :selectedRowKeys="selectedRowKeys" + :selectedItems="selectedItems" + :columns="columns" + :selectedColumns="selectedColumns" + action="eraseKeypairs" + :loading="loading" + :message="bulkDeleteMessage" + @group-action="eraseKeypairs" + @handle-cancel="handleCancelBulk" + @close-modal="closeModalBulk" /> + <generate-api-key-pair + :showAddKeyPair="showAddKeyPair" + :resource="resource" + @fetch-data="fetchData" + @refresh-data="handleRefreshData" + @close-modal="closeModalAddKeyPair" /> + </div> +</template> +<script> +import { getAPI, postAPI } from '@/api' +import Status from '@/components/widgets/Status' +import TooltipButton from '@/components/widgets/TooltipButton' +import BulkActionView from '@/components/view/BulkActionView.vue' +import eventBus from '@/config/eventBus' +import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue' +import GenerateApiKeyPair from '@/views/iam/GenerateApiKeyPair.vue' + +export default { + name: 'ApiKeyPairsTab', + components: { + OwnershipSelection, + Status, + TooltipButton, + BulkActionView, + GenerateApiKeyPair + }, + props: { + resource: { + type: Object, + required: true + }, + loading: { + type: Boolean, + default: false + } + }, + data () { + return { + fetchLoading: false, + keypairs: [], + page: 1, + pageSize: 10, + totalKeypairs: 0, + selectedRowKeys: [], + selectedItems: [], + selectedColumns: [], + filterColumns: ['Action'], + showConfirmationAction: false, + showAddKeyPair: false, + showGroupActionModal: false, + bulkDeleteMessage: { + title: this.$t('label.action.bulk.delete.api.keys'), + confirmMessage: this.$t('label.confirm.delete.api.keys') + }, + columns: [ + { + title: this.$t('label.name'), + dataIndex: 'name', + slots: { customRender: 'name' } + }, + { + title: this.$t('label.apikey'), + dataIndex: 'apikey', + slots: { customRender: 'apikey' } + }, + { + title: this.$t('label.secretkey'), + dataIndex: 'secretkey', + slots: { customRender: 'secretkey' } + }, + { + title: this.$t('label.start.date'), + dataIndex: 'startdate', + slots: { customRender: 'startdate' } + }, + { + title: this.$t('label.end.date'), + dataIndex: 'enddate', + slots: { customRender: 'enddate' } + }, + { + title: this.$t('label.created'), + dataIndex: 'created', + slots: { customRender: 'created' } + } + ] + } + }, + created () { + this.fetchData() + }, + watch: { + resource: { + deep: true, + handler (newItem) { + if (!newItem || !newItem.id) { + return + } + this.fetchData() + } + } + }, + inject: ['parentFetchData'], + methods: { + fetchData () { + const params = { + listall: true, + page: this.page, + pagesize: this.pageSize, + userid: this.resource.id + } + this.fetchLoading = true + getAPI('listUserKeys', params).then(json => { + this.totalKeypairs = json.listuserkeysresponse.count || 0 + this.keypairs = json.listuserkeysresponse.userapikey || [] + }).finally(() => { + this.fetchLoading = false + }) + }, + setSelection (selection) { + this.selectedRowKeys = selection + this.$emit('selection-change', this.selectedRowKeys) + this.selectedItems = (this.keypairs.filter(function (item) { + return selection.indexOf(item.id) !== -1 + })) + }, + changePage (page, pageSize) { + this.page = page + this.pageSize = pageSize + this.fetchData() + }, + changePageSize (currentPage, pageSize) { + this.page = currentPage + this.pageSize = pageSize + this.fetchData() + }, + onShowAddKeyPair () { + this.showAddKeyPair = true + }, + eraseKeypairs () { + this.selectedColumns.splice(0, 0, { + dataIndex: 'status', + title: this.$t('label.operation.status'), + slots: { customRender: 'status' }, + filters: [ + { text: 'In Progress', value: 'InProgress' }, + { text: 'Success', value: 'success' }, + { text: 'Failed', value: 'failed' } + ] + }) + if (this.selectedRowKeys.length > 0) { + this.showGroupActionModal = true + } + this.deleteKeypairs(this.selectedItems) + }, + async deleteKeypairs (keypairs) { + if (!keypairs || keypairs.length === 0) { + this.fetchLoading = false + return + } + + this.fetchLoading = true + try { + await Promise.all(keypairs.map(async keypair => { + try { + const jobId = await this.deleteKeyPair({ + keypairid: keypair.id + }) + await this.$pollJob({ + jobId, + action: { + isFetchData: false + }, + successMethod: () => { + console.log('success method') Review Comment: Remove the leftover `console.log('success method')` from the bulk-delete poll success handler; it will spam browser devtools during normal usage. ########## ui/public/locales/en.json: ########## @@ -3943,6 +3963,7 @@ "message.success.create.keypair": "Successfully created SSH key pair", "message.success.create.kubernetes.cluster": "Successfully created Kubernetes Cluster", "message.success.create.l2.network": "Successfully created L2 Network", +"message.success.create.password": "Successfully created API key pair for user", Review Comment: The `message.success.create.password` translation key added here appears unrelated to API key pairs and is not referenced anywhere in the UI. If this is meant for API key pair creation, rename it to the correct/used key (or remove it) to avoid accumulating unused i18n entries. ########## ui/src/views/AutogenView.vue: ########## @@ -1132,6 +1132,10 @@ export default { params.name = this.$route.params.id } } + if (['listUserKeys'].includes(this.apiName)) { + delete params.listall Review Comment: In the `listUserKeys` special-case, `params.id` is still set earlier from the route param. `listUserKeys` does not define an `id` parameter, so this results in an “Unknown parameters: id” warning on the management server logs. Remove `params.id` here (or avoid setting it for this API) when mapping the route param to `keypairid`. ########## ui/src/components/view/ApiKeyPairsTab.vue: ########## @@ -0,0 +1,456 @@ +// 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> + <a-spin :spinning="fetchLoading"> + <a-button + v-if="'registerUserKeys' in $store.getters.apis" + type="dashed" + style="width: 100%; margin-bottom: 15px" + @click="onShowAddKeyPair()"> + <template #icon><plus-outlined /></template> + {{ $t('label.register.api.key') }} + </a-button> + <a-button + v-if="this.selectedRowKeys.length > 0 && ('deleteUserKeys' in $store.getters.apis)" + type="primary" + danger + style="width: 100%; margin-bottom: 15px" + @click="bulkActionConfirmation()"> + <template #icon><delete-outlined /></template> + {{ $t('label.action.bulk.delete.api.keys') }} + </a-button> + <a-table + size="small" + style="overflow-y: auto" + :columns="columns" + :dataSource="keypairs" + :rowKey="item => item.id" + :rowSelection="rowSelection()" + :pagination="false" > + <template #name="{ record }"> + <div> + <router-link :to="{ path: '/keypair/' + record.id }" > + {{ record.name }} + </router-link> + </div> + </template> + <template #apikey="{ record }"> + <strong> + <tooltip-button + tooltipPlacement="right" + :tooltip="$t('label.copy')" + icon="CopyOutlined" + type="dashed" + size="small" + @onClick="$message.success($t('label.copied.clipboard'))" + :copyResource="record.apikey" /> + </strong> + <div> + {{ record.apikey.substring(0, 20) }}... + </div> + </template> + + <template #secretkey="{ record }"> + <strong> + <tooltip-button + tooltipPlacement="right" + :tooltip="$t('label.copy')" + icon="CopyOutlined" + type="dashed" + size="small" + @onClick="$message.success($t('label.copied.clipboard'))" + :copyResource="record.secretkey" /> + </strong> + <div> + {{ record.secretkey.substring(0, 20) }}... + </div> + </template> + + <template #startdate="{ record }"> + <div> {{ $toLocaleDate(record.startdate) }} </div> + </template> + + <template #enddate="{ record }"> + <div> {{ $toLocaleDate(record.enddate)}} </div> + </template> + + <template #created="{ record }"> + <div> {{ $toLocaleDate(record.created) }} </div> + </template> + + </a-table> + <a-divider/> + <a-pagination + class="row-element pagination" + size="small" + :current="page" + :pageSize="pageSize" + :total="totalKeypairs" + :showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`" + :pageSizeOptions="['10', '20', '40', '80', '100']" + @change="changePage" + @showSizeChange="changePageSize" + showSizeChanger> + <template #buildOptionText="props"> + <span>{{ props.value }} / {{ $t('label.page') }}</span> + </template> + </a-pagination> + </a-spin> + <bulk-action-view + v-if="(showConfirmationAction || showGroupActionModal)" + :showConfirmationAction="showConfirmationAction" + :showGroupActionModal="showGroupActionModal" + :items="keypairs" + :selectedRowKeys="selectedRowKeys" + :selectedItems="selectedItems" + :columns="columns" + :selectedColumns="selectedColumns" + action="eraseKeypairs" + :loading="loading" + :message="bulkDeleteMessage" + @group-action="eraseKeypairs" + @handle-cancel="handleCancelBulk" + @close-modal="closeModalBulk" /> + <generate-api-key-pair + :showAddKeyPair="showAddKeyPair" + :resource="resource" + @fetch-data="fetchData" + @refresh-data="handleRefreshData" + @close-modal="closeModalAddKeyPair" /> + </div> +</template> +<script> +import { getAPI, postAPI } from '@/api' +import Status from '@/components/widgets/Status' +import TooltipButton from '@/components/widgets/TooltipButton' +import BulkActionView from '@/components/view/BulkActionView.vue' +import eventBus from '@/config/eventBus' +import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue' +import GenerateApiKeyPair from '@/views/iam/GenerateApiKeyPair.vue' + +export default { + name: 'ApiKeyPairsTab', + components: { + OwnershipSelection, + Status, + TooltipButton, + BulkActionView, + GenerateApiKeyPair + }, + props: { + resource: { + type: Object, + required: true + }, + loading: { + type: Boolean, + default: false + } + }, + data () { + return { + fetchLoading: false, + keypairs: [], + page: 1, + pageSize: 10, + totalKeypairs: 0, + selectedRowKeys: [], + selectedItems: [], + selectedColumns: [], + filterColumns: ['Action'], + showConfirmationAction: false, + showAddKeyPair: false, + showGroupActionModal: false, + bulkDeleteMessage: { + title: this.$t('label.action.bulk.delete.api.keys'), + confirmMessage: this.$t('label.confirm.delete.api.keys') + }, + columns: [ + { + title: this.$t('label.name'), + dataIndex: 'name', + slots: { customRender: 'name' } + }, + { + title: this.$t('label.apikey'), + dataIndex: 'apikey', + slots: { customRender: 'apikey' } + }, + { + title: this.$t('label.secretkey'), + dataIndex: 'secretkey', + slots: { customRender: 'secretkey' } + }, + { + title: this.$t('label.start.date'), + dataIndex: 'startdate', + slots: { customRender: 'startdate' } + }, + { + title: this.$t('label.end.date'), + dataIndex: 'enddate', + slots: { customRender: 'enddate' } + }, + { + title: this.$t('label.created'), + dataIndex: 'created', + slots: { customRender: 'created' } + } + ] + } + }, + created () { + this.fetchData() + }, + watch: { + resource: { + deep: true, + handler (newItem) { + if (!newItem || !newItem.id) { + return + } + this.fetchData() + } + } + }, + inject: ['parentFetchData'], + methods: { + fetchData () { + const params = { + listall: true, + page: this.page, + pagesize: this.pageSize, + userid: this.resource.id + } + this.fetchLoading = true + getAPI('listUserKeys', params).then(json => { + this.totalKeypairs = json.listuserkeysresponse.count || 0 + this.keypairs = json.listuserkeysresponse.userapikey || [] + }).finally(() => { + this.fetchLoading = false + }) + }, + setSelection (selection) { + this.selectedRowKeys = selection + this.$emit('selection-change', this.selectedRowKeys) + this.selectedItems = (this.keypairs.filter(function (item) { + return selection.indexOf(item.id) !== -1 + })) + }, + changePage (page, pageSize) { + this.page = page + this.pageSize = pageSize + this.fetchData() + }, + changePageSize (currentPage, pageSize) { + this.page = currentPage + this.pageSize = pageSize + this.fetchData() + }, + onShowAddKeyPair () { + this.showAddKeyPair = true + }, + eraseKeypairs () { + this.selectedColumns.splice(0, 0, { + dataIndex: 'status', + title: this.$t('label.operation.status'), + slots: { customRender: 'status' }, + filters: [ + { text: 'In Progress', value: 'InProgress' }, + { text: 'Success', value: 'success' }, + { text: 'Failed', value: 'failed' } + ] + }) Review Comment: `eraseKeypairs()` always prepends the status column to `selectedColumns`. If the user re-runs the bulk delete in the same session, this will add duplicate status columns. Guard against re-adding the column (e.g., check whether `selectedColumns` already contains `dataIndex: 'status'` before splicing). ########## api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterUserKeysCmd.java: ########## @@ -138,6 +138,9 @@ public List<Map<String, Object>> getRules() { String description = detail.get(ApiConstants.DESCRIPTION); if (StringUtils.isNotEmpty(description)) { + if (description.length() > 255) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Description cannot be longer than 255 characters."); Review Comment: This error message is ambiguous because the API also has a top-level `description` parameter for the key pair. Consider clarifying that this limit applies to the rule's description (e.g., “Rule description cannot be longer than 255 characters”). ########## ui/src/views/iam/ApiKeyPairPermissionTable.vue: ########## @@ -0,0 +1,520 @@ +// 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> + <loading-outlined v-if="loadingTable" class="main-loading-spinner" /> + <div v-else> + <div v-if="!disabled" class="rules-list ant-list ant-list-bordered"> + <div class="rules-table-item ant-list-item"> + <div class="rules-table__col rules-table__col--grab" /> + <div class="rules-table__col rules-table__col--rule rules-table__col--new"> + <a-auto-complete + :key="autocompleteKey" + v-focus="true" + :filterOption="filterOption" + :options="apis" + v-model:value="newRule" + :placeholder="$t('label.rule')" + :class="{'rule-dropdown-error' : newRuleSelectError}" /> Review Comment: `newRuleSelectError` is only used for CSS binding and is never updated anywhere, so the error styling can never activate. Either implement the error state updates (e.g., when the rule is empty/duplicate/invalid) or remove the unused state+CSS to keep the component consistent. ########## ui/src/views/iam/GenerateApiKeyPair.vue: ########## @@ -0,0 +1,229 @@ +// 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 class="form-layout" v-ctrl-enter="handleSubmit"> + <a-modal + v-if="showAddKeyPair" + :visible="showAddKeyPair" + :closable="true" + :maskClosable="false" + :okText="$t('label.ok')" + :cancelText="$t('label.cancel')" + style="top: 20px;" + width="50vw" + @cancel="closeModal" + @ok="handleSubmit" + :ok-button-props="{props: { type: 'default' } }" + :cancel-button-props="{props: { type: 'primary' } }" + centered> + <template #title> + {{ $t('label.action.create.api.key') }} + </template> + <a-spin :spinning="loading"> + <a-form + :ref="formRef" + :model="form" + layout="vertical" + @finish="handleSubmit"> + <a-alert + style="margin-bottom: 10px; " + :message="$t('message.note.about.keypair.permissions.title')" + :description="$t('message.note.about.keypair.permissions.body')" + type="info" + show-icon + /> + <a-form-item name="name" ref="name"> + <template #label> + <tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/> + </template> + <a-input + v-focus="true" + :placeholder="$t('label.apikeypair.name')" + v-model:value="form.name" /> + </a-form-item> + <a-form-item name="description" ref="description"> + <template #label> + <tooltip-label :title="$t('label.description')" :tooltip="apiParams.description.description"/> + </template> + <a-input + v-model:value="form.description" + :placeholder="$t('label.apikeypair.description')" /> + </a-form-item> + <a-row> + <a-form-item ref="startDate" name="startDate"> + <template #label> + <tooltip-label :title="$t('label.start.date')" :tooltip="apiParams.startdate.description"/> + </template> + <a-date-picker + v-model:value="form.startDate" + :disabled-date="disabledStartDate" + show-time + /> + </a-form-item> + <a-form-item ref="endDate" name="endDate" style="margin: 0 8px"> + <template #label> + <tooltip-label :title="$t('label.end.date')" :tooltip="apiParams.enddate.description"/> + </template> + <a-date-picker + :disabled-date="disabledEndDate" + v-model:value="form.endDate" + show-time /> + </a-form-item> + </a-row> + <a-form-item> + <template #label> + <tooltip-label :title="$t('label.rules')" :tooltip="apiParams.rules.description"/> + </template> + <api-key-pair-permission-table + :resource="resource" + @update-rules="updateRules"/> + </a-form-item> + </a-form> + </a-spin> + </a-modal> + </div> +</template> + +<script> +import { ref, reactive, toRaw } from 'vue' +import { postAPI } from '@/api' +import TooltipLabel from '@/components/widgets/TooltipLabel' +import ApiKeyPairPermissionTable from '@/views/iam/ApiKeyPairPermissionTable.vue' +import { dayjs, parseDayJsObject } from '@/utils/date' + +export default { + name: 'GenerateApiKeyPair', + components: { + TooltipLabel, + ApiKeyPairPermissionTable + }, + props: { + showAddKeyPair: { + type: Boolean, + default: false + }, + resource: { + type: Object, + required: true + } + }, + data () { + return { + rules: [], + loading: false + } + }, + beforeCreate () { + this.apiParams = this.$getApiParams('registerUserKeys') + }, + created () { + this.initForm() + }, + methods: { + initForm () { + this.formRef = ref() + this.form = reactive({}) + }, + isValidValueForKey (obj, key) { + return key in obj && obj[key] != null + }, Review Comment: `isValidValueForKey` is declared but never used. Please remove it (or use it) to avoid dead code in this component. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
