feat: rebuild frontend with react

This commit is contained in:
liumangmang
2026-04-22 09:53:06 +08:00
parent 42836aa4c3
commit 423cca97a6
81 changed files with 2907 additions and 10230 deletions
+17
View File
@@ -0,0 +1,17 @@
import http from './http'
import type { CurrentUser, LoginResponse } from '../types'
export function login(username: string, password: string) {
return http.post<LoginResponse>('/auth/login', { username, password })
}
export function getMe() {
return http.get<CurrentUser>('/auth/me')
}
export function changePassword(currentPassword: string, newPassword: string) {
return http.post<{ message: string; passwordChangeRequired: boolean }>('/auth/change-password', {
currentPassword,
newPassword,
})
}
+22
View File
@@ -0,0 +1,22 @@
import http from './http'
import type { BatchCommandResponse, Connection, ConnectionCreateRequest } from '../types'
export function listConnections() {
return http.get<Connection[]>('/connections')
}
export function createConnection(payload: ConnectionCreateRequest) {
return http.post<Connection>('/connections', payload)
}
export function updateConnection(id: number, payload: ConnectionCreateRequest) {
return http.put<Connection>(`/connections/${id}`, payload)
}
export function deleteConnection(id: number) {
return http.delete<{ message: string }>(`/connections/${id}`)
}
export function executeBatchCommand(connectionIds: number[], command: string) {
return http.post<BatchCommandResponse>('/connections/batch-command', { connectionIds, command })
}
+35
View File
@@ -0,0 +1,35 @@
import axios from 'axios'
const http = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json',
},
})
http.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
if (typeof FormData !== 'undefined' && config.data instanceof FormData) {
const headers = config.headers as Record<string, string> | undefined
if (headers) {
delete headers['Content-Type']
delete headers['content-type']
}
}
return config
})
http.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
}
return Promise.reject(error)
},
)
export default http
+6
View File
@@ -0,0 +1,6 @@
import http from './http'
import type { MonitorMetrics } from '../types'
export function getMetrics(connectionId: number) {
return http.get<MonitorMetrics>(`/monitor/${connectionId}`)
}
+10
View File
@@ -0,0 +1,10 @@
import http from './http'
import type { SessionTreeLayoutPayload } from '../types'
export function getSessionTree() {
return http.get<SessionTreeLayoutPayload>('/session-tree')
}
export function saveSessionTree(payload: SessionTreeLayoutPayload) {
return http.put<SessionTreeLayoutPayload>('/session-tree', payload)
}
+91
View File
@@ -0,0 +1,91 @@
import http from './http'
import type { RemoteTransferTask, SftpFileInfo, UploadTask } from '../types'
export function getPwd(connectionId: number) {
return http.get<{ path: string }>('/sftp/pwd', { params: { connectionId } })
}
export function listFiles(connectionId: number, path: string) {
return http.get<SftpFileInfo[]>('/sftp/list', { params: { connectionId, path: path || '.' } })
}
export async function downloadFile(connectionId: number, path: string, downloadName?: string) {
const token = localStorage.getItem('token')
const params = new URLSearchParams({ connectionId: String(connectionId), path })
const response = await fetch(`/api/sftp/download?${params.toString()}`, {
headers: token ? { Authorization: `Bearer ${token}` } : {},
})
if (!response.ok) throw new Error('Download failed')
const blob = await response.blob()
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = downloadName || path.split('/').pop() || 'download'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}
export function uploadFile(connectionId: number, path: string, file: File) {
const form = new FormData()
form.append('file', file, file.name)
return http.post<{ taskId: string; message: string }>('/sftp/upload', form, {
params: { connectionId, path },
})
}
export function subscribeUploadProgress(taskId: string, onProgress: (task: UploadTask) => void) {
const token = localStorage.getItem('token')
const source = new EventSource(
`/api/sftp/upload/tasks/${encodeURIComponent(taskId)}/progress?token=${encodeURIComponent(token || '')}`,
)
source.addEventListener('progress', (event) => {
onProgress(JSON.parse((event as MessageEvent).data))
})
source.addEventListener('error', () => {
source.close()
})
return () => source.close()
}
export function createDir(connectionId: number, path: string) {
return http.post('/sftp/mkdir', null, { params: { connectionId, path } })
}
export function renameFile(connectionId: number, oldPath: string, newPath: string) {
return http.post('/sftp/rename', null, { params: { connectionId, oldPath, newPath } })
}
export function deleteFile(connectionId: number, path: string, directory: boolean) {
return http.delete('/sftp/delete', { params: { connectionId, path, directory } })
}
export function createRemoteTransferTask(
sourceConnectionId: number,
sourcePath: string,
targetConnectionId: number,
targetPath: string,
) {
return http.post<RemoteTransferTask>('/sftp/transfer-remote/tasks', null, {
params: { sourceConnectionId, sourcePath, targetConnectionId, targetPath },
})
}
export function subscribeRemoteTransferProgress(taskId: string, onProgress: (task: RemoteTransferTask) => void) {
const token = localStorage.getItem('token')
const source = new EventSource(
`/api/sftp/transfer-remote/tasks/${encodeURIComponent(taskId)}/progress?token=${encodeURIComponent(token || '')}`,
)
source.addEventListener('progress', (event) => {
onProgress(JSON.parse((event as MessageEvent).data))
})
source.addEventListener('error', () => {
source.close()
})
return () => source.close()
}
export function cancelRemoteTransferTask(taskId: string) {
return http.delete(`/sftp/transfer-remote/tasks/${encodeURIComponent(taskId)}`)
}