mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: 增加系统API_Keys
--story=1015434 --user=王孝刚 【X-pack】系统API Key管理及接口文档 https://www.tapd.cn/57709429/s/1545690
This commit is contained in:
parent
60e65a8b17
commit
9ad82d5369
|
|
@ -0,0 +1,58 @@
|
|||
import {Result} from '@/request/Result'
|
||||
import {get, post, del, put} from '@/request/index'
|
||||
|
||||
import {type Ref} from 'vue'
|
||||
|
||||
const prefix = '/system/api_key'
|
||||
|
||||
/**
|
||||
* API_KEY列表
|
||||
*/
|
||||
const getAPIKey: (loading?: Ref<boolean>) => Promise<Result<any>> = () => {
|
||||
return get(`${prefix}/`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增API_KEY
|
||||
*/
|
||||
const postAPIKey: (loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
loading
|
||||
) => {
|
||||
return post(`${prefix}/`, {}, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除API_KEY
|
||||
* @param 参数 application_id api_key_id
|
||||
*/
|
||||
const delAPIKey: (
|
||||
api_key_id: String,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (api_key_id, loading) => {
|
||||
return del(`${prefix}/${api_key_id}/`, undefined, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改API_KEY
|
||||
* data {
|
||||
* is_active: boolean
|
||||
* }
|
||||
* @param api_key_id
|
||||
* @param data
|
||||
* @param loading
|
||||
*/
|
||||
const putAPIKey: (
|
||||
api_key_id: String,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (api_key_id, data, loading) => {
|
||||
return put(`${prefix}/${api_key_id}/`, data, undefined, loading)
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getAPIKey,
|
||||
postAPIKey,
|
||||
delAPIKey,
|
||||
putAPIKey
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
<template>
|
||||
<el-dialog :title="$t('layout.topbar.avatar.apiKey')" v-model="dialogVisible" width="800">
|
||||
<div class="mb-16 grey-background">
|
||||
<el-text type="info">{{ $t('layout.topbar.avatar.apiServiceAddress') }}</el-text>
|
||||
<p style="margin-top: 10px">
|
||||
<a target="_blank" :href="apiUrl" class="vertical-middle lighter break-all" style="color: black;">
|
||||
{{ apiUrl }}
|
||||
</a>
|
||||
<el-button type="primary" text @click="copyClick(apiUrl)">
|
||||
<AppIcon iconName="app-copy"></AppIcon>
|
||||
</el-button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<el-button type="primary" class="mb-16" @click="createApiKey">
|
||||
{{ $t('views.applicationOverview.appInfo.APIKeyDialog.creatApiKey') }}
|
||||
</el-button>
|
||||
<el-table :data="apiKey" class="mb-16" :loading="loading">
|
||||
<el-table-column prop="secret_key" label="API Key">
|
||||
<template #default="{ row }">
|
||||
<span class="vertical-middle lighter break-all">
|
||||
{{ row.secret_key }}
|
||||
</span>
|
||||
<el-button type="primary" text @click="copyClick(row.secret_key)">
|
||||
<AppIcon iconName="app-copy"></AppIcon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.status')"
|
||||
width="60"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div @click.stop>
|
||||
<el-switch size="small" v-model="row.is_active" @change="changeState($event, row)"/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.creationDate')"
|
||||
width="170"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.operations')"
|
||||
align="left"
|
||||
width="80"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="mr-4">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.settings')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="primary" text @click.stop="settingApiKey(row)">
|
||||
<el-icon><Setting/></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="primary" text @click="deleteApiKey(row)">
|
||||
<el-icon>
|
||||
<Delete/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<SettingAPIKeyDialog ref="SettingAPIKeyDialogRef" @refresh="refresh"/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ref, watch} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
import {copyClick} from '@/utils/clipboard'
|
||||
import overviewApi from '@/api/system-api-key'
|
||||
import {datetimeFormat} from '@/utils/time'
|
||||
import {MsgSuccess, MsgConfirm} from '@/utils/message'
|
||||
import {t} from '@/locales'
|
||||
import SettingAPIKeyDialog from "./SettingAPIKeyDialog.vue";
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: {id}
|
||||
} = route
|
||||
|
||||
const props = defineProps({
|
||||
userId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['addData'])
|
||||
|
||||
const apiUrl = window.location.origin + '/doc'
|
||||
const SettingAPIKeyDialogRef = ref()
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const loading = ref(false)
|
||||
const apiKey = ref<any>(null)
|
||||
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
apiKey.value = null
|
||||
}
|
||||
})
|
||||
|
||||
function settingApiKey(row: any) {
|
||||
SettingAPIKeyDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function deleteApiKey(row: any) {
|
||||
MsgConfirm(
|
||||
// @ts-ignore
|
||||
`${t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm1')}: ${row.secret_key}?`,
|
||||
t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm2'),
|
||||
{
|
||||
confirmButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.confirmDelete'),
|
||||
cancelButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
overviewApi.delAPIKey(row.id, loading).then(() => {
|
||||
MsgSuccess(t('views.applicationOverview.appInfo.APIKeyDialog.deleteSuccess'))
|
||||
getApiKeyList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
function changeState(bool: Boolean, row: any) {
|
||||
const obj = {
|
||||
is_active: bool
|
||||
}
|
||||
const str = bool
|
||||
? t('views.applicationOverview.appInfo.APIKeyDialog.enabledSuccess')
|
||||
: t('views.applicationOverview.appInfo.APIKeyDialog.disabledSuccess')
|
||||
overviewApi.putAPIKey(row.id, obj, loading).then((res) => {
|
||||
MsgSuccess(str)
|
||||
getApiKeyList()
|
||||
})
|
||||
}
|
||||
|
||||
function createApiKey() {
|
||||
overviewApi.postAPIKey(loading).then((res) => {
|
||||
getApiKeyList()
|
||||
})
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
getApiKeyList()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function getApiKeyList() {
|
||||
overviewApi.getAPIKey().then((res) => {
|
||||
apiKey.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
getApiKeyList()
|
||||
}
|
||||
|
||||
defineExpose({open})
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.grey-background {
|
||||
background-color: #f2f2f2; /* 灰色背景 */
|
||||
padding: 20px; /* 内边距 */
|
||||
border-radius: 8px; /* 可选:添加圆角 */
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<el-dialog :title="$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.dialogTitle')" v-model="dialogVisible">
|
||||
<el-form label-position="top" ref="settingFormRef" :model="form">
|
||||
<el-form-item :label="$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.allowCrossDomainLabel')"
|
||||
@click.prevent>
|
||||
<el-switch size="small" v-model="form.allow_cross_domain"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.cross_domain_list"
|
||||
:placeholder="$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.crossDomainPlaceholder')"
|
||||
:rows="10"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button
|
||||
@click.prevent="dialogVisible = false">{{
|
||||
$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.cancelButtonText')
|
||||
}}</el-button>
|
||||
<el-button type="primary" @click="submit(settingFormRef)" :loading="loading">
|
||||
{{ $t('views.applicationOverview.appInfo.SettingAPIKeyDialog.saveButtonText') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ref, watch} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
import type {FormInstance} from 'element-plus'
|
||||
import overviewApi from '@/api/system-api-key'
|
||||
import {MsgSuccess} from '@/utils/message'
|
||||
import {t} from '@/locales'
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: {id}
|
||||
} = route
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const settingFormRef = ref()
|
||||
const form = ref<any>({
|
||||
allow_cross_domain: false,
|
||||
cross_domain_list: ''
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const APIKeyId = ref('')
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
allow_cross_domain: false,
|
||||
cross_domain_list: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const open = (data: any) => {
|
||||
APIKeyId.value = data.id
|
||||
form.value.allow_cross_domain = data.allow_cross_domain
|
||||
form.value.cross_domain_list = data.cross_domain_list?.length
|
||||
? data.cross_domain_list?.join('\n')
|
||||
: ''
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
const obj = {
|
||||
allow_cross_domain: form.value.allow_cross_domain,
|
||||
cross_domain_list: form.value.cross_domain_list
|
||||
? form.value.cross_domain_list.split('\n').filter(function (item: string) {
|
||||
return item !== ''
|
||||
})
|
||||
: []
|
||||
}
|
||||
overviewApi.putAPIKey(APIKeyId.value, obj, loading).then((res) => {
|
||||
emit('refresh')
|
||||
//@ts-ignore
|
||||
MsgSuccess(t('views.applicationOverview.appInfo.SettingAPIKeyDialog.successMessage'))
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({open})
|
||||
</script>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
@ -22,6 +22,9 @@
|
|||
</div>
|
||||
<el-dropdown-item class="border-t p-8" @click="openResetPassword">
|
||||
{{ $t("layout.topbar.avatar.resetPassword") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item class="border-t p-8" @click="openAPIKeyDialog">
|
||||
{{ $t("layout.topbar.avatar.apiKey") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item class="border-t" @click="openAbout"> {{ $t("layout.topbar.avatar.about") }} </el-dropdown-item>
|
||||
<el-dropdown-item class="border-t" @click="logout"> {{ $t("layout.topbar.avatar.logout") }} </el-dropdown-item>
|
||||
|
|
@ -30,6 +33,7 @@
|
|||
</el-dropdown>
|
||||
<ResetPassword ref="resetPasswordRef"></ResetPassword>
|
||||
<AboutDialog ref="AboutDialogRef"></AboutDialog>
|
||||
<APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef" />
|
||||
<UserPwdDialog ref="UserPwdDialogRef" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -39,16 +43,21 @@ import { useRouter } from 'vue-router'
|
|||
import ResetPassword from './ResetPassword.vue'
|
||||
import AboutDialog from './AboutDialog.vue'
|
||||
import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
|
||||
import APIKeyDialog from "./APIKeyDialog.vue";
|
||||
const { user } = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const UserPwdDialogRef = ref()
|
||||
const AboutDialogRef = ref()
|
||||
const APIKeyDialogRef = ref()
|
||||
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
|
||||
|
||||
const openAbout = () => {
|
||||
AboutDialogRef.value?.open()
|
||||
}
|
||||
function openAPIKeyDialog() {
|
||||
APIKeyDialogRef.value.open()
|
||||
}
|
||||
|
||||
const openResetPassword = () => {
|
||||
resetPasswordRef.value?.open()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
export default {
|
||||
breadcrumb: {
|
||||
|
||||
},
|
||||
sidebar: {
|
||||
|
||||
},
|
||||
breadcrumb: {},
|
||||
sidebar: {},
|
||||
topbar: {
|
||||
github: "Project address",
|
||||
wiki: "User manual",
|
||||
|
|
@ -18,21 +14,23 @@ export default {
|
|||
resetPassword: "Change password",
|
||||
about: "About",
|
||||
logout: "Logout",
|
||||
version:"Version",
|
||||
dialog:{
|
||||
newPassword:"New password",
|
||||
version: "Version",
|
||||
apiKey: "API Key",
|
||||
apiServiceAddress: "API Service Address",
|
||||
dialog: {
|
||||
newPassword: "New password",
|
||||
enterPassword: "Please enter new password",
|
||||
confirmPassword: "Confirm password",
|
||||
passwordLength:"Password length should be between 6 and 20 characters",
|
||||
passwordMismatch:"Passwords do not match",
|
||||
useEmail:"Use email",
|
||||
passwordLength: "Password length should be between 6 and 20 characters",
|
||||
passwordMismatch: "Passwords do not match",
|
||||
useEmail: "Use email",
|
||||
enterEmail: "Please enter email",
|
||||
enterVerificationCode: "Please enter verification code",
|
||||
getVerificationCode: "Get verification code",
|
||||
verificationCodeSentSuccess:"Verification code sent successfully",
|
||||
resend:"Resend",
|
||||
cancel:"Cancel",
|
||||
save:"Save",
|
||||
verificationCodeSentSuccess: "Verification code sent successfully",
|
||||
resend: "Resend",
|
||||
cancel: "Cancel",
|
||||
save: "Save",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export default {
|
|||
about: "关于",
|
||||
logout: "退出",
|
||||
version:"版本号",
|
||||
apiKey: "API Key 管理",
|
||||
apiServiceAddress: "API 服务地址",
|
||||
dialog:{
|
||||
newPassword:"新密码",
|
||||
enterPassword: "请输入修改密码",
|
||||
|
|
|
|||
Loading…
Reference in New Issue