2026-07-23 18:36:24 +08:00
|
|
|
import { getToken } from '@/utils/auth'
|
|
|
|
|
|
|
|
|
|
export interface DeliveryTarget {
|
|
|
|
|
id: number
|
|
|
|
|
name: string
|
|
|
|
|
target_type: string
|
|
|
|
|
awx_inventory_id: number
|
|
|
|
|
awx_template_id: number
|
|
|
|
|
enabled: boolean
|
|
|
|
|
metadata?: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 14:45:17 +08:00
|
|
|
export interface DeliveryMountPath {
|
|
|
|
|
path: string
|
|
|
|
|
available_gi: number
|
|
|
|
|
fstype?: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 18:36:24 +08:00
|
|
|
export interface CreateMySQLDeliveryPayload {
|
|
|
|
|
business_line_id: number
|
|
|
|
|
target_id: number
|
|
|
|
|
namespace: string
|
|
|
|
|
instance_name: string
|
|
|
|
|
mysql_version: string
|
2026-07-27 15:20:03 +08:00
|
|
|
topology: string
|
|
|
|
|
mysql_port?: number
|
|
|
|
|
data_disk: string
|
2026-07-28 09:34:37 +08:00
|
|
|
target_host?: string
|
2026-07-23 18:36:24 +08:00
|
|
|
cpu_milli: number
|
|
|
|
|
memory_mi: number
|
|
|
|
|
storage_gi: number
|
2026-07-28 19:15:14 +08:00
|
|
|
cpu_cores?: number
|
|
|
|
|
memory_gb?: number
|
|
|
|
|
storage_gb?: number
|
|
|
|
|
timezone?: string
|
|
|
|
|
lower_case_table_names?: number
|
|
|
|
|
character_set?: string
|
|
|
|
|
collation?: string
|
|
|
|
|
max_connections?: string | number
|
|
|
|
|
innodb_redo_log_capacity?: string
|
|
|
|
|
innodb_flush_log_at_trx_commit?: number
|
|
|
|
|
sync_binlog?: number
|
|
|
|
|
innodb_io_capacity?: number
|
|
|
|
|
long_query_time?: number
|
|
|
|
|
binlog_expire_logs_seconds?: number
|
|
|
|
|
max_binlog_size?: string
|
|
|
|
|
mysql_root_password?: string
|
|
|
|
|
mysql_admin_password?: string
|
2026-07-23 18:36:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface DeliveryTask {
|
|
|
|
|
id: string
|
|
|
|
|
business_line_id: number
|
|
|
|
|
requested_by: number
|
2026-07-27 15:20:03 +08:00
|
|
|
component: string
|
2026-07-23 18:36:24 +08:00
|
|
|
target_type: string
|
|
|
|
|
target_id: number
|
|
|
|
|
namespace: string
|
|
|
|
|
instance_name: string
|
|
|
|
|
target_host?: string
|
|
|
|
|
target_host_ip?: string
|
|
|
|
|
mysql_port?: number
|
|
|
|
|
status: string
|
|
|
|
|
error_message?: string
|
|
|
|
|
created_at: string
|
|
|
|
|
updated_at: string
|
|
|
|
|
started_at?: string
|
|
|
|
|
finished_at?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TaskEvent {
|
|
|
|
|
id: number
|
|
|
|
|
task_id: string
|
|
|
|
|
from_state: string
|
|
|
|
|
to_state: string
|
2026-07-27 15:20:03 +08:00
|
|
|
stage?: string
|
|
|
|
|
event_status?: string
|
2026-07-23 18:36:24 +08:00
|
|
|
message: string
|
|
|
|
|
created_at: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 09:55:04 +08:00
|
|
|
export interface DeliveryTaskSnapshot {
|
|
|
|
|
task: DeliveryTask
|
|
|
|
|
events: TaskEvent[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 19:15:14 +08:00
|
|
|
export interface DeploymentCredential {
|
|
|
|
|
username: string
|
|
|
|
|
host: string
|
|
|
|
|
password: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 17:56:58 +08:00
|
|
|
export interface MySQLServiceLedgerItem {
|
|
|
|
|
name: string
|
|
|
|
|
datacenter: string
|
|
|
|
|
business_tag: string
|
|
|
|
|
instances: number
|
|
|
|
|
healthy: number
|
|
|
|
|
address: string
|
|
|
|
|
status: string
|
|
|
|
|
status_class: string
|
|
|
|
|
version: string
|
|
|
|
|
namespace: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 18:36:24 +08:00
|
|
|
export const deliveryApi = {
|
|
|
|
|
async listTargets(): Promise<DeliveryTarget[]> {
|
|
|
|
|
const data = await authRequest('/auth/api/v1/delivery/targets?component=mysql')
|
|
|
|
|
return Array.isArray(data.items) ? data.items : []
|
|
|
|
|
},
|
|
|
|
|
|
2026-07-28 14:45:17 +08:00
|
|
|
async listTargetMountPaths(targetId: number, host: string): Promise<DeliveryMountPath[]> {
|
|
|
|
|
const data = await authRequest(`/auth/api/v1/delivery/targets/${targetId}/hosts/${encodeURIComponent(host)}/mount-paths`)
|
|
|
|
|
return Array.isArray(data.items) ? data.items : []
|
|
|
|
|
},
|
|
|
|
|
|
2026-07-23 18:36:24 +08:00
|
|
|
async createMySQL(payload: CreateMySQLDeliveryPayload): Promise<DeliveryTask> {
|
|
|
|
|
const data = await authRequest('/auth/api/v1/delivery/mysql', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Idempotency-Key': createIdempotencyKey(payload),
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
})
|
|
|
|
|
return data.task
|
|
|
|
|
},
|
|
|
|
|
|
2026-07-27 15:20:03 +08:00
|
|
|
async listTasks(params: { businessLineId?: number; component?: string; active?: boolean } = {}): Promise<DeliveryTask[]> {
|
|
|
|
|
const search = new URLSearchParams()
|
|
|
|
|
if (params.businessLineId) search.set('business_line_id', String(params.businessLineId))
|
|
|
|
|
if (params.component) search.set('component', params.component)
|
|
|
|
|
if (params.active) search.set('active', 'true')
|
|
|
|
|
const query = search.toString()
|
|
|
|
|
const data = await authRequest(`/auth/api/v1/delivery/tasks${query ? `?${query}` : ''}`)
|
|
|
|
|
return Array.isArray(data.items) ? data.items : []
|
|
|
|
|
},
|
|
|
|
|
|
2026-07-28 17:56:58 +08:00
|
|
|
async listMySQLServices(businessLineId: number): Promise<MySQLServiceLedgerItem[]> {
|
|
|
|
|
const data = await authRequest(`/auth/api/v1/delivery/business-lines/${businessLineId}/mysql-services`)
|
|
|
|
|
return Array.isArray(data.items) ? data.items : []
|
|
|
|
|
},
|
|
|
|
|
|
2026-07-23 18:36:24 +08:00
|
|
|
async getTask(taskId: string): Promise<{ task: DeliveryTask; events: TaskEvent[] }> {
|
|
|
|
|
const data = await authRequest(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}`)
|
|
|
|
|
return {
|
|
|
|
|
task: data.task,
|
|
|
|
|
events: Array.isArray(data.events) ? data.events : [],
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async cancel(taskId: string): Promise<void> {
|
|
|
|
|
await authRequest(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}/cancel`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
})
|
|
|
|
|
},
|
2026-07-28 09:55:04 +08:00
|
|
|
|
|
|
|
|
streamTask(taskId: string): EventSource {
|
|
|
|
|
const token = getToken()
|
|
|
|
|
const search = new URLSearchParams()
|
|
|
|
|
if (token) search.set('access_token', token)
|
|
|
|
|
const query = search.toString()
|
|
|
|
|
return new EventSource(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}/stream${query ? `?${query}` : ''}`)
|
|
|
|
|
},
|
2026-07-28 15:08:37 +08:00
|
|
|
|
2026-07-28 14:29:34 +08:00
|
|
|
async retryRollback(taskId: string): Promise<void> {
|
|
|
|
|
await authRequest(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}/rollback/retry`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async releaseRollback(taskId: string): Promise<void> {
|
|
|
|
|
await authRequest(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}/rollback/release`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async retryCloudDMRegistration(taskId: string): Promise<void> {
|
|
|
|
|
await authRequest(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}/clouddm/retry`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
})
|
|
|
|
|
},
|
2026-07-28 19:15:14 +08:00
|
|
|
|
|
|
|
|
async revealCredentials(taskId: string): Promise<DeploymentCredential[]> {
|
|
|
|
|
const data = await authRequest(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}/credentials/reveal`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
})
|
|
|
|
|
return Array.isArray(data.items) ? data.items : []
|
|
|
|
|
},
|
2026-07-23 18:36:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createIdempotencyKey(payload: CreateMySQLDeliveryPayload) {
|
|
|
|
|
return [
|
|
|
|
|
'mysql',
|
|
|
|
|
payload.business_line_id,
|
|
|
|
|
payload.target_id,
|
|
|
|
|
payload.namespace,
|
|
|
|
|
payload.instance_name,
|
|
|
|
|
Date.now(),
|
|
|
|
|
].join(':')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function authRequest(path: string, init: RequestInit = {}) {
|
|
|
|
|
const token = getToken()
|
|
|
|
|
const response = await fetch(path, {
|
|
|
|
|
...init,
|
|
|
|
|
headers: {
|
|
|
|
|
Accept: 'application/json',
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
|
|
|
...init.headers,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
const text = await response.text()
|
|
|
|
|
const data = parseResponseBody(text)
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const message = data?.error || data?.message || text || `HTTP ${response.status}`
|
|
|
|
|
throw new Error(message)
|
|
|
|
|
}
|
|
|
|
|
return data || {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseResponseBody(text: string) {
|
|
|
|
|
if (!text.trim()) {
|
|
|
|
|
return {}
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(text)
|
|
|
|
|
} catch {
|
|
|
|
|
return { error: text }
|
|
|
|
|
}
|
|
|
|
|
}
|