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")
|
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id")
|
||||||
application = models.ForeignKey(Application, 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="是否开启")
|
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:
|
class Meta:
|
||||||
db_table = "application_api_key"
|
db_table = "application_api_key"
|
||||||
|
|||||||
@ -37,7 +37,6 @@ from dataset.serializers.common_serializers import list_paragraph
|
|||||||
from embedding.models import SearchMode
|
from embedding.models import SearchMode
|
||||||
from setting.models import AuthOperate
|
from setting.models import AuthOperate
|
||||||
from setting.models.model_management import Model
|
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 setting.serializers.provider_serializers import ModelSerializer
|
||||||
from smartdoc.conf import PROJECT_DIR
|
from smartdoc.conf import PROJECT_DIR
|
||||||
|
|
||||||
@ -583,6 +582,15 @@ class ApplicationSerializer(serializers.Serializer):
|
|||||||
class Edit(serializers.Serializer):
|
class Edit(serializers.Serializer):
|
||||||
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("是否可用"))
|
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):
|
class Operate(serializers.Serializer):
|
||||||
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
|
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):
|
def edit(self, instance, with_valid=True):
|
||||||
if with_valid:
|
if with_valid:
|
||||||
self.is_valid(raise_exception=True)
|
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:
|
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.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={
|
properties={
|
||||||
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否激活",
|
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否激活",
|
||||||
description="是否激活"),
|
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))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
43
apps/common/middleware/cross_domain_middleware.py
Normal file
43
apps/common/middleware/cross_domain_middleware.py
Normal file
@ -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.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'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>
|
||||||
<el-table-column label="操作" align="left" width="80">
|
<el-table-column label="操作" align="left" width="80">
|
||||||
<template #default="{ row }">
|
<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-tooltip effect="dark" content="删除" placement="top">
|
||||||
<el-button type="primary" text @click="deleteApiKey(row)">
|
<el-button type="primary" text @click="deleteApiKey(row)">
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon><Delete /></el-icon>
|
||||||
@ -34,6 +41,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<SettingAPIKeyDialog ref="SettingAPIKeyDialogRef" @refresh="refresh" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -41,17 +49,17 @@ import { ref, watch } from 'vue'
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { copyClick } from '@/utils/clipboard'
|
import { copyClick } from '@/utils/clipboard'
|
||||||
import overviewApi from '@/api/application-overview'
|
import overviewApi from '@/api/application-overview'
|
||||||
|
import SettingAPIKeyDialog from './SettingAPIKeyDialog.vue'
|
||||||
import { datetimeFormat } from '@/utils/time'
|
import { datetimeFormat } from '@/utils/time'
|
||||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
import useStore from '@/stores'
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const {
|
const {
|
||||||
params: { id }
|
params: { id }
|
||||||
} = route
|
} = route
|
||||||
const { application } = useStore()
|
|
||||||
|
|
||||||
const emit = defineEmits(['addData'])
|
const emit = defineEmits(['addData'])
|
||||||
|
|
||||||
|
const SettingAPIKeyDialogRef = ref()
|
||||||
const dialogVisible = ref<boolean>(false)
|
const dialogVisible = ref<boolean>(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const apiKey = ref<any>(null)
|
const apiKey = ref<any>(null)
|
||||||
@ -62,6 +70,10 @@ watch(dialogVisible, (bool) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function settingApiKey(row: any) {
|
||||||
|
SettingAPIKeyDialogRef.value.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
function deleteApiKey(row: any) {
|
function deleteApiKey(row: any) {
|
||||||
MsgConfirm(
|
MsgConfirm(
|
||||||
`是否删除API Key:${row.secret_key} ?`,
|
`是否删除API Key:${row.secret_key} ?`,
|
||||||
@ -108,6 +120,10 @@ function getApiKeyList() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
getApiKeyList()
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scope>
|
<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
Block a user