原文地址:django+paramiko结合layui实现webssh,sftp的多文件直传功能
环境准备
python: 3.7.5
django:3.2.15
paramiko:3.1.0
layui:2.8.2
要求django+layui基础环境已经搭建完成。
主方法--views.py
# 上传基于SFTP直传--web终端
@csrf_exempt
@xframe_options_exempt
@k8s.login_auth_required
def webssh_upload_terminal_info(request):
id = request.GET.get("id")
remote_path = request.GET.get('selectedFilePath')
print("文件上传--web终端主机ID:",id)
print("传递的路径地址:",remote_path)
return render(request, 'monitor/webssh_upload_terminal.html',{"id":id,"remote_path":remote_path})
@csrf_exempt
@xframe_options_exempt
@k8s.login_auth_required
def webssh_update_file_api(request):
if request.method == "POST":
host_ssh_id = request.GET.get('id')
remote_path = request.POST.get('remote_path')
file = request.FILES.get('file')
print("主机ID:",host_ssh_id,"上传路径:",remote_path,"文件:",file)
# 允许上传的文件格式
allowed_types = [
'application/octet-stream',
'application/zip',
'application/x-zip-compressed',
'image/jpeg',
'image/png',
'image/gif',
'image/x-icon',
]
# 如果想不限制文件的上传类型,可以去掉这个判断即可
if file.content_type in allowed_types:
# 文件在内存中,将其内容写入临时文件
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
temp_file.write(file.read())
file_path = temp_file.name
print("文件临时路径:",file_path)
try:
# 通过ID在数据库中查出用于认证的信息并建立连接
host_ssh = HostMonitoring.objects.get(id=host_ssh_id)
host_ip = host_ssh.ipv4_address
host_port = int(host_ssh.port)
sys_user_name = host_ssh.username
sys_user_passwd = host_ssh.password
try:
# 建立SSH连接
with paramiko.Transport((host_ip, host_port)) as sftp_client:
sftp_client.connect(username=sys_user_name, password=sys_user_passwd)
sftp = paramiko.SFTPClient.from_transport(sftp_client)
sftp.put(file_path, remote_path + '/' + file.name)
sftp_client.close()
return JsonResponse({'success': True, 'message': '文件上传成功'})
except HostMonitoring.DoesNotExist:
return JsonResponse({'success': False, 'message': '无效的主机ID'})
except Exception as e:
return JsonResponse({'success': False, 'message': str(e)})
except Exception as e:
return JsonResponse({'success': False, 'message': '数据库连接异常'})
else:
print("上传失败",file.content_type)
return JsonResponse({'success': False, 'message': '无效的文件类型'})
else:
return JsonResponse({'success': False, 'message': '请求方法错误'})
前端文件--webssh_upload_terminal.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>文件上传</title>
<link href="/static/layui/css/layui.css" rel="stylesheet" type="text/css"/>
<style>
.upload-status {
font-weight: bold;
}
.upload-success {
color: green;
}
.upload-failed {
color: red;
}
</style>
</head>
<body>
<div class="layui-upload">
<div class="layui-inline">
<button type="button" class="layui-btn layui-btn-normal" id="ID-upload-demo-files">选择多文件</button>
</div>
<!-- <div class="layui-inline">
<label class="layui-form-label">远程路径:</label>
<div class="layui-input-inline">
<input type="text" id="remote-path" placeholder="请输入远程存储路径" autocomplete="off" class="layui-input">
</div>
</div> -->
<div class="layui-inline">
<button type="button" class="layui-btn layui-btn-warm" id="ID-upload-demo-files-action">开始上传</button>
</div>
<div class="layui-upload-list">
<table class="layui-table">
<colgroup>
<col style="min-width: 100px;">
<col width="150">
<col width="260">
<col width="150">
</colgroup>
<thead>
<th>文件名</th>
<th>大小</th>
<th>上传进度</th>
<th>操作</th>
</thead>
<tbody id="ID-upload-demo-files-list"></tbody>
</table>
</div>
</div>
<script src="/static/layui/layui.js"></script>
<script>
layui.use(['jquery','upload','element'],function(upload){
var upload = layui.upload;
var element = layui.element;
var $ = layui.$;
var hostId = "{{ id }}";
var remote_path = "{{ remote_path }}";
console.log("第二层弹出上传栏:" + remote_path)
var uploadListIns = upload.render({
elem: '#ID-upload-demo-files',
elemList: $('#ID-upload-demo-files-list'),
url: '{% url "webssh_update_file_api" %}?id=' + hostId,
accept: 'file',
multiple: true,
number: 10,
auto: false,
data:{remote_path: ''},
bindAction: '#ID-upload-demo-files-action',
before: function(obj){
this.data.remote_path = remote_path
},
choose: function(obj){
var that = this;
var files = this.files = obj.pushFile();
obj.preview(function(index, file, result){
var tr = $(['<tr id="upload-'+ index +'">',
'<td>'+ file.name +'</td>',
'<td>'+ (file.size/1024/1024).toFixed(1) +'Mb</td>',
'<td><div class="layui-progress" lay-filter="progress-demo-'+ index +'"><div class="layui-progress-bar" lay-percent=""></div></div></td>',
'<td><button class="layui-btn layui-btn-xs demo-delete">删除</button></td>',
'<td id="status-'+ index +'" class="upload-status">等待上传</td>',
'</tr>'].join(''));
tr.find('.demo-delete').on('click', function(){
delete files[index];
tr.remove();
uploadListIns.config.elem.next()[0].value = '';
});
that.elemList.append(tr);
element.render('progress');
});
},
done: function(res, index, upload){
var statusElem = $('#status-' + index);
if (res.success) {
statusElem.html('<span class="upload-success">上传成功</span>');
} else {
statusElem.html('<button class="layui-btn layui-btn-xs demo-reload">重传</button>' +
'<button class="layui-btn layui-btn-xs layui-btn-danger demo-delete">删除</button>');
}
},
error: function(index, upload){
var statusElem = $('#status-' + index);
statusElem.html('<button class="layui-btn layui-btn-xs demo-reload">重传</button>' +
'<button class="layui-btn layui-btn-xs layui-btn-danger demo-delete">删除</button>');
},
progress: function(n, elem, e, index){
var statusElem = $('#status-' + index);
statusElem.text('上传中 ' + n + '%');
element.progress('progress-demo-'+ index, n + '%');
}
});
$('#ID-upload-demo-files-action').on('click', function(){
// Change the status to "上传中" for all selected files
$('.upload-status').text('上传中 0%').addClass('upload-failed').removeClass('upload-success');
$('.layui-progress-bar').css('width', '0%');
});
});
</script>
</body>
</html>
前端文件--webssh_file.html
<!--文件sftp直传代码-->
<script>
layui.use('layer', function () {
var layer = layui.layer;
var hostId = "{{ id }}";
$('#upload').on('click', function () {
var selectedFilePath = $("#current-path").val();
console.log("获取到的路径" + selectedFilePath)
layer.open({
type: 2,
offset: 'r',
anim: 'slideLeft', // 从右往左
area: ['70%', '100%'],
closeBtn: true, // 1或者2表示开启关闭按钮,0表示不开启
title: "文件管理器",
shade: 0.1,
shift: 2,
shadeClose: false,
id: 'ID-demo-layer-direction-r', //2.8.0新特性,抽屉效果
content: '{% url "webssh_upload_terminal_info" %}?id=' + hostId + '&selectedFilePath=' + selectedFilePath,
move: false, // 禁止拖动
resize: false, // 禁止调整大小
skin: 'white-background' // 应用自定义的背景颜色类
});
});
});
</script>
<!--上传进度条弹窗-->
<div id="file-upload-progress" style="margin:50px 0 0 50px;display: none;">
<div id="picker" style="float:left;">请选择</div>
<div id="progress" class="progress" style="width:500px;float:left;margin:10px 0 0 20px;">
<div class="progress-bar progress-bar-striped active" role="progressbar" style="width:0%;"></div>
</div>
<div style="clear:both;"></div>
</div>
<!--头部选项栏-->
<div class="layui-container">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<fieldset class="layui-elem-field layui-field-title">
<legend>Linux 文件管理器</legend>
<div class="layui-field-box">
<div class="layui-form-item">
<button id="go-back" class="layui-btn return-btn control-btn">返回</button>
<label class="layui-form-label" style="margin-left: 20px;">当前路径:</label>
<div class="layui-input-inline">
<input type="text" id="current-path" value="/" readonly class="layui-input">
</div>
<button class="layui-btn control-btn action-btn" data-action="createDir">新建目录</button>
<!-- 设置一个隐藏的file input接收用户上传的文件-->
<input type="file" id="file-upload" style="display: none;">
<!--将上传文件按钮设为触发file input的点击事件-->
<button class="layui-btn layui-btn-normal control-btn upload-file" id="upload" data-action="upload">上传文件</button> <!--文件sftp直传方式入口-->
<!-- 实现弹出层 -->
<button id="test1">查看上传进度</button> <!-- 触发按钮 -->
<button id="refresh" class="layui-btn layui-btn-normal control-btn">刷新</button>
</div>
<table id="file-table" lay-filter="demo"></table>
<script type="text/html" id="operations">
<button class="layui-btn layui-btn-xs layui-btn-normal download" data-action="download">下载</button>
<button class="layui-btn layui-btn-xs layui-btn-danger delete" data-action="delete">删除</button>
</script>
</div>
</fieldset>
</div>
</div>
</div>
效果图如下
这段代码的流程如下:linux 文件管理器选择要上传的文件目录》选择上传》选择文件》开始上传 如果上传失败,会显示重传按钮,功能还没实现,上传文件会先保存到/tmp临时目录下,上传浏览器端上传主机完成,会写入到用户选择的上传目录下。
这个版本体验要好一点了,且支持多文件同时上传,但仍需要打磨。