mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: 跨域设置(#276)
This commit is contained in:
parent
d4e742f7c6
commit
267be441e3
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 4.1.13 on 2024-05-08 13:57
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('application', '0005_alter_chat_abstract_alter_chatrecord_answer_text'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='applicationapikey',
|
||||
name='allow_cross_domain',
|
||||
field=models.BooleanField(default=False, verbose_name='是否允许跨域'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='applicationapikey',
|
||||
name='cross_domain_list',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表'),
|
||||
),
|
||||
]
|
||||
|
|
@ -22,6 +22,10 @@ class ApplicationApiKey(AppModelMixin):
|
|||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id")
|
||||
application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用id")
|
||||
is_active = models.BooleanField(default=True, verbose_name="是否开启")
|
||||
allow_cross_domain = models.BooleanField(default=False, verbose_name="是否允许跨域")
|
||||
cross_domain_list = ArrayField(verbose_name="跨域列表",
|
||||
base_field=models.CharField(max_length=128, blank=True)
|
||||
, default=list)
|
||||
|
||||
class Meta:
|
||||
db_table = "application_api_key"
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ from dataset.serializers.common_serializers import list_paragraph
|
|||
from embedding.models import SearchMode
|
||||
from setting.models import AuthOperate
|
||||
from setting.models.model_management import Model
|
||||
from setting.models_provider.constants.model_provider_constants import ModelProvideConstants
|
||||
from setting.serializers.provider_serializers import ModelSerializer
|
||||
from smartdoc.conf import PROJECT_DIR
|
||||
|
||||
|
|
@ -583,6 +582,15 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
class Edit(serializers.Serializer):
|
||||
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("是否可用"))
|
||||
|
||||
allow_cross_domain = serializers.BooleanField(required=False,
|
||||
error_messages=ErrMessage.boolean("是否允许跨域"))
|
||||
|
||||
cross_domain_list = serializers.ListSerializer(required=False,
|
||||
child=serializers.CharField(required=True,
|
||||
error_messages=ErrMessage.char(
|
||||
"跨域列表")),
|
||||
error_messages=ErrMessage.char("跨域地址"))
|
||||
|
||||
class Operate(serializers.Serializer):
|
||||
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
|
||||
|
||||
|
|
@ -599,15 +607,17 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
def edit(self, instance, with_valid=True):
|
||||
if with_valid:
|
||||
self.is_valid(raise_exception=True)
|
||||
ApplicationSerializer.Edit(data=instance).is_valid(raise_exception=True)
|
||||
|
||||
ApplicationSerializer.ApplicationKeySerializer.Edit(data=instance).is_valid(raise_exception=True)
|
||||
api_key_id = self.data.get("api_key_id")
|
||||
application_id = self.data.get('application_id')
|
||||
application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id,
|
||||
application_id=application_id).first()
|
||||
if application_api_key is None:
|
||||
raise AppApiException(500, '不存在')
|
||||
if 'is_active' in instance and instance.get('is_active') is not None:
|
||||
api_key_id = self.data.get("api_key_id")
|
||||
application_id = self.data.get('application_id')
|
||||
application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id,
|
||||
application_id=application_id).first()
|
||||
if application_api_key is None:
|
||||
raise AppApiException(500, '不存在')
|
||||
|
||||
application_api_key.is_active = instance.get('is_active')
|
||||
application_api_key.save()
|
||||
if 'allow_cross_domain' in instance and instance.get('allow_cross_domain') is not None:
|
||||
application_api_key.allow_cross_domain = instance.get('allow_cross_domain')
|
||||
if 'cross_domain_list' in instance and instance.get('cross_domain_list') is not None:
|
||||
application_api_key.cross_domain_list = instance.get('cross_domain_list')
|
||||
application_api_key.save()
|
||||
|
|
|
|||
|
|
@ -101,7 +101,10 @@ class ApplicationApi(ApiMixin):
|
|||
properties={
|
||||
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否激活",
|
||||
description="是否激活"),
|
||||
|
||||
'allow_cross_domain': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否允许跨域",
|
||||
description="是否允许跨域"),
|
||||
'cross_domain_list': openapi.Schema(type=openapi.TYPE_ARRAY, title='跨域列表',
|
||||
items=openapi.Schema(type=openapi.TYPE_STRING))
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
@project: maxkb
|
||||
@Author:虎
|
||||
@file: cross_domain_middleware.py
|
||||
@date:2024/5/8 13:36
|
||||
@desc:
|
||||
"""
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpResponse
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from application.models.api_key_model import ApplicationApiKey
|
||||
|
||||
|
||||
class CrossDomainMiddleware(MiddlewareMixin):
|
||||
|
||||
def process_request(self, request):
|
||||
if request.method == 'OPTIONS':
|
||||
auth = request.META.get('HTTP_AUTHORIZATION')
|
||||
if auth is not None and str(auth).startswith("application-"):
|
||||
application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=auth).first()
|
||||
if application_api_key.allow_cross_domain:
|
||||
return HttpResponse(status=200,
|
||||
headers={
|
||||
"Access-Control-Allow-Origin": "*" if application_api_key.cross_domain_list is None or len(
|
||||
application_api_key.cross_domain_list) == 0 else ",".join(
|
||||
application_api_key.cross_domain_list),
|
||||
"Access-Control-Allow-Methods": "GET,POST,DELETE,PUT",
|
||||
"Access-Control-Allow-Headers": "Origin,X-Requested-With,Content-Type,Accept,Authorization,token"})
|
||||
|
||||
def process_response(self, request, response):
|
||||
auth = request.META.get('HTTP_AUTHORIZATION')
|
||||
if auth is not None and str(auth).startswith("application-"):
|
||||
application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=auth).first()
|
||||
if application_api_key.allow_cross_domain:
|
||||
response['Access-Control-Allow-Origin'] = "*" if application_api_key.cross_domain_list is None or len(
|
||||
application_api_key.cross_domain_list) == 0 else ",".join(
|
||||
application_api_key.cross_domain_list)
|
||||
response['Access-Control-Allow-Methods'] = 'GET,POST,DELETE,PUT'
|
||||
response[
|
||||
'Access-Control-Allow-Headers'] = "Origin,X-Requested-With,Content-Type,Accept,Authorization,token"
|
||||
return response
|
||||
|
|
@ -47,7 +47,8 @@ MIDDLEWARE = [
|
|||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'common.middleware.static_headers_middleware.StaticHeadersMiddleware'
|
||||
'common.middleware.static_headers_middleware.StaticHeadersMiddleware',
|
||||
'common.middleware.cross_domain_middleware.CrossDomainMiddleware'
|
||||
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="操作" align="left" width="80">
|
||||
<template #default="{ row }">
|
||||
<span class="mr-4">
|
||||
<el-tooltip effect="dark" content="设置" 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="删除" placement="top">
|
||||
<el-button type="primary" text @click="deleteApiKey(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
|
|
@ -34,6 +41,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<SettingAPIKeyDialog ref="SettingAPIKeyDialogRef" @refresh="refresh" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -41,17 +49,17 @@ import { ref, watch } from 'vue'
|
|||
import { useRoute } from 'vue-router'
|
||||
import { copyClick } from '@/utils/clipboard'
|
||||
import overviewApi from '@/api/application-overview'
|
||||
import SettingAPIKeyDialog from './SettingAPIKeyDialog.vue'
|
||||
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 SettingAPIKeyDialogRef = ref()
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const loading = ref(false)
|
||||
const apiKey = ref<any>(null)
|
||||
|
|
@ -62,6 +70,10 @@ watch(dialogVisible, (bool) => {
|
|||
}
|
||||
})
|
||||
|
||||
function settingApiKey(row: any) {
|
||||
SettingAPIKeyDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function deleteApiKey(row: any) {
|
||||
MsgConfirm(
|
||||
`是否删除API Key:${row.secret_key} ?`,
|
||||
|
|
@ -108,6 +120,10 @@ function getApiKeyList() {
|
|||
})
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
getApiKeyList()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<el-dialog title="设置" v-model="dialogVisible">
|
||||
<el-form label-position="top" ref="settingFormRef" :model="form">
|
||||
<el-form-item label="允许跨域地址" @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="请输入允许的跨域地址,开启后不输入跨域地址则不限制。
|
||||
跨域地址一行一个,如:
|
||||
http://127.0.0.1:5678
|
||||
https://dataease.io"
|
||||
:rows="10"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||
<el-button type="primary" @click="submit(settingFormRef)" :loading="loading">
|
||||
保存
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import overviewApi from '@/api/application-overview'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
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')
|
||||
: []
|
||||
}
|
||||
overviewApi.putAPIKey(id as string, APIKeyId.value, obj, loading).then((res) => {
|
||||
emit('refresh')
|
||||
MsgSuccess('设置成功')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scope></style>
|
||||
Loading…
Reference in New Issue