mirror of
https://github.com/veops/oneterm.git
synced 2025-09-27 03:36:02 +08:00
feat(backend): file transfer
This commit is contained in:
@@ -9,9 +9,10 @@ import (
|
||||
// Connect handles WebSocket connections for terminal sessions
|
||||
// @Tags connect
|
||||
// @Success 200 {object} HttpResponse
|
||||
// @Param w query int false "width"
|
||||
// @Param h query int false "height"
|
||||
// @Param dpi query int false "dpi"
|
||||
// @Param w query int false "width"
|
||||
// @Param h query int false "height"
|
||||
// @Param dpi query int false "dpi"
|
||||
// @Param session_id query string false "session_id"
|
||||
// @Success 200 {object} HttpResponse{}
|
||||
// @Router /connect/:asset_id/:account_id/:protocol [get]
|
||||
func (c *Controller) Connect(ctx *gin.Context) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -188,160 +188,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files": {
|
||||
"get": {
|
||||
"description": "Get file list for RDP session drive",
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "List RDP session files",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Directory path",
|
||||
"name": "path",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files/download": {
|
||||
"get": {
|
||||
"description": "Download file from RDP session drive",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/octet-stream"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Download file from RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "File path",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files/mkdir": {
|
||||
"post": {
|
||||
"description": "Create directory in RDP session drive",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Create directory in RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Directory creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.RDPMkdirRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files/upload": {
|
||||
"post": {
|
||||
"description": "Upload file to RDP session drive",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Upload file to RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "File to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Target directory path",
|
||||
"name": "path",
|
||||
"in": "formData"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/asset": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -906,6 +752,12 @@ const docTemplate = `{
|
||||
"description": "dpi",
|
||||
"name": "dpi",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1181,8 +1033,173 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/download": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dir",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "names (comma-separated for multiple files)",
|
||||
"name": "names",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/ls": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dir",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "show hidden files (default: false)",
|
||||
"name": "show_hidden",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/mkdir": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dir",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/upload": {
|
||||
"post": {
|
||||
"description": "Uploads file via server temp storage then transfers to target using optimized SFTP with performance enhancements. HTTP response only after file reaches target machine.",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"summary": "High-performance file upload using optimized SFTP",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "target directory path (default: /tmp)",
|
||||
"name": "dir",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID for progress tracking (frontend generated)",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/transfer/progress/id/:transfer_id": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"responses": {}
|
||||
}
|
||||
},
|
||||
"/file/upload/:asset_id/:account_id": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
@@ -1203,9 +1220,21 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"description": "target directory path (default: /tmp)",
|
||||
"name": "dir",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID for progress tracking (frontend generated)",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
@@ -2090,6 +2119,217 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files": {
|
||||
"get": {
|
||||
"description": "Get file list for RDP session drive",
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "List RDP session files",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Directory path",
|
||||
"name": "path",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/download": {
|
||||
"get": {
|
||||
"description": "Download files from RDP session drive (supports multiple files via names parameter)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/octet-stream"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Download files from RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Directory path",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "File names (comma-separated for multiple files)",
|
||||
"name": "names",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/mkdir": {
|
||||
"post": {
|
||||
"description": "Create directory in RDP session drive",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Create directory in RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Directory creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/service.RDPMkdirRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/prepare": {
|
||||
"post": {
|
||||
"description": "Create transfer record before RDP upload starts for progress tracking",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Create transfer record for RDP upload",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filename",
|
||||
"name": "filename",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/upload": {
|
||||
"post": {
|
||||
"description": "Upload file to RDP session drive",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Upload file to RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID for progress tracking (frontend generated)",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Target directory path",
|
||||
"name": "path",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "File to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/session": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2883,17 +3123,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.RDPMkdirRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.AccessAuth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -3737,6 +3966,17 @@ const docTemplate = `{
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"service.RDPMkdirRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
@@ -177,160 +177,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files": {
|
||||
"get": {
|
||||
"description": "Get file list for RDP session drive",
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "List RDP session files",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Directory path",
|
||||
"name": "path",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files/download": {
|
||||
"get": {
|
||||
"description": "Download file from RDP session drive",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/octet-stream"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Download file from RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "File path",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files/mkdir": {
|
||||
"post": {
|
||||
"description": "Create directory in RDP session drive",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Create directory in RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Directory creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.RDPMkdirRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/rdp/sessions/{session_id}/files/upload": {
|
||||
"post": {
|
||||
"description": "Upload file to RDP session drive",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Upload file to RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "File to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Target directory path",
|
||||
"name": "path",
|
||||
"in": "formData"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/asset": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -895,6 +741,12 @@
|
||||
"description": "dpi",
|
||||
"name": "dpi",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1170,8 +1022,173 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/download": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dir",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "names (comma-separated for multiple files)",
|
||||
"name": "names",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/ls": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dir",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "show hidden files (default: false)",
|
||||
"name": "show_hidden",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/mkdir": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "dir",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/session/:session_id/upload": {
|
||||
"post": {
|
||||
"description": "Uploads file via server temp storage then transfers to target using optimized SFTP with performance enhancements. HTTP response only after file reaches target machine.",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"summary": "High-performance file upload using optimized SFTP",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "session_id",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "target directory path (default: /tmp)",
|
||||
"name": "dir",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID for progress tracking (frontend generated)",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/file/transfer/progress/id/:transfer_id": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
"responses": {}
|
||||
}
|
||||
},
|
||||
"/file/upload/:asset_id/:account_id": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"file"
|
||||
],
|
||||
@@ -1192,9 +1209,21 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "path",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"description": "target directory path (default: /tmp)",
|
||||
"name": "dir",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID for progress tracking (frontend generated)",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
@@ -2079,6 +2108,217 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files": {
|
||||
"get": {
|
||||
"description": "Get file list for RDP session drive",
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "List RDP session files",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Directory path",
|
||||
"name": "path",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/download": {
|
||||
"get": {
|
||||
"description": "Download files from RDP session drive (supports multiple files via names parameter)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/octet-stream"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Download files from RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Directory path",
|
||||
"name": "dir",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "File names (comma-separated for multiple files)",
|
||||
"name": "names",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/mkdir": {
|
||||
"post": {
|
||||
"description": "Create directory in RDP session drive",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Create directory in RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Directory creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/service.RDPMkdirRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/prepare": {
|
||||
"post": {
|
||||
"description": "Create transfer record before RDP upload starts for progress tracking",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Create transfer record for RDP upload",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filename",
|
||||
"name": "filename",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/rdp/sessions/{session_id}/files/upload": {
|
||||
"post": {
|
||||
"description": "Upload file to RDP session drive",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"RDP File"
|
||||
],
|
||||
"summary": "Upload file to RDP session",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session ID",
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom transfer ID for progress tracking (frontend generated)",
|
||||
"name": "transfer_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Target directory path",
|
||||
"name": "path",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "File to upload",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/session": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2872,17 +3112,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.RDPMkdirRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.AccessAuth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -3726,6 +3955,17 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"service.RDPMkdirRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,13 +15,6 @@ definitions:
|
||||
items: {}
|
||||
type: array
|
||||
type: object
|
||||
controller.RDPMkdirRequest:
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
model.AccessAuth:
|
||||
properties:
|
||||
allow:
|
||||
@@ -580,6 +573,13 @@ definitions:
|
||||
paste:
|
||||
type: boolean
|
||||
type: object
|
||||
service.RDPMkdirRequest:
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
paths:
|
||||
@@ -689,108 +689,6 @@ paths:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
tags:
|
||||
- account
|
||||
/api/v1/rdp/sessions/{session_id}/files:
|
||||
get:
|
||||
description: Get file list for RDP session drive
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Directory path
|
||||
in: query
|
||||
name: path
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: List RDP session files
|
||||
tags:
|
||||
- RDP File
|
||||
/api/v1/rdp/sessions/{session_id}/files/download:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Download file from RDP session drive
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: File path
|
||||
in: query
|
||||
name: path
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/octet-stream
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
summary: Download file from RDP session
|
||||
tags:
|
||||
- RDP File
|
||||
/api/v1/rdp/sessions/{session_id}/files/mkdir:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create directory in RDP session drive
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Directory creation request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controller.RDPMkdirRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: Create directory in RDP session
|
||||
tags:
|
||||
- RDP File
|
||||
/api/v1/rdp/sessions/{session_id}/files/upload:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: Upload file to RDP session drive
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: File to upload
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
- description: Target directory path
|
||||
in: formData
|
||||
name: path
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: Upload file to RDP session
|
||||
tags:
|
||||
- RDP File
|
||||
/asset:
|
||||
get:
|
||||
parameters:
|
||||
@@ -1134,6 +1032,10 @@ paths:
|
||||
in: query
|
||||
name: dpi
|
||||
type: integer
|
||||
- description: session_id
|
||||
in: query
|
||||
name: session_id
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@@ -1307,8 +1209,118 @@ paths:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
tags:
|
||||
- file
|
||||
/file/session/:session_id/download:
|
||||
get:
|
||||
parameters:
|
||||
- description: session_id
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: dir
|
||||
in: query
|
||||
name: dir
|
||||
required: true
|
||||
type: string
|
||||
- description: names (comma-separated for multiple files)
|
||||
in: query
|
||||
name: names
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
tags:
|
||||
- file
|
||||
/file/session/:session_id/ls:
|
||||
get:
|
||||
parameters:
|
||||
- description: session_id
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: dir
|
||||
in: query
|
||||
name: dir
|
||||
required: true
|
||||
type: string
|
||||
- description: 'show hidden files (default: false)'
|
||||
in: query
|
||||
name: show_hidden
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
tags:
|
||||
- file
|
||||
/file/session/:session_id/mkdir:
|
||||
post:
|
||||
parameters:
|
||||
- description: session_id
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: dir
|
||||
in: query
|
||||
name: dir
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
tags:
|
||||
- file
|
||||
/file/session/:session_id/upload:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: Uploads file via server temp storage then transfers to target using
|
||||
optimized SFTP with performance enhancements. HTTP response only after file
|
||||
reaches target machine.
|
||||
parameters:
|
||||
- description: session_id
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: 'target directory path (default: /tmp)'
|
||||
in: query
|
||||
name: dir
|
||||
type: string
|
||||
- description: Custom transfer ID for progress tracking (frontend generated)
|
||||
in: query
|
||||
name: transfer_id
|
||||
type: string
|
||||
- description: file to upload
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: High-performance file upload using optimized SFTP
|
||||
tags:
|
||||
- file
|
||||
/file/transfer/progress/id/:transfer_id:
|
||||
get:
|
||||
responses: {}
|
||||
tags:
|
||||
- file
|
||||
/file/upload/:asset_id/:account_id:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
parameters:
|
||||
- description: asset_id
|
||||
in: path
|
||||
@@ -1320,11 +1332,19 @@ paths:
|
||||
name: account_id
|
||||
required: true
|
||||
type: integer
|
||||
- description: path
|
||||
- description: 'target directory path (default: /tmp)'
|
||||
in: query
|
||||
name: path
|
||||
required: true
|
||||
name: dir
|
||||
type: string
|
||||
- description: Custom transfer ID for progress tracking (frontend generated)
|
||||
in: query
|
||||
name: transfer_id
|
||||
type: string
|
||||
- description: file to upload
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@@ -1861,6 +1881,147 @@ paths:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
tags:
|
||||
- QuickCommand
|
||||
/rdp/sessions/{session_id}/files:
|
||||
get:
|
||||
description: Get file list for RDP session drive
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Directory path
|
||||
in: query
|
||||
name: path
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: List RDP session files
|
||||
tags:
|
||||
- RDP File
|
||||
/rdp/sessions/{session_id}/files/download:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Download files from RDP session drive (supports multiple files
|
||||
via names parameter)
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Directory path
|
||||
in: query
|
||||
name: dir
|
||||
required: true
|
||||
type: string
|
||||
- description: File names (comma-separated for multiple files)
|
||||
in: query
|
||||
name: names
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/octet-stream
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
summary: Download files from RDP session
|
||||
tags:
|
||||
- RDP File
|
||||
/rdp/sessions/{session_id}/files/mkdir:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create directory in RDP session drive
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Directory creation request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/service.RDPMkdirRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: Create directory in RDP session
|
||||
tags:
|
||||
- RDP File
|
||||
/rdp/sessions/{session_id}/files/prepare:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create transfer record before RDP upload starts for progress tracking
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Custom transfer ID
|
||||
in: query
|
||||
name: transfer_id
|
||||
type: string
|
||||
- description: Filename
|
||||
in: query
|
||||
name: filename
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: Create transfer record for RDP upload
|
||||
tags:
|
||||
- RDP File
|
||||
/rdp/sessions/{session_id}/files/upload:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: Upload file to RDP session drive
|
||||
parameters:
|
||||
- description: Session ID
|
||||
in: path
|
||||
name: session_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Custom transfer ID for progress tracking (frontend generated)
|
||||
in: query
|
||||
name: transfer_id
|
||||
type: string
|
||||
- description: Target directory path
|
||||
in: query
|
||||
name: path
|
||||
type: string
|
||||
- description: File to upload
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
summary: Upload file to RDP session
|
||||
tags:
|
||||
- RDP File
|
||||
/session:
|
||||
get:
|
||||
parameters:
|
||||
|
@@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
@@ -24,7 +25,11 @@ func AuthMiddleware() gin.HandlerFunc {
|
||||
)
|
||||
|
||||
m := make(map[string]any)
|
||||
ctx.ShouldBindBodyWithJSON(&m)
|
||||
contentType := ctx.GetHeader("Content-Type")
|
||||
if !strings.Contains(contentType, "multipart/form-data") {
|
||||
ctx.ShouldBindBodyWithJSON(&m)
|
||||
}
|
||||
|
||||
if ctx.Request.Method == "GET" {
|
||||
if _, ok := ctx.GetQuery("_key"); ok {
|
||||
m["_key"] = ctx.Query("_key")
|
||||
|
@@ -26,7 +26,7 @@ func Error2RespMiddleware() gin.HandlerFunc {
|
||||
// Skip middleware for session replay and file download endpoints
|
||||
urlPath := ctx.Request.URL.String()
|
||||
if strings.Contains(urlPath, "session/replay") ||
|
||||
strings.Contains(urlPath, "/file/download/") {
|
||||
strings.Contains(urlPath, "/download") {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func SetupRouter(r *gin.Engine) {
|
||||
r.SetTrustedProxies([]string{"0.0.0.0/0", "::/0"})
|
||||
r.MaxMultipartMemory = 128 << 20
|
||||
r.MaxMultipartMemory = 32 << 20 // 32MB, match with controller constant
|
||||
r.Use(gin.Recovery(), middleware.LoggerMiddleware())
|
||||
|
||||
docs.SwaggerInfo.Title = "ONETERM API"
|
||||
@@ -101,10 +101,23 @@ func SetupRouter(r *gin.Engine) {
|
||||
file := v1.Group("file")
|
||||
{
|
||||
file.GET("/history", c.GetFileHistory)
|
||||
|
||||
// Legacy asset-based file operations (for backward compatibility)
|
||||
file.GET("/ls/:asset_id/:account_id", c.FileLS)
|
||||
file.POST("/mkdir/:asset_id/:account_id", c.FileMkdir)
|
||||
file.POST("/upload/:asset_id/:account_id", c.FileUpload)
|
||||
file.GET("/download/:asset_id/:account_id", c.FileDownload)
|
||||
|
||||
sftpFile := file.Group("/session/:session_id")
|
||||
{
|
||||
sftpFile.GET("/ls", c.SftpFileLS)
|
||||
sftpFile.POST("/mkdir", c.SftpFileMkdir)
|
||||
sftpFile.POST("/upload", c.SftpFileUpload)
|
||||
sftpFile.GET("/download", c.SftpFileDownload)
|
||||
}
|
||||
|
||||
// File transfer progress tracking
|
||||
file.GET("/transfer/progress/id/:transfer_id", c.TransferProgressById)
|
||||
}
|
||||
|
||||
config := v1.Group("config")
|
||||
|
@@ -168,11 +168,16 @@ func DoConnect(ctx *gin.Context, ws *websocket.Conn) (sess *gsession.Session, er
|
||||
return
|
||||
}
|
||||
|
||||
sessionId := ctx.Query("session_id")
|
||||
if sessionId == "" {
|
||||
sessionId = uuid.New().String()
|
||||
}
|
||||
|
||||
sess = gsession.NewSession(ctx)
|
||||
sess.Ws = ws
|
||||
sess.Session = &model.Session{
|
||||
SessionType: ctx.GetInt("sessionType"),
|
||||
SessionId: uuid.New().String(),
|
||||
SessionId: sessionId,
|
||||
Uid: currentUser.GetUid(),
|
||||
UserName: currentUser.GetUserName(),
|
||||
AssetId: assetId,
|
||||
@@ -256,6 +261,30 @@ func DoConnect(ctx *gin.Context, ws *websocket.Conn) (sess *gsession.Session, er
|
||||
gsession.GetOnlineSession().Store(sess.SessionId, sess)
|
||||
gsession.UpsertSession(sess)
|
||||
|
||||
// Initialize session-based file client for high-performance file operations
|
||||
// Only for SSH-based protocols that support SFTP
|
||||
protocol := strings.Split(sess.Protocol, ":")[0]
|
||||
if protocol == "ssh" {
|
||||
if err := service.DefaultFileService.InitSessionFileClient(sess.SessionId, sess.AssetId, sess.AccountId); err != nil {
|
||||
logger.L().Warn("Failed to initialize session file client",
|
||||
zap.String("sessionId", sess.SessionId),
|
||||
zap.Int("assetId", sess.AssetId),
|
||||
zap.Int("accountId", sess.AccountId),
|
||||
zap.Error(err))
|
||||
// Don't fail the session creation for file service initialization failure
|
||||
} else {
|
||||
logger.L().Info("Session file client initialized successfully",
|
||||
zap.String("sessionId", sess.SessionId),
|
||||
zap.Int("assetId", sess.AssetId),
|
||||
zap.Int("accountId", sess.AccountId))
|
||||
}
|
||||
} else if protocol == "rdp" || protocol == "vnc" {
|
||||
logger.L().Debug("Skipping session file client initialization for Guacamole protocol",
|
||||
zap.String("protocol", protocol),
|
||||
zap.String("sessionId", sess.SessionId))
|
||||
// RDP and VNC use Guacamole protocol for file transfer, not SSH/SFTP
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -263,12 +292,20 @@ func DoConnect(ctx *gin.Context, ws *websocket.Conn) (sess *gsession.Session, er
|
||||
func HandleTerm(sess *gsession.Session, ctx *gin.Context) (err error) {
|
||||
defer func() {
|
||||
logger.L().Debug("defer HandleTerm", zap.String("sessionId", sess.SessionId))
|
||||
|
||||
// Clean up session-based file client (only for SSH-based protocols)
|
||||
protocol := strings.Split(sess.Protocol, ":")[0]
|
||||
if protocol == "ssh" {
|
||||
service.DefaultFileService.CloseSessionFileClient(sess.SessionId)
|
||||
// Clear SSH client from session to ensure proper cleanup
|
||||
sess.ClearSSHClient()
|
||||
}
|
||||
|
||||
sess.SshParser.Close(sess.Prompt)
|
||||
sess.Status = model.SESSIONSTATUS_OFFLINE
|
||||
sess.ClosedAt = lo.ToPtr(time.Now())
|
||||
if err = gsession.UpsertSession(sess); err != nil {
|
||||
logger.L().Error("offline session failed", zap.String("sessionId", sess.SessionId), zap.Error(err))
|
||||
return
|
||||
logger.L().Error("upsert session failed", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
chs := sess.Chans
|
||||
|
@@ -49,6 +49,10 @@ func ConnectSsh(ctx *gin.Context, sess *gsession.Session, asset *model.Asset, ac
|
||||
return
|
||||
}
|
||||
|
||||
// CRITICAL: Store SSH client in session for file transfer reuse
|
||||
sess.SetSSHClient(sshCli)
|
||||
logger.L().Info("SSH client stored in session for reuse", zap.String("sessionId", sess.SessionId))
|
||||
|
||||
sshSess, err := sshCli.NewSession()
|
||||
if err != nil {
|
||||
logger.L().Error("ssh session create failed", zap.Error(err))
|
||||
|
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
myi18n "github.com/veops/oneterm/internal/i18n"
|
||||
"github.com/veops/oneterm/internal/model"
|
||||
"github.com/veops/oneterm/internal/service"
|
||||
gsession "github.com/veops/oneterm/internal/session"
|
||||
myErrors "github.com/veops/oneterm/pkg/errors"
|
||||
"github.com/veops/oneterm/pkg/logger"
|
||||
@@ -224,6 +225,10 @@ func IsActive(message []byte) bool {
|
||||
func OfflineSession(ctx *gin.Context, sessionId string, closer string) {
|
||||
logger.L().Debug("offline", zap.String("session_id", sessionId), zap.String("closer", closer))
|
||||
defer gsession.GetOnlineSession().Delete(sessionId)
|
||||
|
||||
// Clean up session-based file client
|
||||
service.DefaultFileService.CloseSessionFileClient(sessionId)
|
||||
|
||||
session := gsession.GetOnlineSessionById(sessionId)
|
||||
if session == nil {
|
||||
return
|
||||
|
@@ -5,13 +5,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cast"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/veops/oneterm/internal/model"
|
||||
"github.com/veops/oneterm/internal/tunneling"
|
||||
@@ -33,6 +31,7 @@ const (
|
||||
DRIVE_CREATE_PATH = "create-drive-path"
|
||||
DRIVE_DISABLE_UPLOAD = "disable-upload"
|
||||
DRIVE_DISABLE_DOWNLOAD = "disable-download"
|
||||
DRIVE_NAME = "drive-name"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
@@ -87,6 +86,7 @@ func NewTunnel(connectionId, sessionId string, w, h, dpi int, protocol string, a
|
||||
func() map[string]string {
|
||||
return map[string]string{
|
||||
"version": VERSION,
|
||||
"client-name": "OneTerm",
|
||||
"recording-path": RECORDING_PATH,
|
||||
"create-recording-path": CREATE_RECORDING,
|
||||
"ignore-cert": IGNORE_CERT,
|
||||
@@ -100,12 +100,19 @@ func NewTunnel(connectionId, sessionId string, w, h, dpi int, protocol string, a
|
||||
"password": account.Password,
|
||||
"disable-copy": cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), !cfg.RdpConfig.Copy, !cfg.VncConfig.Copy)),
|
||||
"disable-paste": cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), !cfg.RdpConfig.Paste, !cfg.VncConfig.Paste)),
|
||||
"resize-method": "display-update",
|
||||
// Set file transfer related parameters from config
|
||||
DRIVE_ENABLE: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.EnableDrive, false)),
|
||||
DRIVE_PATH: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.DrivePath, "")),
|
||||
DRIVE_CREATE_PATH: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.CreateDrivePath, false)),
|
||||
DRIVE_DISABLE_UPLOAD: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.DisableUpload, false)),
|
||||
DRIVE_DISABLE_DOWNLOAD: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.DisableDownload, false)),
|
||||
// DRIVE_ENABLE: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.EnableDrive, false)),
|
||||
// DRIVE_PATH: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.DrivePath, "")),
|
||||
// DRIVE_CREATE_PATH: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.CreateDrivePath, false)),
|
||||
// DRIVE_DISABLE_UPLOAD: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.DisableUpload, false)),
|
||||
// DRIVE_DISABLE_DOWNLOAD: cast.ToString(lo.Ternary(strings.Contains(protocol, "rdp"), cfg.RdpConfig.DisableDownload, false)),
|
||||
DRIVE_ENABLE: "true",
|
||||
DRIVE_PATH: fmt.Sprintf("/rdp/asset_%d", asset.Id),
|
||||
DRIVE_CREATE_PATH: "true",
|
||||
DRIVE_DISABLE_UPLOAD: "false",
|
||||
DRIVE_DISABLE_DOWNLOAD: "false",
|
||||
DRIVE_NAME: "Drive",
|
||||
}
|
||||
}, func() map[string]string {
|
||||
return map[string]string{
|
||||
@@ -130,21 +137,6 @@ func NewTunnel(connectionId, sessionId string, w, h, dpi int, protocol string, a
|
||||
t.Config.Parameters["port"] = cast.ToString(t.gw.LocalPort)
|
||||
}
|
||||
|
||||
// If RDP protocol and file transfer is enabled
|
||||
if strings.Contains(protocol, "rdp") && t.Config.Parameters[DRIVE_ENABLE] == "true" {
|
||||
// Get drive path
|
||||
t.drivePath = t.Config.Parameters[DRIVE_PATH]
|
||||
|
||||
// Create drive path if needed
|
||||
if t.Config.Parameters[DRIVE_CREATE_PATH] == "true" && t.drivePath != "" {
|
||||
if err := os.MkdirAll(t.drivePath, 0755); err != nil {
|
||||
logger.L().Error("Failed to create RDP drive path", zap.Error(err))
|
||||
// Don't terminate the connection, just disable file transfer
|
||||
t.drivePath = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = t.handshake()
|
||||
|
||||
return
|
||||
@@ -284,7 +276,7 @@ func (t *Tunnel) HandleFileUpload(filename string, size int64) (string, error) {
|
||||
return "", fmt.Errorf("file upload is disabled")
|
||||
}
|
||||
|
||||
transfer, err := t.transferManager.CreateUpload(filename, t.drivePath)
|
||||
transfer, err := t.transferManager.CreateUpload(t.SessionId, filename, t.drivePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -298,7 +290,7 @@ func (t *Tunnel) HandleFileDownload(filename string) (string, int64, error) {
|
||||
return "", 0, fmt.Errorf("file download is disabled")
|
||||
}
|
||||
|
||||
transfer, err := t.transferManager.CreateDownload(filename, t.drivePath)
|
||||
transfer, err := t.transferManager.CreateDownload(t.SessionId, filename, t.drivePath)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
@@ -15,14 +15,25 @@ const (
|
||||
INSTRUCTION_FILE_ERROR = "file-error"
|
||||
)
|
||||
|
||||
// RDP file transfer related parameters
|
||||
// Object instruction constants for filesystem operations
|
||||
const (
|
||||
RDP_ENABLE_DRIVE = "enable-drive"
|
||||
RDP_DRIVE_PATH = "drive-path"
|
||||
RDP_DRIVE_NAME = "drive-name"
|
||||
RDP_DISABLE_DOWNLOAD = "disable-download"
|
||||
RDP_DISABLE_UPLOAD = "disable-upload"
|
||||
RDP_CREATE_DRIVE_PATH = "create-drive-path"
|
||||
INSTRUCTION_FILESYSTEM = "filesystem"
|
||||
INSTRUCTION_GET = "get"
|
||||
INSTRUCTION_PUT = "put"
|
||||
INSTRUCTION_BODY = "body"
|
||||
INSTRUCTION_UNDEFINE = "undefine"
|
||||
)
|
||||
|
||||
// Stream instruction constants
|
||||
const (
|
||||
INSTRUCTION_BLOB = "blob"
|
||||
INSTRUCTION_END = "end"
|
||||
)
|
||||
|
||||
// Filesystem mimetypes
|
||||
const (
|
||||
MIMETYPE_STREAM_INDEX = "application/vnd.glyptodon.guacamole.stream-index+json"
|
||||
MIMETYPE_TEXT_PLAIN = "text/plain"
|
||||
)
|
||||
|
||||
// HandleFileInstruction processes file transfer related instructions
|
||||
|
@@ -22,18 +22,39 @@ type FileTransferManager struct {
|
||||
|
||||
// FileTransfer represents a single file transfer
|
||||
type FileTransfer struct {
|
||||
ID string
|
||||
Filename string
|
||||
Path string
|
||||
Size int64
|
||||
Offset int64
|
||||
Created time.Time
|
||||
Completed bool
|
||||
IsUpload bool
|
||||
ID string `json:"id"`
|
||||
SessionID string `json:"session_id"`
|
||||
Filename string `json:"filename"`
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
Offset int64 `json:"offset"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Completed bool `json:"completed"`
|
||||
IsUpload bool `json:"is_upload"`
|
||||
Status string `json:"status"` // "pending", "uploading", "completed", "failed"
|
||||
Error string `json:"error,omitempty"`
|
||||
file *os.File
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// FileTransferProgress represents transfer progress information
|
||||
type FileTransferProgress struct {
|
||||
ID string `json:"id"`
|
||||
SessionID string `json:"session_id"`
|
||||
Filename string `json:"filename"`
|
||||
Size int64 `json:"size"`
|
||||
Offset int64 `json:"offset"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
Status string `json:"status"`
|
||||
IsUpload bool `json:"is_upload"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Speed int64 `json:"speed"` // bytes per second
|
||||
ETA int64 `json:"eta"` // estimated time to completion in seconds
|
||||
}
|
||||
|
||||
// Global file transfer manager instance
|
||||
var (
|
||||
DefaultFileTransferManager = NewFileTransferManager()
|
||||
@@ -47,72 +68,89 @@ func NewFileTransferManager() *FileTransferManager {
|
||||
}
|
||||
|
||||
// CreateUpload creates an upload file transfer
|
||||
func (m *FileTransferManager) CreateUpload(filename, drivePath string) (*FileTransfer, error) {
|
||||
func (m *FileTransferManager) CreateUpload(sessionID, filename, drivePath string) (*FileTransfer, error) {
|
||||
return m.CreateUploadWithID("", sessionID, filename, drivePath)
|
||||
}
|
||||
|
||||
// CreateUploadWithID creates an upload file transfer with custom ID
|
||||
func (m *FileTransferManager) CreateUploadWithID(transferID, sessionID, filename, drivePath string) (*FileTransfer, error) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
id := uuid.New().String()
|
||||
var id string
|
||||
if transferID != "" {
|
||||
if _, exists := m.transfers[transferID]; exists {
|
||||
return nil, fmt.Errorf("transfer ID already exists: %s", transferID)
|
||||
}
|
||||
id = transferID
|
||||
} else {
|
||||
id = uuid.New().String()
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(drivePath, filename)
|
||||
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
// Create file
|
||||
file, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
transfer := &FileTransfer{
|
||||
ID: id,
|
||||
Filename: filename,
|
||||
Path: fullPath,
|
||||
Created: time.Now(),
|
||||
IsUpload: true,
|
||||
file: file,
|
||||
ID: id,
|
||||
SessionID: sessionID,
|
||||
Filename: filename,
|
||||
Path: fullPath,
|
||||
Created: now,
|
||||
Updated: now,
|
||||
IsUpload: true,
|
||||
Status: "pending",
|
||||
file: file,
|
||||
}
|
||||
|
||||
m.transfers[id] = transfer
|
||||
logger.L().Debug("Created file upload", zap.String("id", id), zap.String("filename", filename))
|
||||
logger.L().Debug("Created file upload", zap.String("id", id), zap.String("sessionId", sessionID), zap.String("filename", filename))
|
||||
return transfer, nil
|
||||
}
|
||||
|
||||
// CreateDownload creates a download file transfer
|
||||
func (m *FileTransferManager) CreateDownload(filename, drivePath string) (*FileTransfer, error) {
|
||||
// CreateDownload creates a download file transfer (no progress tracking)
|
||||
func (m *FileTransferManager) CreateDownload(sessionID, filename, drivePath string) (*FileTransfer, error) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
id := uuid.New().String()
|
||||
fullPath := filepath.Join(drivePath, filename)
|
||||
|
||||
// Open file
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
|
||||
// Get file info
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return nil, fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
transfer := &FileTransfer{
|
||||
ID: id,
|
||||
Filename: filename,
|
||||
Path: fullPath,
|
||||
Size: stat.Size(),
|
||||
Created: time.Now(),
|
||||
IsUpload: false,
|
||||
file: file,
|
||||
ID: id,
|
||||
SessionID: sessionID,
|
||||
Filename: filename,
|
||||
Path: fullPath,
|
||||
Size: stat.Size(),
|
||||
Created: now,
|
||||
Updated: now,
|
||||
IsUpload: false,
|
||||
Status: "completed",
|
||||
file: file,
|
||||
}
|
||||
|
||||
m.transfers[id] = transfer
|
||||
logger.L().Debug("Created file download", zap.String("id", id), zap.String("filename", filename))
|
||||
logger.L().Debug("Created file download", zap.String("id", id), zap.String("sessionId", sessionID), zap.String("filename", filename))
|
||||
return transfer, nil
|
||||
}
|
||||
|
||||
@@ -123,6 +161,54 @@ func (m *FileTransferManager) GetTransfer(id string) *FileTransfer {
|
||||
return m.transfers[id]
|
||||
}
|
||||
|
||||
// GetTransfersBySession gets all transfers for a session
|
||||
func (m *FileTransferManager) GetTransfersBySession(sessionID string) []*FileTransfer {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
var transfers []*FileTransfer
|
||||
for _, transfer := range m.transfers {
|
||||
if transfer.SessionID == sessionID {
|
||||
transfers = append(transfers, transfer)
|
||||
}
|
||||
}
|
||||
return transfers
|
||||
}
|
||||
|
||||
// GetAllTransfers gets all active transfers
|
||||
func (m *FileTransferManager) GetAllTransfers() []*FileTransfer {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
var transfers []*FileTransfer
|
||||
for _, transfer := range m.transfers {
|
||||
transfers = append(transfers, transfer)
|
||||
}
|
||||
return transfers
|
||||
}
|
||||
|
||||
// GetTransferProgress gets progress information for a transfer
|
||||
func (m *FileTransferManager) GetTransferProgress(id string) (*FileTransferProgress, error) {
|
||||
transfer := m.GetTransfer(id)
|
||||
if transfer == nil {
|
||||
return nil, fmt.Errorf("transfer not found")
|
||||
}
|
||||
|
||||
return transfer.GetProgress(), nil
|
||||
}
|
||||
|
||||
// GetSessionProgress gets progress information for all transfers in a session
|
||||
func (m *FileTransferManager) GetSessionProgress(sessionID string) ([]*FileTransferProgress, error) {
|
||||
transfers := m.GetTransfersBySession(sessionID)
|
||||
|
||||
var progresses []*FileTransferProgress
|
||||
for _, transfer := range transfers {
|
||||
progresses = append(progresses, transfer.GetProgress())
|
||||
}
|
||||
|
||||
return progresses, nil
|
||||
}
|
||||
|
||||
// RemoveTransfer removes a transfer by ID
|
||||
func (m *FileTransferManager) RemoveTransfer(id string) {
|
||||
m.mutex.Lock()
|
||||
@@ -135,6 +221,23 @@ func (m *FileTransferManager) RemoveTransfer(id string) {
|
||||
}
|
||||
}
|
||||
|
||||
// CleanupCompletedTransfers removes completed transfers older than specified duration
|
||||
func (m *FileTransferManager) CleanupCompletedTransfers(maxAge time.Duration) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
cutoff := time.Now().Add(-maxAge)
|
||||
for id, transfer := range m.transfers {
|
||||
if transfer.Completed && transfer.Updated.Before(cutoff) {
|
||||
if transfer.file != nil {
|
||||
transfer.file.Close()
|
||||
}
|
||||
delete(m.transfers, id)
|
||||
logger.L().Debug("Cleaned up completed transfer", zap.String("id", id), zap.String("filename", transfer.Filename))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes data to an upload file
|
||||
func (t *FileTransfer) Write(data []byte) (int, error) {
|
||||
t.mutex.Lock()
|
||||
@@ -148,16 +251,30 @@ func (t *FileTransfer) Write(data []byte) (int, error) {
|
||||
return 0, fmt.Errorf("cannot write to download transfer")
|
||||
}
|
||||
|
||||
if t.Status == "pending" {
|
||||
t.Status = "uploading"
|
||||
}
|
||||
|
||||
n, err := t.file.Write(data)
|
||||
if err != nil {
|
||||
t.Status = "failed"
|
||||
t.Error = err.Error()
|
||||
t.Updated = time.Now()
|
||||
return n, err
|
||||
}
|
||||
|
||||
t.Offset += int64(n)
|
||||
t.Updated = time.Now()
|
||||
|
||||
if t.Size > 0 && t.Offset >= t.Size {
|
||||
t.Completed = true
|
||||
t.Status = "completed"
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Read reads data from a download file
|
||||
// Read reads data from a download file (no progress tracking)
|
||||
func (t *FileTransfer) Read(p []byte) (int, error) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
@@ -167,19 +284,56 @@ func (t *FileTransfer) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
n, err := t.file.Read(p)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
t.Completed = true
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
return n, err
|
||||
}
|
||||
|
||||
t.Offset += int64(n)
|
||||
if t.Offset >= t.Size {
|
||||
t.Completed = true
|
||||
return n, err
|
||||
}
|
||||
|
||||
// SetSize sets the total size for the transfer (useful for uploads where size is known)
|
||||
func (t *FileTransfer) SetSize(size int64) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
t.Size = size
|
||||
}
|
||||
|
||||
// GetProgress returns the current progress information
|
||||
func (t *FileTransfer) GetProgress() *FileTransferProgress {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
var percentage float64
|
||||
if t.Size > 0 {
|
||||
percentage = float64(t.Offset) / float64(t.Size) * 100
|
||||
}
|
||||
|
||||
return n, nil
|
||||
var speed int64
|
||||
var eta int64
|
||||
if !t.Created.Equal(t.Updated) && t.Offset > 0 {
|
||||
duration := t.Updated.Sub(t.Created).Seconds()
|
||||
speed = int64(float64(t.Offset) / duration)
|
||||
|
||||
if speed > 0 && t.Size > t.Offset {
|
||||
eta = (t.Size - t.Offset) / speed
|
||||
}
|
||||
}
|
||||
|
||||
return &FileTransferProgress{
|
||||
ID: t.ID,
|
||||
SessionID: t.SessionID,
|
||||
Filename: t.Filename,
|
||||
Size: t.Size,
|
||||
Offset: t.Offset,
|
||||
Percentage: percentage,
|
||||
Status: t.Status,
|
||||
IsUpload: t.IsUpload,
|
||||
Created: t.Created,
|
||||
Updated: t.Updated,
|
||||
Error: t.Error,
|
||||
Speed: speed,
|
||||
ETA: eta,
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the file transfer
|
||||
@@ -187,7 +341,14 @@ func (t *FileTransfer) Close() error {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
t.Completed = true
|
||||
if !t.Completed {
|
||||
if t.Status != "failed" {
|
||||
t.Status = "completed"
|
||||
}
|
||||
t.Completed = true
|
||||
t.Updated = time.Now()
|
||||
}
|
||||
|
||||
if t.file != nil {
|
||||
return t.file.Close()
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
// IFileRepository file history repository interface
|
||||
type IFileRepository interface {
|
||||
AddFileHistory(ctx context.Context, history *model.FileHistory) error
|
||||
GetFileHistory(ctx context.Context, filters map[string]interface{}) ([]*model.FileHistory, int64, error)
|
||||
}
|
||||
|
||||
// FileRepository file history repository implementation
|
||||
@@ -30,29 +29,3 @@ func NewFileRepository(db *gorm.DB) IFileRepository {
|
||||
func (r *FileRepository) AddFileHistory(ctx context.Context, history *model.FileHistory) error {
|
||||
return r.db.Create(history).Error
|
||||
}
|
||||
|
||||
// GetFileHistory gets file history records
|
||||
func (r *FileRepository) GetFileHistory(ctx context.Context, filters map[string]interface{}) ([]*model.FileHistory, int64, error) {
|
||||
db := r.db.Model(&model.FileHistory{})
|
||||
|
||||
// Apply filter conditions
|
||||
for key, value := range filters {
|
||||
if value != nil && value != "" {
|
||||
db = db.Where(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Count total records
|
||||
var count int64
|
||||
if err := db.Count(&count).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Query records
|
||||
var histories []*model.FileHistory
|
||||
if err := db.Find(&histories).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return histories, count, nil
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/gorilla/websocket"
|
||||
"go.uber.org/zap"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
@@ -125,6 +126,10 @@ type Session struct {
|
||||
ShareEnd time.Time `json:"-" gorm:"-"`
|
||||
Once sync.Once `json:"-" gorm:"-"`
|
||||
Prompt string `json:"-" gorm:"-"`
|
||||
|
||||
// SSH connection reuse for file transfers
|
||||
SSHClient *gossh.Client `json:"-" gorm:"-"`
|
||||
sshMutex sync.RWMutex `json:"-" gorm:"-"`
|
||||
}
|
||||
|
||||
func (m *Session) HasMonitors() (has bool) {
|
||||
@@ -166,3 +171,36 @@ func UpsertSession(data *Session) (err error) {
|
||||
Create(data).
|
||||
Error
|
||||
}
|
||||
|
||||
// SetSSHClient stores SSH client for connection reuse
|
||||
func (s *Session) SetSSHClient(client *gossh.Client) {
|
||||
s.sshMutex.Lock()
|
||||
defer s.sshMutex.Unlock()
|
||||
s.SSHClient = client
|
||||
logger.L().Debug("SSH client stored for session", zap.String("sessionId", s.SessionId))
|
||||
}
|
||||
|
||||
// GetSSHClient gets stored SSH client for connection reuse
|
||||
func (s *Session) GetSSHClient() *gossh.Client {
|
||||
s.sshMutex.RLock()
|
||||
defer s.sshMutex.RUnlock()
|
||||
return s.SSHClient
|
||||
}
|
||||
|
||||
// ClearSSHClient clears stored SSH client
|
||||
func (s *Session) ClearSSHClient() {
|
||||
s.sshMutex.Lock()
|
||||
defer s.sshMutex.Unlock()
|
||||
if s.SSHClient != nil {
|
||||
s.SSHClient.Close()
|
||||
s.SSHClient = nil
|
||||
logger.L().Debug("SSH client cleared for session", zap.String("sessionId", s.SessionId))
|
||||
}
|
||||
}
|
||||
|
||||
// HasSSHClient checks if session has an active SSH client
|
||||
func (s *Session) HasSSHClient() bool {
|
||||
s.sshMutex.RLock()
|
||||
defer s.sshMutex.RUnlock()
|
||||
return s.SSHClient != nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user