Initial commit: SSH Manager (backend + frontend)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
20
frontend/src/api/auth.ts
Normal file
20
frontend/src/api/auth.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import client from './client'
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string
|
||||
username: string
|
||||
displayName: string
|
||||
}
|
||||
|
||||
export function login(data: LoginRequest) {
|
||||
return client.post<LoginResponse>('/auth/login', data)
|
||||
}
|
||||
|
||||
export function getMe() {
|
||||
return client.get<{ username: string; displayName: string }>('/auth/me')
|
||||
}
|
||||
29
frontend/src/api/client.ts
Normal file
29
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: '/api',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default client
|
||||
45
frontend/src/api/connections.ts
Normal file
45
frontend/src/api/connections.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import client from './client'
|
||||
|
||||
export type AuthType = 'PASSWORD' | 'PRIVATE_KEY'
|
||||
|
||||
export interface Connection {
|
||||
id: number
|
||||
name: string
|
||||
host: string
|
||||
port: number
|
||||
username: string
|
||||
authType: AuthType
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface ConnectionCreateRequest {
|
||||
name: string
|
||||
host: string
|
||||
port?: number
|
||||
username: string
|
||||
authType?: AuthType
|
||||
password?: string
|
||||
privateKey?: string
|
||||
passphrase?: string
|
||||
}
|
||||
|
||||
export function listConnections() {
|
||||
return client.get<Connection[]>('/connections')
|
||||
}
|
||||
|
||||
export function getConnection(id: number) {
|
||||
return client.get<Connection>(`/connections/${id}`)
|
||||
}
|
||||
|
||||
export function createConnection(data: ConnectionCreateRequest) {
|
||||
return client.post<Connection>('/connections', data)
|
||||
}
|
||||
|
||||
export function updateConnection(id: number, data: ConnectionCreateRequest) {
|
||||
return client.put<Connection>(`/connections/${id}`, data)
|
||||
}
|
||||
|
||||
export function deleteConnection(id: number) {
|
||||
return client.delete(`/connections/${id}`)
|
||||
}
|
||||
81
frontend/src/api/sftp.ts
Normal file
81
frontend/src/api/sftp.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import client from './client'
|
||||
|
||||
export interface SftpFileInfo {
|
||||
name: string
|
||||
directory: boolean
|
||||
size: number
|
||||
mtime: number
|
||||
}
|
||||
|
||||
export function listFiles(connectionId: number, path: string) {
|
||||
return client.get<SftpFileInfo[]>('/sftp/list', {
|
||||
params: { connectionId, path: path || '.' },
|
||||
})
|
||||
}
|
||||
|
||||
export function getPwd(connectionId: number) {
|
||||
return client.get<{ path: string }>('/sftp/pwd', {
|
||||
params: { connectionId },
|
||||
})
|
||||
}
|
||||
|
||||
export async function downloadFile(connectionId: number, path: string) {
|
||||
const token = localStorage.getItem('token')
|
||||
const params = new URLSearchParams({ connectionId: String(connectionId), path })
|
||||
const res = await fetch(`/api/sftp/download?${params}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
if (!res.ok) throw new Error('Download failed')
|
||||
const blob = await res.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = 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)
|
||||
return client.post('/sftp/upload', form, {
|
||||
params: { connectionId, path },
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteFile(connectionId: number, path: string, directory: boolean) {
|
||||
return client.delete('/sftp/delete', {
|
||||
params: { connectionId, path, directory },
|
||||
})
|
||||
}
|
||||
|
||||
export function createDir(connectionId: number, path: string) {
|
||||
return client.post('/sftp/mkdir', null, {
|
||||
params: { connectionId, path },
|
||||
})
|
||||
}
|
||||
|
||||
export function renameFile(connectionId: number, oldPath: string, newPath: string) {
|
||||
return client.post('/sftp/rename', null, {
|
||||
params: { connectionId, oldPath, newPath },
|
||||
})
|
||||
}
|
||||
|
||||
export function transferRemote(
|
||||
sourceConnectionId: number,
|
||||
sourcePath: string,
|
||||
targetConnectionId: number,
|
||||
targetPath: string
|
||||
) {
|
||||
return client.post<{ message: string }>('/sftp/transfer-remote', null, {
|
||||
params: {
|
||||
sourceConnectionId,
|
||||
sourcePath,
|
||||
targetConnectionId,
|
||||
targetPath,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user