功能: 新增 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:
Wu Qing
2026-04-16 23:43:46 +08:00
committed by GitHub
parent 241a4808be
commit e04774ff68
22 changed files with 2247 additions and 43 deletions

View File

@@ -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)

View File

@@ -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: '',
}
}

View File

@@ -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 {