feat: add data import/export bundle
This commit is contained in:
@@ -96,6 +96,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #afterCreate>
|
||||
<button @click="showImportData = true" class="btn btn-secondary">
|
||||
{{ t('admin.accounts.dataImport') }}
|
||||
</button>
|
||||
<button @click="openExportDataDialog" class="btn btn-secondary">
|
||||
{{ selIds.length ? t('admin.accounts.dataExportSelected') : t('admin.accounts.dataExport') }}
|
||||
</button>
|
||||
</template>
|
||||
</AccountTableActions>
|
||||
</div>
|
||||
</template>
|
||||
@@ -218,9 +226,16 @@
|
||||
<AccountStatsModal :show="showStats" :account="statsAcc" @close="closeStatsModal" />
|
||||
<AccountActionMenu :show="menu.show" :account="menu.acc" :position="menu.pos" @close="menu.show = false" @test="handleTest" @stats="handleViewStats" @reauth="handleReAuth" @refresh-token="handleRefresh" @reset-status="handleResetStatus" @clear-rate-limit="handleClearRateLimit" />
|
||||
<SyncFromCrsModal :show="showSync" @close="showSync = false" @synced="reload" />
|
||||
<ImportDataModal :show="showImportData" @close="showImportData = false" @imported="handleDataImported" />
|
||||
<BulkEditAccountModal :show="showBulkEdit" :account-ids="selIds" :proxies="proxies" :groups="groups" @close="showBulkEdit = false" @updated="handleBulkUpdated" />
|
||||
<TempUnschedStatusModal :show="showTempUnsched" :account="tempUnschedAcc" @close="showTempUnsched = false" @reset="handleTempUnschedReset" />
|
||||
<ConfirmDialog :show="showDeleteDialog" :title="t('admin.accounts.deleteAccount')" :message="t('admin.accounts.deleteConfirm', { name: deletingAcc?.name })" :confirm-text="t('common.delete')" :cancel-text="t('common.cancel')" :danger="true" @confirm="confirmDelete" @cancel="showDeleteDialog = false" />
|
||||
<ConfirmDialog :show="showExportDataDialog" :title="t('admin.accounts.dataExport')" :message="t('admin.accounts.dataExportConfirmMessage')" :confirm-text="t('admin.accounts.dataExportConfirm')" :cancel-text="t('common.cancel')" @confirm="handleExportData" @cancel="showExportDataDialog = false">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500" v-model="includeProxyOnExport" />
|
||||
<span>{{ t('admin.accounts.dataExportIncludeProxies') }}</span>
|
||||
</label>
|
||||
</ConfirmDialog>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
@@ -242,6 +257,7 @@ import AccountTableActions from '@/components/admin/account/AccountTableActions.
|
||||
import AccountTableFilters from '@/components/admin/account/AccountTableFilters.vue'
|
||||
import AccountBulkActionsBar from '@/components/admin/account/AccountBulkActionsBar.vue'
|
||||
import AccountActionMenu from '@/components/admin/account/AccountActionMenu.vue'
|
||||
import ImportDataModal from '@/components/admin/account/ImportDataModal.vue'
|
||||
import ReAuthAccountModal from '@/components/admin/account/ReAuthAccountModal.vue'
|
||||
import AccountTestModal from '@/components/admin/account/AccountTestModal.vue'
|
||||
import AccountStatsModal from '@/components/admin/account/AccountStatsModal.vue'
|
||||
@@ -265,6 +281,9 @@ const selIds = ref<number[]>([])
|
||||
const showCreate = ref(false)
|
||||
const showEdit = ref(false)
|
||||
const showSync = ref(false)
|
||||
const showImportData = ref(false)
|
||||
const showExportDataDialog = ref(false)
|
||||
const includeProxyOnExport = ref(true)
|
||||
const showBulkEdit = ref(false)
|
||||
const showTempUnsched = ref(false)
|
||||
const showDeleteDialog = ref(false)
|
||||
@@ -279,6 +298,7 @@ const testingAcc = ref<Account | null>(null)
|
||||
const statsAcc = ref<Account | null>(null)
|
||||
const togglingSchedulable = ref<number | null>(null)
|
||||
const menu = reactive<{show:boolean, acc:Account|null, pos:{top:number, left:number}|null}>({ show: false, acc: null, pos: null })
|
||||
const exportingData = ref(false)
|
||||
|
||||
// Column settings
|
||||
const showColumnDropdown = ref(false)
|
||||
@@ -405,6 +425,8 @@ const isAnyModalOpen = computed(() => {
|
||||
showCreate.value ||
|
||||
showEdit.value ||
|
||||
showSync.value ||
|
||||
showImportData.value ||
|
||||
showExportDataDialog.value ||
|
||||
showBulkEdit.value ||
|
||||
showTempUnsched.value ||
|
||||
showDeleteDialog.value ||
|
||||
@@ -633,6 +655,50 @@ const handleBulkToggleSchedulable = async (schedulable: boolean) => {
|
||||
}
|
||||
}
|
||||
const handleBulkUpdated = () => { showBulkEdit.value = false; selIds.value = []; reload() }
|
||||
const handleDataImported = () => { showImportData.value = false; reload() }
|
||||
const formatExportTimestamp = () => {
|
||||
const now = new Date()
|
||||
const pad2 = (value: number) => String(value).padStart(2, '0')
|
||||
return `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`
|
||||
}
|
||||
const openExportDataDialog = () => {
|
||||
includeProxyOnExport.value = true
|
||||
showExportDataDialog.value = true
|
||||
}
|
||||
const handleExportData = async () => {
|
||||
if (exportingData.value) return
|
||||
exportingData.value = true
|
||||
try {
|
||||
const dataPayload = await adminAPI.accounts.exportData(
|
||||
selIds.value.length > 0
|
||||
? { ids: selIds.value, includeProxies: includeProxyOnExport.value }
|
||||
: {
|
||||
includeProxies: includeProxyOnExport.value,
|
||||
filters: {
|
||||
platform: params.platform,
|
||||
type: params.type,
|
||||
status: params.status,
|
||||
search: params.search
|
||||
}
|
||||
}
|
||||
)
|
||||
const timestamp = formatExportTimestamp()
|
||||
const filename = `sub2api-account-${timestamp}.json`
|
||||
const blob = new Blob([JSON.stringify(dataPayload, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
appStore.showSuccess(t('admin.accounts.dataExported'))
|
||||
} catch (error: any) {
|
||||
appStore.showError(error?.message || t('admin.accounts.dataExportFailed'))
|
||||
} finally {
|
||||
exportingData.value = false
|
||||
showExportDataDialog.value = false
|
||||
}
|
||||
}
|
||||
const closeTestModal = () => { showTest.value = false; testingAcc.value = null }
|
||||
const closeStatsModal = () => { showStats.value = false; statsAcc.value = null }
|
||||
const closeReAuthModal = () => { showReAuth.value = false; reAuthAcc.value = null }
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
<Icon name="trash" size="md" class="mr-2" />
|
||||
{{ t('admin.proxies.batchDeleteAction') }}
|
||||
</button>
|
||||
<button @click="showExportDataDialog = true" class="btn btn-secondary">
|
||||
{{ t('admin.proxies.dataExport') }}
|
||||
</button>
|
||||
<button @click="showCreateModal = true" class="btn btn-primary">
|
||||
<Icon name="plus" size="md" class="mr-2" />
|
||||
{{ t('admin.proxies.createProxy') }}
|
||||
@@ -606,6 +609,15 @@
|
||||
@confirm="confirmBatchDelete"
|
||||
@cancel="showBatchDeleteDialog = false"
|
||||
/>
|
||||
<ConfirmDialog
|
||||
:show="showExportDataDialog"
|
||||
:title="t('admin.proxies.dataExport')"
|
||||
:message="t('admin.proxies.dataExportConfirmMessage')"
|
||||
:confirm-text="t('admin.proxies.dataExportConfirm')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
@confirm="handleExportData"
|
||||
@cancel="showExportDataDialog = false"
|
||||
/>
|
||||
|
||||
<!-- Proxy Accounts Dialog -->
|
||||
<BaseDialog
|
||||
@@ -733,8 +745,10 @@ const showCreateModal = ref(false)
|
||||
const showEditModal = ref(false)
|
||||
const showDeleteDialog = ref(false)
|
||||
const showBatchDeleteDialog = ref(false)
|
||||
const showExportDataDialog = ref(false)
|
||||
const showAccountsModal = ref(false)
|
||||
const submitting = ref(false)
|
||||
const exportingData = ref(false)
|
||||
const testingProxyIds = ref<Set<number>>(new Set())
|
||||
const batchTesting = ref(false)
|
||||
const selectedProxyIds = ref<Set<number>>(new Set())
|
||||
@@ -1228,6 +1242,39 @@ const handleBatchTest = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const formatExportTimestamp = () => {
|
||||
const now = new Date()
|
||||
const pad2 = (value: number) => String(value).padStart(2, '0')
|
||||
return `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`
|
||||
}
|
||||
|
||||
const handleExportData = async () => {
|
||||
if (exportingData.value) return
|
||||
exportingData.value = true
|
||||
try {
|
||||
const dataPayload = await adminAPI.proxies.exportData({
|
||||
protocol: filters.protocol || undefined,
|
||||
status: (filters.status || undefined) as 'active' | 'inactive' | undefined,
|
||||
search: searchQuery.value || undefined
|
||||
})
|
||||
const timestamp = formatExportTimestamp()
|
||||
const filename = `sub2api-proxy-${timestamp}.json`
|
||||
const blob = new Blob([JSON.stringify(dataPayload, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
appStore.showSuccess(t('admin.proxies.dataExported'))
|
||||
} catch (error: any) {
|
||||
appStore.showError(error?.message || t('admin.proxies.dataExportFailed'))
|
||||
} finally {
|
||||
exportingData.value = false
|
||||
showExportDataDialog.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (proxy: Proxy) => {
|
||||
if ((proxy.account_count || 0) > 0) {
|
||||
appStore.showError(t('admin.proxies.deleteBlockedInUse'))
|
||||
|
||||
Reference in New Issue
Block a user