需要引入的 js 文件:
#zmodem.min.js
https://github.com/pythonzm/Ops/blob/dev/static/xterm/zmodem/zmodem.min.js
#bootbox.min.js
https://github.com/pythonzm/Ops/blob/dev/static/js/bootbox.min.js
编辑前端页面
其中省略了其他无关内容
let socket = new WebSocket(ws_scheme + '://' + window.location.host + '/ws/webssh/{{ pk }}/?remote_ip={{ remote_ip }}' + '&width=' + cols + '&height=' + rows);
socket.binaryType = "arraybuffer";
let term = new Terminal({
screenKeys: true,
useStyle: true,
cursorBlink: true,
cols: cols,
rows: rows
});
let zsentry = new Zmodem.Sentry({
to_terminal: function (octets) {
}, //i.e. send to the terminal
on_detect(detection) {
let zsession = detection.confirm();
if (zsession.type === "send") {
uploadFile(zsession);
} else {
downloadFile(zsession);
}
},
on_retract: function () {
},
sender: function (octets) {
socket.send(new Uint8Array(octets))
},
});
socket.onopen = function () {
// $("#terms").css("height", document.documentElement.clientHeight - $('.main-header').height());
term.open(document.getElementById('terms'), true);
// term.fit();
};
term.on('data', function (data) {
socket.send(data);
});
socket.onmessage = function (msg) {
if (typeof (msg.data) === 'string') {
term.write(msg.data);
} else {
zsentry.consume(msg.data);
}
};
socket.onerror = function () {
document.getElementById('terms').innerHTML = '<h3>连接失败!</h3>';
};
socket.onclose = function (e) {
// pass
console.log(e)
};
function uploadFile(zsession) {
let uploadHtml = "<div>" +
"<label class='upload-area' style='width:100%;text-align:center;' for='fupload'>" +
"<input id='fupload' name='fupload' type='file' style='display:none;' multiple='true'>" +
"<i class='fa fa-cloud-upload fa-3x'></i>" +
"<br />" +
"点击选择文件" +
"</label>" +
"<br />" +
"<span style='margin-left:5px !important;' id='fileList'></span>" +
"</div><div class='clearfix'></div>";
let upload_dialog = bootbox.dialog({
message: uploadHtml,
title: "上传文件",
buttons: {
cancel: {
label: '关闭',
className: 'btn-default',
callback: function (res) {
try {
// zsession 每 5s 发送一个 ZACK 包,5s 后会出现提示最后一个包是 ”ZACK“ 无法正常关闭
// 这里直接设置 _last_header_name 为 ZRINIT,就可以强制关闭了
zsession._last_header_name = "ZRINIT";
zsession.close();
} catch (e) {
console.log(e);
}
}
},
},
closeButton: false,
});
function hideModal() {
upload_dialog.modal('hide');
}
let file_el = document.getElementById("fupload");
return new Promise((res) => {
file_el.onchange = function (e) {
let files_obj = file_el.files;
hideModal();
Zmodem.Browser.send_files(zsession, files_obj, {
on_offer_response(obj, xfer) {
if (xfer) {
// term.write("\r\n");
} else {
term.write(obj.name + " was upload skipped\r\n");
}
},
on_progress(obj, xfer) {
updateProgress(xfer);
},
on_file_complete(obj) {
term.write("\r\n" + obj.name + " was upload success\r\n");
},
}
).then(zsession.close.bind(zsession), console.error.bind(console)
).then(() => {
res();
});
};
});
}
function updateProgress(xfer) {
let detail = xfer.get_details();
let name = detail.name;
let total = detail.size;
let percent;
if (total === 0) {
percent = 100
} else {
percent = Math.round(xfer._file_offset / total * 100);
}
term.write("\r" + name + ": " + total + " " + xfer._file_offset + " " + percent + "% ");
}
function downloadFile(zsession) {
zsession.on("offer", (xfer) => {
let FILE_BUFFER = [];
xfer.on("input", (payload) => {
updateProgress(xfer);
FILE_BUFFER.push(new Uint8Array(payload));
});
xfer.accept().then(
() => {
Zmodem.Browser.save_to_disk(
FILE_BUFFER,
xfer.get_details().name
);
},
console.error.bind(console)
);
});
zsession.on("session_end", () => {
term.write("\r\n");
});
zsession.start();
}
编辑后端内容
省略其他无关内容
zmodemszstart = b'rz\r**\x18B00000000000000\r\x8a'
zmodemend = b'**\x18B0800000000022d\r\x8a'
zmodemrzstart = b'rz waiting to receive.**\x18B0100000023be50\r\x8a'
zmodemcancel = b'\x18\x18\x18\x18\x18\x08\x08\x08\x08\x08'
class MyThread(threading.Thread):
# 省略内容
def run(self):
while not self._stop_event.is_set() or not self.chan.chan.exit_status_ready():
try:
if self.chan.zmodemOO:
self.chan.zmodemOO = False
data = self.chan.chan.recv(2)
if not len(data):
return
if data == b'OO':
self.chan.send(bytes_data=data)
continue
else:
data += self.chan.chan.recv(4096)
else:
data = self.chan.chan.recv(4096)
if not len(data):
return
if self.chan.zmodem:
if zmodemend in data:
self.chan.zmodem = False
self.chan.zmodemOO = True
if zmodemcancel in data:
self.chan.zmodem = False
self.chan.chan.send('\n')
self.chan.send(bytes_data=data)
else:
if zmodemszstart in data or zmodemrzstart in data:
self.chan.zmodem = True
self.chan.send(bytes_data=data)
else:
str_data = data.decode('utf-8', 'ignore')
self.chan.send(str_data)
except timeout:
break
self.chan.send('\n由于长时间没有操作,连接已断开!', close=True)
self.stdout.append([time.time() - self.start_time, 'o', '\n由于长时间没有操作,连接已断开!'])
self.chan.close()
class MySSH(WebsocketConsumer):
def __init__(self, *args, **kwargs):
super(MySSH, self).__init__(*args, **kwargs)
# 省略其他内容
self.zmodem = False
self.zmodemOO = False
# 省略其他内容
def receive(self, text_data=None, bytes_data=None):
if text_data:
self.chan.send(text_data)
else:
self.chan.send(bytes_data)
zmodem.min.js怎么引进来啊?我单独去下载这个文件引入的话 会报错找不到Zmodem