Initial commit: SSH Manager (backend + frontend)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
liu
2026-02-03 09:10:06 +08:00
commit 1c5a44ff71
63 changed files with 6946 additions and 0 deletions

20
frontend/src/api/auth.ts Normal file
View 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')
}

View 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

View 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
View 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,
},
})
}