Merge branch 'main' of github.com:maxkb-dev/maxkb

This commit is contained in:
shaohuzhang1 2023-12-08 18:11:21 +08:00
commit 6e31965976
10 changed files with 325 additions and 92 deletions

View File

@ -0,0 +1,63 @@
import { Result } from '@/request/Result'
import { get, post, postStream, del, put } from '@/request/index'
import { type Ref } from 'vue'
const prefix = '/application'
/**
* API_KEY列表
* @param applicaiton_id
*/
const getAPIKey: (applicaiton_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
applicaiton_id,
loading
) => {
return get(`${prefix}/${applicaiton_id}/api_key`, undefined, loading)
}
/**
* API_KEY
* @param applicaiton_id
*/
const postAPIKey: (applicaiton_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
applicaiton_id,
loading
) => {
return post(`${prefix}/${applicaiton_id}/api_key`, {}, undefined, loading)
}
/**
* API_KEY
* @param applicaiton_id api_key_id
*/
const delAPIKey: (
applicaiton_id: String,
api_key_id: String,
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (applicaiton_id, api_key_id, loading) => {
return del(`${prefix}/${applicaiton_id}/api_key/${api_key_id}`, undefined, undefined, loading)
}
/**
* API_KEY
* @param applicaiton_id,api_key_id
* data {
* is_active: boolean
* }
*/
const putAPIKey: (
applicaiton_id: string,
api_key_id: String,
data: any,
loading?: Ref<boolean>
) => Promise<Result<any>> = (applicaiton_id, api_key_id, data, loading) => {
return put(`${prefix}/${applicaiton_id}/api_key/${api_key_id}`, data, undefined, loading)
}
export default {
getAPIKey,
postAPIKey,
delAPIKey,
putAPIKey
}

View File

@ -111,17 +111,6 @@ const getApplicationDataset: (
return get(`${prefix}/${applicaiton_id}/list_dataset`, undefined, loading) return get(`${prefix}/${applicaiton_id}/list_dataset`, undefined, loading)
} }
/**
* API_KEY列表
* @param applicaiton_id
*/
const getAPIKey: (applicaiton_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
applicaiton_id,
loading
) => {
return get(`${prefix}/${applicaiton_id}/api_key`, undefined, loading)
}
/** /**
* AccessToken * AccessToken
* @param applicaiton_id * @param applicaiton_id
@ -133,6 +122,21 @@ const getAccessToken: (applicaiton_id: string, loading?: Ref<boolean>) => Promis
return get(`${prefix}/${applicaiton_id}/access_token`, undefined, loading) return get(`${prefix}/${applicaiton_id}/access_token`, undefined, loading)
} }
/**
* AccessToken
* @param applicaiton_id
* data {
* "is_active": true
* }
*/
const putAccessToken: (
applicaiton_id: string,
data: any,
loading?: Ref<boolean>
) => Promise<Result<any>> = (applicaiton_id, data, loading) => {
return put(`${prefix}/${applicaiton_id}/access_token`, data, undefined, loading)
}
/** /**
* *
* @param * @param
@ -235,8 +239,8 @@ export default {
delApplication, delApplication,
getApplicationDetail, getApplicationDetail,
getApplicationDataset, getApplicationDataset,
getAPIKey,
getAccessToken, getAccessToken,
putAccessToken,
postAppAuthentication, postAppAuthentication,
getProfile, getProfile,
putChatVote putChatVote

View File

@ -141,8 +141,8 @@ export const post: (
*/ */
export const put: ( export const put: (
url: string, url: string,
params?: unknown,
data?: unknown, data?: unknown,
params?: unknown,
loading?: NProgress | Ref<boolean> loading?: NProgress | Ref<boolean>
) => Promise<Result<any>> = (url, data, params, loading) => { ) => Promise<Result<any>> = (url, data, params, loading) => {
return promise(request({ url: url, method: 'put', data, params }), loading) return promise(request({ url: url, method: 'put', data, params }), loading)

View File

@ -35,7 +35,7 @@ const applicationRouter = {
parentPath: '/application/:id', parentPath: '/application/:id',
parentName: 'ApplicationDetail' parentName: 'ApplicationDetail'
}, },
component: () => import('@/views/application/AppOverview.vue') component: () => import('@/views/applicaiton-overview/index.vue')
}, },
{ {
path: 'setting', path: 'setting',

View File

@ -0,0 +1,124 @@
<template>
<el-dialog title="API Key" v-model="dialogVisible" width="800">
<el-button type="primary" class="mb-16" @click="createApiKey"> 创建 </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="状态" width="100">
<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="创建日期" width="170">
<template #default="{ row }">
{{ datetimeFormat(row.create_time) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80">
<template #default="{ row }">
<el-tooltip effect="dark" content="删除" 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>
</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/application-overview'
import { datetimeFormat } from '@/utils/time'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import useStore from '@/stores'
const route = useRoute()
const {
params: { id }
} = route
const { application } = useStore()
const emit = defineEmits(['addData'])
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const apiKey = ref<any>(null)
watch(dialogVisible, (bool) => {
if (!bool) {
apiKey.value = null
}
})
function deleteApiKey(row: any) {
MsgConfirm(
`是否删除API Key${row.secret_key} ?`,
`删除后无法使用该 API Key 调用接口,请谨慎操作`,
{
confirmButtonText: '删除',
confirmButtonClass: 'danger'
}
)
.then(() => {
overviewApi.delAPIKey(id as string, row.id, loading).then(() => {
MsgSuccess('删除成功')
getApiKeyList()
})
})
.catch(() => {})
}
function changeState(bool: Boolean, row: any) {
const obj = {
is_active: bool
}
const str = bool ? '启用成功' : '禁用成功'
overviewApi.putAPIKey(id as string, row.id, obj, loading).then((res) => {
MsgSuccess(str)
getApiKeyList()
})
}
function createApiKey() {
overviewApi.postAPIKey(id as string, loading).then((res) => {
getApiKeyList()
})
}
const open = () => {
getApiKeyList()
dialogVisible.value = true
}
function getApiKeyList() {
overviewApi.getAPIKey(id as string, loading).then((res) => {
apiKey.value = res.data
})
}
defineExpose({ open })
</script>
<style lang="scss" scope>
.embed-dialog {
.code {
color: var(--app-text-color) !important;
background: var(--app-layout-bg-color);
font-weight: 400;
font-size: 13px;
white-space: pre;
height: 180px;
}
}
</style>

View File

@ -2,7 +2,7 @@
<LayoutContainer header="概览"> <LayoutContainer header="概览">
<div class="main-calc-height p-24"> <div class="main-calc-height p-24">
<h4 class="title-decoration-1 mb-16">应用信息</h4> <h4 class="title-decoration-1 mb-16">应用信息</h4>
<el-card shadow="never" class="overview-card"> <el-card shadow="never" class="overview-card" v-loading="loading">
<div class="title flex align-center"> <div class="title flex align-center">
<AppAvatar <AppAvatar
v-if="detail?.name" v-if="detail?.name"
@ -20,15 +20,17 @@
<div class="flex"> <div class="flex">
<el-text type="info">公开访问链接</el-text> <el-text type="info">公开访问链接</el-text>
<el-switch <el-switch
v-model="accessToken.is_active"
class="ml-8" class="ml-8"
size="small" size="small"
inline-prompt inline-prompt
active-text="开" active-text="开"
inactive-text="关" inactive-text="关"
@change="changeState"
/> />
</div> </div>
<div class="mt-4"> <div class="mt-4 url-height">
<span class="vertical-middle lighter break-all"> <span class="vertical-middle lighter break-all">
{{ shareUrl }} {{ shareUrl }}
</span> </span>
@ -36,40 +38,46 @@
<el-button type="primary" text @click="copyClick(shareUrl)"> <el-button type="primary" text @click="copyClick(shareUrl)">
<AppIcon iconName="app-copy"></AppIcon> <AppIcon iconName="app-copy"></AppIcon>
</el-button> </el-button>
<el-button @click="getAccessToken" type="primary" text style="margin-left: 1px">
<el-icon><RefreshRight /></el-icon>
</el-button>
</div> </div>
<div class="mt-16"> <div>
<el-button type="primary"><a :href="shareUrl" target="_blank">演示</a></el-button> <el-button :disabled="!accessToken?.is_active" type="primary"><a :href="shareUrl" target="_blank">演示</a></el-button>
<el-button @click="openDialog"> 嵌入第三方 </el-button> <el-button :disabled="!accessToken?.is_active" @click="openDialog"> 嵌入第三方 </el-button>
</div> </div>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="mt-16"> <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="mt-16">
<div class="flex"> <div class="flex">
<el-text type="info">API访问凭据</el-text> <el-text type="info">API访问凭据</el-text>
</div> </div>
<div class="mt-4"> <div class="mt-4 url-height">
<span class="vertical-middle lighter break-all"> <span class="vertical-middle lighter break-all">
API Key: OGZmZThlZjYyYzU2MWE1OTlkYTVjZTBi {{ apiUrl }}
</span> </span>
<el-button type="primary" text> <el-button type="primary" text @click="copyClick(apiUrl)">
<AppIcon iconName="app-copy"></AppIcon> <AppIcon iconName="app-copy"></AppIcon>
</el-button> </el-button>
</div> </div>
<div class="mt-16"> <div>
<el-button @click="openDialog"> 获取密钥 </el-button> <el-button @click="openAPIKeyDialog"> API Key </el-button>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</el-card> </el-card>
</div> </div>
<EmbedDialog ref="EmbedDialogRef" /> <EmbedDialog ref="EmbedDialogRef" />
<APIKeyDialog ref="APIKeyDialogRef" />
</LayoutContainer> </LayoutContainer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue' import { reactive, ref, watch, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import EmbedDialog from './component/EmbedDialog.vue'
import APIKeyDialog from './component/APIKeyDialog.vue'
import applicationApi from '@/api/application' import applicationApi from '@/api/application'
import EmbedDialog from './components/EmbedDialog.vue' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { copyClick } from '@/utils/clipboard' import { copyClick } from '@/utils/clipboard'
import useStore from '@/stores' import useStore from '@/stores'
const { application } = useStore() const { application } = useStore()
@ -79,27 +87,37 @@ const {
params: { id } params: { id }
} = route as any } = route as any
const apiUrl = window.location.origin + '/doc'
const APIKeyDialogRef = ref()
const EmbedDialogRef = ref() const EmbedDialogRef = ref()
const shareUrl = ref('') const shareUrl = ref('')
const accessToken = ref('') const accessToken = ref<any>({})
const detail = ref<any>(null) const detail = ref<any>(null)
const apiKey = ref<any>(null)
const loading = ref(false) const loading = ref(false)
function openDialog() { function changeState(bool: Boolean) {
EmbedDialogRef.value.open(accessToken.value) const obj = {
} is_active: bool
function getAccessToken() { }
application.asyncGetAccessToken(id, loading).then((res: any) => { const str = bool ? '启用成功' : '禁用成功'
accessToken.value = res?.data?.access_token applicationApi.putAccessToken(id as string, obj, loading).then((res) => {
shareUrl.value = application.location + res?.data?.access_token MsgSuccess(str)
getDetail()
}) })
} }
function getApiKey() { function openAPIKeyDialog() {
applicationApi.getAPIKey(id, loading).then((res) => { APIKeyDialogRef.value.open()
apiKey.value = res.data }
function openDialog() {
EmbedDialogRef.value.open(accessToken.value?.access_token)
}
function getAccessToken() {
application.asyncGetAccessToken(id, loading).then((res: any) => {
accessToken.value = res?.data
shareUrl.value = application.location + res?.data?.access_token
}) })
} }
@ -110,7 +128,6 @@ function getDetail() {
} }
onMounted(() => { onMounted(() => {
getDetail() getDetail()
getApiKey()
getAccessToken() getAccessToken()
}) })
</script> </script>
@ -122,5 +139,8 @@ onMounted(() => {
right: 16px; right: 16px;
top: 21px; top: 21px;
} }
.url-height {
min-height: 50px;
}
} }
</style> </style>

View File

@ -57,7 +57,7 @@
</el-text> </el-text>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="name" label="启用状态"> <el-table-column label="启用状态">
<template #default="{ row }"> <template #default="{ row }">
<div @click.stop> <div @click.stop>
<el-switch <el-switch
@ -78,7 +78,7 @@
{{ datetimeFormat(row.update_time) }} {{ datetimeFormat(row.update_time) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="name" label="操作" align="center"> <el-table-column label="操作" align="center">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.status === 2"> <span v-if="row.status === 2">
<el-tooltip effect="dark" content="刷新" placement="top"> <el-tooltip effect="dark" content="刷新" placement="top">

View File

@ -1,50 +1,56 @@
<template> <template>
<el-input v-model="filterText" placeholder="搜索" prefix-icon="Search" class="mb-16" /> <el-input
v-model="filterText"
<el-table :data="data" :max-height="tableHeight"> placeholder="搜索"
<el-table-column prop="name" :label="isApplication ? '应用名称' : '数据集名称'"> prefix-icon="Search"
<template #default="{ row }"> class="p-24 pt-0 pb-0 mb-16 mt-4"
<div class="flex align-center"> />
<AppAvatar <div class="p-24 pt-0">
v-if="isApplication" <el-table :data="data" :max-height="tableHeight">
:name="row.name" <el-table-column prop="name" :label="isApplication ? '应用名称' : '数据集名称'">
pinyinColor <template #default="{ row }">
class="mr-12" <div class="flex align-center">
shape="square" <AppAvatar
:size="24" v-if="isApplication"
/> :name="row.name"
<AppAvatar v-else-if="isDataset" class="mr-12" shape="square" :size="24"> pinyinColor
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" /> class="mr-12"
</AppAvatar> shape="square"
<span class="ellipsis-1"> {{ row?.name }}</span> :size="24"
</div> />
</template> <AppAvatar v-else-if="isDataset" class="mr-12" shape="square" :size="24">
</el-table-column> <img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
<el-table-column label="管理" align="center" width="60"> </AppAvatar>
<!-- <template #header> <span class="ellipsis-1"> {{ row?.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="管理" align="center" width="60">
<!-- <template #header>
<el-checkbox <el-checkbox
v-model="allChecked[MANAGE]" v-model="allChecked[MANAGE]"
label="管理" label="管理"
@change="handleCheckAllChange($event, MANAGE)" @change="handleCheckAllChange($event, MANAGE)"
/> />
</template> --> </template> -->
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.operate[MANAGE]" @change="checkedOperateChange(MANAGE, row)" /> <el-checkbox v-model="row.operate[MANAGE]" @change="checkedOperateChange(MANAGE, row)" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="使用" align="center" width="60"> <el-table-column label="使用" align="center" width="60">
<!-- <template #header> <!-- <template #header>
<el-checkbox <el-checkbox
v-model="allChecked[USE]" v-model="allChecked[USE]"
label="使用" label="使用"
@change="handleCheckAllChange($event, USE)" @change="handleCheckAllChange($event, USE)"
/> />
</template> --> </template> -->
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.operate[USE]" @change="checkedOperateChange(USE, row)" /> <el-checkbox v-model="row.operate[USE]" @change="checkedOperateChange(USE, row)" />
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, watch, computed } from 'vue' import { ref, onMounted, watch, computed } from 'vue'
@ -56,7 +62,8 @@ const props = defineProps({
default: () => [] default: () => []
}, },
id: String, id: String,
type: String type: String,
tableHeight: Number
}) })
const isDataset = computed(() => props.type === DATASET) const isDataset = computed(() => props.type === DATASET)
@ -68,8 +75,6 @@ const allChecked: any = ref({
[USE]: false [USE]: false
}) })
const tableHeight = ref(0)
const filterText = ref('') const filterText = ref('')
watch( watch(
@ -113,12 +118,6 @@ function compare(attrs: string | number) {
} }
onMounted(() => { onMounted(() => {
tableHeight.value = window.innerHeight - 300
window.onresize = () => {
return (() => {
tableHeight.value = window.innerHeight - 300
})()
}
Object.keys(allChecked.value).map((item) => { Object.keys(allChecked.value).map((item) => {
allChecked.value[item] = compare(item) allChecked.value[item] = compare(item)
}) })

View File

@ -40,21 +40,26 @@
</common-list> </common-list>
</div> </div>
<div class="permission-setting flex" v-loading="rLoading"> <div class="permission-setting flex" v-loading="rLoading">
<div class="team-manage__table p-24"> <div class="team-manage__table">
<h4>权限设置</h4> <h4 class="p-24 pb-0 mb-4">权限设置</h4>
<el-tabs v-model="activeName" class="team-manage__tabs"> <el-tabs v-model="activeName" class="team-manage__tabs">
<el-tab-pane <el-tab-pane
v-for="item in settingTags" v-for="(item, index) in settingTags"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:name="item.value" :name="item.value"
> >
<PermissionSetting :data="item.data" :type="item.value"></PermissionSetting> <PermissionSetting
:key="index"
:data="item.data"
:type="item.value"
:tableHeight="tableHeight"
></PermissionSetting>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
<div class="team-manage__footer border-t p-16 flex"> <div class="submit-button">
<el-button type="primary" @click="submitPermissions">保存</el-button> <el-button type="primary" @click="submitPermissions">保存</el-button>
</div> </div>
</div> </div>
@ -81,6 +86,7 @@ const currentUser = ref<String>('')
const filterText = ref('') const filterText = ref('')
const activeName = ref(DATASET) const activeName = ref(DATASET)
const tableHeight = ref(0)
const settingTags = reactive([ const settingTags = reactive([
{ {
@ -201,6 +207,12 @@ function refresh() {
} }
onMounted(() => { onMounted(() => {
tableHeight.value = window.innerHeight - 330
window.onresize = () => {
return (() => {
tableHeight.value = window.innerHeight - 330
})()
}
getMember() getMember()
}) })
</script> </script>
@ -223,17 +235,28 @@ onMounted(() => {
box-sizing: border-box; box-sizing: border-box;
width: calc(100% - var(--setting-left-width)); width: calc(100% - var(--setting-left-width));
flex-direction: column; flex-direction: column;
position: relative;
.submit-button {
position: absolute;
top: 54px;
right: 24px;
}
} }
&__tabs { &__tabs {
margin-top: 10px; margin-top: 10px;
:deep(.el-tabs__nav-wrap::after) {
height: 1px;
}
:deep(.el-tabs__nav-scroll) {
padding: 0 24px;
}
:deep(.el-tabs__active-bar) {
height: 3px;
}
} }
&__table { &__table {
flex: 1; flex: 1;
} }
&__footer {
flex: 0 0 auto;
justify-content: right;
}
} }
</style> </style>