Merge pull request #722 from 1Panel-dev/pr@main@feat_api_key
feat: 增加系统API_Keys
This commit is contained in:
commit
9f04b1f5f8
58
ui/src/api/system-api-key.ts
Normal file
58
ui/src/api/system-api-key.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
185
ui/src/layout/components/top-bar/avatar/APIKeyDialog.vue
Normal file
185
ui/src/layout/components/top-bar/avatar/APIKeyDialog.vue
Normal file
@ -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>
|
</div>
|
||||||
<el-dropdown-item class="border-t p-8" @click="openResetPassword">
|
<el-dropdown-item class="border-t p-8" @click="openResetPassword">
|
||||||
{{ $t("layout.topbar.avatar.resetPassword") }}
|
{{ $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>
|
||||||
<el-dropdown-item class="border-t" @click="openAbout"> {{ $t("layout.topbar.avatar.about") }} </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>
|
<el-dropdown-item class="border-t" @click="logout"> {{ $t("layout.topbar.avatar.logout") }} </el-dropdown-item>
|
||||||
@ -30,6 +33,7 @@
|
|||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
<ResetPassword ref="resetPasswordRef"></ResetPassword>
|
<ResetPassword ref="resetPasswordRef"></ResetPassword>
|
||||||
<AboutDialog ref="AboutDialogRef"></AboutDialog>
|
<AboutDialog ref="AboutDialogRef"></AboutDialog>
|
||||||
|
<APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef" />
|
||||||
<UserPwdDialog ref="UserPwdDialogRef" />
|
<UserPwdDialog ref="UserPwdDialogRef" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -39,16 +43,21 @@ import { useRouter } from 'vue-router'
|
|||||||
import ResetPassword from './ResetPassword.vue'
|
import ResetPassword from './ResetPassword.vue'
|
||||||
import AboutDialog from './AboutDialog.vue'
|
import AboutDialog from './AboutDialog.vue'
|
||||||
import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
|
import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
|
||||||
|
import APIKeyDialog from "./APIKeyDialog.vue";
|
||||||
const { user } = useStore()
|
const { user } = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const UserPwdDialogRef = ref()
|
const UserPwdDialogRef = ref()
|
||||||
const AboutDialogRef = ref()
|
const AboutDialogRef = ref()
|
||||||
|
const APIKeyDialogRef = ref()
|
||||||
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
|
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
|
||||||
|
|
||||||
const openAbout = () => {
|
const openAbout = () => {
|
||||||
AboutDialogRef.value?.open()
|
AboutDialogRef.value?.open()
|
||||||
}
|
}
|
||||||
|
function openAPIKeyDialog() {
|
||||||
|
APIKeyDialogRef.value.open()
|
||||||
|
}
|
||||||
|
|
||||||
const openResetPassword = () => {
|
const openResetPassword = () => {
|
||||||
resetPasswordRef.value?.open()
|
resetPasswordRef.value?.open()
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
breadcrumb: {
|
breadcrumb: {},
|
||||||
|
sidebar: {},
|
||||||
},
|
|
||||||
sidebar: {
|
|
||||||
|
|
||||||
},
|
|
||||||
topbar: {
|
topbar: {
|
||||||
github: "Project address",
|
github: "Project address",
|
||||||
wiki: "User manual",
|
wiki: "User manual",
|
||||||
@ -18,21 +14,23 @@ export default {
|
|||||||
resetPassword: "Change password",
|
resetPassword: "Change password",
|
||||||
about: "About",
|
about: "About",
|
||||||
logout: "Logout",
|
logout: "Logout",
|
||||||
version:"Version",
|
version: "Version",
|
||||||
dialog:{
|
apiKey: "API Key",
|
||||||
newPassword:"New password",
|
apiServiceAddress: "API Service Address",
|
||||||
|
dialog: {
|
||||||
|
newPassword: "New password",
|
||||||
enterPassword: "Please enter new password",
|
enterPassword: "Please enter new password",
|
||||||
confirmPassword: "Confirm password",
|
confirmPassword: "Confirm password",
|
||||||
passwordLength:"Password length should be between 6 and 20 characters",
|
passwordLength: "Password length should be between 6 and 20 characters",
|
||||||
passwordMismatch:"Passwords do not match",
|
passwordMismatch: "Passwords do not match",
|
||||||
useEmail:"Use email",
|
useEmail: "Use email",
|
||||||
enterEmail: "Please enter email",
|
enterEmail: "Please enter email",
|
||||||
enterVerificationCode: "Please enter verification code",
|
enterVerificationCode: "Please enter verification code",
|
||||||
getVerificationCode: "Get verification code",
|
getVerificationCode: "Get verification code",
|
||||||
verificationCodeSentSuccess:"Verification code sent successfully",
|
verificationCodeSentSuccess: "Verification code sent successfully",
|
||||||
resend:"Resend",
|
resend: "Resend",
|
||||||
cancel:"Cancel",
|
cancel: "Cancel",
|
||||||
save:"Save",
|
save: "Save",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -19,6 +19,8 @@ export default {
|
|||||||
about: "关于",
|
about: "关于",
|
||||||
logout: "退出",
|
logout: "退出",
|
||||||
version:"版本号",
|
version:"版本号",
|
||||||
|
apiKey: "API Key 管理",
|
||||||
|
apiServiceAddress: "API 服务地址",
|
||||||
dialog:{
|
dialog:{
|
||||||
newPassword:"新密码",
|
newPassword:"新密码",
|
||||||
enterPassword: "请输入修改密码",
|
enterPassword: "请输入修改密码",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user