feat(ops): add object storage management and site-mapping CSV import/export
Ship the Ops backstage 对象存储 page (browse / upload / move / delete / folder ops) backed by a new object-storage service + handler, with the shared storage client extended with List/Stat/Copy/Usage/DownloadURL so both MinIO and Aliyun providers cover the new surface. Add ForcePathStyle to the object-storage config and treat r2/s3/custom_s3 as external providers so Cloudflare R2 and other S3-compatibles can share the MinIO client path without spinning up the bundled MinIO. Also add CSV import/export + template download to the site-domain mappings page, raise gin MaxMultipartMemory to 8 MiB for the new multipart endpoints, register Ops swagger routes, and extend the descriptions-parity test to cover the ops router. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { http } from '@/lib/http'
|
||||
|
||||
export interface ObjectStorageObject {
|
||||
key: string
|
||||
size: number
|
||||
etag?: string
|
||||
last_modified: string
|
||||
content_type?: string
|
||||
storage_class?: string
|
||||
}
|
||||
|
||||
export interface ObjectStorageList {
|
||||
objects: ObjectStorageObject[]
|
||||
common_prefixes: string[]
|
||||
continuation_token?: string
|
||||
next_continuation_token?: string
|
||||
is_truncated: boolean
|
||||
prefix: string
|
||||
keyword?: string
|
||||
recursive: boolean
|
||||
}
|
||||
|
||||
export interface ObjectStorageDetail extends ObjectStorageObject {
|
||||
public_url?: string | null
|
||||
preview_url?: string | null
|
||||
}
|
||||
|
||||
export interface ObjectStorageUploadResult {
|
||||
object: ObjectStorageObject
|
||||
}
|
||||
|
||||
export interface ObjectStorageFolderCreateResult {
|
||||
prefix: string
|
||||
}
|
||||
|
||||
export interface ObjectStorageFolderDeleteResult {
|
||||
prefix: string
|
||||
deleted_count: number
|
||||
}
|
||||
|
||||
export interface ObjectStorageUsage {
|
||||
available: boolean
|
||||
provider?: string
|
||||
storage_size: number
|
||||
object_count: number
|
||||
}
|
||||
|
||||
export const objectStorageApi = {
|
||||
list(params: {
|
||||
prefix?: string
|
||||
keyword?: string
|
||||
continuation_token?: string
|
||||
start_after?: string
|
||||
recursive?: boolean
|
||||
size?: number
|
||||
}) {
|
||||
return http.get<ObjectStorageList>('/object-storage/objects', params)
|
||||
},
|
||||
usage() {
|
||||
return http.get<ObjectStorageUsage>('/object-storage/usage')
|
||||
},
|
||||
detail(key: string) {
|
||||
return http.get<ObjectStorageDetail>('/object-storage/objects/detail', { key })
|
||||
},
|
||||
resolveURL(key: string) {
|
||||
return http.get<{ url: string }>('/object-storage/objects/url', { key })
|
||||
},
|
||||
resolveDownloadURL(key: string) {
|
||||
return http.get<{ url: string }>('/object-storage/objects/download-url', { key })
|
||||
},
|
||||
async upload(file: File, target: { key: string; overwrite: boolean; content_type?: string }) {
|
||||
const form = new FormData()
|
||||
form.append('file', file, file.name)
|
||||
form.append('key', target.key)
|
||||
form.append('overwrite', String(target.overwrite))
|
||||
if (target.content_type) form.append('content_type', target.content_type)
|
||||
const response = await http.raw.post<{
|
||||
code: number
|
||||
message: string
|
||||
data: ObjectStorageUploadResult
|
||||
}>('/object-storage/objects/upload', form)
|
||||
return response.data.data
|
||||
},
|
||||
move(payload: {
|
||||
source_key: string
|
||||
destination_key: string
|
||||
overwrite: boolean
|
||||
delete_source: boolean
|
||||
}) {
|
||||
return http.post<ObjectStorageDetail, typeof payload>('/object-storage/objects/move', payload)
|
||||
},
|
||||
delete(key: string) {
|
||||
return http.delete<{ key: string }>(
|
||||
`/object-storage/objects?${new URLSearchParams({ key }).toString()}`,
|
||||
)
|
||||
},
|
||||
createFolder(prefix: string) {
|
||||
return http.post<ObjectStorageFolderCreateResult, { prefix: string }>('/object-storage/folders', {
|
||||
prefix,
|
||||
})
|
||||
},
|
||||
deleteFolder(prefix: string) {
|
||||
return http.delete<ObjectStorageFolderDeleteResult>(
|
||||
`/object-storage/folders?${new URLSearchParams({ prefix }).toString()}`,
|
||||
)
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user