mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-09-24 16:29:01 +08:00
Merge pull request #1086 from Windfarer/storage-allow-list
feat: add STORAGE_ALLOW_LIST env var
This commit is contained in:
@@ -123,6 +123,9 @@ DB_DRIVER=postgres
|
||||
# 向量存储类型(postgres/elasticsearch_v7/elasticsearch_v8/qdrant/milvus/weaviate)
|
||||
RETRIEVE_DRIVER=postgres
|
||||
|
||||
# 允许用户使用哪些文件存储类型,使用逗号分隔,留空则允许所有类型的存储
|
||||
# STORAGE_ALLOW_LIST=local,minio,cos,tos,s3
|
||||
|
||||
# 文件存储类型(local/minio/cos/tos/s3)
|
||||
STORAGE_TYPE=local
|
||||
|
||||
|
||||
@@ -170,9 +170,9 @@ export function reconnectDocReader(addr: string): Promise<ParserEnginesResponse
|
||||
|
||||
export interface StorageEngineConfig {
|
||||
default_provider: string // "local" | "minio" | "cos" | "tos" | "s3" | "oss"
|
||||
local?: { path_prefix: string }
|
||||
minio?: { mode: string; endpoint: string; access_key_id: string; secret_access_key: string; bucket_name: string; use_ssl: boolean; path_prefix: string }
|
||||
cos?: {
|
||||
local: { path_prefix: string }
|
||||
minio: { mode: string; endpoint: string; access_key_id: string; secret_access_key: string; bucket_name: string; use_ssl: boolean; path_prefix: string }
|
||||
cos: {
|
||||
secret_id: string
|
||||
secret_key: string
|
||||
region: string
|
||||
@@ -180,7 +180,7 @@ export interface StorageEngineConfig {
|
||||
app_id: string
|
||||
path_prefix: string
|
||||
}
|
||||
tos?: {
|
||||
tos: {
|
||||
endpoint: string
|
||||
region: string
|
||||
access_key: string
|
||||
@@ -188,7 +188,7 @@ export interface StorageEngineConfig {
|
||||
bucket_name: string
|
||||
path_prefix: string
|
||||
}
|
||||
s3?: {
|
||||
s3: {
|
||||
endpoint: string
|
||||
region: string
|
||||
access_key: string
|
||||
@@ -196,7 +196,7 @@ export interface StorageEngineConfig {
|
||||
bucket_name: string
|
||||
path_prefix: string
|
||||
}
|
||||
oss?: {
|
||||
oss: {
|
||||
endpoint: string
|
||||
region: string
|
||||
access_key: string
|
||||
@@ -211,12 +211,14 @@ export interface StorageEngineConfig {
|
||||
|
||||
export interface StorageEngineStatusItem {
|
||||
name: string
|
||||
allowed?: boolean
|
||||
available: boolean
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface GetStorageEngineStatusResponse {
|
||||
engines: StorageEngineStatusItem[]
|
||||
allowed_providers?: string[]
|
||||
minio_env_available: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
>
|
||||
<span class="select-option">
|
||||
<span>{{ opt.label }}</span>
|
||||
<t-tag v-if="opt.disabled" theme="warning" variant="light" size="small">{{ $t('kbSettings.storage.notConfigured') }}</t-tag>
|
||||
<t-tag v-if="opt.disabled && opt.allowed === false" theme="danger" variant="light" size="small">{{ $t('kbSettings.storage.unavailable') }}</t-tag>
|
||||
<t-tag v-else-if="opt.disabled" theme="warning" variant="light" size="small">{{ $t('kbSettings.storage.notConfigured') }}</t-tag>
|
||||
<t-tag v-else-if="opt.available === false" theme="danger" variant="light" size="small">{{ $t('kbSettings.storage.unavailable') }}</t-tag>
|
||||
</span>
|
||||
</t-option>
|
||||
@@ -71,55 +72,64 @@ const localProvider = ref(props.storageProvider || 'local')
|
||||
const loading = ref(true)
|
||||
const engineStatus = ref<StorageEngineStatusItem[]>([])
|
||||
const defaultProvider = ref('local')
|
||||
const allowedProviders = ref<string[]>([])
|
||||
const hasAnyConfig = ref(false)
|
||||
|
||||
const engineOptions = computed(() => {
|
||||
const statusMap: Record<string, boolean> = {}
|
||||
const allowedMap: Record<string, boolean> = {}
|
||||
for (const e of engineStatus.value) {
|
||||
statusMap[e.name] = e.available
|
||||
allowedMap[e.name] = e.allowed !== false
|
||||
}
|
||||
return [
|
||||
{
|
||||
value: 'local',
|
||||
label: t('kbSettings.storage.engineLocal'),
|
||||
desc: t('kbSettings.storage.engineLocalDesc'),
|
||||
allowed: allowedMap.local !== false,
|
||||
available: statusMap.local !== false,
|
||||
disabled: false,
|
||||
disabled: allowedMap.local === false,
|
||||
},
|
||||
{
|
||||
value: 'minio',
|
||||
label: 'MinIO',
|
||||
desc: t('kbSettings.storage.engineMinioDesc'),
|
||||
allowed: allowedMap.minio !== false,
|
||||
available: statusMap.minio,
|
||||
disabled: statusMap.minio === false,
|
||||
disabled: allowedMap.minio === false || statusMap.minio === false,
|
||||
},
|
||||
{
|
||||
value: 'cos',
|
||||
label: t('kbSettings.storage.engineCos'),
|
||||
desc: t('kbSettings.storage.engineCosDesc'),
|
||||
allowed: allowedMap.cos !== false,
|
||||
available: statusMap.cos,
|
||||
disabled: statusMap.cos === false,
|
||||
disabled: allowedMap.cos === false || statusMap.cos === false,
|
||||
},
|
||||
{
|
||||
value: 'tos',
|
||||
label: t('kbSettings.storage.engineTos'),
|
||||
desc: t('kbSettings.storage.engineTosDesc'),
|
||||
allowed: allowedMap.tos !== false,
|
||||
available: statusMap.tos,
|
||||
disabled: statusMap.tos === false,
|
||||
disabled: allowedMap.tos === false || statusMap.tos === false,
|
||||
},
|
||||
{
|
||||
value: 's3',
|
||||
label: t('kbSettings.storage.engineS3'),
|
||||
desc: t('kbSettings.storage.engineS3Desc'),
|
||||
allowed: allowedMap.s3 !== false,
|
||||
available: statusMap.s3,
|
||||
disabled: statusMap.s3 === false,
|
||||
disabled: allowedMap.s3 === false || statusMap.s3 === false,
|
||||
},
|
||||
{
|
||||
value: 'oss',
|
||||
label: t('kbSettings.storage.engineOss'),
|
||||
desc: t('kbSettings.storage.engineOssDesc'),
|
||||
allowed: allowedMap.oss !== false,
|
||||
available: statusMap.oss,
|
||||
disabled: statusMap.oss === false,
|
||||
disabled: allowedMap.oss === false || statusMap.oss === false,
|
||||
},
|
||||
]
|
||||
})
|
||||
@@ -136,6 +146,14 @@ function handleChange() {
|
||||
emit('update:storageProvider', localProvider.value)
|
||||
}
|
||||
|
||||
function ensureAllowedProvider() {
|
||||
const current = engineOptions.value.find(o => o.value === localProvider.value && !o.disabled)
|
||||
if (current) return
|
||||
const fallback = engineOptions.value.find(o => !o.disabled)?.value || defaultProvider.value || 'local'
|
||||
localProvider.value = fallback
|
||||
emit('update:storageProvider', localProvider.value)
|
||||
}
|
||||
|
||||
function goToStorageSettings() {
|
||||
uiStore.closeKBEditor?.()
|
||||
uiStore.openSettings?.('storage')
|
||||
@@ -150,6 +168,7 @@ async function load() {
|
||||
])
|
||||
const engines = statusRes?.data?.engines ?? []
|
||||
engineStatus.value = engines
|
||||
allowedProviders.value = statusRes?.data?.allowed_providers ?? []
|
||||
defaultProvider.value = configRes?.data?.default_provider || 'local'
|
||||
const d = configRes?.data
|
||||
hasAnyConfig.value = !!(d?.local?.path_prefix || d?.minio?.bucket_name || d?.cos?.bucket_name || d?.tos?.bucket_name || d?.s3?.bucket_name)
|
||||
@@ -157,6 +176,7 @@ async function load() {
|
||||
localProvider.value = defaultProvider.value
|
||||
emit('update:storageProvider', localProvider.value)
|
||||
}
|
||||
ensureAllowedProvider()
|
||||
} catch {
|
||||
engineStatus.value = []
|
||||
} finally {
|
||||
|
||||
@@ -32,14 +32,16 @@
|
||||
v-model="config.default_provider"
|
||||
style="width: 280px;"
|
||||
:placeholder="$t('settings.storage.defaultEngine')"
|
||||
:disabled="!hasAllowedProviders"
|
||||
@change="onSaveDefaultEngine"
|
||||
>
|
||||
<t-option value="local" :label="$t('settings.storage.engineLocal')" />
|
||||
<t-option value="minio" label="MinIO" />
|
||||
<t-option value="cos" :label="$t('settings.storage.engineCos')" />
|
||||
<t-option value="tos" :label="$t('settings.storage.engineTos')" />
|
||||
<t-option value="s3" label="AWS S3" />
|
||||
<t-option value="oss" :label="$t('settings.storage.engineOss')" />
|
||||
<t-option
|
||||
v-for="opt in providerOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:label="opt.label"
|
||||
:disabled="!opt.allowed"
|
||||
/>
|
||||
</t-select>
|
||||
<span v-if="saveMessage && !drawerVisible" :class="['save-msg', saveSuccess ? 'success' : 'error']" style="margin-left: 12px;">
|
||||
{{ saveMessage }}
|
||||
@@ -49,8 +51,11 @@
|
||||
</div>
|
||||
|
||||
<div class="engine-cards">
|
||||
<!-- Local -->
|
||||
<div :class="['engine-card', { active: drawerVisible && currentEngine === 'local' }]" @click="openDrawer('local')">
|
||||
<div
|
||||
v-if="isProviderAllowed('local')"
|
||||
:class="['engine-card', { active: drawerVisible && currentEngine === 'local' }]"
|
||||
@click="openDrawer('local')"
|
||||
>
|
||||
<div class="engine-card-header">
|
||||
<h3>{{ $t('settings.storage.localTitle') }}</h3>
|
||||
<t-tag theme="success" variant="light" size="small">{{ $t('settings.storage.available') }}</t-tag>
|
||||
@@ -58,8 +63,11 @@
|
||||
<p class="engine-card-desc">{{ $t('settings.storage.localDesc') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- MinIO -->
|
||||
<div :class="['engine-card', { active: drawerVisible && currentEngine === 'minio' }]" @click="openDrawer('minio')">
|
||||
<div
|
||||
v-if="isProviderAllowed('minio')"
|
||||
:class="['engine-card', { active: drawerVisible && currentEngine === 'minio' }]"
|
||||
@click="openDrawer('minio')"
|
||||
>
|
||||
<div class="engine-card-header">
|
||||
<h3>MinIO</h3>
|
||||
<t-tag v-if="minioAvailable" theme="success" variant="light" size="small">{{ $t('settings.storage.available') }}</t-tag>
|
||||
@@ -68,8 +76,11 @@
|
||||
<p class="engine-card-desc">{{ $t('settings.storage.minioDesc') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- COS -->
|
||||
<div :class="['engine-card', { active: drawerVisible && currentEngine === 'cos' }]" @click="openDrawer('cos')">
|
||||
<div
|
||||
v-if="isProviderAllowed('cos')"
|
||||
:class="['engine-card', { active: drawerVisible && currentEngine === 'cos' }]"
|
||||
@click="openDrawer('cos')"
|
||||
>
|
||||
<div class="engine-card-header">
|
||||
<h3>{{ $t('settings.storage.cosTitle') }}</h3>
|
||||
<t-tag theme="success" variant="light" size="small">{{ $t('settings.storage.configurable') }}</t-tag>
|
||||
@@ -77,8 +88,11 @@
|
||||
<p class="engine-card-desc">{{ $t('settings.storage.cosDesc') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- TOS -->
|
||||
<div :class="['engine-card', { active: drawerVisible && currentEngine === 'tos' }]" @click="openDrawer('tos')">
|
||||
<div
|
||||
v-if="isProviderAllowed('tos')"
|
||||
:class="['engine-card', { active: drawerVisible && currentEngine === 'tos' }]"
|
||||
@click="openDrawer('tos')"
|
||||
>
|
||||
<div class="engine-card-header">
|
||||
<h3>{{ $t('settings.storage.tosTitle') }}</h3>
|
||||
<t-tag theme="success" variant="light" size="small">{{ $t('settings.storage.configurable') }}</t-tag>
|
||||
@@ -86,8 +100,11 @@
|
||||
<p class="engine-card-desc">{{ $t('settings.storage.tosDesc') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- S3 -->
|
||||
<div :class="['engine-card', { active: drawerVisible && currentEngine === 's3' }]" @click="openDrawer('s3')">
|
||||
<div
|
||||
v-if="isProviderAllowed('s3')"
|
||||
:class="['engine-card', { active: drawerVisible && currentEngine === 's3' }]"
|
||||
@click="openDrawer('s3')"
|
||||
>
|
||||
<div class="engine-card-header">
|
||||
<h3>{{ $t('settings.storage.s3Title') }}</h3>
|
||||
<t-tag theme="success" variant="light" size="small">{{ $t('settings.storage.configurable') }}</t-tag>
|
||||
@@ -95,8 +112,11 @@
|
||||
<p class="engine-card-desc">{{ $t('settings.storage.s3Desc') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- OSS -->
|
||||
<div :class="['engine-card', { active: drawerVisible && currentEngine === 'oss' }]" @click="openDrawer('oss')">
|
||||
<div
|
||||
v-if="isProviderAllowed('oss')"
|
||||
:class="['engine-card', { active: drawerVisible && currentEngine === 'oss' }]"
|
||||
@click="openDrawer('oss')"
|
||||
>
|
||||
<div class="engine-card-header">
|
||||
<h3>{{ $t('settings.storage.ossTitle') }}</h3>
|
||||
<t-tag theme="success" variant="light" size="small">{{ $t('settings.storage.configurable') }}</t-tag>
|
||||
@@ -106,7 +126,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 配置抽屉 -->
|
||||
<t-drawer
|
||||
v-model:visible="drawerVisible"
|
||||
:header="drawerTitle"
|
||||
@@ -115,7 +134,6 @@
|
||||
@confirm="onSave"
|
||||
>
|
||||
<div class="drawer-content">
|
||||
<!-- Local -->
|
||||
<template v-if="currentEngine === 'local'">
|
||||
<div class="engine-info-block">
|
||||
<p class="engine-desc">{{ $t('settings.storage.localDesc') }}</p>
|
||||
@@ -132,7 +150,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- MinIO -->
|
||||
<template v-else-if="currentEngine === 'minio'">
|
||||
<div class="engine-info-block">
|
||||
<p class="engine-desc">{{ $t('settings.storage.minioDesc') }}</p>
|
||||
@@ -154,7 +171,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Docker mode -->
|
||||
<div v-if="config.minio.mode !== 'remote'">
|
||||
<div v-if="minioEnvAvailable" class="engine-hint success">
|
||||
{{ $t('settings.storage.minioDockerDetected') }}
|
||||
@@ -187,42 +203,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remote mode -->
|
||||
<div v-else>
|
||||
<div class="engine-hint">{{ $t('settings.storage.minioRemoteHint') }}</div>
|
||||
<div class="engine-form">
|
||||
<div class="form-item">
|
||||
<label class="form-label">Endpoint</label>
|
||||
<t-input
|
||||
v-model="config.minio.endpoint"
|
||||
placeholder="e.g. minio.example.com:9000"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.minio.endpoint" placeholder="e.g. minio.example.com:9000" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Access Key ID</label>
|
||||
<t-input
|
||||
v-model="config.minio.access_key_id"
|
||||
placeholder="MinIO Access Key"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.minio.access_key_id" placeholder="MinIO Access Key" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Secret Access Key</label>
|
||||
<t-input
|
||||
v-model="config.minio.secret_access_key"
|
||||
type="password"
|
||||
placeholder="MinIO Secret Key"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.minio.secret_access_key" type="password" placeholder="MinIO Secret Key" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.bucketName') }}</label>
|
||||
<t-input
|
||||
v-model="config.minio.bucket_name"
|
||||
:placeholder="$t('settings.storage.bucketPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.minio.bucket_name" :placeholder="$t('settings.storage.bucketPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item form-item--inline">
|
||||
<label class="form-label">Use SSL</label>
|
||||
@@ -230,17 +228,12 @@
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.pathPrefix') }}</label>
|
||||
<t-input
|
||||
v-model="config.minio.path_prefix"
|
||||
:placeholder="$t('settings.storage.prefixPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.minio.path_prefix" :placeholder="$t('settings.storage.prefixPlaceholder')" clearable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- COS -->
|
||||
<template v-else-if="currentEngine === 'cos'">
|
||||
<div class="engine-info-block">
|
||||
<p class="engine-desc">
|
||||
@@ -252,57 +245,31 @@
|
||||
<div class="engine-form">
|
||||
<div class="form-item">
|
||||
<label class="form-label">Secret ID</label>
|
||||
<t-input
|
||||
v-model="config.cos.secret_id"
|
||||
:placeholder="$t('settings.storage.cosSecretIdPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.cos.secret_id" :placeholder="$t('settings.storage.cosSecretIdPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Secret Key</label>
|
||||
<t-input
|
||||
v-model="config.cos.secret_key"
|
||||
type="password"
|
||||
:placeholder="$t('settings.storage.cosSecretKeyPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.cos.secret_key" type="password" :placeholder="$t('settings.storage.cosSecretKeyPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Region</label>
|
||||
<t-input
|
||||
v-model="config.cos.region"
|
||||
placeholder="e.g. ap-guangzhou"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.cos.region" placeholder="e.g. ap-guangzhou" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.bucketName') }}</label>
|
||||
<t-input
|
||||
v-model="config.cos.bucket_name"
|
||||
:placeholder="$t('settings.storage.bucketPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.cos.bucket_name" :placeholder="$t('settings.storage.bucketPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">App ID</label>
|
||||
<t-input
|
||||
v-model="config.cos.app_id"
|
||||
:placeholder="$t('settings.storage.cosAppIdPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.cos.app_id" :placeholder="$t('settings.storage.cosAppIdPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.pathPrefix') }}</label>
|
||||
<t-input
|
||||
v-model="config.cos.path_prefix"
|
||||
:placeholder="$t('settings.storage.prefixPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.cos.path_prefix" :placeholder="$t('settings.storage.prefixPlaceholder')" clearable />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- TOS -->
|
||||
<template v-else-if="currentEngine === 'tos'">
|
||||
<div class="engine-info-block">
|
||||
<p class="engine-desc">
|
||||
@@ -314,57 +281,31 @@
|
||||
<div class="engine-form">
|
||||
<div class="form-item">
|
||||
<label class="form-label">Endpoint</label>
|
||||
<t-input
|
||||
v-model="config.tos.endpoint"
|
||||
placeholder="e.g. https://tos-cn-beijing.volces.com"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.tos.endpoint" placeholder="e.g. https://tos-cn-beijing.volces.com" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Region</label>
|
||||
<t-input
|
||||
v-model="config.tos.region"
|
||||
placeholder="e.g. cn-beijing"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.tos.region" placeholder="e.g. cn-beijing" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Access Key</label>
|
||||
<t-input
|
||||
v-model="config.tos.access_key"
|
||||
:placeholder="$t('settings.storage.tosAccessKeyPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.tos.access_key" :placeholder="$t('settings.storage.tosAccessKeyPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Secret Key</label>
|
||||
<t-input
|
||||
v-model="config.tos.secret_key"
|
||||
type="password"
|
||||
:placeholder="$t('settings.storage.tosSecretKeyPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.tos.secret_key" type="password" :placeholder="$t('settings.storage.tosSecretKeyPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.bucketName') }}</label>
|
||||
<t-input
|
||||
v-model="config.tos.bucket_name"
|
||||
:placeholder="$t('settings.storage.bucketPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.tos.bucket_name" :placeholder="$t('settings.storage.bucketPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.pathPrefix') }}</label>
|
||||
<t-input
|
||||
v-model="config.tos.path_prefix"
|
||||
:placeholder="$t('settings.storage.prefixPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.tos.path_prefix" :placeholder="$t('settings.storage.prefixPlaceholder')" clearable />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- S3 -->
|
||||
<template v-else-if="currentEngine === 's3'">
|
||||
<div class="engine-info-block">
|
||||
<p class="engine-desc">
|
||||
@@ -376,57 +317,31 @@
|
||||
<div class="engine-form">
|
||||
<div class="form-item">
|
||||
<label class="form-label">Endpoint</label>
|
||||
<t-input
|
||||
v-model="config.s3.endpoint"
|
||||
placeholder="e.g. https://s3.amazonaws.com"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.s3.endpoint" placeholder="e.g. https://s3.amazonaws.com" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Region</label>
|
||||
<t-input
|
||||
v-model="config.s3.region"
|
||||
placeholder="e.g. us-east-1"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.s3.region" placeholder="e.g. us-east-1" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Access Key</label>
|
||||
<t-input
|
||||
v-model="config.s3.access_key"
|
||||
:placeholder="$t('settings.storage.s3AccessKeyPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.s3.access_key" :placeholder="$t('settings.storage.s3AccessKeyPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Secret Key</label>
|
||||
<t-input
|
||||
v-model="config.s3.secret_key"
|
||||
type="password"
|
||||
:placeholder="$t('settings.storage.s3SecretKeyPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.s3.secret_key" type="password" :placeholder="$t('settings.storage.s3SecretKeyPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.bucketName') }}</label>
|
||||
<t-input
|
||||
v-model="config.s3.bucket_name"
|
||||
:placeholder="$t('settings.storage.bucketPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.s3.bucket_name" :placeholder="$t('settings.storage.bucketPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.pathPrefix') }}</label>
|
||||
<t-input
|
||||
v-model="config.s3.path_prefix"
|
||||
:placeholder="$t('settings.storage.prefixPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.s3.path_prefix" :placeholder="$t('settings.storage.prefixPlaceholder')" clearable />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- OSS -->
|
||||
<template v-else-if="currentEngine === 'oss'">
|
||||
<div class="engine-info-block">
|
||||
<p class="engine-desc">
|
||||
@@ -438,55 +353,31 @@
|
||||
<div class="engine-form">
|
||||
<div class="form-item">
|
||||
<label class="form-label">Endpoint</label>
|
||||
<t-input
|
||||
v-model="config.oss.endpoint"
|
||||
placeholder="e.g. https://oss-cn-hangzhou.aliyuncs.com"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.oss.endpoint" placeholder="e.g. https://oss-cn-hangzhou.aliyuncs.com" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Region</label>
|
||||
<t-input
|
||||
v-model="config.oss.region"
|
||||
placeholder="e.g. cn-hangzhou"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.oss.region" placeholder="e.g. cn-hangzhou" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Access Key</label>
|
||||
<t-input
|
||||
v-model="config.oss.access_key"
|
||||
:placeholder="$t('settings.storage.ossAccessKeyPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.oss.access_key" :placeholder="$t('settings.storage.ossAccessKeyPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">Secret Key</label>
|
||||
<t-input
|
||||
v-model="config.oss.secret_key"
|
||||
type="password"
|
||||
:placeholder="$t('settings.storage.ossSecretKeyPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.oss.secret_key" type="password" :placeholder="$t('settings.storage.ossSecretKeyPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.bucketName') }}</label>
|
||||
<t-input
|
||||
v-model="config.oss.bucket_name"
|
||||
:placeholder="$t('settings.storage.bucketPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.oss.bucket_name" :placeholder="$t('settings.storage.bucketPlaceholder')" clearable />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">{{ $t('settings.storage.pathPrefix') }}</label>
|
||||
<t-input
|
||||
v-model="config.oss.path_prefix"
|
||||
:placeholder="$t('settings.storage.prefixPlaceholder')"
|
||||
clearable
|
||||
/>
|
||||
<t-input v-model="config.oss.path_prefix" :placeholder="$t('settings.storage.prefixPlaceholder')" clearable />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="form-item" v-if="currentEngine && currentEngine !== 'local'">
|
||||
<label class="form-label">{{ $t('settings.storage.testConnection') }}</label>
|
||||
<div class="api-test-section">
|
||||
@@ -511,13 +402,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
getStorageEngineConfig,
|
||||
updateStorageEngineConfig,
|
||||
getStorageEngineStatus,
|
||||
checkStorageEngine,
|
||||
getStorageEngineConfig,
|
||||
getStorageEngineStatus,
|
||||
updateStorageEngineConfig,
|
||||
type StorageEngineConfig,
|
||||
} from '@/api/system'
|
||||
|
||||
@@ -527,30 +418,9 @@ const defaultConfig = (): StorageEngineConfig => ({
|
||||
default_provider: 'local',
|
||||
local: { path_prefix: '' },
|
||||
minio: { mode: 'docker', endpoint: '', access_key_id: '', secret_access_key: '', bucket_name: '', use_ssl: false, path_prefix: '' },
|
||||
cos: {
|
||||
secret_id: '',
|
||||
secret_key: '',
|
||||
region: '',
|
||||
bucket_name: '',
|
||||
app_id: '',
|
||||
path_prefix: '',
|
||||
},
|
||||
tos: {
|
||||
endpoint: '',
|
||||
region: '',
|
||||
access_key: '',
|
||||
secret_key: '',
|
||||
bucket_name: '',
|
||||
path_prefix: '',
|
||||
},
|
||||
s3: {
|
||||
endpoint: '',
|
||||
region: '',
|
||||
access_key: '',
|
||||
secret_key: '',
|
||||
bucket_name: '',
|
||||
path_prefix: '',
|
||||
},
|
||||
cos: { secret_id: '', secret_key: '', region: '', bucket_name: '', app_id: '', path_prefix: '' },
|
||||
tos: { endpoint: '', region: '', access_key: '', secret_key: '', bucket_name: '', path_prefix: '' },
|
||||
s3: { endpoint: '', region: '', access_key: '', secret_key: '', bucket_name: '', path_prefix: '' },
|
||||
oss: {
|
||||
endpoint: '',
|
||||
region: '',
|
||||
@@ -567,11 +437,8 @@ const defaultConfig = (): StorageEngineConfig => ({
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const config = ref<StorageEngineConfig>(defaultConfig())
|
||||
const engineStatus = ref<{ local: boolean; minio: boolean; cos: boolean }>({
|
||||
local: true,
|
||||
minio: false,
|
||||
cos: true,
|
||||
})
|
||||
const allowedProviders = ref<string[] | null>(null)
|
||||
const engineStatus = ref<{ local: boolean; minio: boolean; cos: boolean }>({ local: true, minio: false, cos: true })
|
||||
const minioEnvAvailable = ref(false)
|
||||
const saving = ref(false)
|
||||
const saveMessage = ref('')
|
||||
@@ -591,14 +458,31 @@ const ossCheckResult = ref<{ ok: boolean; message: string } | null>(null)
|
||||
const drawerVisible = ref(false)
|
||||
const currentEngine = ref<string | null>(null)
|
||||
|
||||
const providerOptions = computed(() => [
|
||||
{ value: 'local', label: t('settings.storage.engineLocal'), allowed: isProviderAllowed('local') },
|
||||
{ value: 'minio', label: 'MinIO', allowed: isProviderAllowed('minio') },
|
||||
{ value: 'cos', label: t('settings.storage.engineCos'), allowed: isProviderAllowed('cos') },
|
||||
{ value: 'tos', label: t('settings.storage.engineTos'), allowed: isProviderAllowed('tos') },
|
||||
{ value: 's3', label: 'AWS S3', allowed: isProviderAllowed('s3') },
|
||||
{ value: 'oss', label: t('settings.storage.engineOss'), allowed: isProviderAllowed('oss') },
|
||||
])
|
||||
|
||||
const hasAllowedProviders = computed(() => (allowedProviders.value?.length ?? 0) > 0)
|
||||
|
||||
const currentCheckState = computed(() => {
|
||||
switch (currentEngine.value) {
|
||||
case 'minio': return { loading: checkingMinio.value, result: minioCheckResult.value, onCheck: onCheckMinio }
|
||||
case 'cos': return { loading: checkingCos.value, result: cosCheckResult.value, onCheck: onCheckCos }
|
||||
case 'tos': return { loading: checkingTos.value, result: tosCheckResult.value, onCheck: onCheckTos }
|
||||
case 's3': return { loading: checkingS3.value, result: s3CheckResult.value, onCheck: onCheckS3 }
|
||||
case 'oss': return { loading: checkingOss.value, result: ossCheckResult.value, onCheck: onCheckOss }
|
||||
default: return { loading: false, result: null, onCheck: () => {} }
|
||||
case 'minio':
|
||||
return { loading: checkingMinio.value, result: minioCheckResult.value, onCheck: onCheckMinio }
|
||||
case 'cos':
|
||||
return { loading: checkingCos.value, result: cosCheckResult.value, onCheck: onCheckCos }
|
||||
case 'tos':
|
||||
return { loading: checkingTos.value, result: tosCheckResult.value, onCheck: onCheckTos }
|
||||
case 's3':
|
||||
return { loading: checkingS3.value, result: s3CheckResult.value, onCheck: onCheckS3 }
|
||||
case 'oss':
|
||||
return { loading: checkingOss.value, result: ossCheckResult.value, onCheck: onCheckOss }
|
||||
default:
|
||||
return { loading: false, result: null, onCheck: () => undefined }
|
||||
}
|
||||
})
|
||||
|
||||
@@ -622,12 +506,21 @@ const minioAvailable = computed(() => {
|
||||
return minioEnvAvailable.value
|
||||
})
|
||||
|
||||
function isProviderAllowed(provider: string) {
|
||||
if (allowedProviders.value === null) return true
|
||||
return allowedProviders.value.includes(provider)
|
||||
}
|
||||
|
||||
function ensureAllowedDefaultProvider() {
|
||||
if (isProviderAllowed(config.value.default_provider)) return
|
||||
config.value.default_provider = allowedProviders.value?.[0] || 'local'
|
||||
}
|
||||
|
||||
function openDrawer(engine: string) {
|
||||
if (!isProviderAllowed(engine)) return
|
||||
currentEngine.value = engine
|
||||
drawerVisible.value = true
|
||||
saveMessage.value = ''
|
||||
|
||||
// Reset check results
|
||||
minioCheckResult.value = null
|
||||
cosCheckResult.value = null
|
||||
tosCheckResult.value = null
|
||||
@@ -653,7 +546,7 @@ async function loadConfig() {
|
||||
use_ssl: d.minio.use_ssl ?? false,
|
||||
path_prefix: d.minio.path_prefix || '',
|
||||
}
|
||||
: defaultConfig().minio!,
|
||||
: defaultConfig().minio,
|
||||
cos: d.cos
|
||||
? {
|
||||
secret_id: d.cos.secret_id || '',
|
||||
@@ -663,7 +556,7 @@ async function loadConfig() {
|
||||
app_id: d.cos.app_id || '',
|
||||
path_prefix: d.cos.path_prefix || '',
|
||||
}
|
||||
: defaultConfig().cos!,
|
||||
: defaultConfig().cos,
|
||||
tos: d.tos
|
||||
? {
|
||||
endpoint: d.tos.endpoint || '',
|
||||
@@ -673,7 +566,7 @@ async function loadConfig() {
|
||||
bucket_name: d.tos.bucket_name || '',
|
||||
path_prefix: d.tos.path_prefix || '',
|
||||
}
|
||||
: defaultConfig().tos!,
|
||||
: defaultConfig().tos,
|
||||
s3: d.s3
|
||||
? {
|
||||
endpoint: d.s3.endpoint || '',
|
||||
@@ -683,7 +576,7 @@ async function loadConfig() {
|
||||
bucket_name: d.s3.bucket_name || '',
|
||||
path_prefix: d.s3.path_prefix || '',
|
||||
}
|
||||
: defaultConfig().s3!,
|
||||
: defaultConfig().s3,
|
||||
oss: d.oss
|
||||
? {
|
||||
endpoint: d.oss.endpoint || '',
|
||||
@@ -696,7 +589,7 @@ async function loadConfig() {
|
||||
temp_bucket_name: d.oss.temp_bucket_name || '',
|
||||
temp_region: d.oss.temp_region || '',
|
||||
}
|
||||
: defaultConfig().oss!,
|
||||
: defaultConfig().oss,
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -708,6 +601,9 @@ async function loadStatus() {
|
||||
try {
|
||||
const res = await getStorageEngineStatus()
|
||||
const engines = res?.data?.engines ?? []
|
||||
allowedProviders.value = res?.data?.allowed_providers?.length
|
||||
? res.data.allowed_providers
|
||||
: engines.filter(e => e.allowed !== false).map(e => e.name)
|
||||
const status = { local: true, minio: false, cos: true }
|
||||
for (const e of engines) {
|
||||
if (e.name === 'local') status.local = e.available
|
||||
@@ -718,6 +614,7 @@ async function loadStatus() {
|
||||
minioEnvAvailable.value = res?.data?.minio_env_available ?? false
|
||||
} catch {
|
||||
engineStatus.value = { local: true, minio: false, cos: true }
|
||||
allowedProviders.value = ['local', 'minio', 'cos', 'tos', 's3', 'oss']
|
||||
minioEnvAvailable.value = false
|
||||
}
|
||||
}
|
||||
@@ -727,6 +624,7 @@ async function loadAll() {
|
||||
error.value = ''
|
||||
try {
|
||||
await Promise.all([loadConfig(), loadStatus()])
|
||||
ensureAllowedDefaultProvider()
|
||||
} catch (e: unknown) {
|
||||
error.value = e instanceof Error ? e.message : t('settings.storage.loadFailed')
|
||||
} finally {
|
||||
@@ -779,7 +677,6 @@ function buildPayload(): StorageEngineConfig {
|
||||
secret_key: (config.value.oss?.secret_key || '').trim(),
|
||||
bucket_name: (config.value.oss?.bucket_name || '').trim(),
|
||||
path_prefix: (config.value.oss?.path_prefix || '').trim(),
|
||||
// Temp bucket fields: not exposed in UI; server manages these independently
|
||||
use_temp_bucket: config.value.oss?.use_temp_bucket ?? false,
|
||||
temp_bucket_name: (config.value.oss?.temp_bucket_name || '').trim(),
|
||||
temp_region: (config.value.oss?.temp_region || '').trim(),
|
||||
@@ -791,8 +688,10 @@ async function onSave() {
|
||||
saving.value = true
|
||||
saveMessage.value = ''
|
||||
try {
|
||||
ensureAllowedDefaultProvider()
|
||||
await updateStorageEngineConfig(buildPayload())
|
||||
await loadStatus()
|
||||
ensureAllowedDefaultProvider()
|
||||
saveSuccess.value = true
|
||||
saveMessage.value = t('settings.storage.saveSuccess')
|
||||
drawerVisible.value = false
|
||||
@@ -965,7 +864,6 @@ onMounted(loadAll)
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// ---- 引擎卡片布局 ----
|
||||
.engine-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
@@ -1024,7 +922,6 @@ onMounted(loadAll)
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// ---- 抽屉内容 ----
|
||||
.drawer-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1040,7 +937,6 @@ onMounted(loadAll)
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框样式
|
||||
:deep(.t-input),
|
||||
:deep(.t-select) {
|
||||
width: 100%;
|
||||
|
||||
@@ -333,6 +333,10 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) {
|
||||
if provider == "" {
|
||||
provider = "local"
|
||||
}
|
||||
if !isStorageProviderAllowed(provider) {
|
||||
c.Error(errors.NewBadRequestError("Storage provider is not allowed by STORAGE_ALLOW_LIST"))
|
||||
return
|
||||
}
|
||||
oldProvider := kb.GetStorageProvider()
|
||||
if oldProvider == "" {
|
||||
oldProvider = "local"
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
stderrors "errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/agent/tools"
|
||||
@@ -130,6 +131,11 @@ func (h *KnowledgeBaseHandler) CreateKnowledgeBase(c *gin.Context) {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
provider := strings.ToLower(strings.TrimSpace(req.GetStorageProvider()))
|
||||
if provider != "" && !isStorageProviderAllowed(provider) {
|
||||
c.Error(apperrors.NewBadRequestError("Storage provider is not allowed by STORAGE_ALLOW_LIST"))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Infof(ctx, "Creating knowledge base, name: %s", secutils.SanitizeForLog(req.Name))
|
||||
// Create knowledge base using the service
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const storageAllowListEnv = "STORAGE_ALLOW_LIST"
|
||||
|
||||
var supportedStorageProviders = []string{"local", "minio", "cos", "tos", "s3", "oss"}
|
||||
|
||||
func getSupportedStorageProviders() []string {
|
||||
providers := make([]string, len(supportedStorageProviders))
|
||||
copy(providers, supportedStorageProviders)
|
||||
return providers
|
||||
}
|
||||
|
||||
func getAllowedStorageProviders() map[string]bool {
|
||||
raw := strings.TrimSpace(os.Getenv(storageAllowListEnv))
|
||||
allowed := make(map[string]bool, len(supportedStorageProviders))
|
||||
|
||||
if raw == "" {
|
||||
for _, provider := range supportedStorageProviders {
|
||||
allowed[provider] = true
|
||||
}
|
||||
return allowed
|
||||
}
|
||||
|
||||
for _, item := range strings.FieldsFunc(raw, func(r rune) bool {
|
||||
switch r {
|
||||
case ',', ';', '|', '\n', '\t', ' ':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
provider := strings.ToLower(strings.TrimSpace(item))
|
||||
if provider == "" {
|
||||
continue
|
||||
}
|
||||
for _, supported := range supportedStorageProviders {
|
||||
if provider == supported {
|
||||
allowed[provider] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allowed
|
||||
}
|
||||
|
||||
func isStorageProviderAllowed(provider string) bool {
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
if provider == "" {
|
||||
return true
|
||||
}
|
||||
return getAllowedStorageProviders()[provider]
|
||||
}
|
||||
|
||||
func firstAllowedStorageProvider() string {
|
||||
allowed := getAllowedStorageProviders()
|
||||
for _, provider := range supportedStorageProviders {
|
||||
if allowed[provider] {
|
||||
return provider
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -444,6 +444,7 @@ func (h *SystemHandler) isTOSEnvAvailable() bool {
|
||||
// StorageEngineStatusItem describes one storage engine's availability and description.
|
||||
type StorageEngineStatusItem struct {
|
||||
Name string `json:"name"` // "local", "minio", "cos", "tos"
|
||||
Allowed bool `json:"allowed"`
|
||||
Available bool `json:"available"` // whether the engine can be used
|
||||
Description string `json:"description"` // short description for UI
|
||||
}
|
||||
@@ -451,6 +452,7 @@ type StorageEngineStatusItem struct {
|
||||
// GetStorageEngineStatusResponse is the response for GET /system/storage-engine-status.
|
||||
type GetStorageEngineStatusResponse struct {
|
||||
Engines []StorageEngineStatusItem `json:"engines"`
|
||||
AllowedProviders []string `json:"allowed_providers"`
|
||||
MinioEnvAvailable bool `json:"minio_env_available"`
|
||||
}
|
||||
|
||||
@@ -466,18 +468,27 @@ func (h *SystemHandler) GetStorageEngineStatus(c *gin.Context) {
|
||||
minioEnvAvailable := h.isMinioEnvAvailable()
|
||||
cosConfigured := h.isCOSConfigured(c)
|
||||
tosConfigured := h.isTOSConfigured(c)
|
||||
s3Configured := h.isS3Configured(c)
|
||||
ossConfigured := h.isOSSConfigured(c)
|
||||
allowed := getAllowedStorageProviders()
|
||||
allowedProviders := make([]string, 0, len(supportedStorageProviders))
|
||||
for _, provider := range getSupportedStorageProviders() {
|
||||
if allowed[provider] {
|
||||
allowedProviders = append(allowedProviders, provider)
|
||||
}
|
||||
}
|
||||
engines := []StorageEngineStatusItem{
|
||||
{Name: "local", Available: true, Description: "本地文件系统存储,仅适合单机部署"},
|
||||
{Name: "minio", Available: minioConfigured || minioEnvAvailable, Description: "S3 兼容的自托管对象存储,适合内网和私有云部署"},
|
||||
{Name: "cos", Available: cosConfigured, Description: "腾讯云对象存储服务,适合公有云部署,支持 CDN 加速"},
|
||||
{Name: "tos", Available: tosConfigured, Description: "火山引擎对象存储服务,适合公有云部署"},
|
||||
{Name: "oss", Available: ossConfigured, Description: "阿里云对象存储服务,适合公有云部署,支持 S3 兼容协议"},
|
||||
{Name: "local", Allowed: allowed["local"], Available: true, Description: "本地文件系统存储,仅适合单机部署"},
|
||||
{Name: "minio", Allowed: allowed["minio"], Available: minioConfigured || minioEnvAvailable, Description: "S3 兼容的自托管对象存储,适合内网和私有云部署"},
|
||||
{Name: "cos", Allowed: allowed["cos"], Available: cosConfigured, Description: "腾讯云对象存储服务,适合公有云部署,支持 CDN 加速"},
|
||||
{Name: "tos", Allowed: allowed["tos"], Available: tosConfigured, Description: "火山引擎对象存储服务,适合公有云部署"},
|
||||
{Name: "s3", Allowed: allowed["s3"], Available: s3Configured, Description: "AWS S3 与兼容对象存储服务,适合公有云与混合云部署"},
|
||||
{Name: "oss", Allowed: allowed["oss"], Available: ossConfigured, Description: "阿里云对象存储服务,适合公有云部署,支持 S3 兼容协议"},
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": GetStorageEngineStatusResponse{Engines: engines, MinioEnvAvailable: minioEnvAvailable},
|
||||
"data": GetStorageEngineStatusResponse{Engines: engines, AllowedProviders: allowedProviders, MinioEnvAvailable: minioEnvAvailable},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -607,6 +618,10 @@ func (h *SystemHandler) CheckStorageEngine(c *gin.Context) {
|
||||
c.JSON(400, gin.H{"code": 1, "msg": "请求体格式错误"})
|
||||
return
|
||||
}
|
||||
if !isStorageProviderAllowed(req.Provider) {
|
||||
c.JSON(403, gin.H{"code": 1, "msg": "该存储引擎已被禁用"})
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Provider {
|
||||
case "minio":
|
||||
@@ -624,6 +639,16 @@ func (h *SystemHandler) CheckStorageEngine(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SystemHandler) isS3Configured(c *gin.Context) bool {
|
||||
if v, exists := c.Get(types.TenantInfoContextKey.String()); exists {
|
||||
if tenant, ok := v.(*types.Tenant); ok && tenant != nil && tenant.StorageEngineConfig != nil && tenant.StorageEngineConfig.S3 != nil {
|
||||
s3Conf := tenant.StorageEngineConfig.S3
|
||||
return s3Conf.Endpoint != "" && s3Conf.Region != "" && s3Conf.AccessKey != "" && s3Conf.SecretKey != "" && s3Conf.BucketName != ""
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *SystemHandler) checkMinio(c *gin.Context, ctx context.Context, cfg *types.MinIOEngineConfig) {
|
||||
if cfg == nil {
|
||||
c.JSON(200, gin.H{"code": 0, "data": StorageCheckResponse{OK: false, Message: "未提供 MinIO 配置"}})
|
||||
|
||||
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -842,6 +843,19 @@ func (h *TenantHandler) updateTenantStorageEngineConfigInternal(c *gin.Context)
|
||||
c.Error(errors.NewValidationError("Invalid request data").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
provider := strings.ToLower(strings.TrimSpace(cfg.DefaultProvider))
|
||||
if provider == "" {
|
||||
provider = firstAllowedStorageProvider()
|
||||
}
|
||||
if provider == "" {
|
||||
c.Error(errors.NewBadRequestError("No storage provider is allowed by STORAGE_ALLOW_LIST"))
|
||||
return
|
||||
}
|
||||
if !isStorageProviderAllowed(provider) {
|
||||
c.Error(errors.NewBadRequestError("Storage provider is not allowed by STORAGE_ALLOW_LIST"))
|
||||
return
|
||||
}
|
||||
cfg.DefaultProvider = provider
|
||||
tenant, _ := types.TenantInfoFromContext(ctx)
|
||||
if tenant == nil {
|
||||
logger.Error(ctx, "Tenant is empty")
|
||||
|
||||
Reference in New Issue
Block a user