mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat: Add API Key Page
This commit is contained in:
committed by
fit2cloud-chenyw
parent
2e4789bd57
commit
1780750606
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.98985 12.3334C10.1485 12.3334 12.1472 10.9603 14 7.99138C12.1948 5.03308 10.2007 3.66671 7.98985 3.66671C5.77975 3.66671 3.79281 5.03246 2 7.99136C3.84044 10.9609 5.83197 12.3334 7.98985 12.3334ZM1.00162 8.5934C0.786957 8.228 0.785505 7.77372 0.995211 7.40545C1.00268 7.39233 1.00984 7.37982 1.01663 7.36805C2.95201 4.0116 5.27617 2.33337 7.98914 2.33337C10.7132 2.33337 13.0517 4.02532 15.0048 7.40921C15.0106 7.41934 15.0168 7.43006 15.0232 7.44126C15.2207 7.78726 15.2192 8.21438 15.0171 8.55775C13.0126 11.9637 10.6699 13.6667 7.98914 13.6667C5.31775 13.6667 2.98858 11.9756 1.00162 8.5934ZM8 10.6667C6.52724 10.6667 5.33333 9.4728 5.33333 8.00004C5.33333 6.52728 6.52724 5.33337 8 5.33337C9.47276 5.33337 10.6667 6.52728 10.6667 8.00004C10.6667 9.4728 9.47276 10.6667 8 10.6667ZM8 9.33337C8.73637 9.33337 9.33333 8.73642 9.33333 8.00004C9.33333 7.26366 8.73637 6.66671 8 6.66671C7.26362 6.66671 6.66666 7.26366 6.66666 8.00004C6.66666 8.73642 7.26362 9.33337 8 9.33337Z" fill="#1F2329"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.28326 0.387285C8.10024 0.33382 7.90574 0.333795 7.72271 0.387213L2.05322 2.0418C1.62663 2.1663 1.33337 2.55737 1.33337 3.00175V6.34282C1.33337 10.367 3.88351 13.9439 7.67689 15.2607C7.88662 15.3335 8.11525 15.3335 8.32497 15.2607C12.1174 13.9438 14.6667 10.3677 14.6667 6.34448V3.00169C14.6667 2.5574 14.3736 2.16638 13.9471 2.0418L8.28326 0.387285ZM2.66671 3.25171L8.00282 1.69442L13.3334 3.25158V6.34448C13.3334 9.75911 11.1948 12.7988 8.00088 13.9609C4.80601 12.7988 2.66671 9.75836 2.66671 6.34282V3.25171Z" fill="#646A73"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 8C6.89533 8 6 7.10333 6 6C6 4.89667 6.89533 4 8 4C9.10467 4 10 4.89667 10 6C10 7.10333 9.10467 8 8 8ZM8 6.66667C8.36833 6.66667 8.66667 6.36667 8.66667 6C8.66667 5.63333 8.36833 5.33333 8 5.33333C7.63167 5.33333 7.33333 5.63333 7.33333 6C7.33333 6.36667 7.63167 6.66667 8 6.66667Z" fill="#646A73"/>
|
||||
<path d="M8.66671 11.6735C8.66671 11.8538 8.51747 12 8.33337 12H7.66671C7.48261 12 7.33337 11.8538 7.33337 11.6735L7.33337 6.9932C7.33337 6.81286 7.48261 6.66667 7.66671 6.66667H8.33337C8.51747 6.66667 8.66671 6.81286 8.66671 6.9932V11.6735Z" fill="#646A73"/>
|
||||
<path d="M8.28571 10.0001C8.12792 10.0001 8 9.85084 8 9.66675V9.00008C8 8.81599 8.12792 8.66675 8.28571 8.66675H9.71429C9.87208 8.66675 10 8.81599 10 9.00008V9.66675C10 9.85084 9.87208 10.0001 9.71429 10.0001H8.28571Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,357 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import icon_warning_filled from '@/assets/svg/icon_info_colorful.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import icon_visible_outlined_blod from '@/assets/embedded/icon_visible_outlined_blod.svg'
|
||||
import icon_copy_outlined from '@/assets/svg/icon_copy_outlined.svg'
|
||||
import IconOpeDelete from '@/assets/svg/icon_delete.svg'
|
||||
import icon_invisible_outlined from '@/assets/embedded/icon_invisible_outlined.svg'
|
||||
import icon_visible_outlined from '@/assets/embedded/icon_visible_outlined.svg'
|
||||
import { formatTimestamp } from '@/utils/date'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
const { t } = useI18n()
|
||||
|
||||
const limitCount = ref(5)
|
||||
const limitValid = ref(true)
|
||||
|
||||
const triggerLimit = computed(() => {
|
||||
return limitValid.value && state.tableData.length >= limitCount.value
|
||||
})
|
||||
const state = reactive({
|
||||
tableData: [] as any,
|
||||
})
|
||||
|
||||
state.tableData = [
|
||||
{
|
||||
access_key: 'fwafwafwafwaf',
|
||||
secret_key: '1234567',
|
||||
status: false,
|
||||
create_time: 1766455902237,
|
||||
},
|
||||
{
|
||||
access_key: 'asdasdasdasd',
|
||||
secret_key: '987654321',
|
||||
status: true,
|
||||
create_time: 1766455902237,
|
||||
},
|
||||
{
|
||||
access_key: 'asdasdasdasd',
|
||||
secret_key: '987654321',
|
||||
status: true,
|
||||
create_time: 1766455902237,
|
||||
},
|
||||
{
|
||||
access_key: 'asdasdasdasd',
|
||||
secret_key: '987654321',
|
||||
status: true,
|
||||
create_time: 1766455902237,
|
||||
},
|
||||
]
|
||||
const handleAdd = () => {
|
||||
if (triggerLimit.value) {
|
||||
return
|
||||
}
|
||||
console.log('Add API Key')
|
||||
}
|
||||
const pwd = ref('**********')
|
||||
const toApiDoc = () => {
|
||||
console.log('Add API Key')
|
||||
const url = '/docs'
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
const statusHandler = (row: any) => {
|
||||
/* state.form = { ...row }
|
||||
editTerm() */
|
||||
const param = {
|
||||
id: row.id,
|
||||
status: row.status,
|
||||
}
|
||||
console.log(row, param)
|
||||
// userApi.status(param)
|
||||
}
|
||||
const { copy } = useClipboard({ legacy: true })
|
||||
|
||||
const copyCode = (row: any, key: any = 'secret_key') => {
|
||||
copy(row[key])
|
||||
.then(function () {
|
||||
ElMessage.success(t('embedded.copy_successful'))
|
||||
})
|
||||
.catch(function () {
|
||||
ElMessage.error(t('embedded.copy_failed'))
|
||||
})
|
||||
}
|
||||
const deleteHandler = (row: any) => {
|
||||
ElMessageBox.confirm(t('user.del_user', { msg: row.name }), {
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
}).then(() => {
|
||||
/* userApi.delete(row.id).then(() => {
|
||||
multipleSelectionAll.value = multipleSelectionAll.value.filter((ele) => ele.id !== row.id)
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('dashboard.delete_success'),
|
||||
})
|
||||
search()
|
||||
}) */
|
||||
console.log('execute delete')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sqlbot-apikey-container">
|
||||
<div class="warn-template">
|
||||
<span class="icon-span">
|
||||
<el-icon>
|
||||
<Icon name="icon_warning_filled"><icon_warning_filled class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<div class="warn-template-content">
|
||||
<span>{{ t('api_key.info_tips') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="api-key-btn">
|
||||
<el-tooltip
|
||||
v-if="triggerLimit"
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="t('api_key.trigger_limit', [limitCount])"
|
||||
placement="top"
|
||||
>
|
||||
<el-button v-if="triggerLimit" type="info" disabled>
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</template>
|
||||
{{ $t('api_key.create') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-button v-else type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</template>
|
||||
{{ $t('api_key.create') }}
|
||||
</el-button>
|
||||
|
||||
<el-button secondary @click="toApiDoc">
|
||||
<template #icon>
|
||||
<icon_visible_outlined_blod></icon_visible_outlined_blod>
|
||||
</template>
|
||||
{{ $t('api_key.to_doc') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="api-key-grid">
|
||||
<el-table ref="multipleTableRef" :data="state.tableData" style="width: 100%">
|
||||
<el-table-column prop="access_key" label="Access Key" width="256">
|
||||
<template #default="scope">
|
||||
<div class="user-status-container">
|
||||
<div :title="scope.row.access_key" class="ellipsis" style="max-width: 208px">
|
||||
{{ scope.row.access_key }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
:offset="12"
|
||||
effect="dark"
|
||||
:content="t('datasource.copy')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon
|
||||
size="16"
|
||||
class="hover-icon_with_bg"
|
||||
@click="copyCode(scope.row, 'access_key')"
|
||||
>
|
||||
<icon_copy_outlined></icon_copy_outlined>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="secret_key" label="Secret Key" width="256">
|
||||
<template #default="scope">
|
||||
<div class="user-status-container">
|
||||
<div
|
||||
:title="scope.row.showPwd ? scope.row.secret_key : pwd"
|
||||
class="ellipsis"
|
||||
style="max-width: 208px"
|
||||
>
|
||||
{{ scope.row.showPwd ? scope.row.secret_key : pwd }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
:offset="12"
|
||||
effect="dark"
|
||||
:content="t('datasource.copy')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="hover-icon_with_bg" size="16" @click="copyCode(scope.row)">
|
||||
<icon_copy_outlined></icon_copy_outlined>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
v-if="scope.row.showPwd"
|
||||
:offset="12"
|
||||
effect="dark"
|
||||
:content="t('embedded.click_to_hide')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="hover-icon_with_bg" size="16" @click="scope.row.showPwd = false">
|
||||
<icon_visible_outlined></icon_visible_outlined>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
v-if="!scope.row.showPwd"
|
||||
:offset="12"
|
||||
effect="dark"
|
||||
:content="t('embedded.click_to_show')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="hover-icon_with_bg" size="16" @click="scope.row.showPwd = true">
|
||||
<icon_invisible_outlined></icon_invisible_outlined>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" width="180" sortable :label="t('user.creation_time')">
|
||||
<template #default="scope">
|
||||
<span>{{ formatTimestamp(scope.row.create_time, 'YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" width="100" :label="$t('ds.actions')">
|
||||
<template #default="scope">
|
||||
<div class="table-operate">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
size="small"
|
||||
@change="statusHandler(scope.row)"
|
||||
/>
|
||||
<div class="line"></div>
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('dashboard.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="deleteHandler(scope.row)">
|
||||
<IconOpeDelete></IconOpeDelete>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<EmptyBackground
|
||||
v-if="!state.tableData.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="none"
|
||||
/>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sqlbot-apikey-container {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
row-gap: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.warn-template {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #d2f1e9;
|
||||
// border: 1px solid #ffe7ba;
|
||||
border-radius: 6px;
|
||||
padding: 9px 16px;
|
||||
|
||||
.icon-span {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: -20px;
|
||||
i {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.warn-template-content {
|
||||
font-size: 14px;
|
||||
color: #1f2329;
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-btn {
|
||||
margin-bottom: 0px;
|
||||
|
||||
.el-button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-key-grid {
|
||||
width: 100%;
|
||||
.el-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-operate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: #e8e8e8;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
color: #646a73;
|
||||
|
||||
&:hover {
|
||||
background-color: #1f23291a;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-status-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
height: 24px;
|
||||
|
||||
.ed-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.ed-icon + .ed-icon {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -6,6 +6,7 @@ import icon_info_outlined_1 from '@/assets/svg/icon_info_outlined_1.svg'
|
||||
import { useAppearanceStoreWithOut } from '@/stores/appearance'
|
||||
import icon_maybe_outlined from '@/assets/svg/icon-maybe_outlined.svg'
|
||||
import icon_key_outlined from '@/assets/svg/icon-key_outlined.svg'
|
||||
import icon_api_key from '@/assets/svg/icon-api_key.svg'
|
||||
import icon_translate_outlined from '@/assets/svg/icon_translate_outlined.svg'
|
||||
import icon_logout_outlined from '@/assets/svg/icon_logout_outlined.svg'
|
||||
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
|
||||
@@ -13,6 +14,7 @@ import AboutDialog from '@/components/about/index.vue'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PwdForm from './PwdForm.vue'
|
||||
import Apikey from './Apikey.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { userApi } from '@/api/auth'
|
||||
@@ -33,6 +35,7 @@ const currentLanguage = computed(() => userStore.getLanguage)
|
||||
const isAdmin = computed(() => userStore.isAdmin)
|
||||
const isLocalUser = computed(() => !userStore.getOrigin)
|
||||
const dialogVisible = ref(false)
|
||||
const apikeyDialogVisible = ref(false)
|
||||
const aboutRef = ref()
|
||||
const languageList = computed(() => [
|
||||
{
|
||||
@@ -76,7 +79,9 @@ const openPwd = () => {
|
||||
const closePwd = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const openApikey = () => {
|
||||
apikeyDialogVisible.value = true
|
||||
}
|
||||
const toAbout = () => {
|
||||
aboutRef.value?.open()
|
||||
}
|
||||
@@ -126,6 +131,12 @@ const logout = async () => {
|
||||
</el-icon>
|
||||
<div class="datasource-name">{{ $t('user.change_password') }}</div>
|
||||
</div>
|
||||
<div class="popover-item" @click="openApikey">
|
||||
<el-icon size="16">
|
||||
<icon_api_key></icon_api_key>
|
||||
</el-icon>
|
||||
<div class="datasource-name">API Key</div>
|
||||
</div>
|
||||
<el-popover :teleported="false" popper-class="system-language" placement="right">
|
||||
<template #reference>
|
||||
<div class="popover-item">
|
||||
@@ -185,6 +196,9 @@ const logout = async () => {
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="apikeyDialogVisible" title="API Key" width="840">
|
||||
<apikey v-if="apikeyDialogVisible" ref="apikeyRef" />
|
||||
</el-dialog>
|
||||
<AboutDialog ref="aboutRef" />
|
||||
</template>
|
||||
|
||||
|
||||
@@ -819,5 +819,11 @@
|
||||
},
|
||||
"modelType": {
|
||||
"llm": "Large Language Model"
|
||||
},
|
||||
"api_key": {
|
||||
"info_tips": "The API Key is your credential for accessing the SQLBot API, with full permissions for your account. Please keep it secure! Do not disclose the API Key in any public channels to avoid security risks from unauthorized use.",
|
||||
"create": "Create",
|
||||
"to_doc": "View API",
|
||||
"trigger_limit": "Supports up to {0} API Keys"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,5 +819,11 @@
|
||||
},
|
||||
"modelType": {
|
||||
"llm": "대형 언어 모델"
|
||||
},
|
||||
"api_key": {
|
||||
"info_tips": "API 키는 SQLBot API에 액세스하는 비밀키로 계정의 모든 권한을 갖고 있습니다. 꼭 안전하게 보관하세요! 외부 채널에 어떠한 방식으로도 API 키를 공개하지 마시고, 타인이 악용하여 보안 위협이 발생하는 것을 방지하세요.",
|
||||
"create": "생성",
|
||||
"to_doc": "API 보기",
|
||||
"trigger_limit": "최대 {0}개의 API 키 생성 지원"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,5 +820,11 @@
|
||||
},
|
||||
"modelType": {
|
||||
"llm": "大语言模型"
|
||||
},
|
||||
"api_key": {
|
||||
"info_tips": "API Key 是您访问 SQLBot API 的密钥,具有账户的完全权限,请您务必妥善保管!不要以任何方式公开 API Key 到外部渠道,避免被他人利用造成安全威胁。",
|
||||
"create": "创建",
|
||||
"to_doc": "查看 API",
|
||||
"trigger_limit": "最多支持创建 {0} 个 API Key"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user