添加SFTP文件上传功能
This commit is contained in:
parent
7deb10638d
commit
f438fe919d
@ -5,6 +5,8 @@ go版本多用户webssh管理工具
|
||||
|
||||
ssh2ws部分代码修改自https://github.com/hequan2017/go-webssh
|
||||
|
||||
2020/12/17 新增WEB_SFTP功能,拖动文件到终端窗口里即可上传
|
||||
|
||||
服务端不保存用户明文密码,且不保存解密秘钥,如需对其他用户开放,请不要修改此部分代码,以免造成不必要的损失!
|
||||
|
||||
|
||||
@ -14,10 +16,11 @@ Gin + gorm
|
||||
## 更新日志
|
||||
2020/12/14 修复无操作自动断开、修复网络延迟造成的js加载延迟问题
|
||||
2020/12/16 前端新增文件/文件夹拖动到Terminal的自动解析功能(SFTP需要),修改layer弹出窗口逻辑,增加回车提交事件
|
||||
2020/12/17 增加在线sftp文件上传功能 *
|
||||
## 开发计划
|
||||
✔ ssh功能
|
||||
|
||||
× 服务器文件管理
|
||||
✔ sftp文件上传功能
|
||||
|
||||
## 在线演示
|
||||
[点击进入SSH云管理平台](https://www.do18.cn)
|
||||
|
||||
@ -194,7 +194,7 @@ func (ssConn *SshConn) SessionWait(quitChan chan bool) {
|
||||
// log.Println("ssh session wait failed")
|
||||
// setQuit(quitChan)
|
||||
//}
|
||||
timer := time.NewTicker(time.Second * 3)
|
||||
timer := time.NewTicker(time.Second * 5)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
|
||||
181
common/sftp_clients/sftp.go
Normal file
181
common/sftp_clients/sftp.go
Normal file
@ -0,0 +1,181 @@
|
||||
package sftp_clients
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/sftp"
|
||||
"log"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
getpwd = "getpwd"
|
||||
upload = "upload"
|
||||
)
|
||||
|
||||
type sftp_req struct {
|
||||
Type string `json:"type"`
|
||||
FilePath string `json:"filepath"`
|
||||
FileName string `json:"filename"`
|
||||
FileData string `json:"filedata"`
|
||||
}
|
||||
|
||||
type sftp_resp struct {
|
||||
Code int `json:"code"`
|
||||
Type string `json:"type"`
|
||||
Msg string `json:"msg"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type MyClient struct {
|
||||
Uid uint
|
||||
Sftp *sftp.Client
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
sync.RWMutex
|
||||
C map[string]*MyClient
|
||||
}
|
||||
|
||||
var Client clients
|
||||
|
||||
func init() {
|
||||
Client = clients{C: make(map[string]*MyClient, 1000)}
|
||||
}
|
||||
|
||||
func (c *MyClient) ReceiveWsMsg(wsConn *websocket.Conn, exitCh chan bool) {
|
||||
defer setQuit(exitCh)
|
||||
go c.SessionWait(wsConn, exitCh)
|
||||
for {
|
||||
select {
|
||||
case <-exitCh:
|
||||
return
|
||||
default:
|
||||
_, wsData, err := wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
//logrus.WithError(err).Error("reading webSocket message failed")
|
||||
return
|
||||
}
|
||||
//unmashal bytes into struct
|
||||
msgObj := sftp_req{}
|
||||
if err := json.Unmarshal(wsData, &msgObj); err != nil {
|
||||
log.Println("unmarshal websocket message failed:", string(wsData))
|
||||
continue
|
||||
}
|
||||
msgresp := sftp_resp{}
|
||||
switch msgObj.Type {
|
||||
case getpwd:
|
||||
msgresp.Code = 200
|
||||
msgresp.Type = "pwd"
|
||||
path, err := c.Sftp.Getwd()
|
||||
if err != nil {
|
||||
msgresp.Code = 404
|
||||
msgresp.Msg = "服务器Path获取失败"
|
||||
msgresp.Data = err.Error()
|
||||
log.Println("sftp getpwd err:", err.Error())
|
||||
}
|
||||
msgresp.Data = path
|
||||
msg, _ := json.Marshal(msgresp)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp client getpwd err:", err.Error())
|
||||
return
|
||||
}
|
||||
case upload:
|
||||
msgresp.Code = 200
|
||||
msgresp.Type = "upload"
|
||||
io_data, err := base64.StdEncoding.DecodeString(msgObj.FileData)
|
||||
if err != nil {
|
||||
msgresp.Code = 401
|
||||
msgresp.Msg = "文件解析失败"
|
||||
msgresp.Data = err.Error()
|
||||
log.Println("sftp base64decode err:", err.Error())
|
||||
msg, _ := json.Marshal(msgresp)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp base64decode send err:", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.Sftp.MkdirAll(msgObj.FilePath); err != nil {
|
||||
msgresp.Code = 402
|
||||
msgresp.Msg = "服务器创建目录失败"
|
||||
msgresp.Data = err.Error()
|
||||
log.Println("sftp mkdir err:", err.Error())
|
||||
msg, _ := json.Marshal(msgresp)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp mkdir send err:", err.Error())
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := c.Sftp.Create(path.Join(msgObj.FilePath, msgObj.FileName))
|
||||
if err != nil {
|
||||
msgresp.Code = 403
|
||||
msgresp.Msg = "服务器文件创建失败"
|
||||
msgresp.Data = err.Error()
|
||||
log.Println("sftp create file err:", err.Error())
|
||||
msg, _ := json.Marshal(msgresp)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp create file send err:", err.Error())
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
if _, err := file.Write(io_data); err != nil {
|
||||
msgresp.Code = 405
|
||||
msgresp.Msg = "服务器文件写入失败"
|
||||
msgresp.Data = err.Error()
|
||||
log.Println("sftp write file err:", err.Error())
|
||||
msg, _ := json.Marshal(msgresp)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp write file send err:", err.Error())
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
file.Close()
|
||||
filepath := path.Join(msgObj.FilePath, msgObj.FileName)
|
||||
msgresp.Code = 200
|
||||
msgresp.Msg = "OK"
|
||||
msgresp.Data = filepath
|
||||
//log.Println("file write ok")
|
||||
msg, _ := json.Marshal(msgresp)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp write file send err:", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MyClient) SessionWait(wsConn *websocket.Conn, quitChan chan bool) {
|
||||
timer := time.NewTicker(time.Second * 30)
|
||||
defer timer.Stop()
|
||||
defer setQuit(quitChan)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
{
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, []byte("pong")); err != nil {
|
||||
log.Println("sftp pong send err :", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-quitChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setQuit(ch chan bool) {
|
||||
ch <- true
|
||||
}
|
||||
114
controller/stfp.go
Normal file
114
controller/stfp.go
Normal file
@ -0,0 +1,114 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"log"
|
||||
"ssh_manage/common"
|
||||
"ssh_manage/common/core"
|
||||
"ssh_manage/common/sftp_clients"
|
||||
"ssh_manage/errcode"
|
||||
"ssh_manage/model/Apiform"
|
||||
)
|
||||
|
||||
type sftp_req struct {
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type sftp_resp struct {
|
||||
Code int `json:"code"`
|
||||
Type string `json:"type"`
|
||||
Msg string `json:"msg"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
func Sftp_ssh(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if core.HandleError(c, err) {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
var auth Apiform.WsAuth
|
||||
|
||||
if c.ShouldBindUri(&auth) != nil {
|
||||
wsConn.WriteMessage(websocket.TextMessage, []byte("参数错误\r\n"))
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
_, wsData, err := wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
wsConn.Close()
|
||||
//logrus.WithError(err).Error("reading webSocket message failed")
|
||||
return
|
||||
}
|
||||
//unmashal bytes into struct
|
||||
msgObj := sftp_req{}
|
||||
if err := json.Unmarshal(wsData, &msgObj); err != nil {
|
||||
log.Println("Auth : unmarshal websocket message failed:", string(wsData))
|
||||
continue
|
||||
}
|
||||
resp_msg := sftp_resp{}
|
||||
token := msgObj.Token
|
||||
claims, err := common.ParseToken(token)
|
||||
valid := claims.Valid()
|
||||
if valid != nil || err != nil {
|
||||
resp_msg.Code = errcode.S_auth_fmt_err
|
||||
resp_msg.Msg = "身份令牌校验不通过"
|
||||
resp_msg.Data = err.Error()
|
||||
msg, _ := json.Marshal(resp_msg)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp token fmt err:", err)
|
||||
}
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
if claims.Userid != sftp_clients.Client.C[auth.Sid].Uid { //身份与缓存不符合
|
||||
resp_msg.Code = errcode.S_auth_fmt_err
|
||||
resp_msg.Msg = "用户权限不通过"
|
||||
resp_msg.Data = err.Error()
|
||||
msg, _ := json.Marshal(resp_msg)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp server_user err:", err)
|
||||
}
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
path, err := sftp_clients.Client.C[auth.Sid].Sftp.Getwd()
|
||||
if err != nil {
|
||||
resp_msg.Code = errcode.S_send_err
|
||||
resp_msg.Type = "connect"
|
||||
resp_msg.Msg = "SFTP连接失败"
|
||||
msg, _ := json.Marshal(resp_msg)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp connect err:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
resp_msg.Code = 200
|
||||
resp_msg.Type = "connect"
|
||||
resp_msg.Msg = "连接成功"
|
||||
resp_msg.Data = path
|
||||
msg, _ := json.Marshal(resp_msg)
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
log.Println("sftp return err:", err)
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
//break
|
||||
}
|
||||
quitChan := make(chan bool, 2)
|
||||
go sftp_clients.Client.C[auth.Sid].ReceiveWsMsg(wsConn, quitChan)
|
||||
<-quitChan //任意协程退出则结束
|
||||
fmt.Println("Sftp Exit")
|
||||
log.Println("sftp websocket finished")
|
||||
}
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"ssh_manage/common"
|
||||
"ssh_manage/common/core"
|
||||
"ssh_manage/common/sftp_clients"
|
||||
"ssh_manage/database"
|
||||
"ssh_manage/model/Apiform"
|
||||
"strconv"
|
||||
@ -52,8 +53,8 @@ func WsSsh(c *gin.Context) {
|
||||
var ser_info Apiform.SerInfo //接收反序列化数据
|
||||
var auth Apiform.WsAuth
|
||||
|
||||
if c.ShouldBindUri(&auth) != nil{
|
||||
wsConn.WriteMessage(websocket.TextMessage,[]byte("参数错误\r\n"))
|
||||
if c.ShouldBindUri(&auth) != nil {
|
||||
wsConn.WriteMessage(websocket.TextMessage, []byte("参数错误\r\n"))
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
@ -83,20 +84,20 @@ func WsSsh(c *gin.Context) {
|
||||
cache := database.Cache.Get()
|
||||
defer cache.Close()
|
||||
//log.Println(auth)
|
||||
s_info,err := redis.Bytes(cache.Do("GET", auth.Sid))
|
||||
s_info, err := redis.Bytes(cache.Do("GET", auth.Sid))
|
||||
//log.Println(string(s_info))
|
||||
if err != nil || len(s_info) == 0{
|
||||
if err != nil || len(s_info) == 0 {
|
||||
wsConn.WriteMessage(websocket.TextMessage, []byte("连接超时,请重试!\r\n"))
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
if json.Unmarshal(s_info,&ser_info) != nil{
|
||||
if json.Unmarshal(s_info, &ser_info) != nil {
|
||||
wsConn.WriteMessage(websocket.TextMessage, []byte("服务器信息获取失败,请重试!\r\n"))
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
//log.Println(ser_info)
|
||||
if claims.Userid != ser_info.BindUser{ //验证权限
|
||||
if claims.Userid != ser_info.BindUser { //验证权限
|
||||
wsConn.WriteMessage(websocket.TextMessage, []byte("权限验证失败,请重试!\r\n"))
|
||||
wsConn.Close()
|
||||
return
|
||||
@ -110,10 +111,18 @@ func WsSsh(c *gin.Context) {
|
||||
}
|
||||
defer client.Close()
|
||||
//startTime := time.Now()
|
||||
ssConn, err := core.NewSshConn(cols, rows, client) //加入sftp客户端
|
||||
ssConn, err := core.NewSshConn(cols, rows, client) //加入sftp客户端
|
||||
if core.WshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
sftp_clients.Client.Lock()
|
||||
sftp_clients.Client.C[auth.Sid] = &sftp_clients.MyClient{ser_info.BindUser, ssConn.SftpClient}
|
||||
sftp_clients.Client.Unlock()
|
||||
defer func() {
|
||||
sftp_clients.Client.Lock()
|
||||
delete(sftp_clients.Client.C, auth.Sid) //释放SFTP客户端
|
||||
sftp_clients.Client.Unlock()
|
||||
}()
|
||||
defer ssConn.Close()
|
||||
quitChan := make(chan bool, 3)
|
||||
|
||||
|
||||
1
serve.go
1
serve.go
@ -55,6 +55,7 @@ func main() {
|
||||
api.POST("/login", controller.Login)
|
||||
api.POST("/send", controller.Send)
|
||||
api.GET("/term/:sid", controller.WsSsh)
|
||||
api.GET("/sftp/:sid", controller.Sftp_ssh)
|
||||
api.Use(middleware.Auth()).GET("/userinfo", controller.Info)
|
||||
api.Use(middleware.Auth()).POST("/nickname", controller.UpdataNick)
|
||||
api.Use(middleware.Auth()).POST("/addser", controller.Addser)
|
||||
|
||||
@ -65,7 +65,7 @@ getinfo = function () {
|
||||
}
|
||||
, parseData: function (res) { //将原始数据解析成 table 组件所规定的数据
|
||||
if (res.token) {
|
||||
window.localStorage.setItem("token", result.token) //更新token
|
||||
window.localStorage.setItem("token", res.token) //更新token
|
||||
}
|
||||
if (res.code == 301 || res.code == 302) { //Token校验失败或过期
|
||||
layer.msg(res.msg, function () {
|
||||
|
||||
@ -8,8 +8,8 @@ let fileDrop = {
|
||||
filesList: [], // 文件列表数组
|
||||
errorLength: 0, //上传失败文件数量
|
||||
isUpload: true, //上传状态,是否可以上传
|
||||
//uploadSuspend:[], //上传暂停参数
|
||||
isUploadNumber: 800,//限制单次上传数量
|
||||
uploadSuspend: false, //上传暂停参数
|
||||
isUploadNumber: 150,//限制单次上传数量
|
||||
uploadAllSize: 0, // 上传文件总大小
|
||||
uploadedSize: 0, // 已上传文件大小
|
||||
topUploadedSize: 0, // 上一次文件上传大小
|
||||
@ -19,6 +19,30 @@ let fileDrop = {
|
||||
timerSpeed: 0, //速度
|
||||
uploading: false,
|
||||
cancel: false,
|
||||
done: false,
|
||||
}
|
||||
|
||||
function file_init() {
|
||||
fileDrop = {
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
uploadLength: 0, //上传数量
|
||||
//splitSize: 1024 * 1024 * 2, //文件上传分片大小
|
||||
filesList: [], // 文件列表数组
|
||||
errorLength: 0, //上传失败文件数量
|
||||
isUpload: true, //上传状态,是否可以上传
|
||||
//uploadSuspend:[], //上传暂停参数
|
||||
isUploadNumber: 800,//限制单次上传数量
|
||||
uploadAllSize: 0, // 上传文件总大小
|
||||
uploadedSize: 0, // 已上传文件大小
|
||||
topUploadedSize: 0, // 上一次文件上传大小
|
||||
uploadExpectTime: 0, // 预计上传时间
|
||||
//initTimer:0, // 初始化计时
|
||||
speedInterval: null, //平局速度定时器
|
||||
timerSpeed: 0, //速度
|
||||
uploading: false,
|
||||
cancel: false,
|
||||
}
|
||||
}
|
||||
|
||||
dropbox.addEventListener("dragleave", function (e) {
|
||||
@ -39,8 +63,9 @@ dropbox.addEventListener("dragover", function (e) {
|
||||
dropbox.addEventListener("drop", changes, false);
|
||||
|
||||
function changes(e) {
|
||||
if(!is_login){
|
||||
if (!sftp_ready) {
|
||||
layer.msg("请等待服务器连接!")
|
||||
return false
|
||||
}
|
||||
e.preventDefault();
|
||||
let items = e.dataTransfer.items, time, num = 0
|
||||
@ -67,10 +92,11 @@ function changes(e) {
|
||||
if (typeof (filesAndDirs[i].getFilesAndDirectories) == 'function') {
|
||||
update_sync(filesAndDirs[i])
|
||||
} else {
|
||||
if (num > 100) {
|
||||
if (num > fileDrop.isUploadNumber) {
|
||||
//fileDrop.isUpload = false;
|
||||
layer.msg(' '+ fileDrop.isUploadNumber +'份,无法上传,请压缩后上传!。',{icon:2,area:'405px'});
|
||||
layer.msg(' ' + fileDrop.isUploadNumber + '份,无法上传,请压缩后上传!。', {icon: 2, area: '405px'});
|
||||
//clearTimeout(time);
|
||||
file_init()
|
||||
return false;
|
||||
}
|
||||
fileDrop.filesList.push({
|
||||
@ -94,30 +120,34 @@ function changes(e) {
|
||||
}
|
||||
//console.log(fileDrop.filesList)
|
||||
layer.load(1, {
|
||||
shade: [0.1,'#fff'] //0.1透明度的白色背景
|
||||
shade: [0.1, '#fff'] //0.1透明度的白色背景
|
||||
});
|
||||
getpwd(getpwd_callback) //采用回调函数的方式检查SFTP服务是否可用
|
||||
}
|
||||
|
||||
function getpwd_callback(msg) {
|
||||
server_pwd = msg.data
|
||||
setTimeout(function () {
|
||||
layer.closeAll('loading')
|
||||
open_upload_window()
|
||||
},3000)
|
||||
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
function open_upload_window() {
|
||||
|
||||
//console.log(fileDrop.filesList[0])
|
||||
let template = `
|
||||
<table class="layui-table" lay-even="" lay-skin="row" id="file_upload" style="table-layout: fixed;padding-top: 0">
|
||||
<colgroup>
|
||||
<col width="250">
|
||||
<col width="150">
|
||||
<col width="150">
|
||||
<col width="35%">
|
||||
<col width="35%">
|
||||
<col width="30%">
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件路径</th>
|
||||
<th>文件大小</th>
|
||||
<th>状态</th>
|
||||
<th>文件路径(共` + fileDrop.uploadLength + `个文件)</th>
|
||||
<th>文件大小(共` + to_size(fileDrop.uploadAllSize) + `)</th>
|
||||
<th>文件状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody align="center">
|
||||
@ -130,8 +160,8 @@ function open_upload_window() {
|
||||
closeBtn: 1,
|
||||
maxmin: true,
|
||||
area: ['550px', '455px'],
|
||||
btn: ['开始上传', '取消上传'],
|
||||
title: '上传文件',
|
||||
btn: [fileDrop.uploadSuspend ? "继续上传" : "开始上传", '取消上传'],
|
||||
title: '上传文件到:' + session_path,
|
||||
skin: 'file_dir_uploads',
|
||||
shade: 0.4,
|
||||
shadeClose: false,
|
||||
@ -140,13 +170,139 @@ function open_upload_window() {
|
||||
for (let i = 0; i < fileDrop.filesList.length; i++) {
|
||||
$("#file_upload tbody").append(create_row(i, fileDrop.filesList[i]));
|
||||
}
|
||||
}
|
||||
},
|
||||
yes: function (index) {
|
||||
if (fileDrop.filesList.length <= 0) {
|
||||
layer.msg("请选择要上传的文件!", {icon: 0})
|
||||
file_init()
|
||||
layer.close(index)
|
||||
return false
|
||||
}
|
||||
if (fileDrop.uploading && fileDrop.uploadSuspend == false) {
|
||||
return false
|
||||
}
|
||||
//console.log("开始上传")
|
||||
$('.layui-layer-btn0').css({
|
||||
'cursor': 'no-drop',
|
||||
'background': '#4e8ccc'
|
||||
}).attr('data-upload', 'true').text('上传中');
|
||||
fileDrop.uploadSuspend = false
|
||||
fileDrop.uploading = true
|
||||
start_upload()
|
||||
},
|
||||
btn2: function (index) {
|
||||
if(fileDrop.done){
|
||||
layer.close(index);
|
||||
file_init()
|
||||
return false
|
||||
}
|
||||
if (fileDrop.uploading) {
|
||||
layer.confirm('是否取消上传当前列表的文件,若取消上传,已上传的文件,需用户手动删除,是否取消上传?', {
|
||||
title: '取消上传文件',
|
||||
icon: 0,
|
||||
btn: ['取消上传', '继续上传']
|
||||
}, function (indexs) {
|
||||
layer.close(index);
|
||||
layer.close(indexs);
|
||||
file_init()
|
||||
}, function () {
|
||||
fileDrop.uploadSuspend = true //开启暂停标识,用于继续上传功能
|
||||
open_upload_window()
|
||||
});
|
||||
//return false;
|
||||
} else {
|
||||
layer.close(index);
|
||||
file_init()
|
||||
}
|
||||
},
|
||||
cancel: function (index) {
|
||||
if(fileDrop.done){
|
||||
layer.close(index);
|
||||
file_init()
|
||||
return false
|
||||
}
|
||||
if (fileDrop.uploading) {
|
||||
layer.confirm('是否取消上传当前列表的文件,若取消上传,已上传的文件,需用户手动删除,是否取消上传?', {
|
||||
title: '取消上传文件',
|
||||
icon: 0,
|
||||
btn: ['取消上传', '继续上传']
|
||||
}, function (indexs) {
|
||||
layer.close(index);
|
||||
layer.close(indexs);
|
||||
file_init()
|
||||
}, function () {
|
||||
fileDrop.uploadSuspend = true //开启暂停标识,用于继续上传功能
|
||||
open_upload_window()
|
||||
});
|
||||
//return false;
|
||||
} else {
|
||||
layer.close(index);
|
||||
file_init()
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function start_upload(file_index, status, msg) {
|
||||
file_index = file_index == null ? 0 : file_index
|
||||
if (status) { //这个参数只在上传回调中传递,用于更新文件上传结果,如果这个参数存在,表示file_index已经走完上传流程,故file_index需自增1,代表开始下一个文件的上传
|
||||
// if(msg){
|
||||
// upload_view(file_index,msg)
|
||||
// }else{
|
||||
// upload_view(file_index,status)
|
||||
// }
|
||||
fileDrop.filesList[file_index].upload = status //更新文件列表中的状态
|
||||
upload_view(file_index, msg == undefined ? status : msg)
|
||||
file_index++
|
||||
}
|
||||
let reader = new FileReader(); //这个可以选择函数外声明,可避免重复创建
|
||||
if (reader == undefined) {
|
||||
file_init()
|
||||
layer.msg("您的浏览器暂不支持在线上传文件!")
|
||||
return false
|
||||
}
|
||||
reader.onload = function () {
|
||||
let fileData = _arrayBufferToBase64(this.result);
|
||||
fileDrop.filesList[file_index].upload = 1 //修改状态为上传中
|
||||
upload_view(file_index, 1) //修改视图内容
|
||||
sendFile(fileDrop.filesList[file_index], fileData, file_index, start_upload)
|
||||
//console.log(fileData);
|
||||
}
|
||||
if (file_index < fileDrop.filesList.length) { //采用回调方式上传,进度更可控
|
||||
if (fileDrop.filesList[file_index].upload < 2) { //可继续上传未上传的文件
|
||||
let f_id = "#file_" + file_index + " td";
|
||||
$(f_id)[2].innerHTML = "<font color='#808080'>解析中</font>";
|
||||
reader.readAsArrayBuffer(fileDrop.filesList[file_index].file)
|
||||
}
|
||||
} else {
|
||||
layer.msg("上传完成")
|
||||
$('.layui-layer-btn0').text('上传完成');
|
||||
$('.layui-layer-btn1').text('关闭窗口');
|
||||
fileDrop.done = true
|
||||
}
|
||||
// for (let i = 0; i < fileDrop.filesList.length; i++) {
|
||||
// reader.readAsArrayBuffer(fileDrop.filesList[i].file);
|
||||
// }
|
||||
}
|
||||
|
||||
function upload_view(file_index, status) {
|
||||
let f_id = "#file_" + file_index + " td";
|
||||
$(f_id)[2].innerHTML = getstatu(status);
|
||||
}
|
||||
|
||||
function _arrayBufferToBase64(buffer) {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(buffer);
|
||||
let len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
function create_row(index, file) {
|
||||
console.log(file)
|
||||
return "<tr id='" + index + "'><td title='" + file.local + "' style=\"white-space:nowrap;overflow:hidden;text-overflow: ellipsis;\">" + file.local + "</td> <td>" + file.size + "</td> <td>" + getstatu(file.upload) + "</td></tr>"
|
||||
//console.log(file)
|
||||
return "<tr id='file_" + index + "'><td title='" + file.local + "' style=\"white-space:nowrap;overflow:hidden;text-overflow: ellipsis;\">" + file.local + "</td> <td>" + file.size + "</td> <td>" + getstatu(file.upload) + "</td></tr>"
|
||||
}
|
||||
|
||||
function getstatu(statu) {
|
||||
@ -158,8 +314,10 @@ function getstatu(statu) {
|
||||
return "<font color='black'>未上传</font>"
|
||||
} else if (statu == 1) {
|
||||
return "<font color='#808080'>上传中</font>"
|
||||
} else {
|
||||
} else if (statu == 2) {
|
||||
return "<font color='green'>已上传</font>"
|
||||
} else {
|
||||
return "<font color='red'>" + statu + "</font>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
109
static/js/sftp.js
Normal file
109
static/js/sftp.js
Normal file
@ -0,0 +1,109 @@
|
||||
var sftp = null;
|
||||
var sftp_ready = false;
|
||||
var server_pwd = "";
|
||||
var session_path = "";
|
||||
var callback_success = undefined;
|
||||
//var callback_error = undefined;
|
||||
var upload_file_index = 0;
|
||||
|
||||
let sendmsg = {
|
||||
type: "",
|
||||
filepath: "",
|
||||
filename: "",
|
||||
filedata: "",
|
||||
}
|
||||
|
||||
let msg_type = {
|
||||
connect: "connect",
|
||||
getpwd: "getpwd",
|
||||
upload: "upload",
|
||||
}
|
||||
|
||||
let file_status = {
|
||||
success: 2, //上传成功
|
||||
error: -1, //上传失败
|
||||
}
|
||||
|
||||
create_sftp = function () {
|
||||
sftp = new WebSocket(ws_p + '://' + window.location.host + '/v1/sftp/' + GetQueryString("sid"));
|
||||
sftp.onopen = function () {
|
||||
sftp.send(JSON.stringify(auth)); //验证权限
|
||||
|
||||
sftp.onmessage = default_msg;
|
||||
sftp.onerror = function (e) {
|
||||
sftp_ready = false
|
||||
console.log(e);
|
||||
layer.msg("SFTP连接出错")
|
||||
};
|
||||
|
||||
sftp.onclose = function (e) {
|
||||
sftp_ready = false
|
||||
console.log(e);
|
||||
layer.msg("SFTP连接断开")
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
default_msg = function (msg) {
|
||||
if (isJSON(msg.data)) {
|
||||
msg = JSON.parse(msg.data)
|
||||
if (msg.type == msg_type.connect && msg.code == 200) {
|
||||
sftp_ready = true
|
||||
server_pwd = msg.data //初始化工作路径
|
||||
} else if (msg.type == msg_type.upload) {
|
||||
if (msg.code == 200) {
|
||||
callback_success(upload_file_index, file_status.success)
|
||||
} else {
|
||||
//layer.msg("文件上传失败:" + msg.msg) //可以回调修改状态文字内容,这里为了图方便,暂时不写了;(觉得不妥,又写上了)
|
||||
callback_success(upload_file_index, file_status.error,msg.msg) //上传失败正常回调,开始处理下一个
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getpwd = function (callback) {
|
||||
sendmsg.type = msg_type.getpwd
|
||||
sftp.onmessage = function (msg) {
|
||||
if (isJSON(msg.data)) {
|
||||
msg = JSON.parse(msg.data)
|
||||
if (msg.code == 200) {
|
||||
if (session_path == "") {
|
||||
session_path = server_pwd
|
||||
}
|
||||
callback(msg)
|
||||
} else {
|
||||
layer.msg(msg.msg)
|
||||
}
|
||||
sftp.onmessage = default_msg
|
||||
}
|
||||
}
|
||||
sftp.send(JSON.stringify(sendmsg))
|
||||
}
|
||||
|
||||
sendFile = function (fileinfo, fileData, file_index, callback) {
|
||||
sendmsg.type = msg_type.upload
|
||||
sendmsg.filepath = session_path + fileinfo.path
|
||||
sendmsg.filename = fileinfo.name
|
||||
sendmsg.filedata = fileData
|
||||
callback_success = callback
|
||||
upload_file_index = file_index
|
||||
sftp.send(JSON.stringify(sendmsg))
|
||||
}
|
||||
|
||||
isJSON = function (str) {
|
||||
if (typeof str == 'string') {
|
||||
try {
|
||||
var obj = JSON.parse(str);
|
||||
if (typeof obj == 'object' && obj) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
//console.log('error:' + str + '!!!' + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//console.log('It is not a string!')
|
||||
}
|
||||
@ -1,4 +1,23 @@
|
||||
var is_login = false;
|
||||
// var is_login = { watchValue:false };
|
||||
// var lastTimeValue=is_login.watchValue;
|
||||
// Object.defineProperty(is_login, 'watchValue', {
|
||||
// get: function() {
|
||||
// console.log('get:' + watchValue);
|
||||
// return watchValue;
|
||||
// },
|
||||
// set: function(value) {
|
||||
// watchValue = value;
|
||||
// if(lastTimeValue!=watchValue){
|
||||
// lastTimeValue=watchValue;
|
||||
// console.log('value changed!! set: ' + watchValue);
|
||||
// if(watchValue == true){
|
||||
// create_sftp()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
var is_login = false
|
||||
const protocol = document.location.protocol.split(':')[0];
|
||||
var ws_p = "ws";
|
||||
if (protocol == "https") {
|
||||
@ -15,7 +34,16 @@ const auth = {
|
||||
type: "auth",
|
||||
token: token,
|
||||
}
|
||||
|
||||
function completeLoading() {
|
||||
console.log("Loading Success")
|
||||
if (document.readyState == "complete") {
|
||||
layer.close(index);
|
||||
}
|
||||
}
|
||||
|
||||
const socket = new WebSocket(ws_p + '://' + window.location.host + '/v1/term/' + GetQueryString("sid"));
|
||||
|
||||
const term = new Terminal({cols: 180, rows: 50, screenKeys: true, cursorBlink: true, cursorStyle: "block"});
|
||||
term.open(document.getElementById('terms'));
|
||||
window.onresize = function () {
|
||||
@ -45,20 +73,51 @@ socket.onopen = function () {
|
||||
});
|
||||
|
||||
socket.onmessage = function (msg) {
|
||||
if(!is_login){
|
||||
if (!is_login) {
|
||||
term.clear()
|
||||
create_sftp()
|
||||
is_login = true
|
||||
}
|
||||
term.write(msg.data);
|
||||
update_path(msg.data)
|
||||
};
|
||||
socket.onerror = function (e) {
|
||||
is_login = false
|
||||
layer.msg("链接出错:" + JSON.stringify(e))
|
||||
console.log(e);
|
||||
};
|
||||
|
||||
socket.onclose = function (e) {
|
||||
is_login = false
|
||||
console.log(e);
|
||||
term.write("连接已断开:" + e.reason + "\r\n");
|
||||
layer.msg("链接断开:" + JSON.stringify(e))
|
||||
term.write("连接已断开" + "\r\n");
|
||||
//term.destroy();
|
||||
};
|
||||
};
|
||||
|
||||
function update_path(str) { //判断是否有效路径后更新当前所在路径,用于SFTP功能
|
||||
//console.log(str)
|
||||
let splice = String.fromCharCode(7)
|
||||
let start = str.indexOf(":")
|
||||
let end = str.indexOf(splice)
|
||||
if (start >= 0 && end >= 0) {
|
||||
let path = trimStr(str.substring(start + 1, end))
|
||||
let verify = path.indexOf(" ")
|
||||
if(verify == -1){
|
||||
//console.log(path)
|
||||
if(path.substr(0,1) == "~" || path.substr(0,1) == "/"){
|
||||
if(path.substr(0,1) == "~"){ //替换~为用户目录
|
||||
path = server_pwd + path.substr(1)
|
||||
}
|
||||
//console.log(path)
|
||||
if(session_path != path){
|
||||
session_path = path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function trimStr(str){
|
||||
return str.replace(/(^\s*)|(\s*$)/g,"");
|
||||
}
|
||||
@ -44,7 +44,7 @@
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="/static/assets/layui.all.js?v=1"></script>
|
||||
<script src="/static/js/net.js?v=1.3"></script>
|
||||
<script src="/static/js/console.js?v=1.99.1"></script>
|
||||
<script src="/static/js/console.js?v=1.99.2"></script>
|
||||
<script>
|
||||
var index = layer.load(0, {shade: [0.1,'#fff']});
|
||||
document.onreadystatechange = completeLoading;
|
||||
@ -53,6 +53,16 @@
|
||||
console.log("Loading Success")
|
||||
if (document.readyState == "complete") {
|
||||
layer.close(index);
|
||||
if(window.localStorage.getItem("upload_info") == undefined){
|
||||
layer.alert('更新提示', {
|
||||
skin: 'layui-layer-lan'
|
||||
,closeBtn: 0
|
||||
,anim: 4 //动画类型
|
||||
,title: "更新提示"
|
||||
,content: "在线SFTP上传功能已实现,可拖动本地文件或文件夹到终端窗口内进行上传操作!"
|
||||
});
|
||||
}
|
||||
window.localStorage.setItem("upload_info","read")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -38,7 +38,8 @@
|
||||
<script src="/static/xterm/fit.min.js"></script>
|
||||
<script src="/static/xterm/search.min.js"></script>
|
||||
<script src="/static/js/net.js?v=1.9"></script>
|
||||
<script src="/static/xterm/main.js?v=0.62"></script>
|
||||
<script src="/static/js/sftp.js?v=0.17"></script>
|
||||
<script src="/static/xterm/main.js?v=0.87"></script>
|
||||
<script src="/static/js/polyfill.js"></script>
|
||||
<script src="/static/js/fileupload.js?v=0.30"></script>
|
||||
<script src="/static/js/fileupload.js?v=0.75"></script>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user