mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-11 18:10:23 +08:00
功能: 新增 SAP HANA 完整备份支持与 Backint 协议代理 (#37)
* chore: ignore web/dist directory in git repository * 功能: 新增 SAP HANA 完整备份支持与 Backint 协议代理 - 修复 service 层校验 bug,使 SAP HANA 类型可正常创建 - 增强 hdbsql Runner:支持完整/增量/差异/日志备份、并行通道、失败重试 - 新增 Backint 协议代理(backupx backint 子命令),HANA 原生接口直连 BackupX 存储后端 - 新增本地 SQLite 目录维护 EBID↔对象键映射 - 前端新增 SAP HANA 扩展字段表单(备份类型/级别/通道数/重试次数/实例编号) - README 中英文补充 SAP HANA 两种模式的使用说明
This commit is contained in:
@@ -11,10 +11,15 @@ import { StorageTargetFormDrawer } from '../storage-targets/StorageTargetFormDra
|
||||
import {
|
||||
backupCompressionOptions,
|
||||
backupTaskTypeOptions,
|
||||
defaultSapHanaExtraConfig,
|
||||
getDefaultPort,
|
||||
isDatabaseBackupTask,
|
||||
isFileBackupTask,
|
||||
isSapHanaBackupTask,
|
||||
isSQLiteBackupTask,
|
||||
sapHanaBackupLevelOptions,
|
||||
sapHanaBackupTypeOptions,
|
||||
type SapHanaExtraConfig,
|
||||
} from './field-config'
|
||||
|
||||
interface BackupTaskFormDrawerProps {
|
||||
@@ -55,6 +60,7 @@ function createEmptyDraft(storageTargets?: StorageTargetSummary[]): BackupTaskPa
|
||||
compression: 'gzip',
|
||||
encrypt: false,
|
||||
maxBackups: 10,
|
||||
extraConfig: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +120,7 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa
|
||||
compression: initialValue.compression,
|
||||
encrypt: initialValue.encrypt,
|
||||
maxBackups: initialValue.maxBackups,
|
||||
extraConfig: initialValue.extraConfig,
|
||||
})
|
||||
setExcludePatternsText(initialValue.excludePatterns.join('\n'))
|
||||
setCurrentStep(0)
|
||||
@@ -152,12 +159,28 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa
|
||||
dbPassword: value === 'mysql' || value === 'postgresql' || value === 'saphana' ? current.dbPassword : '',
|
||||
dbName: value === 'mysql' || value === 'postgresql' || value === 'saphana' ? current.dbName : '',
|
||||
dbPath: value === 'sqlite' ? current.dbPath : '',
|
||||
// 切换到 SAP HANA 时初始化扩展配置;切换到其他类型时清空
|
||||
extraConfig: value === 'saphana'
|
||||
? ({ ...defaultSapHanaExtraConfig(), ...(current.extraConfig as SapHanaExtraConfig | undefined) } as unknown as Record<string, unknown>)
|
||||
: undefined,
|
||||
}))
|
||||
if (value !== 'file') {
|
||||
setExcludePatternsText('')
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 SAP HANA 扩展配置的辅助函数
|
||||
function updateHanaExtraConfig(patch: Partial<SapHanaExtraConfig>) {
|
||||
setDraft((current) => {
|
||||
const merged: SapHanaExtraConfig = {
|
||||
...defaultSapHanaExtraConfig(),
|
||||
...(current.extraConfig as SapHanaExtraConfig | undefined),
|
||||
...patch,
|
||||
}
|
||||
return { ...current, extraConfig: merged as unknown as Record<string, unknown> }
|
||||
})
|
||||
}
|
||||
|
||||
function validate(value: BackupTaskPayload) {
|
||||
if (!value.name.trim()) {
|
||||
return '请输入任务名称'
|
||||
@@ -368,12 +391,78 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa
|
||||
<Input value={draft.dbName} placeholder="例如:app_prod" onChange={(value) => updateDraft({ dbName: value })} />
|
||||
)}
|
||||
</div>
|
||||
{isSapHanaBackupTask(draft.type) ? renderSapHanaExtraFields() : null}
|
||||
</>
|
||||
) : null}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
|
||||
function renderSapHanaExtraFields() {
|
||||
const hana: SapHanaExtraConfig = {
|
||||
...defaultSapHanaExtraConfig(),
|
||||
...(draft.extraConfig as SapHanaExtraConfig | undefined),
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Divider style={{ margin: '8px 0' }} orientation="left">
|
||||
<Typography.Text type="secondary">SAP HANA 扩展配置</Typography.Text>
|
||||
</Divider>
|
||||
<div>
|
||||
<Typography.Text>备份类型</Typography.Text>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
value={hana.backupType}
|
||||
options={[...sapHanaBackupTypeOptions]}
|
||||
onChange={(value) => updateHanaExtraConfig({ backupType: value as SapHanaExtraConfig['backupType'] })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text>备份级别</Typography.Text>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
value={hana.backupLevel}
|
||||
options={[...sapHanaBackupLevelOptions]}
|
||||
disabled={hana.backupType === 'log'}
|
||||
onChange={(value) => updateHanaExtraConfig({ backupLevel: value as SapHanaExtraConfig['backupLevel'] })}
|
||||
/>
|
||||
{hana.backupType === 'log' ? (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>日志备份不支持级别配置</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text>并行通道数</Typography.Text>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
value={hana.backupChannels}
|
||||
min={1}
|
||||
max={32}
|
||||
onChange={(value) => updateHanaExtraConfig({ backupChannels: Number(value ?? 1) })}
|
||||
/>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>{'>'} 1 时启用 HANA 多路径并行备份</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text>失败重试次数</Typography.Text>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
value={hana.maxRetries}
|
||||
min={1}
|
||||
max={10}
|
||||
onChange={(value) => updateHanaExtraConfig({ maxRetries: Number(value ?? 3) })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text>实例编号(可选)</Typography.Text>
|
||||
<Input
|
||||
value={hana.instanceNumber}
|
||||
placeholder="留空将根据端口自动推断(例如 30015 → 0)"
|
||||
onChange={(value) => updateHanaExtraConfig({ instanceNumber: value })}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function handleQuickCreateSubmit(value: StorageTargetPayload) {
|
||||
if (!onCreateStorageTarget) return
|
||||
setQuickCreateLoading(true)
|
||||
|
||||
@@ -86,3 +86,39 @@ export function getDefaultPort(type: BackupTaskType) {
|
||||
export function getCompressionLabel(compression: BackupCompression) {
|
||||
return compression === 'gzip' ? 'Gzip' : '无'
|
||||
}
|
||||
|
||||
/** SAP HANA 备份级别选项 */
|
||||
export const sapHanaBackupLevelOptions = [
|
||||
{ label: '完整备份 (Full)', value: 'full' },
|
||||
{ label: '增量备份 (Incremental)', value: 'incremental' },
|
||||
{ label: '差异备份 (Differential)', value: 'differential' },
|
||||
] as const
|
||||
|
||||
/** SAP HANA 备份类型选项 */
|
||||
export const sapHanaBackupTypeOptions = [
|
||||
{ label: '数据备份 (Data)', value: 'data' },
|
||||
{ label: '日志备份 (Log)', value: 'log' },
|
||||
] as const
|
||||
|
||||
/** SAP HANA 扩展配置默认值 */
|
||||
export interface SapHanaExtraConfig {
|
||||
backupType?: 'data' | 'log'
|
||||
backupLevel?: 'full' | 'incremental' | 'differential'
|
||||
backupChannels?: number
|
||||
maxRetries?: number
|
||||
instanceNumber?: string
|
||||
}
|
||||
|
||||
export function isSapHanaBackupTask(type: BackupTaskType) {
|
||||
return type === 'saphana'
|
||||
}
|
||||
|
||||
export function defaultSapHanaExtraConfig(): SapHanaExtraConfig {
|
||||
return {
|
||||
backupType: 'data',
|
||||
backupLevel: 'full',
|
||||
backupChannels: 1,
|
||||
maxRetries: 3,
|
||||
instanceNumber: '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ export interface BackupTaskDetail extends BackupTaskSummary {
|
||||
dbUser: string
|
||||
dbName: string
|
||||
dbPath: string
|
||||
/** 类型特有的扩展配置(如 SAP HANA 的 backupLevel/backupChannels 等) */
|
||||
extraConfig?: Record<string, unknown>
|
||||
maskedFields?: string[]
|
||||
createdAt: string
|
||||
}
|
||||
@@ -59,6 +61,8 @@ export interface BackupTaskPayload {
|
||||
compression: BackupCompression
|
||||
encrypt: boolean
|
||||
maxBackups: number
|
||||
/** 类型特有的扩展配置(如 SAP HANA 的 backupLevel/backupChannels 等) */
|
||||
extraConfig?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface BackupTaskTogglePayload {
|
||||
|
||||
Reference in New Issue
Block a user