Unverified Commit fa68cbad authored by InCerryGit's avatar InCerryGit Committed by GitHub
Browse files

Merge branch 'Wei-Shaw:main' into main

parents 995ef134 0f033930
...@@ -1979,6 +1979,8 @@ export default { ...@@ -1979,6 +1979,8 @@ export default {
expiresAt: 'Expires At', expiresAt: 'Expires At',
actions: 'Actions' actions: 'Actions'
}, },
allPrivacyModes: 'All Privacy States',
privacyUnset: 'Unset',
privacyTrainingOff: 'Training data sharing disabled', privacyTrainingOff: 'Training data sharing disabled',
privacyCfBlocked: 'Blocked by Cloudflare, training may still be on', privacyCfBlocked: 'Blocked by Cloudflare, training may still be on',
privacyFailed: 'Failed to disable training', privacyFailed: 'Failed to disable training',
...@@ -3494,7 +3496,12 @@ export default { ...@@ -3494,7 +3496,12 @@ export default {
typeRequest: 'Request', typeRequest: 'Request',
typeAuth: 'Auth', typeAuth: 'Auth',
typeRouting: 'Routing', typeRouting: 'Routing',
typeInternal: 'Internal' typeInternal: 'Internal',
endpoint: 'Endpoint',
requestType: 'Type',
requestTypeSync: 'Sync',
requestTypeStream: 'Stream',
requestTypeWs: 'WS'
}, },
// Error Details Modal // Error Details Modal
errorDetails: { errorDetails: {
...@@ -3580,6 +3587,16 @@ export default { ...@@ -3580,6 +3587,16 @@ export default {
latency: 'Request Duration', latency: 'Request Duration',
businessLimited: 'Business Limited', businessLimited: 'Business Limited',
requestPath: 'Request Path', requestPath: 'Request Path',
inboundEndpoint: 'Inbound Endpoint',
upstreamEndpoint: 'Upstream Endpoint',
requestedModel: 'Requested Model',
upstreamModel: 'Upstream Model',
requestType: 'Request Type',
requestTypeUnknown: 'Unknown',
requestTypeSync: 'Sync',
requestTypeStream: 'Stream',
requestTypeWs: 'WebSocket',
modelMapping: 'Model Mapping',
timings: 'Timings', timings: 'Timings',
auth: 'Auth', auth: 'Auth',
routing: 'Routing', routing: 'Routing',
......
...@@ -2017,6 +2017,8 @@ export default { ...@@ -2017,6 +2017,8 @@ export default {
expiresAt: '过期时间', expiresAt: '过期时间',
actions: '操作' actions: '操作'
}, },
allPrivacyModes: '全部Privacy状态',
privacyUnset: '未设置',
privacyTrainingOff: '已关闭训练数据共享', privacyTrainingOff: '已关闭训练数据共享',
privacyCfBlocked: '被 Cloudflare 拦截,训练可能仍开启', privacyCfBlocked: '被 Cloudflare 拦截,训练可能仍开启',
privacyFailed: '关闭训练数据共享失败', privacyFailed: '关闭训练数据共享失败',
...@@ -3659,7 +3661,12 @@ export default { ...@@ -3659,7 +3661,12 @@ export default {
typeRequest: '请求', typeRequest: '请求',
typeAuth: '认证', typeAuth: '认证',
typeRouting: '路由', typeRouting: '路由',
typeInternal: '内部' typeInternal: '内部',
endpoint: '端点',
requestType: '类型',
requestTypeSync: '同步',
requestTypeStream: '流式',
requestTypeWs: 'WS'
}, },
// Error Details Modal // Error Details Modal
errorDetails: { errorDetails: {
...@@ -3745,6 +3752,16 @@ export default { ...@@ -3745,6 +3752,16 @@ export default {
latency: '请求时长', latency: '请求时长',
businessLimited: '业务限制', businessLimited: '业务限制',
requestPath: '请求路径', requestPath: '请求路径',
inboundEndpoint: '入站端点',
upstreamEndpoint: '上游端点',
requestedModel: '请求模型',
upstreamModel: '上游模型',
requestType: '请求类型',
requestTypeUnknown: '未知',
requestTypeSync: '同步',
requestTypeStream: '流式',
requestTypeWs: 'WebSocket',
modelMapping: '模型映射',
timings: '时序信息', timings: '时序信息',
auth: '认证', auth: '认证',
routing: '路由', routing: '路由',
......
...@@ -581,7 +581,7 @@ const { ...@@ -581,7 +581,7 @@ const {
handlePageSizeChange: baseHandlePageSizeChange handlePageSizeChange: baseHandlePageSizeChange
} = useTableLoader<Account, any>({ } = useTableLoader<Account, any>({
fetchFn: adminAPI.accounts.list, fetchFn: adminAPI.accounts.list,
initialParams: { platform: '', type: '', status: '', group: '', search: '' } initialParams: { platform: '', type: '', status: '', privacy_mode: '', group: '', search: '' }
}) })
const { const {
...@@ -758,6 +758,7 @@ const refreshAccountsIncrementally = async () => { ...@@ -758,6 +758,7 @@ const refreshAccountsIncrementally = async () => {
platform?: string platform?: string
type?: string type?: string
status?: string status?: string
privacy_mode?: string
group?: string group?: string
search?: string search?: string
......
...@@ -1655,7 +1655,7 @@ ...@@ -1655,7 +1655,7 @@
<button <button
type="button" type="button"
@click="testSmtpConnection" @click="testSmtpConnection"
:disabled="testingSmtp" :disabled="testingSmtp || loadFailed"
class="btn btn-secondary btn-sm" class="btn btn-secondary btn-sm"
> >
<svg v-if="testingSmtp" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24"> <svg v-if="testingSmtp" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
...@@ -1725,6 +1725,11 @@ ...@@ -1725,6 +1725,11 @@
v-model="form.smtp_password" v-model="form.smtp_password"
type="password" type="password"
class="input" class="input"
autocomplete="new-password"
autocapitalize="off"
spellcheck="false"
@keydown="smtpPasswordManuallyEdited = true"
@paste="smtpPasswordManuallyEdited = true"
:placeholder=" :placeholder="
form.smtp_password_configured form.smtp_password_configured
? t('admin.settings.smtp.passwordConfiguredPlaceholder') ? t('admin.settings.smtp.passwordConfiguredPlaceholder')
...@@ -1807,7 +1812,7 @@ ...@@ -1807,7 +1812,7 @@
<button <button
type="button" type="button"
@click="sendTestEmail" @click="sendTestEmail"
:disabled="sendingTestEmail || !testEmailAddress" :disabled="sendingTestEmail || !testEmailAddress || loadFailed"
class="btn btn-secondary" class="btn btn-secondary"
> >
<svg <svg
...@@ -1853,7 +1858,7 @@ ...@@ -1853,7 +1858,7 @@
<!-- Save Button --> <!-- Save Button -->
<div v-show="activeTab !== 'backup' && activeTab !== 'data'" class="flex justify-end"> <div v-show="activeTab !== 'backup' && activeTab !== 'data'" class="flex justify-end">
<button type="submit" :disabled="saving" class="btn btn-primary"> <button type="submit" :disabled="saving || loadFailed" class="btn btn-primary">
<svg v-if="saving" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24"> <svg v-if="saving" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle <circle
class="opacity-25" class="opacity-25"
...@@ -1924,9 +1929,11 @@ const settingsTabs = [ ...@@ -1924,9 +1929,11 @@ const settingsTabs = [
const { copyToClipboard } = useClipboard() const { copyToClipboard } = useClipboard()
const loading = ref(true) const loading = ref(true)
const loadFailed = ref(false)
const saving = ref(false) const saving = ref(false)
const testingSmtp = ref(false) const testingSmtp = ref(false)
const sendingTestEmail = ref(false) const sendingTestEmail = ref(false)
const smtpPasswordManuallyEdited = ref(false)
const testEmailAddress = ref('') const testEmailAddress = ref('')
const registrationEmailSuffixWhitelistTags = ref<string[]>([]) const registrationEmailSuffixWhitelistTags = ref<string[]>([])
const registrationEmailSuffixWhitelistDraft = ref('') const registrationEmailSuffixWhitelistDraft = ref('')
...@@ -2201,6 +2208,7 @@ function removeEndpoint(index: number) { ...@@ -2201,6 +2208,7 @@ function removeEndpoint(index: number) {
async function loadSettings() { async function loadSettings() {
loading.value = true loading.value = true
loadFailed.value = false
try { try {
const settings = await adminAPI.settings.getSettings() const settings = await adminAPI.settings.getSettings()
Object.assign(form, settings) Object.assign(form, settings)
...@@ -2218,9 +2226,11 @@ async function loadSettings() { ...@@ -2218,9 +2226,11 @@ async function loadSettings() {
) )
registrationEmailSuffixWhitelistDraft.value = '' registrationEmailSuffixWhitelistDraft.value = ''
form.smtp_password = '' form.smtp_password = ''
smtpPasswordManuallyEdited.value = false
form.turnstile_secret_key = '' form.turnstile_secret_key = ''
form.linuxdo_connect_client_secret = '' form.linuxdo_connect_client_secret = ''
} catch (error: any) { } catch (error: any) {
loadFailed.value = true
appStore.showError( appStore.showError(
t('admin.settings.failedToLoad') + ': ' + (error.message || t('common.unknownError')) t('admin.settings.failedToLoad') + ': ' + (error.message || t('common.unknownError'))
) )
...@@ -2372,6 +2382,7 @@ async function saveSettings() { ...@@ -2372,6 +2382,7 @@ async function saveSettings() {
) )
registrationEmailSuffixWhitelistDraft.value = '' registrationEmailSuffixWhitelistDraft.value = ''
form.smtp_password = '' form.smtp_password = ''
smtpPasswordManuallyEdited.value = false
form.turnstile_secret_key = '' form.turnstile_secret_key = ''
form.linuxdo_connect_client_secret = '' form.linuxdo_connect_client_secret = ''
// Refresh cached settings so sidebar/header update immediately // Refresh cached settings so sidebar/header update immediately
...@@ -2390,11 +2401,12 @@ async function saveSettings() { ...@@ -2390,11 +2401,12 @@ async function saveSettings() {
async function testSmtpConnection() { async function testSmtpConnection() {
testingSmtp.value = true testingSmtp.value = true
try { try {
const smtpPasswordForTest = smtpPasswordManuallyEdited.value ? form.smtp_password : ''
const result = await adminAPI.settings.testSmtpConnection({ const result = await adminAPI.settings.testSmtpConnection({
smtp_host: form.smtp_host, smtp_host: form.smtp_host,
smtp_port: form.smtp_port, smtp_port: form.smtp_port,
smtp_username: form.smtp_username, smtp_username: form.smtp_username,
smtp_password: form.smtp_password, smtp_password: smtpPasswordForTest,
smtp_use_tls: form.smtp_use_tls smtp_use_tls: form.smtp_use_tls
}) })
// API returns { message: "..." } on success, errors are thrown as exceptions // API returns { message: "..." } on success, errors are thrown as exceptions
...@@ -2416,12 +2428,13 @@ async function sendTestEmail() { ...@@ -2416,12 +2428,13 @@ async function sendTestEmail() {
sendingTestEmail.value = true sendingTestEmail.value = true
try { try {
const smtpPasswordForSend = smtpPasswordManuallyEdited.value ? form.smtp_password : ''
const result = await adminAPI.settings.sendTestEmail({ const result = await adminAPI.settings.sendTestEmail({
email: testEmailAddress.value, email: testEmailAddress.value,
smtp_host: form.smtp_host, smtp_host: form.smtp_host,
smtp_port: form.smtp_port, smtp_port: form.smtp_port,
smtp_username: form.smtp_username, smtp_username: form.smtp_username,
smtp_password: form.smtp_password, smtp_password: smtpPasswordForSend,
smtp_from_email: form.smtp_from_email, smtp_from_email: form.smtp_from_email,
smtp_from_name: form.smtp_from_name, smtp_from_name: form.smtp_from_name,
smtp_use_tls: form.smtp_use_tls smtp_use_tls: form.smtp_use_tls
......
...@@ -59,7 +59,28 @@ ...@@ -59,7 +59,28 @@
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900"> <div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.model') }}</div> <div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.model') }}</div>
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white"> <div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
{{ detail.model || '—' }} <template v-if="hasModelMapping(detail)">
<span class="font-mono">{{ detail.requested_model }}</span>
<span class="mx-1 text-gray-400"></span>
<span class="font-mono text-primary-600 dark:text-primary-400">{{ detail.upstream_model }}</span>
</template>
<template v-else>
{{ displayModel(detail) || '' }}
</template>
</div>
</div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.inboundEndpoint') }}</div>
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
{{ detail.inbound_endpoint || '—' }}
</div>
</div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.upstreamEndpoint') }}</div>
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
{{ detail.upstream_endpoint || '—' }}
</div> </div>
</div> </div>
...@@ -72,6 +93,13 @@ ...@@ -72,6 +93,13 @@
</div> </div>
</div> </div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.requestType') }}</div>
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
{{ formatRequestTypeLabel(detail.request_type) }}
</div>
</div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900"> <div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.message') }}</div> <div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.message') }}</div>
<div class="mt-1 truncate text-sm font-medium text-gray-900 dark:text-white" :title="detail.message"> <div class="mt-1 truncate text-sm font-medium text-gray-900 dark:text-white" :title="detail.message">
...@@ -213,6 +241,31 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean { ...@@ -213,6 +241,31 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean {
return phase === 'upstream' && owner === 'provider' return phase === 'upstream' && owner === 'provider'
} }
function formatRequestTypeLabel(type: number | null | undefined): string {
switch (type) {
case 1: return t('admin.ops.errorDetail.requestTypeSync')
case 2: return t('admin.ops.errorDetail.requestTypeStream')
case 3: return t('admin.ops.errorDetail.requestTypeWs')
default: return t('admin.ops.errorDetail.requestTypeUnknown')
}
}
function hasModelMapping(d: OpsErrorDetail | null): boolean {
if (!d) return false
const requested = String(d.requested_model || '').trim()
const upstream = String(d.upstream_model || '').trim()
return !!requested && !!upstream && requested !== upstream
}
function displayModel(d: OpsErrorDetail | null): string {
if (!d) return ''
const upstream = String(d.upstream_model || '').trim()
if (upstream) return upstream
const requested = String(d.requested_model || '').trim()
if (requested) return requested
return String(d.model || '').trim()
}
const correlatedUpstream = ref<OpsErrorDetail[]>([]) const correlatedUpstream = ref<OpsErrorDetail[]>([])
const correlatedUpstreamLoading = ref(false) const correlatedUpstreamLoading = ref(false)
......
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400"> <th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
{{ t('admin.ops.errorLog.type') }} {{ t('admin.ops.errorLog.type') }}
</th> </th>
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
{{ t('admin.ops.errorLog.endpoint') }}
</th>
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400"> <th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
{{ t('admin.ops.errorLog.platform') }} {{ t('admin.ops.errorLog.platform') }}
</th> </th>
...@@ -42,7 +45,7 @@ ...@@ -42,7 +45,7 @@
</thead> </thead>
<tbody class="divide-y divide-gray-100 dark:divide-dark-700"> <tbody class="divide-y divide-gray-100 dark:divide-dark-700">
<tr v-if="rows.length === 0"> <tr v-if="rows.length === 0">
<td colspan="9" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500"> <td colspan="10" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500">
{{ t('admin.ops.errorLog.noErrors') }} {{ t('admin.ops.errorLog.noErrors') }}
</td> </td>
</tr> </tr>
...@@ -74,6 +77,18 @@ ...@@ -74,6 +77,18 @@
</span> </span>
</td> </td>
<!-- Endpoint -->
<td class="px-4 py-2">
<div class="max-w-[160px]">
<el-tooltip v-if="log.inbound_endpoint" :content="formatEndpointTooltip(log)" placement="top" :show-after="500">
<span class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
{{ log.inbound_endpoint }}
</span>
</el-tooltip>
<span v-else class="text-xs text-gray-400">-</span>
</div>
</td>
<!-- Platform --> <!-- Platform -->
<td class="whitespace-nowrap px-4 py-2"> <td class="whitespace-nowrap px-4 py-2">
<span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold uppercase text-gray-600 dark:bg-dark-700 dark:text-gray-300"> <span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold uppercase text-gray-600 dark:bg-dark-700 dark:text-gray-300">
...@@ -83,11 +98,22 @@ ...@@ -83,11 +98,22 @@
<!-- Model --> <!-- Model -->
<td class="px-4 py-2"> <td class="px-4 py-2">
<div class="max-w-[120px] truncate" :title="log.model"> <div class="max-w-[160px]">
<span v-if="log.model" class="font-mono text-[11px] text-gray-700 dark:text-gray-300"> <template v-if="hasModelMapping(log)">
{{ log.model }} <el-tooltip :content="modelMappingTooltip(log)" placement="top" :show-after="500">
</span> <span class="flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
<span v-else class="text-xs text-gray-400">-</span> <span class="truncate">{{ log.requested_model }}</span>
<span class="flex-shrink-0 text-gray-400"></span>
<span class="truncate text-primary-600 dark:text-primary-400">{{ log.upstream_model }}</span>
</span>
</el-tooltip>
</template>
<template v-else>
<span v-if="displayModel(log)" class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300" :title="displayModel(log)">
{{ displayModel(log) }}
</span>
<span v-else class="text-xs text-gray-400">-</span>
</template>
</div> </div>
</td> </td>
...@@ -138,6 +164,12 @@ ...@@ -138,6 +164,12 @@
> >
{{ log.severity }} {{ log.severity }}
</span> </span>
<span
v-if="log.request_type != null && log.request_type > 0"
class="rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold text-gray-600 dark:bg-dark-700 dark:text-gray-300"
>
{{ formatRequestType(log.request_type) }}
</span>
</div> </div>
</td> </td>
...@@ -193,6 +225,44 @@ function isUpstreamRow(log: OpsErrorLog): boolean { ...@@ -193,6 +225,44 @@ function isUpstreamRow(log: OpsErrorLog): boolean {
return phase === 'upstream' && owner === 'provider' return phase === 'upstream' && owner === 'provider'
} }
function formatEndpointTooltip(log: OpsErrorLog): string {
const parts: string[] = []
if (log.inbound_endpoint) parts.push(`Inbound: ${log.inbound_endpoint}`)
if (log.upstream_endpoint) parts.push(`Upstream: ${log.upstream_endpoint}`)
return parts.join('\n') || ''
}
function hasModelMapping(log: OpsErrorLog): boolean {
const requested = String(log.requested_model || '').trim()
const upstream = String(log.upstream_model || '').trim()
return !!requested && !!upstream && requested !== upstream
}
function modelMappingTooltip(log: OpsErrorLog): string {
const requested = String(log.requested_model || '').trim()
const upstream = String(log.upstream_model || '').trim()
if (!requested && !upstream) return ''
if (requested && upstream) return `${requested}${upstream}`
return upstream || requested
}
function displayModel(log: OpsErrorLog): string {
const upstream = String(log.upstream_model || '').trim()
if (upstream) return upstream
const requested = String(log.requested_model || '').trim()
if (requested) return requested
return String(log.model || '').trim()
}
function formatRequestType(type: number | null | undefined): string {
switch (type) {
case 1: return t('admin.ops.errorLog.requestTypeSync')
case 2: return t('admin.ops.errorLog.requestTypeStream')
case 3: return t('admin.ops.errorLog.requestTypeWs')
default: return ''
}
}
function getTypeBadge(log: OpsErrorLog): { label: string; className: string } { function getTypeBadge(log: OpsErrorLog): { label: string; className: string } {
const phase = String(log.phase || '').toLowerCase() const phase = String(log.phase || '').toLowerCase()
const owner = String(log.error_owner || '').toLowerCase() const owner = String(log.error_owner || '').toLowerCase()
...@@ -263,4 +333,4 @@ function formatSmartMessage(msg: string): string { ...@@ -263,4 +333,4 @@ function formatSmartMessage(msg: string): string {
return msg.length > 200 ? msg.substring(0, 200) + '...' : msg return msg.length > 200 ? msg.substring(0, 200) + '...' : msg
} }
</script> </script>
\ No newline at end of file
...@@ -344,7 +344,7 @@ onMounted(async () => { ...@@ -344,7 +344,7 @@ onMounted(async () => {
<div class="text-xs font-semibold text-gray-700 dark:text-gray-200">运行时日志配置(实时生效)</div> <div class="text-xs font-semibold text-gray-700 dark:text-gray-200">运行时日志配置(实时生效)</div>
<span v-if="runtimeLoading" class="text-xs text-gray-500">加载中...</span> <span v-if="runtimeLoading" class="text-xs text-gray-500">加载中...</span>
</div> </div>
<div class="grid grid-cols-1 gap-3 md:grid-cols-6"> <div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-6">
<label class="text-xs text-gray-600 dark:text-gray-300"> <label class="text-xs text-gray-600 dark:text-gray-300">
级别 级别
<select v-model="runtimeConfig.level" class="input mt-1"> <select v-model="runtimeConfig.level" class="input mt-1">
...@@ -374,21 +374,27 @@ onMounted(async () => { ...@@ -374,21 +374,27 @@ onMounted(async () => {
保留天数 保留天数
<input v-model.number="runtimeConfig.retention_days" type="number" min="1" max="3650" class="input mt-1" /> <input v-model.number="runtimeConfig.retention_days" type="number" min="1" max="3650" class="input mt-1" />
</label> </label>
<div class="flex items-end gap-2"> <div class="md:col-span-2 xl:col-span-6">
<label class="inline-flex items-center gap-2 text-xs text-gray-600 dark:text-gray-300"> <div class="grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end">
<input v-model="runtimeConfig.caller" type="checkbox" /> <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
caller <label class="inline-flex items-center gap-2 text-xs text-gray-600 dark:text-gray-300">
</label> <input v-model="runtimeConfig.caller" type="checkbox" />
<label class="inline-flex items-center gap-2 text-xs text-gray-600 dark:text-gray-300"> caller
<input v-model="runtimeConfig.enable_sampling" type="checkbox" /> </label>
sampling <label class="inline-flex items-center gap-2 text-xs text-gray-600 dark:text-gray-300">
</label> <input v-model="runtimeConfig.enable_sampling" type="checkbox" />
<button type="button" class="btn btn-primary btn-sm" :disabled="runtimeSaving" @click="saveRuntimeConfig"> sampling
{{ runtimeSaving ? '保存中...' : '保存并生效' }} </label>
</button> </div>
<button type="button" class="btn btn-secondary btn-sm" :disabled="runtimeSaving" @click="resetRuntimeConfig"> <div class="flex flex-wrap items-center gap-2 lg:justify-end">
回滚默认值 <button type="button" class="btn btn-primary btn-sm" :disabled="runtimeSaving" @click="saveRuntimeConfig">
</button> {{ runtimeSaving ? '保存中...' : '保存并生效' }}
</button>
<button type="button" class="btn btn-secondary btn-sm" :disabled="runtimeSaving" @click="resetRuntimeConfig">
回滚默认值
</button>
</div>
</div>
</div> </div>
</div> </div>
<p v-if="health.last_error" class="mt-2 text-xs text-red-600 dark:text-red-400">最近写入错误:{{ health.last_error }}</p> <p v-if="health.last_error" class="mt-2 text-xs text-red-600 dark:text-red-400">最近写入错误:{{ health.last_error }}</p>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment