remove deprecated data management frontend
This commit is contained in:
@@ -1,332 +0,0 @@
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export type BackupType = 'postgres' | 'redis' | 'full'
|
||||
export type BackupJobStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'partial_succeeded'
|
||||
|
||||
export interface BackupAgentInfo {
|
||||
status: string
|
||||
version: string
|
||||
uptime_seconds: number
|
||||
}
|
||||
|
||||
export interface BackupAgentHealth {
|
||||
enabled: boolean
|
||||
reason: string
|
||||
socket_path: string
|
||||
agent?: BackupAgentInfo
|
||||
}
|
||||
|
||||
export interface DataManagementPostgresConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password?: string
|
||||
password_configured?: boolean
|
||||
database: string
|
||||
ssl_mode: string
|
||||
container_name: string
|
||||
}
|
||||
|
||||
export interface DataManagementRedisConfig {
|
||||
addr: string
|
||||
username: string
|
||||
password?: string
|
||||
password_configured?: boolean
|
||||
db: number
|
||||
container_name: string
|
||||
}
|
||||
|
||||
export interface DataManagementS3Config {
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
secret_access_key_configured?: boolean
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
use_ssl: boolean
|
||||
}
|
||||
|
||||
export interface DataManagementConfig {
|
||||
source_mode: 'direct' | 'docker_exec'
|
||||
backup_root: string
|
||||
sqlite_path?: string
|
||||
retention_days: number
|
||||
keep_last: number
|
||||
active_postgres_profile_id?: string
|
||||
active_redis_profile_id?: string
|
||||
active_s3_profile_id?: string
|
||||
postgres: DataManagementPostgresConfig
|
||||
redis: DataManagementRedisConfig
|
||||
s3: DataManagementS3Config
|
||||
}
|
||||
|
||||
export type SourceType = 'postgres' | 'redis'
|
||||
|
||||
export interface DataManagementSourceConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password?: string
|
||||
database: string
|
||||
ssl_mode: string
|
||||
addr: string
|
||||
username: string
|
||||
db: number
|
||||
container_name: string
|
||||
}
|
||||
|
||||
export interface DataManagementSourceProfile {
|
||||
source_type: SourceType
|
||||
profile_id: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
password_configured?: boolean
|
||||
config: DataManagementSourceConfig
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface TestS3Request {
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix?: string
|
||||
force_path_style?: boolean
|
||||
use_ssl?: boolean
|
||||
}
|
||||
|
||||
export interface TestS3Response {
|
||||
ok: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface CreateBackupJobRequest {
|
||||
backup_type: BackupType
|
||||
upload_to_s3?: boolean
|
||||
s3_profile_id?: string
|
||||
postgres_profile_id?: string
|
||||
redis_profile_id?: string
|
||||
idempotency_key?: string
|
||||
}
|
||||
|
||||
export interface CreateBackupJobResponse {
|
||||
job_id: string
|
||||
status: BackupJobStatus
|
||||
}
|
||||
|
||||
export interface BackupArtifactInfo {
|
||||
local_path: string
|
||||
size_bytes: number
|
||||
sha256: string
|
||||
}
|
||||
|
||||
export interface BackupS3Info {
|
||||
bucket: string
|
||||
key: string
|
||||
etag: string
|
||||
}
|
||||
|
||||
export interface BackupJob {
|
||||
job_id: string
|
||||
backup_type: BackupType
|
||||
status: BackupJobStatus
|
||||
triggered_by: string
|
||||
s3_profile_id?: string
|
||||
postgres_profile_id?: string
|
||||
redis_profile_id?: string
|
||||
started_at?: string
|
||||
finished_at?: string
|
||||
error_message?: string
|
||||
artifact?: BackupArtifactInfo
|
||||
s3?: BackupS3Info
|
||||
}
|
||||
|
||||
export interface ListSourceProfilesResponse {
|
||||
items: DataManagementSourceProfile[]
|
||||
}
|
||||
|
||||
export interface CreateSourceProfileRequest {
|
||||
profile_id: string
|
||||
name: string
|
||||
config: DataManagementSourceConfig
|
||||
set_active?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateSourceProfileRequest {
|
||||
name: string
|
||||
config: DataManagementSourceConfig
|
||||
}
|
||||
|
||||
export interface DataManagementS3Profile {
|
||||
profile_id: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
s3: DataManagementS3Config
|
||||
secret_access_key_configured?: boolean
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface ListS3ProfilesResponse {
|
||||
items: DataManagementS3Profile[]
|
||||
}
|
||||
|
||||
export interface CreateS3ProfileRequest {
|
||||
profile_id: string
|
||||
name: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix?: string
|
||||
force_path_style?: boolean
|
||||
use_ssl?: boolean
|
||||
set_active?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateS3ProfileRequest {
|
||||
name: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix?: string
|
||||
force_path_style?: boolean
|
||||
use_ssl?: boolean
|
||||
}
|
||||
|
||||
export interface ListBackupJobsRequest {
|
||||
page_size?: number
|
||||
page_token?: string
|
||||
status?: BackupJobStatus
|
||||
backup_type?: BackupType
|
||||
}
|
||||
|
||||
export interface ListBackupJobsResponse {
|
||||
items: BackupJob[]
|
||||
next_page_token?: string
|
||||
}
|
||||
|
||||
export async function getAgentHealth(): Promise<BackupAgentHealth> {
|
||||
const { data } = await apiClient.get<BackupAgentHealth>('/admin/data-management/agent/health')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getConfig(): Promise<DataManagementConfig> {
|
||||
const { data } = await apiClient.get<DataManagementConfig>('/admin/data-management/config')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateConfig(request: DataManagementConfig): Promise<DataManagementConfig> {
|
||||
const { data } = await apiClient.put<DataManagementConfig>('/admin/data-management/config', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function testS3(request: TestS3Request): Promise<TestS3Response> {
|
||||
const { data } = await apiClient.post<TestS3Response>('/admin/data-management/s3/test', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listSourceProfiles(sourceType: SourceType): Promise<ListSourceProfilesResponse> {
|
||||
const { data } = await apiClient.get<ListSourceProfilesResponse>(`/admin/data-management/sources/${sourceType}/profiles`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createSourceProfile(sourceType: SourceType, request: CreateSourceProfileRequest): Promise<DataManagementSourceProfile> {
|
||||
const { data } = await apiClient.post<DataManagementSourceProfile>(`/admin/data-management/sources/${sourceType}/profiles`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateSourceProfile(sourceType: SourceType, profileID: string, request: UpdateSourceProfileRequest): Promise<DataManagementSourceProfile> {
|
||||
const { data } = await apiClient.put<DataManagementSourceProfile>(`/admin/data-management/sources/${sourceType}/profiles/${profileID}`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteSourceProfile(sourceType: SourceType, profileID: string): Promise<void> {
|
||||
await apiClient.delete(`/admin/data-management/sources/${sourceType}/profiles/${profileID}`)
|
||||
}
|
||||
|
||||
export async function setActiveSourceProfile(sourceType: SourceType, profileID: string): Promise<DataManagementSourceProfile> {
|
||||
const { data } = await apiClient.post<DataManagementSourceProfile>(`/admin/data-management/sources/${sourceType}/profiles/${profileID}/activate`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listS3Profiles(): Promise<ListS3ProfilesResponse> {
|
||||
const { data } = await apiClient.get<ListS3ProfilesResponse>('/admin/data-management/s3/profiles')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createS3Profile(request: CreateS3ProfileRequest): Promise<DataManagementS3Profile> {
|
||||
const { data } = await apiClient.post<DataManagementS3Profile>('/admin/data-management/s3/profiles', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateS3Profile(profileID: string, request: UpdateS3ProfileRequest): Promise<DataManagementS3Profile> {
|
||||
const { data } = await apiClient.put<DataManagementS3Profile>(`/admin/data-management/s3/profiles/${profileID}`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteS3Profile(profileID: string): Promise<void> {
|
||||
await apiClient.delete(`/admin/data-management/s3/profiles/${profileID}`)
|
||||
}
|
||||
|
||||
export async function setActiveS3Profile(profileID: string): Promise<DataManagementS3Profile> {
|
||||
const { data } = await apiClient.post<DataManagementS3Profile>(`/admin/data-management/s3/profiles/${profileID}/activate`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createBackupJob(request: CreateBackupJobRequest): Promise<CreateBackupJobResponse> {
|
||||
const headers = request.idempotency_key
|
||||
? { 'X-Idempotency-Key': request.idempotency_key }
|
||||
: undefined
|
||||
|
||||
const { data } = await apiClient.post<CreateBackupJobResponse>(
|
||||
'/admin/data-management/backups',
|
||||
request,
|
||||
{ headers }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listBackupJobs(request?: ListBackupJobsRequest): Promise<ListBackupJobsResponse> {
|
||||
const { data } = await apiClient.get<ListBackupJobsResponse>('/admin/data-management/backups', {
|
||||
params: request
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getBackupJob(jobID: string): Promise<BackupJob> {
|
||||
const { data } = await apiClient.get<BackupJob>(`/admin/data-management/backups/${jobID}`)
|
||||
return data
|
||||
}
|
||||
|
||||
export const dataManagementAPI = {
|
||||
getAgentHealth,
|
||||
getConfig,
|
||||
updateConfig,
|
||||
listSourceProfiles,
|
||||
createSourceProfile,
|
||||
updateSourceProfile,
|
||||
deleteSourceProfile,
|
||||
setActiveSourceProfile,
|
||||
testS3,
|
||||
listS3Profiles,
|
||||
createS3Profile,
|
||||
updateS3Profile,
|
||||
deleteS3Profile,
|
||||
setActiveS3Profile,
|
||||
createBackupJob,
|
||||
listBackupJobs,
|
||||
getBackupJob
|
||||
}
|
||||
|
||||
export default dataManagementAPI
|
||||
@@ -20,7 +20,6 @@ import antigravityAPI from './antigravity'
|
||||
import userAttributesAPI from './userAttributes'
|
||||
import opsAPI from './ops'
|
||||
import errorPassthroughAPI from './errorPassthrough'
|
||||
import dataManagementAPI from './dataManagement'
|
||||
import apiKeysAPI from './apiKeys'
|
||||
import scheduledTestsAPI from './scheduledTests'
|
||||
import backupAPI from './backup'
|
||||
@@ -50,7 +49,6 @@ export const adminAPI = {
|
||||
userAttributes: userAttributesAPI,
|
||||
ops: opsAPI,
|
||||
errorPassthrough: errorPassthroughAPI,
|
||||
dataManagement: dataManagementAPI,
|
||||
apiKeys: apiKeysAPI,
|
||||
scheduledTests: scheduledTestsAPI,
|
||||
backup: backupAPI,
|
||||
@@ -78,7 +76,6 @@ export {
|
||||
userAttributesAPI,
|
||||
opsAPI,
|
||||
errorPassthroughAPI,
|
||||
dataManagementAPI,
|
||||
apiKeysAPI,
|
||||
scheduledTestsAPI,
|
||||
backupAPI,
|
||||
@@ -93,5 +90,4 @@ export default adminAPI
|
||||
// Re-export types used by components
|
||||
export type { BalanceHistoryItem } from './users'
|
||||
export type { ErrorPassthroughRule, CreateRuleRequest, UpdateRuleRequest } from './errorPassthrough'
|
||||
export type { BackupAgentHealth, DataManagementConfig } from './dataManagement'
|
||||
export type { TLSFingerprintProfile, CreateProfileRequest, UpdateProfileRequest } from './tlsFingerprintProfile'
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const routerPath = resolve(dirname(fileURLToPath(import.meta.url)), '../index.ts')
|
||||
const routerSource = readFileSync(routerPath, 'utf8')
|
||||
const adminApiIndexPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../api/admin/index.ts')
|
||||
const adminApiIndexSource = readFileSync(adminApiIndexPath, 'utf8')
|
||||
|
||||
describe('deprecated admin features', () => {
|
||||
it('does not expose the deprecated data-management admin route', () => {
|
||||
expect(routerSource).not.toContain("path: '/admin/data-management'")
|
||||
expect(routerSource).not.toContain("name: 'AdminDataManagement'")
|
||||
})
|
||||
|
||||
it('does not re-export the deprecated data-management admin API', () => {
|
||||
expect(adminApiIndexSource).not.toContain("import dataManagementAPI from './dataManagement'")
|
||||
expect(adminApiIndexSource).not.toContain('dataManagement: dataManagementAPI')
|
||||
expect(adminApiIndexSource).not.toContain('dataManagementAPI,')
|
||||
expect(adminApiIndexSource).not.toContain("from './dataManagement'")
|
||||
})
|
||||
})
|
||||
@@ -467,20 +467,6 @@ const routes: RouteRecordRaw[] = [
|
||||
descriptionKey: 'admin.usage.description'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/admin/data-management',
|
||||
name: 'AdminDataManagement',
|
||||
component: () => import('@/views/admin/data-management/DataManagementView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
requiresAdmin: true,
|
||||
title: 'Data Management',
|
||||
titleKey: 'admin.dataManagement.title',
|
||||
descriptionKey: 'admin.dataManagement.description'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// ==================== Payment Admin Routes ====================
|
||||
{
|
||||
path: '/admin/orders/dashboard',
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
dataManagementAPI,
|
||||
type BackupAgentHealth
|
||||
} from '@/api/admin/dataManagement'
|
||||
import PostgresProfilesCard from './components/PostgresProfilesCard.vue'
|
||||
import RedisProfilesCard from './components/RedisProfilesCard.vue'
|
||||
import S3ProfilesCard from './components/S3ProfilesCard.vue'
|
||||
import BackupJobsCard from './components/BackupJobsCard.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const agentHealth = ref<BackupAgentHealth | null>(null)
|
||||
|
||||
const postgresCard = ref<InstanceType<typeof PostgresProfilesCard> | null>(null)
|
||||
const redisCard = ref<InstanceType<typeof RedisProfilesCard> | null>(null)
|
||||
const s3Card = ref<InstanceType<typeof S3ProfilesCard> | null>(null)
|
||||
const backupCard = ref<InstanceType<typeof BackupJobsCard> | null>(null)
|
||||
|
||||
function formatUptime(seconds: number): string {
|
||||
const days = Math.floor(seconds / 86400)
|
||||
const hours = Math.floor((seconds % 86400) / 3600)
|
||||
const mins = Math.floor((seconds % 3600) / 60)
|
||||
if (days > 0) return `${days}d ${hours}h`
|
||||
if (hours > 0) return `${hours}h ${mins}m`
|
||||
return `${mins}m`
|
||||
}
|
||||
|
||||
async function fetchAgentHealth() {
|
||||
try {
|
||||
agentHealth.value = await dataManagementAPI.getAgentHealth()
|
||||
} catch (err: any) {
|
||||
console.error('[DataManagementView] Failed to fetch agent health', err)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchAgentHealth)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('admin.dataManagement.title') }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
:class="agentHealth?.enabled ? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400' : 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400'"
|
||||
>
|
||||
{{ agentHealth?.enabled ? t('admin.dataManagement.agent.enabled') : t('admin.dataManagement.agent.disabled') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Agent Status Card -->
|
||||
<div class="card p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('admin.dataManagement.agent.title') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ agentHealth?.reason || t('admin.dataManagement.agent.statusUnknown') }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="agentHealth?.agent" class="text-right text-sm text-gray-500 dark:text-gray-400">
|
||||
<div>{{ t('admin.dataManagement.agent.version') }}: {{ agentHealth.agent.version }}</div>
|
||||
<div>{{ t('admin.dataManagement.agent.uptime') }}: {{ formatUptime(agentHealth.agent.uptime_seconds) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PostgreSQL Profiles -->
|
||||
<PostgresProfilesCard ref="postgresCard" />
|
||||
|
||||
<!-- Redis Profiles -->
|
||||
<RedisProfilesCard ref="redisCard" />
|
||||
|
||||
<!-- S3 Profiles -->
|
||||
<S3ProfilesCard ref="s3Card" />
|
||||
|
||||
<!-- Backup Jobs -->
|
||||
<BackupJobsCard ref="backupCard" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,216 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import {
|
||||
dataManagementAPI,
|
||||
type BackupJob,
|
||||
type BackupType
|
||||
} from '@/api/admin/dataManagement'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const jobs = ref<BackupJob[]>([])
|
||||
const showModal = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const form = ref<{
|
||||
backup_type: BackupType
|
||||
postgres_profile_id: string
|
||||
redis_profile_id: string
|
||||
s3_profile_id: string
|
||||
}>({
|
||||
backup_type: 'full',
|
||||
postgres_profile_id: '',
|
||||
redis_profile_id: '',
|
||||
s3_profile_id: ''
|
||||
})
|
||||
|
||||
function formatTime(time: string): string {
|
||||
if (!time) return '-'
|
||||
return new Date(time).toLocaleString()
|
||||
}
|
||||
|
||||
function getJobStatusClass(status: string): string {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'
|
||||
case 'running':
|
||||
return 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
case 'failed':
|
||||
return 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400'
|
||||
case 'pending':
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400'
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400'
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJobs() {
|
||||
loading.value = true
|
||||
try {
|
||||
const resp = await dataManagementAPI.listBackupJobs()
|
||||
jobs.value = resp.items
|
||||
} catch (err: any) {
|
||||
console.error('[BackupJobsCard] Failed to fetch jobs', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.loadFailed'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
form.value = {
|
||||
backup_type: 'full',
|
||||
postgres_profile_id: '',
|
||||
redis_profile_id: '',
|
||||
s3_profile_id: ''
|
||||
}
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showModal.value = false
|
||||
}
|
||||
|
||||
async function createJob() {
|
||||
saving.value = true
|
||||
try {
|
||||
await dataManagementAPI.createBackupJob(form.value)
|
||||
await fetchJobs()
|
||||
closeModal()
|
||||
appStore.showSuccess(t('admin.dataManagement.backupJobs.created'))
|
||||
} catch (err: any) {
|
||||
console.error('[BackupJobsCard] Failed to create job', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.saveFailed'))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchJobs)
|
||||
|
||||
defineExpose({ refresh: fetchJobs })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card p-6">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('admin.dataManagement.backupJobs.title') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" @click="openModal">
|
||||
{{ t('admin.dataManagement.backupJobs.create') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="jobs.length === 0" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.noJobs') }}
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.jobId') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.type') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.status') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.startedAt') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.finishedAt') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr v-for="job in jobs" :key="job.job_id">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-gray-900 dark:text-white">
|
||||
{{ job.job_id.slice(0, 8) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ job.backup_type }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
:class="getJobStatusClass(job.status)"
|
||||
>
|
||||
{{ job.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ job.started_at ? formatTime(job.started_at) : '-' }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ job.finished_at ? formatTime(job.finished_at) : '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Create Backup Job Modal -->
|
||||
<BaseDialog
|
||||
:show="showModal"
|
||||
:title="t('admin.dataManagement.backupJobs.create')"
|
||||
@close="closeModal"
|
||||
>
|
||||
<form @submit.prevent="createJob">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.type') }}
|
||||
</label>
|
||||
<select v-model="form.backup_type" class="input w-full">
|
||||
<option value="full">Full</option>
|
||||
<option value="incremental">Incremental</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.postgresProfile') }}
|
||||
</label>
|
||||
<input v-model="form.postgres_profile_id" class="input w-full" placeholder="Profile ID" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.redisProfile') }}
|
||||
</label>
|
||||
<input v-model="form.redis_profile_id" class="input w-full" placeholder="Profile ID" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.backupJobs.s3Profile') }}
|
||||
</label>
|
||||
<input v-model="form.s3_profile_id" class="input w-full" placeholder="Profile ID" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
{{ saving ? t('common.loading') : t('common.create') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</BaseDialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,356 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import {
|
||||
dataManagementAPI,
|
||||
type DataManagementSourceProfile
|
||||
} from '@/api/admin/dataManagement'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const profiles = ref<DataManagementSourceProfile[]>([])
|
||||
const showModal = ref(false)
|
||||
const editing = ref<DataManagementSourceProfile | null>(null)
|
||||
const saving = ref(false)
|
||||
|
||||
const form = ref<{
|
||||
profile_id: string
|
||||
name: string
|
||||
config: {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
ssl_mode: string
|
||||
container_name: string
|
||||
}
|
||||
set_active: boolean
|
||||
}>({
|
||||
profile_id: '',
|
||||
name: '',
|
||||
config: {
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
user: 'postgres',
|
||||
password: '',
|
||||
database: '',
|
||||
ssl_mode: 'disable',
|
||||
container_name: ''
|
||||
},
|
||||
set_active: false
|
||||
})
|
||||
|
||||
async function fetchProfiles() {
|
||||
loading.value = true
|
||||
try {
|
||||
const resp = await dataManagementAPI.listSourceProfiles('postgres')
|
||||
profiles.value = resp.items
|
||||
} catch (err: any) {
|
||||
console.error('[PostgresProfilesCard] Failed to fetch profiles', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.loadFailed'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openModal(profile?: DataManagementSourceProfile) {
|
||||
if (profile) {
|
||||
editing.value = profile
|
||||
form.value = {
|
||||
profile_id: profile.profile_id,
|
||||
name: profile.name,
|
||||
config: {
|
||||
host: profile.config.host,
|
||||
port: profile.config.port,
|
||||
user: profile.config.user,
|
||||
password: '',
|
||||
database: profile.config.database,
|
||||
ssl_mode: profile.config.ssl_mode,
|
||||
container_name: profile.config.container_name
|
||||
},
|
||||
set_active: false
|
||||
}
|
||||
} else {
|
||||
editing.value = null
|
||||
form.value = {
|
||||
profile_id: '',
|
||||
name: '',
|
||||
config: {
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
user: 'postgres',
|
||||
password: '',
|
||||
database: '',
|
||||
ssl_mode: 'disable',
|
||||
container_name: ''
|
||||
},
|
||||
set_active: false
|
||||
}
|
||||
}
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showModal.value = false
|
||||
editing.value = null
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
if (editing.value) {
|
||||
await dataManagementAPI.updateSourceProfile('postgres', form.value.profile_id, {
|
||||
name: form.value.name,
|
||||
config: {
|
||||
host: form.value.config.host,
|
||||
port: form.value.config.port,
|
||||
user: form.value.config.user,
|
||||
password: form.value.config.password,
|
||||
database: form.value.config.database,
|
||||
ssl_mode: form.value.config.ssl_mode,
|
||||
container_name: form.value.config.container_name,
|
||||
addr: '',
|
||||
username: '',
|
||||
db: 0
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await dataManagementAPI.createSourceProfile('postgres', {
|
||||
profile_id: form.value.profile_id,
|
||||
name: form.value.name,
|
||||
config: {
|
||||
host: form.value.config.host,
|
||||
port: form.value.config.port,
|
||||
user: form.value.config.user,
|
||||
password: form.value.config.password,
|
||||
database: form.value.config.database,
|
||||
ssl_mode: form.value.config.ssl_mode,
|
||||
container_name: form.value.config.container_name,
|
||||
addr: '',
|
||||
username: '',
|
||||
db: 0
|
||||
},
|
||||
set_active: form.value.set_active
|
||||
})
|
||||
}
|
||||
await fetchProfiles()
|
||||
closeModal()
|
||||
appStore.showSuccess(t('common.saved'))
|
||||
} catch (err: any) {
|
||||
console.error('[PostgresProfilesCard] Failed to save profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.saveFailed'))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function activate(profileId: string) {
|
||||
try {
|
||||
await dataManagementAPI.setActiveSourceProfile('postgres', profileId)
|
||||
await fetchProfiles()
|
||||
appStore.showSuccess(t('common.saved'))
|
||||
} catch (err: any) {
|
||||
console.error('[PostgresProfilesCard] Failed to activate profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.saveFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(profileId: string) {
|
||||
if (!confirm(t('admin.dataManagement.profiles.confirmDelete'))) return
|
||||
try {
|
||||
await dataManagementAPI.deleteSourceProfile('postgres', profileId)
|
||||
await fetchProfiles()
|
||||
appStore.showSuccess(t('common.deleted'))
|
||||
} catch (err: any) {
|
||||
console.error('[PostgresProfilesCard] Failed to delete profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.deleteFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchProfiles)
|
||||
|
||||
defineExpose({ refresh: fetchProfiles })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card p-6">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('admin.dataManagement.postgres.title') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" @click="openModal()">
|
||||
{{ t('admin.dataManagement.postgres.addProfile') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="profiles.length === 0" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.noProfiles') }}
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.name') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.host') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.database') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.status') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.actions') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr v-for="profile in profiles" :key="profile.profile_id">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
||||
{{ profile.name }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ profile.config.host }}:{{ profile.config.port }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ profile.config.database }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
:class="profile.is_active ? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400' : 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400'"
|
||||
>
|
||||
{{ profile.is_active ? t('admin.dataManagement.profiles.active') : t('admin.dataManagement.profiles.inactive') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
|
||||
<button
|
||||
v-if="!profile.is_active"
|
||||
type="button"
|
||||
class="mr-2 text-primary-600 hover:text-primary-700"
|
||||
@click="activate(profile.profile_id)"
|
||||
>
|
||||
{{ t('admin.dataManagement.profiles.activate') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mr-2 text-primary-600 hover:text-primary-700"
|
||||
@click="openModal(profile)"
|
||||
>
|
||||
{{ t('common.edit') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-red-600 hover:text-red-700"
|
||||
@click="remove(profile.profile_id)"
|
||||
>
|
||||
{{ t('common.delete') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<BaseDialog
|
||||
:show="showModal"
|
||||
:title="editing ? t('admin.dataManagement.postgres.editProfile') : t('admin.dataManagement.postgres.addProfile')"
|
||||
width="wide"
|
||||
@close="closeModal"
|
||||
>
|
||||
<form @submit.prevent="save">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.profileId') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.profile_id"
|
||||
:disabled="!!editing"
|
||||
class="input w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.name') }}
|
||||
</label>
|
||||
<input v-model="form.name" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.host') }}
|
||||
</label>
|
||||
<input v-model="form.config.host" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.port') }}
|
||||
</label>
|
||||
<input v-model.number="form.config.port" type="number" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.user') }}
|
||||
</label>
|
||||
<input v-model="form.config.user" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.password') }}
|
||||
</label>
|
||||
<input v-model="form.config.password" type="password" class="input w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.database') }}
|
||||
</label>
|
||||
<input v-model="form.config.database" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.sslMode') }}
|
||||
</label>
|
||||
<select v-model="form.config.ssl_mode" class="input w-full">
|
||||
<option value="disable">disable</option>
|
||||
<option value="require">require</option>
|
||||
<option value="verify-ca">verify-ca</option>
|
||||
<option value="verify-full">verify-full</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.postgres.containerName') }}
|
||||
</label>
|
||||
<input v-model="form.config.container_name" class="input w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
{{ saving ? t('common.loading') : t('common.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</BaseDialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,331 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import {
|
||||
dataManagementAPI,
|
||||
type DataManagementSourceProfile
|
||||
} from '@/api/admin/dataManagement'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const profiles = ref<DataManagementSourceProfile[]>([])
|
||||
const showModal = ref(false)
|
||||
const editing = ref<DataManagementSourceProfile | null>(null)
|
||||
const saving = ref(false)
|
||||
|
||||
const form = ref<{
|
||||
profile_id: string
|
||||
name: string
|
||||
config: {
|
||||
addr: string
|
||||
username: string
|
||||
password: string
|
||||
db: number
|
||||
container_name: string
|
||||
}
|
||||
set_active: boolean
|
||||
}>({
|
||||
profile_id: '',
|
||||
name: '',
|
||||
config: {
|
||||
addr: 'localhost:6379',
|
||||
username: '',
|
||||
password: '',
|
||||
db: 0,
|
||||
container_name: ''
|
||||
},
|
||||
set_active: false
|
||||
})
|
||||
|
||||
async function fetchProfiles() {
|
||||
loading.value = true
|
||||
try {
|
||||
const resp = await dataManagementAPI.listSourceProfiles('redis')
|
||||
profiles.value = resp.items
|
||||
} catch (err: any) {
|
||||
console.error('[RedisProfilesCard] Failed to fetch profiles', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.loadFailed'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openModal(profile?: DataManagementSourceProfile) {
|
||||
if (profile) {
|
||||
editing.value = profile
|
||||
form.value = {
|
||||
profile_id: profile.profile_id,
|
||||
name: profile.name,
|
||||
config: {
|
||||
addr: profile.config.addr,
|
||||
username: profile.config.username,
|
||||
password: '',
|
||||
db: profile.config.db,
|
||||
container_name: profile.config.container_name
|
||||
},
|
||||
set_active: false
|
||||
}
|
||||
} else {
|
||||
editing.value = null
|
||||
form.value = {
|
||||
profile_id: '',
|
||||
name: '',
|
||||
config: {
|
||||
addr: 'localhost:6379',
|
||||
username: '',
|
||||
password: '',
|
||||
db: 0,
|
||||
container_name: ''
|
||||
},
|
||||
set_active: false
|
||||
}
|
||||
}
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showModal.value = false
|
||||
editing.value = null
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
if (editing.value) {
|
||||
await dataManagementAPI.updateSourceProfile('redis', form.value.profile_id, {
|
||||
name: form.value.name,
|
||||
config: {
|
||||
host: '',
|
||||
port: 0,
|
||||
user: '',
|
||||
password: form.value.config.password,
|
||||
database: '',
|
||||
ssl_mode: '',
|
||||
addr: form.value.config.addr,
|
||||
username: form.value.config.username,
|
||||
db: form.value.config.db,
|
||||
container_name: form.value.config.container_name
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await dataManagementAPI.createSourceProfile('redis', {
|
||||
profile_id: form.value.profile_id,
|
||||
name: form.value.name,
|
||||
config: {
|
||||
host: '',
|
||||
port: 0,
|
||||
user: '',
|
||||
password: form.value.config.password,
|
||||
database: '',
|
||||
ssl_mode: '',
|
||||
addr: form.value.config.addr,
|
||||
username: form.value.config.username,
|
||||
db: form.value.config.db,
|
||||
container_name: form.value.config.container_name
|
||||
},
|
||||
set_active: form.value.set_active
|
||||
})
|
||||
}
|
||||
await fetchProfiles()
|
||||
closeModal()
|
||||
appStore.showSuccess(t('common.saved'))
|
||||
} catch (err: any) {
|
||||
console.error('[RedisProfilesCard] Failed to save profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.saveFailed'))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function activate(profileId: string) {
|
||||
try {
|
||||
await dataManagementAPI.setActiveSourceProfile('redis', profileId)
|
||||
await fetchProfiles()
|
||||
appStore.showSuccess(t('common.saved'))
|
||||
} catch (err: any) {
|
||||
console.error('[RedisProfilesCard] Failed to activate profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.saveFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(profileId: string) {
|
||||
if (!confirm(t('admin.dataManagement.profiles.confirmDelete'))) return
|
||||
try {
|
||||
await dataManagementAPI.deleteSourceProfile('redis', profileId)
|
||||
await fetchProfiles()
|
||||
appStore.showSuccess(t('common.deleted'))
|
||||
} catch (err: any) {
|
||||
console.error('[RedisProfilesCard] Failed to delete profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.deleteFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchProfiles)
|
||||
|
||||
defineExpose({ refresh: fetchProfiles })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card p-6">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('admin.dataManagement.redis.title') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" @click="openModal()">
|
||||
{{ t('admin.dataManagement.redis.addProfile') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="profiles.length === 0" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.noProfiles') }}
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.name') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.address') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.database') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.status') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.actions') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr v-for="profile in profiles" :key="profile.profile_id">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
||||
{{ profile.name }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ profile.config.addr }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ profile.config.db }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
:class="profile.is_active ? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400' : 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400'"
|
||||
>
|
||||
{{ profile.is_active ? t('admin.dataManagement.profiles.active') : t('admin.dataManagement.profiles.inactive') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
|
||||
<button
|
||||
v-if="!profile.is_active"
|
||||
type="button"
|
||||
class="mr-2 text-primary-600 hover:text-primary-700"
|
||||
@click="activate(profile.profile_id)"
|
||||
>
|
||||
{{ t('admin.dataManagement.profiles.activate') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mr-2 text-primary-600 hover:text-primary-700"
|
||||
@click="openModal(profile)"
|
||||
>
|
||||
{{ t('common.edit') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-red-600 hover:text-red-700"
|
||||
@click="remove(profile.profile_id)"
|
||||
>
|
||||
{{ t('common.delete') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<BaseDialog
|
||||
:show="showModal"
|
||||
:title="editing ? t('admin.dataManagement.redis.editProfile') : t('admin.dataManagement.redis.addProfile')"
|
||||
width="wide"
|
||||
@close="closeModal"
|
||||
>
|
||||
<form @submit.prevent="save">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.profileId') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.profile_id"
|
||||
:disabled="!!editing"
|
||||
class="input w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.name') }}
|
||||
</label>
|
||||
<input v-model="form.name" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.address') }}
|
||||
</label>
|
||||
<input v-model="form.config.addr" class="input w-full" placeholder="localhost:6379" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.username') }}
|
||||
</label>
|
||||
<input v-model="form.config.username" class="input w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.password') }}
|
||||
</label>
|
||||
<input v-model="form.config.password" type="password" class="input w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.database') }}
|
||||
</label>
|
||||
<input v-model.number="form.config.db" type="number" min="0" class="input w-full" />
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.redis.containerName') }}
|
||||
</label>
|
||||
<input v-model="form.config.container_name" class="input w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
{{ saving ? t('common.loading') : t('common.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</BaseDialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,363 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import {
|
||||
dataManagementAPI,
|
||||
type DataManagementS3Profile,
|
||||
type TestS3Request
|
||||
} from '@/api/admin/dataManagement'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const profiles = ref<DataManagementS3Profile[]>([])
|
||||
const showModal = ref(false)
|
||||
const editing = ref<DataManagementS3Profile | null>(null)
|
||||
const saving = ref(false)
|
||||
|
||||
const form = ref<{
|
||||
profile_id: string
|
||||
name: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key: string
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
use_ssl: boolean
|
||||
set_active: boolean
|
||||
}>({
|
||||
profile_id: '',
|
||||
name: '',
|
||||
enabled: true,
|
||||
endpoint: '',
|
||||
region: 'us-east-1',
|
||||
bucket: '',
|
||||
access_key_id: '',
|
||||
secret_access_key: '',
|
||||
prefix: '',
|
||||
force_path_style: false,
|
||||
use_ssl: true,
|
||||
set_active: false
|
||||
})
|
||||
|
||||
async function fetchProfiles() {
|
||||
loading.value = true
|
||||
try {
|
||||
const resp = await dataManagementAPI.listS3Profiles()
|
||||
profiles.value = resp.items
|
||||
} catch (err: any) {
|
||||
console.error('[S3ProfilesCard] Failed to fetch profiles', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.loadFailed'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openModal(profile?: DataManagementS3Profile) {
|
||||
if (profile) {
|
||||
editing.value = profile
|
||||
form.value = {
|
||||
profile_id: profile.profile_id,
|
||||
name: profile.name,
|
||||
enabled: profile.s3.enabled,
|
||||
endpoint: profile.s3.endpoint,
|
||||
region: profile.s3.region,
|
||||
bucket: profile.s3.bucket,
|
||||
access_key_id: profile.s3.access_key_id,
|
||||
secret_access_key: '',
|
||||
prefix: profile.s3.prefix,
|
||||
force_path_style: profile.s3.force_path_style,
|
||||
use_ssl: profile.s3.use_ssl,
|
||||
set_active: false
|
||||
}
|
||||
} else {
|
||||
editing.value = null
|
||||
form.value = {
|
||||
profile_id: '',
|
||||
name: '',
|
||||
enabled: true,
|
||||
endpoint: '',
|
||||
region: 'us-east-1',
|
||||
bucket: '',
|
||||
access_key_id: '',
|
||||
secret_access_key: '',
|
||||
prefix: '',
|
||||
force_path_style: false,
|
||||
use_ssl: true,
|
||||
set_active: false
|
||||
}
|
||||
}
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showModal.value = false
|
||||
editing.value = null
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
if (editing.value) {
|
||||
await dataManagementAPI.updateS3Profile(form.value.profile_id, {
|
||||
name: form.value.name,
|
||||
enabled: form.value.enabled,
|
||||
endpoint: form.value.endpoint,
|
||||
region: form.value.region,
|
||||
bucket: form.value.bucket,
|
||||
access_key_id: form.value.access_key_id,
|
||||
secret_access_key: form.value.secret_access_key || undefined,
|
||||
prefix: form.value.prefix,
|
||||
force_path_style: form.value.force_path_style,
|
||||
use_ssl: form.value.use_ssl
|
||||
})
|
||||
} else {
|
||||
await dataManagementAPI.createS3Profile(form.value)
|
||||
}
|
||||
await fetchProfiles()
|
||||
closeModal()
|
||||
appStore.showSuccess(t('common.saved'))
|
||||
} catch (err: any) {
|
||||
console.error('[S3ProfilesCard] Failed to save profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.saveFailed'))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function test(profile: DataManagementS3Profile) {
|
||||
try {
|
||||
const req: TestS3Request = {
|
||||
endpoint: profile.s3.endpoint,
|
||||
region: profile.s3.region,
|
||||
bucket: profile.s3.bucket,
|
||||
access_key_id: profile.s3.access_key_id,
|
||||
prefix: profile.s3.prefix,
|
||||
force_path_style: profile.s3.force_path_style,
|
||||
use_ssl: profile.s3.use_ssl
|
||||
}
|
||||
await dataManagementAPI.testS3(req)
|
||||
appStore.showSuccess(t('admin.dataManagement.s3.testSuccess'))
|
||||
} catch (err: any) {
|
||||
console.error('[S3ProfilesCard] Failed to test profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('admin.dataManagement.s3.testFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
async function activate(profileId: string) {
|
||||
try {
|
||||
await dataManagementAPI.setActiveS3Profile(profileId)
|
||||
await fetchProfiles()
|
||||
appStore.showSuccess(t('common.saved'))
|
||||
} catch (err: any) {
|
||||
console.error('[S3ProfilesCard] Failed to activate profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.saveFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(profileId: string) {
|
||||
if (!confirm(t('admin.dataManagement.profiles.confirmDelete'))) return
|
||||
try {
|
||||
await dataManagementAPI.deleteS3Profile(profileId)
|
||||
await fetchProfiles()
|
||||
appStore.showSuccess(t('common.deleted'))
|
||||
} catch (err: any) {
|
||||
console.error('[S3ProfilesCard] Failed to delete profile', err)
|
||||
appStore.showError(err?.response?.data?.detail || t('common.deleteFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchProfiles)
|
||||
|
||||
defineExpose({ refresh: fetchProfiles })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card p-6">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('admin.dataManagement.s3.title') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" @click="openModal()">
|
||||
{{ t('admin.dataManagement.s3.addProfile') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="profiles.length === 0" class="py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.noProfiles') }}
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.name') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.bucket') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.region') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.status') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.actions') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr v-for="profile in profiles" :key="profile.profile_id">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
||||
{{ profile.name }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ profile.s3.bucket }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ profile.s3.region }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
:class="profile.is_active ? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400' : 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400'"
|
||||
>
|
||||
{{ profile.is_active ? t('admin.dataManagement.profiles.active') : t('admin.dataManagement.profiles.inactive') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
|
||||
<button
|
||||
type="button"
|
||||
class="mr-2 text-primary-600 hover:text-primary-700"
|
||||
@click="test(profile)"
|
||||
>
|
||||
{{ t('admin.dataManagement.s3.test') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!profile.is_active"
|
||||
type="button"
|
||||
class="mr-2 text-primary-600 hover:text-primary-700"
|
||||
@click="activate(profile.profile_id)"
|
||||
>
|
||||
{{ t('admin.dataManagement.profiles.activate') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mr-2 text-primary-600 hover:text-primary-700"
|
||||
@click="openModal(profile)"
|
||||
>
|
||||
{{ t('common.edit') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-red-600 hover:text-red-700"
|
||||
@click="remove(profile.profile_id)"
|
||||
>
|
||||
{{ t('common.delete') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<BaseDialog
|
||||
:show="showModal"
|
||||
:title="editing ? t('admin.dataManagement.s3.editProfile') : t('admin.dataManagement.s3.addProfile')"
|
||||
width="wide"
|
||||
@close="closeModal"
|
||||
>
|
||||
<form @submit.prevent="save">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.profileId') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.profile_id"
|
||||
:disabled="!!editing"
|
||||
class="input w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.profiles.name') }}
|
||||
</label>
|
||||
<input v-model="form.name" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.endpoint') }}
|
||||
</label>
|
||||
<input v-model="form.endpoint" class="input w-full" placeholder="https://s3.amazonaws.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.region') }}
|
||||
</label>
|
||||
<input v-model="form.region" class="input w-full" placeholder="us-east-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.bucket') }}
|
||||
</label>
|
||||
<input v-model="form.bucket" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.prefix') }}
|
||||
</label>
|
||||
<input v-model="form.prefix" class="input w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.accessKeyId') }}
|
||||
</label>
|
||||
<input v-model="form.access_key_id" class="input w-full" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.dataManagement.s3.secretAccessKey') }}
|
||||
</label>
|
||||
<input v-model="form.secret_access_key" type="password" class="input w-full" :placeholder="editing ? t('admin.dataManagement.s3.secretPlaceholder') : ''" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4 md:col-span-2">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input v-model="form.use_ssl" type="checkbox" class="checkbox" />
|
||||
<span class="text-sm">{{ t('admin.dataManagement.s3.useSsl') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input v-model="form.force_path_style" type="checkbox" class="checkbox" />
|
||||
<span class="text-sm">{{ t('admin.dataManagement.s3.forcePathStyle') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
{{ saving ? t('common.loading') : t('common.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</BaseDialog>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user