mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-06-25 03:23:41 +08:00
Three community-requested features: 1. CLI password reset: `backupx reset-password --username admin --password xxx` Docker users can run via `docker exec`. No full app init needed. 2. Audit logging: async fire-and-forget audit trail for all key operations (login, CRUD on tasks/targets/records, settings changes). New UI page at /audit with category filter and pagination. 3. Multi-source path backup: file backup tasks now support multiple source directories packed into a single tar archive. Backward compatible with existing single sourcePath field.
121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
import { Button, Checkbox, Input, Message, Space, Spin, Typography } from '@arco-design/web-react'
|
|
import { useState } from 'react'
|
|
import { discoverDatabases } from '../../services/database'
|
|
|
|
interface DatabasePickerProps {
|
|
dbType: 'mysql' | 'postgresql'
|
|
dbHost: string
|
|
dbPort: number
|
|
dbUser: string
|
|
dbPassword: string
|
|
value: string
|
|
onChange: (value: string) => void
|
|
}
|
|
|
|
export function DatabasePicker({ dbType, dbHost, dbPort, dbUser, dbPassword, value, onChange }: DatabasePickerProps) {
|
|
const [databases, setDatabases] = useState<string[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
const [discovered, setDiscovered] = useState(false)
|
|
const [error, setError] = useState('')
|
|
|
|
const selectedDbs = value
|
|
.split(',')
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)
|
|
|
|
const canDiscover = dbHost.trim() && dbPort > 0 && dbUser.trim() && dbPassword.trim()
|
|
|
|
async function handleDiscover() {
|
|
setLoading(true)
|
|
setError('')
|
|
try {
|
|
const result = await discoverDatabases({
|
|
type: dbType,
|
|
host: dbHost.trim(),
|
|
port: dbPort,
|
|
user: dbUser.trim(),
|
|
password: dbPassword.trim(),
|
|
})
|
|
setDatabases(result)
|
|
setDiscovered(true)
|
|
if (result.length === 0) {
|
|
setError('未发现用户数据库')
|
|
}
|
|
} catch (discoverError: any) {
|
|
const msg = discoverError?.response?.data?.message ?? discoverError?.message ?? '发现数据库失败'
|
|
setError(msg)
|
|
Message.error(msg)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
function handleToggle(db: string, checked: boolean) {
|
|
let next: string[]
|
|
if (checked) {
|
|
next = [...selectedDbs, db]
|
|
} else {
|
|
next = selectedDbs.filter((d) => d !== db)
|
|
}
|
|
onChange(next.join(','))
|
|
}
|
|
|
|
function handleSelectAll() {
|
|
onChange(databases.join(','))
|
|
}
|
|
|
|
function handleDeselectAll() {
|
|
onChange('')
|
|
}
|
|
|
|
return (
|
|
<Space direction="vertical" size="medium" style={{ width: '100%' }}>
|
|
<Space style={{ width: '100%' }}>
|
|
<Input
|
|
style={{ flex: 1 }}
|
|
value={value}
|
|
placeholder="数据库名称(多个以逗号分隔)"
|
|
onChange={onChange}
|
|
/>
|
|
<Button
|
|
type="outline"
|
|
size="small"
|
|
loading={loading}
|
|
disabled={!canDiscover}
|
|
onClick={handleDiscover}
|
|
>
|
|
发现数据库
|
|
</Button>
|
|
</Space>
|
|
|
|
{error && <Typography.Text type="error">{error}</Typography.Text>}
|
|
|
|
{loading && <Spin size={16} />}
|
|
|
|
{discovered && databases.length > 0 && (
|
|
<div style={{ border: '1px solid var(--color-border-2)', borderRadius: 4, padding: '8px 12px', maxHeight: 200, overflow: 'auto' }}>
|
|
<Space size="mini" style={{ marginBottom: 8 }}>
|
|
<Button type="text" size="mini" onClick={handleSelectAll}>
|
|
全选
|
|
</Button>
|
|
<Button type="text" size="mini" onClick={handleDeselectAll}>
|
|
清空
|
|
</Button>
|
|
</Space>
|
|
<Space direction="vertical" size={4}>
|
|
{databases.map((db) => (
|
|
<Checkbox
|
|
key={db}
|
|
checked={selectedDbs.includes(db)}
|
|
onChange={(checked) => handleToggle(db, checked)}
|
|
>
|
|
{db}
|
|
</Checkbox>
|
|
))}
|
|
</Space>
|
|
</div>
|
|
)}
|
|
</Space>
|
|
)
|
|
}
|