fix frontend review followups and pin deploy image tags
This commit is contained in:
@@ -6,9 +6,9 @@
|
||||
# Stage 3: Final minimal image
|
||||
# =============================================================================
|
||||
|
||||
ARG NODE_IMAGE=node:24-alpine
|
||||
ARG GOLANG_IMAGE=golang:1.26.2-alpine
|
||||
ARG ALPINE_IMAGE=alpine:3.20
|
||||
ARG NODE_IMAGE=node:24.14.0-alpine
|
||||
ARG GOLANG_IMAGE=golang:1.24.2-alpine
|
||||
ARG ALPINE_IMAGE=alpine:3.21.3
|
||||
ARG GOPROXY=https://goproxy.cn,direct
|
||||
ARG GOSUMDB=sum.golang.google.cn
|
||||
|
||||
|
||||
@@ -901,6 +901,16 @@
|
||||
@confirm="handleMixedChannelConfirm"
|
||||
@cancel="handleMixedChannelCancel"
|
||||
/>
|
||||
<ConfirmDialog
|
||||
:show="showErrorCodeWarningDialog"
|
||||
:title="t('common.confirm')"
|
||||
:message="errorCodeWarningMessage"
|
||||
:confirm-text="t('common.confirm')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="true"
|
||||
@confirm="confirmErrorCodeWarning"
|
||||
@cancel="clearErrorCodeWarningDialog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -1018,6 +1028,9 @@ const submitting = ref(false)
|
||||
const showMixedChannelWarning = ref(false)
|
||||
const mixedChannelWarningMessage = ref('')
|
||||
const pendingUpdatesForConfirm = ref<Record<string, unknown> | null>(null)
|
||||
const showErrorCodeWarningDialog = ref(false)
|
||||
const pendingErrorCodeWarningAction = ref<(() => void) | null>(null)
|
||||
const pendingErrorCodeWarningCode = ref<number | null>(null)
|
||||
const baseUrl = ref('')
|
||||
const modelRestrictionMode = ref<'whitelist' | 'mapping'>('whitelist')
|
||||
const allowedModels = ref<string[]>([])
|
||||
@@ -1093,19 +1106,45 @@ const addPresetMapping = (from: string, to: string) => {
|
||||
modelMappings.value.push({ from, to })
|
||||
}
|
||||
|
||||
const errorCodeWarningMessage = computed(() => {
|
||||
if (pendingErrorCodeWarningCode.value === 429) {
|
||||
return t('admin.accounts.customErrorCodes429Warning')
|
||||
}
|
||||
if (pendingErrorCodeWarningCode.value === 529) {
|
||||
return t('admin.accounts.customErrorCodes529Warning')
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const clearErrorCodeWarningDialog = () => {
|
||||
showErrorCodeWarningDialog.value = false
|
||||
pendingErrorCodeWarningAction.value = null
|
||||
pendingErrorCodeWarningCode.value = null
|
||||
}
|
||||
|
||||
const requestErrorCodeWarning = (code: number, action: () => void) => {
|
||||
pendingErrorCodeWarningCode.value = code
|
||||
pendingErrorCodeWarningAction.value = action
|
||||
showErrorCodeWarningDialog.value = true
|
||||
}
|
||||
|
||||
const confirmErrorCodeWarning = () => {
|
||||
const action = pendingErrorCodeWarningAction.value
|
||||
clearErrorCodeWarningDialog()
|
||||
action?.()
|
||||
}
|
||||
|
||||
// Error code helpers
|
||||
const toggleErrorCode = (code: number) => {
|
||||
const index = selectedErrorCodes.value.indexOf(code)
|
||||
if (index === -1) {
|
||||
// Adding code - check for 429/529 warning
|
||||
if (code === 429) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes429Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(429, () => selectedErrorCodes.value.push(code))
|
||||
return
|
||||
} else if (code === 529) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes529Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(529, () => selectedErrorCodes.value.push(code))
|
||||
return
|
||||
}
|
||||
selectedErrorCodes.value.push(code)
|
||||
} else {
|
||||
@@ -1125,13 +1164,17 @@ const addCustomErrorCode = () => {
|
||||
}
|
||||
// Check for 429/529 warning
|
||||
if (code === 429) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes429Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(429, () => {
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
})
|
||||
return
|
||||
} else if (code === 529) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes529Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(529, () => {
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
})
|
||||
return
|
||||
}
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
@@ -1297,6 +1340,7 @@ const handleClose = () => {
|
||||
mixedChannelWarningMessage.value = ''
|
||||
pendingUpdatesForConfirm.value = null
|
||||
mixedChannelConfirmed.value = false
|
||||
clearErrorCodeWarningDialog()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
@@ -1465,6 +1509,7 @@ watch(
|
||||
mixedChannelWarningMessage.value = ''
|
||||
pendingUpdatesForConfirm.value = null
|
||||
mixedChannelConfirmed.value = false
|
||||
clearErrorCodeWarningDialog()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2792,6 +2792,16 @@
|
||||
@confirm="handleMixedChannelConfirm"
|
||||
@cancel="handleMixedChannelCancel"
|
||||
/>
|
||||
<ConfirmDialog
|
||||
:show="showErrorCodeWarningDialog"
|
||||
:title="t('common.confirm')"
|
||||
:message="errorCodeWarningMessage"
|
||||
:confirm-text="t('common.confirm')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="true"
|
||||
@confirm="confirmErrorCodeWarning"
|
||||
@cancel="clearErrorCodeWarningDialog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -3020,6 +3030,9 @@ const mixedChannelWarningDetails = ref<{ groupName: string; currentPlatform: str
|
||||
)
|
||||
const mixedChannelWarningRawMessage = ref('')
|
||||
const mixedChannelWarningAction = ref<(() => Promise<void>) | null>(null)
|
||||
const showErrorCodeWarningDialog = ref(false)
|
||||
const pendingErrorCodeWarningAction = ref<(() => void) | null>(null)
|
||||
const pendingErrorCodeWarningCode = ref<number | null>(null)
|
||||
const antigravityMixedChannelConfirmed = ref(false)
|
||||
const showAdvancedOAuth = ref(false)
|
||||
const showGeminiHelpDialog = ref(false)
|
||||
@@ -3406,19 +3419,45 @@ const addAntigravityPresetMapping = (from: string, to: string) => {
|
||||
antigravityModelMappings.value.push({ from, to })
|
||||
}
|
||||
|
||||
const errorCodeWarningMessage = computed(() => {
|
||||
if (pendingErrorCodeWarningCode.value === 429) {
|
||||
return t('admin.accounts.customErrorCodes429Warning')
|
||||
}
|
||||
if (pendingErrorCodeWarningCode.value === 529) {
|
||||
return t('admin.accounts.customErrorCodes529Warning')
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const clearErrorCodeWarningDialog = () => {
|
||||
showErrorCodeWarningDialog.value = false
|
||||
pendingErrorCodeWarningAction.value = null
|
||||
pendingErrorCodeWarningCode.value = null
|
||||
}
|
||||
|
||||
const requestErrorCodeWarning = (code: number, action: () => void) => {
|
||||
pendingErrorCodeWarningCode.value = code
|
||||
pendingErrorCodeWarningAction.value = action
|
||||
showErrorCodeWarningDialog.value = true
|
||||
}
|
||||
|
||||
const confirmErrorCodeWarning = () => {
|
||||
const action = pendingErrorCodeWarningAction.value
|
||||
clearErrorCodeWarningDialog()
|
||||
action?.()
|
||||
}
|
||||
|
||||
// Error code toggle helper
|
||||
const toggleErrorCode = (code: number) => {
|
||||
const index = selectedErrorCodes.value.indexOf(code)
|
||||
if (index === -1) {
|
||||
// Adding code - check for 429/529 warning
|
||||
if (code === 429) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes429Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(429, () => selectedErrorCodes.value.push(code))
|
||||
return
|
||||
} else if (code === 529) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes529Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(529, () => selectedErrorCodes.value.push(code))
|
||||
return
|
||||
}
|
||||
selectedErrorCodes.value.push(code)
|
||||
} else {
|
||||
@@ -3439,13 +3478,17 @@ const addCustomErrorCode = () => {
|
||||
}
|
||||
// Check for 429/529 warning
|
||||
if (code === 429) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes429Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(429, () => {
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
})
|
||||
return
|
||||
} else if (code === 529) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes529Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(529, () => {
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
})
|
||||
return
|
||||
}
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
@@ -3561,6 +3604,7 @@ const clearMixedChannelDialog = () => {
|
||||
mixedChannelWarningDetails.value = null
|
||||
mixedChannelWarningRawMessage.value = ''
|
||||
mixedChannelWarningAction.value = null
|
||||
clearErrorCodeWarningDialog()
|
||||
}
|
||||
|
||||
const openMixedChannelDialog = (opts: {
|
||||
|
||||
@@ -1743,6 +1743,16 @@
|
||||
@confirm="handleMixedChannelConfirm"
|
||||
@cancel="handleMixedChannelCancel"
|
||||
/>
|
||||
<ConfirmDialog
|
||||
:show="showErrorCodeWarningDialog"
|
||||
:title="t('common.confirm')"
|
||||
:message="errorCodeWarningMessage"
|
||||
:confirm-text="t('common.confirm')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="true"
|
||||
@confirm="confirmErrorCodeWarning"
|
||||
@cancel="clearErrorCodeWarningDialog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -1864,6 +1874,9 @@ const mixedChannelWarningDetails = ref<{ groupName: string; currentPlatform: str
|
||||
)
|
||||
const mixedChannelWarningRawMessage = ref('')
|
||||
const mixedChannelWarningAction = ref<(() => Promise<void>) | null>(null)
|
||||
const showErrorCodeWarningDialog = ref(false)
|
||||
const pendingErrorCodeWarningAction = ref<(() => void) | null>(null)
|
||||
const pendingErrorCodeWarningCode = ref<number | null>(null)
|
||||
const antigravityMixedChannelConfirmed = ref(false)
|
||||
|
||||
// Quota control state (Anthropic OAuth/SetupToken only)
|
||||
@@ -2353,19 +2366,45 @@ const addAntigravityPresetMapping = (from: string, to: string) => {
|
||||
antigravityModelMappings.value.push({ from, to })
|
||||
}
|
||||
|
||||
const errorCodeWarningMessage = computed(() => {
|
||||
if (pendingErrorCodeWarningCode.value === 429) {
|
||||
return t('admin.accounts.customErrorCodes429Warning')
|
||||
}
|
||||
if (pendingErrorCodeWarningCode.value === 529) {
|
||||
return t('admin.accounts.customErrorCodes529Warning')
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const clearErrorCodeWarningDialog = () => {
|
||||
showErrorCodeWarningDialog.value = false
|
||||
pendingErrorCodeWarningAction.value = null
|
||||
pendingErrorCodeWarningCode.value = null
|
||||
}
|
||||
|
||||
const requestErrorCodeWarning = (code: number, action: () => void) => {
|
||||
pendingErrorCodeWarningCode.value = code
|
||||
pendingErrorCodeWarningAction.value = action
|
||||
showErrorCodeWarningDialog.value = true
|
||||
}
|
||||
|
||||
const confirmErrorCodeWarning = () => {
|
||||
const action = pendingErrorCodeWarningAction.value
|
||||
clearErrorCodeWarningDialog()
|
||||
action?.()
|
||||
}
|
||||
|
||||
// Error code toggle helper
|
||||
const toggleErrorCode = (code: number) => {
|
||||
const index = selectedErrorCodes.value.indexOf(code)
|
||||
if (index === -1) {
|
||||
// Adding code - check for 429/529 warning
|
||||
if (code === 429) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes429Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(429, () => selectedErrorCodes.value.push(code))
|
||||
return
|
||||
} else if (code === 529) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes529Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(529, () => selectedErrorCodes.value.push(code))
|
||||
return
|
||||
}
|
||||
selectedErrorCodes.value.push(code)
|
||||
} else {
|
||||
@@ -2386,13 +2425,17 @@ const addCustomErrorCode = () => {
|
||||
}
|
||||
// Check for 429/529 warning
|
||||
if (code === 429) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes429Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(429, () => {
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
})
|
||||
return
|
||||
} else if (code === 529) {
|
||||
if (!confirm(t('admin.accounts.customErrorCodes529Warning'))) {
|
||||
return
|
||||
}
|
||||
requestErrorCodeWarning(529, () => {
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
})
|
||||
return
|
||||
}
|
||||
selectedErrorCodes.value.push(code)
|
||||
customErrorCodeInput.value = null
|
||||
@@ -2624,6 +2667,7 @@ const clearMixedChannelDialog = () => {
|
||||
mixedChannelWarningDetails.value = null
|
||||
mixedChannelWarningRawMessage.value = ''
|
||||
mixedChannelWarningAction.value = null
|
||||
clearErrorCodeWarningDialog()
|
||||
}
|
||||
|
||||
const openMixedChannelDialog = (opts: {
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<button
|
||||
class="sora-gallery-card-action"
|
||||
title="删除"
|
||||
@click.stop="handleDelete(item.id)"
|
||||
@click.stop="requestDelete(item.id)"
|
||||
>
|
||||
🗑
|
||||
</button>
|
||||
@@ -119,6 +119,16 @@
|
||||
@save="handleSaveFromPreview"
|
||||
@download="handleDownloadUrl"
|
||||
/>
|
||||
<ConfirmDialog
|
||||
:show="showDeleteConfirmDialog"
|
||||
:title="t('common.delete')"
|
||||
:message="t('sora.confirmDelete')"
|
||||
:confirm-text="t('common.delete')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
danger
|
||||
@confirm="confirmDelete"
|
||||
@cancel="closeDeleteConfirmDialog"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -127,6 +137,7 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import soraAPI, { type SoraGeneration } from '@/api/sora'
|
||||
import { getPersistedPageSize } from '@/composables/usePersistedPageSize'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import SoraMediaPreview from './SoraMediaPreview.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -142,6 +153,8 @@ const hasMore = ref(true)
|
||||
const activeFilter = ref('all')
|
||||
const previewVisible = ref(false)
|
||||
const previewItem = ref<SoraGeneration | null>(null)
|
||||
const showDeleteConfirmDialog = ref(false)
|
||||
const pendingDeleteId = ref<number | null>(null)
|
||||
|
||||
const filters = computed(() => [
|
||||
{ value: 'all', label: t('sora.filterAll') },
|
||||
@@ -217,8 +230,17 @@ function openPreview(item: SoraGeneration) {
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
function closeDeleteConfirmDialog() {
|
||||
showDeleteConfirmDialog.value = false
|
||||
pendingDeleteId.value = null
|
||||
}
|
||||
|
||||
function requestDelete(id: number) {
|
||||
pendingDeleteId.value = id
|
||||
showDeleteConfirmDialog.value = true
|
||||
}
|
||||
|
||||
async function handleDelete(id: number) {
|
||||
if (!confirm(t('sora.confirmDelete'))) return
|
||||
try {
|
||||
await soraAPI.deleteGeneration(id)
|
||||
items.value = items.value.filter(i => i.id !== id)
|
||||
@@ -227,6 +249,13 @@ async function handleDelete(id: number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
const id = pendingDeleteId.value
|
||||
closeDeleteConfirmDialog()
|
||||
if (id == null) return
|
||||
await handleDelete(id)
|
||||
}
|
||||
|
||||
function handleDownload(item: SoraGeneration) {
|
||||
if (item.media_url) {
|
||||
window.open(item.media_url, '_blank')
|
||||
|
||||
@@ -382,11 +382,11 @@ export default {
|
||||
passwordLabel: 'Password',
|
||||
passwordPlaceholder: 'Enter your password',
|
||||
createPasswordPlaceholder: 'Create a strong password',
|
||||
passwordHint: 'At least 6 characters',
|
||||
passwordHint: 'At least 8 characters',
|
||||
emailRequired: 'Email is required',
|
||||
invalidEmail: 'Please enter a valid email address',
|
||||
passwordRequired: 'Password is required',
|
||||
passwordMinLength: 'Password must be at least 6 characters',
|
||||
passwordMinLength: 'Password must be at least 8 characters',
|
||||
loginFailed: 'Login failed. Please check your credentials and try again.',
|
||||
registrationFailed: 'Registration failed. Please try again.',
|
||||
emailSuffixNotAllowed: 'This email domain is not allowed for registration.',
|
||||
|
||||
@@ -382,11 +382,11 @@ export default {
|
||||
passwordLabel: '密码',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
createPasswordPlaceholder: '创建一个安全的密码',
|
||||
passwordHint: '至少 6 个字符',
|
||||
passwordHint: '至少 8 个字符',
|
||||
emailRequired: '请输入邮箱',
|
||||
invalidEmail: '请输入有效的邮箱地址',
|
||||
passwordRequired: '请输入密码',
|
||||
passwordMinLength: '密码至少需要 6 个字符',
|
||||
passwordMinLength: '密码至少需要 8 个字符',
|
||||
loginFailed: '登录失败,请检查您的凭据后重试。',
|
||||
registrationFailed: '注册失败,请重试。',
|
||||
emailSuffixNotAllowed: '该邮箱域名不在允许注册范围内。',
|
||||
|
||||
@@ -300,6 +300,16 @@
|
||||
<span>{{ t('admin.accounts.dataExportIncludeProxies') }}</span>
|
||||
</label>
|
||||
</ConfirmDialog>
|
||||
<ConfirmDialog
|
||||
:show="showBulkActionConfirmDialog"
|
||||
:title="bulkActionConfirmTitle"
|
||||
:message="t('common.confirm')"
|
||||
:confirm-text="bulkActionConfirmText"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="pendingBulkAction === 'delete'"
|
||||
@confirm="confirmBulkAction"
|
||||
@cancel="closeBulkActionConfirmDialog"
|
||||
/>
|
||||
<ErrorPassthroughRulesModal :show="showErrorPassthrough" @close="showErrorPassthrough = false" />
|
||||
<TLSFingerprintProfilesModal :show="showTLSFingerprintProfiles" @close="showTLSFingerprintProfiles = false" />
|
||||
</AppLayout>
|
||||
@@ -376,6 +386,7 @@ const includeProxyOnExport = ref(true)
|
||||
const showBulkEdit = ref(false)
|
||||
const showTempUnsched = ref(false)
|
||||
const showDeleteDialog = ref(false)
|
||||
const showBulkActionConfirmDialog = ref(false)
|
||||
const showReAuth = ref(false)
|
||||
const showTest = ref(false)
|
||||
const showStats = ref(false)
|
||||
@@ -387,6 +398,7 @@ const deletingAcc = ref<Account | null>(null)
|
||||
const reAuthAcc = ref<Account | null>(null)
|
||||
const testingAcc = ref<Account | null>(null)
|
||||
const statsAcc = ref<Account | null>(null)
|
||||
const pendingBulkAction = ref<'delete' | 'reset-status' | 'refresh-token' | null>(null)
|
||||
const showSchedulePanel = ref(false)
|
||||
const scheduleAcc = ref<Account | null>(null)
|
||||
const scheduleModelOptions = ref<SelectOption[]>([])
|
||||
@@ -1023,9 +1035,60 @@ const toggleSelectAllVisible = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
toggleVisible(target.checked)
|
||||
}
|
||||
const handleBulkDelete = async () => { if(!confirm(t('common.confirm'))) return; try { await Promise.all(selIds.value.map(id => adminAPI.accounts.delete(id))); clearSelection(); reload() } catch (error) { console.error('Failed to bulk delete accounts:', error) } }
|
||||
const handleBulkResetStatus = async () => {
|
||||
if (!confirm(t('common.confirm'))) return
|
||||
const bulkActionConfirmTitle = computed(() => {
|
||||
switch (pendingBulkAction.value) {
|
||||
case 'delete':
|
||||
return t('common.delete')
|
||||
case 'reset-status':
|
||||
return t('admin.accounts.bulkActions.resetStatus')
|
||||
case 'refresh-token':
|
||||
return t('admin.accounts.bulkActions.refreshToken')
|
||||
default:
|
||||
return t('common.confirm')
|
||||
}
|
||||
})
|
||||
const bulkActionConfirmText = computed(() =>
|
||||
pendingBulkAction.value === 'delete' ? t('common.delete') : t('common.confirm')
|
||||
)
|
||||
const closeBulkActionConfirmDialog = () => {
|
||||
showBulkActionConfirmDialog.value = false
|
||||
pendingBulkAction.value = null
|
||||
}
|
||||
const openBulkActionConfirmDialog = (action: 'delete' | 'reset-status' | 'refresh-token') => {
|
||||
pendingBulkAction.value = action
|
||||
showBulkActionConfirmDialog.value = true
|
||||
}
|
||||
const confirmBulkAction = async () => {
|
||||
const action = pendingBulkAction.value
|
||||
closeBulkActionConfirmDialog()
|
||||
if (action === 'delete') {
|
||||
await performBulkDelete()
|
||||
return
|
||||
}
|
||||
if (action === 'reset-status') {
|
||||
await performBulkResetStatus()
|
||||
return
|
||||
}
|
||||
if (action === 'refresh-token') {
|
||||
await performBulkRefreshToken()
|
||||
}
|
||||
}
|
||||
const handleBulkDelete = () => {
|
||||
openBulkActionConfirmDialog('delete')
|
||||
}
|
||||
const performBulkDelete = async () => {
|
||||
try {
|
||||
await Promise.all(selIds.value.map(id => adminAPI.accounts.delete(id)))
|
||||
clearSelection()
|
||||
reload()
|
||||
} catch (error) {
|
||||
console.error('Failed to bulk delete accounts:', error)
|
||||
}
|
||||
}
|
||||
const handleBulkResetStatus = () => {
|
||||
openBulkActionConfirmDialog('reset-status')
|
||||
}
|
||||
const performBulkResetStatus = async () => {
|
||||
try {
|
||||
const result = await adminAPI.accounts.batchClearError(selIds.value)
|
||||
if (result.failed > 0) {
|
||||
@@ -1040,8 +1103,10 @@ const handleBulkResetStatus = async () => {
|
||||
appStore.showError(String(error))
|
||||
}
|
||||
}
|
||||
const handleBulkRefreshToken = async () => {
|
||||
if (!confirm(t('common.confirm'))) return
|
||||
const handleBulkRefreshToken = () => {
|
||||
openBulkActionConfirmDialog('refresh-token')
|
||||
}
|
||||
const performBulkRefreshToken = async () => {
|
||||
try {
|
||||
const result = await adminAPI.accounts.batchRefresh(selIds.value)
|
||||
if (result.failed > 0) {
|
||||
|
||||
@@ -168,14 +168,14 @@
|
||||
type="button"
|
||||
class="btn btn-secondary btn-xs"
|
||||
:disabled="restoringId === record.id"
|
||||
@click="restoreBackup(record.id)"
|
||||
@click="requestRestoreBackup(record.id)"
|
||||
>
|
||||
{{ restoringId === record.id ? t('common.loading') : t('admin.backup.actions.restore') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger btn-xs"
|
||||
@click="removeBackup(record.id)"
|
||||
@click="requestRemoveBackup(record.id)"
|
||||
>
|
||||
{{ t('common.delete') }}
|
||||
</button>
|
||||
@@ -193,6 +193,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
:show="showBackupConfirmDialog"
|
||||
:title="pendingBackupAction === 'restore' ? t('admin.backup.actions.restore') : t('common.delete')"
|
||||
:message="pendingBackupAction === 'restore' ? t('admin.backup.actions.restoreConfirm') : t('admin.backup.actions.deleteConfirm')"
|
||||
:confirm-text="pendingBackupAction === 'restore' ? t('admin.backup.actions.restore') : t('common.delete')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="pendingBackupAction === 'delete'"
|
||||
@confirm="confirmBackupAction"
|
||||
@cancel="closeBackupConfirmDialog"
|
||||
/>
|
||||
|
||||
<!-- Cloudflare R2 Setup Guide Modal -->
|
||||
<teleport to="body">
|
||||
<transition name="modal">
|
||||
@@ -284,6 +295,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { adminAPI } from '@/api'
|
||||
import { useAppStore } from '@/stores'
|
||||
import type { BackupS3Config, BackupScheduleConfig, BackupRecord } from '@/api/admin/backup'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
@@ -317,6 +329,9 @@ const loadingBackups = ref(false)
|
||||
const creatingBackup = ref(false)
|
||||
const restoringId = ref('')
|
||||
const manualExpireDays = ref(14)
|
||||
const showBackupConfirmDialog = ref(false)
|
||||
const pendingBackupAction = ref<'restore' | 'delete' | null>(null)
|
||||
const pendingBackupId = ref('')
|
||||
|
||||
// Polling
|
||||
const pollingTimer = ref<ReturnType<typeof setInterval> | null>(null)
|
||||
@@ -546,8 +561,37 @@ async function downloadBackup(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function openBackupConfirmDialog(action: 'restore' | 'delete', id: string) {
|
||||
pendingBackupAction.value = action
|
||||
pendingBackupId.value = id
|
||||
showBackupConfirmDialog.value = true
|
||||
}
|
||||
|
||||
function closeBackupConfirmDialog() {
|
||||
showBackupConfirmDialog.value = false
|
||||
pendingBackupAction.value = null
|
||||
pendingBackupId.value = ''
|
||||
}
|
||||
|
||||
async function confirmBackupAction() {
|
||||
const action = pendingBackupAction.value
|
||||
const id = pendingBackupId.value
|
||||
closeBackupConfirmDialog()
|
||||
if (!id) return
|
||||
if (action === 'restore') {
|
||||
await restoreBackup(id)
|
||||
return
|
||||
}
|
||||
if (action === 'delete') {
|
||||
await removeBackup(id)
|
||||
}
|
||||
}
|
||||
|
||||
function requestRestoreBackup(id: string) {
|
||||
openBackupConfirmDialog('restore', id)
|
||||
}
|
||||
|
||||
async function restoreBackup(id: string) {
|
||||
if (!window.confirm(t('admin.backup.actions.restoreConfirm'))) return
|
||||
const password = window.prompt(t('admin.backup.actions.restorePasswordPrompt'))
|
||||
if (!password) return
|
||||
restoringId.value = id
|
||||
@@ -565,8 +609,11 @@ async function restoreBackup(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function requestRemoveBackup(id: string) {
|
||||
openBackupConfirmDialog('delete', id)
|
||||
}
|
||||
|
||||
async function removeBackup(id: string) {
|
||||
if (!window.confirm(t('admin.backup.actions.deleteConfirm'))) return
|
||||
try {
|
||||
await adminAPI.backup.deleteBackup(id)
|
||||
appStore.showSuccess(t('admin.backup.actions.deleted'))
|
||||
|
||||
@@ -2511,6 +2511,16 @@
|
||||
@close="showProviderDialog = false"
|
||||
@save="handleSaveProvider"
|
||||
/>
|
||||
<ConfirmDialog
|
||||
:show="showAdminApiKeyConfirmDialog"
|
||||
:title="pendingAdminApiKeyAction === 'regenerate' ? t('admin.settings.adminApiKey.regenerate') : t('admin.settings.adminApiKey.delete')"
|
||||
:message="pendingAdminApiKeyAction === 'regenerate' ? t('admin.settings.adminApiKey.regenerateConfirm') : t('admin.settings.adminApiKey.deleteConfirm')"
|
||||
:confirm-text="pendingAdminApiKeyAction === 'regenerate' ? t('admin.settings.adminApiKey.regenerate') : t('common.delete')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="pendingAdminApiKeyAction === 'delete'"
|
||||
@confirm="confirmAdminApiKeyAction"
|
||||
@cancel="closeAdminApiKeyConfirmDialog"
|
||||
/>
|
||||
<ConfirmDialog :show="showDeleteProviderDialog" :title="t('admin.settings.payment.deleteProvider')" :message="t('admin.settings.payment.deleteProviderConfirm')" :confirm-text="t('common.delete')" danger @confirm="handleDeleteProvider" @cancel="showDeleteProviderDialog = false" />
|
||||
</div>
|
||||
</AppLayout>
|
||||
@@ -2583,6 +2593,8 @@ const adminApiKeyExists = ref(false)
|
||||
const adminApiKeyMasked = ref('')
|
||||
const adminApiKeyOperating = ref(false)
|
||||
const newAdminApiKey = ref('')
|
||||
const showAdminApiKeyConfirmDialog = ref(false)
|
||||
const pendingAdminApiKeyAction = ref<'regenerate' | 'delete' | null>(null)
|
||||
const subscriptionGroups = ref<AdminGroup[]>([])
|
||||
|
||||
// Overload Cooldown (529) 状态
|
||||
@@ -3269,13 +3281,33 @@ async function createAdminApiKey() {
|
||||
}
|
||||
}
|
||||
|
||||
async function regenerateAdminApiKey() {
|
||||
if (!confirm(t('admin.settings.adminApiKey.regenerateConfirm'))) return
|
||||
await createAdminApiKey()
|
||||
function openAdminApiKeyConfirmDialog(action: 'regenerate' | 'delete') {
|
||||
pendingAdminApiKeyAction.value = action
|
||||
showAdminApiKeyConfirmDialog.value = true
|
||||
}
|
||||
|
||||
async function deleteAdminApiKey() {
|
||||
if (!confirm(t('admin.settings.adminApiKey.deleteConfirm'))) return
|
||||
function closeAdminApiKeyConfirmDialog() {
|
||||
showAdminApiKeyConfirmDialog.value = false
|
||||
pendingAdminApiKeyAction.value = null
|
||||
}
|
||||
|
||||
async function confirmAdminApiKeyAction() {
|
||||
const action = pendingAdminApiKeyAction.value
|
||||
closeAdminApiKeyConfirmDialog()
|
||||
if (action === 'regenerate') {
|
||||
await createAdminApiKey()
|
||||
return
|
||||
}
|
||||
if (action === 'delete') {
|
||||
await performDeleteAdminApiKey()
|
||||
}
|
||||
}
|
||||
|
||||
async function regenerateAdminApiKey() {
|
||||
openAdminApiKeyConfirmDialog('regenerate')
|
||||
}
|
||||
|
||||
async function performDeleteAdminApiKey() {
|
||||
adminApiKeyOperating.value = true
|
||||
try {
|
||||
await adminAPI.settings.deleteAdminApiKey()
|
||||
@@ -3290,6 +3322,10 @@ async function deleteAdminApiKey() {
|
||||
}
|
||||
}
|
||||
|
||||
function deleteAdminApiKey() {
|
||||
openAdminApiKeyConfirmDialog('delete')
|
||||
}
|
||||
|
||||
function copyNewKey() {
|
||||
navigator.clipboard
|
||||
.writeText(newAdminApiKey.value)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { opsAPI, type OpsRuntimeLogConfig, type OpsSystemLog, type OpsSystemLogSinkHealth } from '@/api/admin/ops'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import Pagination from '@/components/common/Pagination.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import { useAppStore } from '@/stores'
|
||||
@@ -32,6 +33,8 @@ const health = ref<OpsSystemLogSinkHealth>({
|
||||
|
||||
const runtimeLoading = ref(false)
|
||||
const runtimeSaving = ref(false)
|
||||
const showConfirmDialog = ref(false)
|
||||
const pendingConfirmAction = ref<'reset-runtime' | 'cleanup' | null>(null)
|
||||
const runtimeConfig = reactive<OpsRuntimeLogConfig>({
|
||||
level: 'info',
|
||||
enable_sampling: false,
|
||||
@@ -249,10 +252,31 @@ const saveRuntimeConfig = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const resetRuntimeConfig = async () => {
|
||||
const ok = window.confirm('确认回滚为启动配置(env/yaml)并立即生效?')
|
||||
if (!ok) return
|
||||
const closeConfirmDialog = () => {
|
||||
showConfirmDialog.value = false
|
||||
pendingConfirmAction.value = null
|
||||
}
|
||||
|
||||
const openConfirmDialog = (action: 'reset-runtime' | 'cleanup') => {
|
||||
pendingConfirmAction.value = action
|
||||
showConfirmDialog.value = true
|
||||
}
|
||||
|
||||
const confirmDialogTitle = computed(() =>
|
||||
pendingConfirmAction.value === 'cleanup' ? '清理系统日志' : '回滚运行时配置'
|
||||
)
|
||||
|
||||
const confirmDialogMessage = computed(() =>
|
||||
pendingConfirmAction.value === 'cleanup'
|
||||
? '确认按当前筛选条件清理系统日志?该操作不可撤销。'
|
||||
: '确认回滚为启动配置(env/yaml)并立即生效?'
|
||||
)
|
||||
|
||||
const resetRuntimeConfig = () => {
|
||||
openConfirmDialog('reset-runtime')
|
||||
}
|
||||
|
||||
const performResetRuntimeConfig = async () => {
|
||||
runtimeSaving.value = true
|
||||
try {
|
||||
const saved = await opsAPI.resetRuntimeLogConfig()
|
||||
@@ -273,9 +297,11 @@ const resetRuntimeConfig = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const cleanupCurrentFilter = async () => {
|
||||
const ok = window.confirm('确认按当前筛选条件清理系统日志?该操作不可撤销。')
|
||||
if (!ok) return
|
||||
const cleanupCurrentFilter = () => {
|
||||
openConfirmDialog('cleanup')
|
||||
}
|
||||
|
||||
const performCleanupCurrentFilter = async () => {
|
||||
try {
|
||||
const payload = {
|
||||
start_time: toRFC3339(filters.start_time),
|
||||
@@ -300,6 +326,18 @@ const cleanupCurrentFilter = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDialogAction = async () => {
|
||||
const action = pendingConfirmAction.value
|
||||
closeConfirmDialog()
|
||||
if (action === 'reset-runtime') {
|
||||
await performResetRuntimeConfig()
|
||||
return
|
||||
}
|
||||
if (action === 'cleanup') {
|
||||
await performCleanupCurrentFilter()
|
||||
}
|
||||
}
|
||||
|
||||
const resetFilters = () => {
|
||||
filters.time_range = '1h'
|
||||
filters.start_time = ''
|
||||
@@ -517,4 +555,14 @@ onMounted(async () => {
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<ConfirmDialog
|
||||
:show="showConfirmDialog"
|
||||
:title="confirmDialogTitle"
|
||||
:message="confirmDialogMessage"
|
||||
:confirm-text="pendingConfirmAction === 'cleanup' ? '清理' : '回滚'"
|
||||
cancel-text="取消"
|
||||
:danger="pendingConfirmAction === 'cleanup'"
|
||||
@confirm="confirmDialogAction"
|
||||
@cancel="closeConfirmDialog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -317,9 +317,6 @@ function validateForm(): boolean {
|
||||
if (!formData.password) {
|
||||
errors.password = t('auth.passwordRequired')
|
||||
isValid = false
|
||||
} else if (formData.password.length < 6) {
|
||||
errors.password = t('auth.passwordMinLength')
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// Turnstile validation
|
||||
|
||||
@@ -629,7 +629,7 @@ function validateForm(): boolean {
|
||||
if (!formData.password) {
|
||||
errors.password = t('auth.passwordRequired')
|
||||
isValid = false
|
||||
} else if (formData.password.length < 6) {
|
||||
} else if (formData.password.length < 8) {
|
||||
errors.password = t('auth.passwordMinLength')
|
||||
isValid = false
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ function validateForm(): boolean {
|
||||
if (!formData.password) {
|
||||
errors.password = t('auth.passwordRequired')
|
||||
isValid = false
|
||||
} else if (formData.password.length < 6) {
|
||||
} else if (formData.password.length < 8) {
|
||||
errors.password = t('auth.passwordMinLength')
|
||||
isValid = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user