feat: filegw frontend
This commit is contained in:
parent
93b34dcc85
commit
12058ed403
@ -7,7 +7,6 @@ stages:
|
||||
cache: &global_cache
|
||||
key:
|
||||
files:
|
||||
- axum-websockify/Cargo.toml
|
||||
- webrdp/Cargo.toml
|
||||
paths:
|
||||
- .cargo/bin
|
||||
|
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
services:
|
||||
websockify:
|
||||
build:
|
||||
context: ./websockify
|
||||
command: ["${HOSTNAME}", "${PORT}"]
|
||||
ports:
|
||||
- 8081:8081
|
||||
env_file:
|
||||
- ./.env
|
||||
|
||||
filegateway:
|
||||
build:
|
||||
context: ./filegateway
|
||||
command: ["${HOSTNAME}"]
|
||||
ports:
|
||||
- 8082:8082
|
||||
env_file:
|
||||
- ./.env
|
@ -11,7 +11,6 @@ if (!host) {
|
||||
console.error("Please provide a host as a command line argument.");
|
||||
process.exit(1);
|
||||
}
|
||||
const client = new smb2.Client(host);
|
||||
const app = express();
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
@ -29,12 +28,14 @@ app.post("/", async (req, res) => {
|
||||
const password = req.body.password;
|
||||
const domain = req.body.domain;
|
||||
|
||||
const client = new smb2.Client(host);
|
||||
const session = await client.authenticate({ username, password, domain });
|
||||
const sessionId = crypto.randomUUID();
|
||||
session.trees = {}
|
||||
|
||||
sessionPool.createSession(sessionId, session, async (session) => await session.logoff());
|
||||
|
||||
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
||||
res.end(JSON.stringify({
|
||||
"error": null,
|
||||
"sessionId": sessionId
|
||||
@ -59,10 +60,11 @@ app.get("/:sessionId/:shareName/*", async (req, res) => {
|
||||
|
||||
if (filePath.endsWith("/")) {
|
||||
const entries = await tree.readDirectory(filePath);
|
||||
res.end(JSON.stringify({
|
||||
res.json({
|
||||
error: null,
|
||||
entries
|
||||
}));
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
const readStream = await tree.createFileReadStream(filePath);
|
||||
readStream.pipe(res);
|
||||
@ -88,28 +90,28 @@ app.post("/:sessionId/:shareName/*", async (req, res) => {
|
||||
const session = sessionPool.getSession(sessionId);
|
||||
const tree = await utility.getTree(session, shareName);
|
||||
|
||||
// Get the uploaded file from the request
|
||||
const file = req.files.file;
|
||||
|
||||
// Check if the file already exists
|
||||
const fileExists = await tree.exists(filePath);
|
||||
|
||||
// Check if the force parameter is provided in the form
|
||||
const force = req.body.force;
|
||||
const force = req.query.force;
|
||||
|
||||
if (fileExists && !force) {
|
||||
res.status(200).json({ error: null, exists: true });
|
||||
res.end();
|
||||
} else {
|
||||
// Create a write stream to the destination file
|
||||
if (!fileExists)
|
||||
await tree.createFile(filePath);
|
||||
const writeStream = await tree.createFileWriteStream(filePath);
|
||||
|
||||
console.log("stream created")
|
||||
// Pipe the uploaded file to the write stream
|
||||
file.pipe(writeStream);
|
||||
req.pipe(writeStream);
|
||||
|
||||
// Handle events for the write stream
|
||||
writeStream.on("finish", () => {
|
||||
res.end(JSON.stringify({ error: null }));
|
||||
res.json({ error: null });
|
||||
res.end();
|
||||
});
|
||||
|
||||
writeStream.on("error", (error) => {
|
||||
@ -118,6 +120,7 @@ app.post("/:sessionId/:shareName/*", async (req, res) => {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("upload error", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
res.end();
|
||||
}
|
||||
@ -142,7 +145,8 @@ app.put("/:sessionId/:shareName/*", async (req, res) => {
|
||||
} else {
|
||||
// Create the folder
|
||||
await tree.createDirectory(filePath);
|
||||
res.end(JSON.stringify({ error: null }));
|
||||
res.json({ error: null });
|
||||
res.end();
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
@ -61,7 +61,11 @@ class SessionPool {
|
||||
for (const [key, session] of this.sessions) {
|
||||
if (session.expiration <= now) {
|
||||
this.sessions.delete(key);
|
||||
await session.off(session.value)
|
||||
try {
|
||||
await session.off(session.value)
|
||||
} catch(e) {
|
||||
console.error("cleanupSessions: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
109
webrdp/assets/file.js
Normal file
109
webrdp/assets/file.js
Normal file
@ -0,0 +1,109 @@
|
||||
window.filegatewayUrl = "/filegw/"
|
||||
function initFile() {
|
||||
window.fileShareName = "drive_c$";
|
||||
window.currentPath = [];
|
||||
listFile();
|
||||
setInterval(listFile, 20000);
|
||||
}
|
||||
function getCurrentPath() { return window.filegatewayUrl + window.fileSessionId + "/" + encodeURI(window.fileShareName) + "/" + encodeURI(window.currentPath.join("/")) + "/" }
|
||||
function getSize(fileSize) {
|
||||
var size = parseFloat(fileSize);
|
||||
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
var i = 0;
|
||||
while (size >= 1024 && i < units.length - 1) {
|
||||
size /= 1024;
|
||||
i++;
|
||||
}
|
||||
|
||||
return size.toFixed(2) + ' ' + units[i];
|
||||
}
|
||||
function listFile() {
|
||||
$.ajax({
|
||||
url: getCurrentPath(),
|
||||
method: "GET",
|
||||
success: function (response) {
|
||||
var data = response;
|
||||
var table = document.getElementById("filesharelist");
|
||||
table.innerHTML = "";
|
||||
if (window.currentPath.length > 0) {
|
||||
var row = table.insertRow();
|
||||
var cell1 = row.insertCell(0);
|
||||
var cell2 = row.insertCell(1);
|
||||
cell1.innerHTML = "<a href='#' onclick='window.currentPath=window.currentPath.slice(0,-1);updatePathText();listFile();false'>↑🔝🔝🔝↑</a>";
|
||||
cell2.innerHTML = "向上";
|
||||
}
|
||||
for (var i = 0; i < data.entries.length; i++) {
|
||||
if (data.entries[i].fileAttributes.indexOf("Hidden") != -1) continue;
|
||||
var row = table.insertRow();
|
||||
var cell1 = row.insertCell(0);
|
||||
var cell2 = row.insertCell(1);
|
||||
var filename = data.entries[i].filename.slice(2);
|
||||
if (data.entries[i].type == "Directory") {
|
||||
cell1.innerHTML = "<a href='#' onclick='onClickOpenDir(this)'>" + filename + "</a>";
|
||||
cell2.innerHTML = "目录";
|
||||
}
|
||||
else {
|
||||
cell1.innerHTML = "<a href='" + getCurrentPath() + encodeURI(filename) +"'>" + filename + "</a>";
|
||||
cell2.innerHTML = getSize(data.entries[i].fileSize);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
alert("文件夹列出失败,可能没有权限或者凭证过期,刷新网页后再试");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
function uploadFile(force) {
|
||||
var fileInput = document.getElementById('fileupload');
|
||||
var file = fileInput.files[0];
|
||||
var suffix = "";
|
||||
var blob = new Blob([file], { type: "application/octet-stream" });
|
||||
if (force)
|
||||
suffix="?force=1"
|
||||
$.ajax({
|
||||
url: getCurrentPath() + encodeURI(file.name) + suffix,
|
||||
method: "POST",
|
||||
data: blob,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (response) {
|
||||
if (response.exist && !force) {
|
||||
if (confirm("文件已存在,是否覆盖?")) {
|
||||
uploadFile(true);
|
||||
}
|
||||
}
|
||||
else if (!response.exist && !response.error)
|
||||
alert("上传成功");
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
alert("上传失败,可能没有权限访问。")
|
||||
console.error("upload error", xhr, status, error)
|
||||
}
|
||||
});
|
||||
}
|
||||
function mkdir() {
|
||||
var dirName = prompt("请输入文件夹名称");
|
||||
if (dirName) {
|
||||
$.ajax({
|
||||
url: getCurrentPath() + encodeURI(dirName),
|
||||
method: "PUT",
|
||||
success: function (response) {
|
||||
if (!response.error)
|
||||
alert("创建成功");
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
alert("创建文件夹失败,可能是没有权限");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function onClickOpenDir(element) {
|
||||
window.currentPath = window.currentPath.concat(element.innerHTML);
|
||||
updatePathText();
|
||||
listFile();
|
||||
}
|
||||
function updatePathText() {
|
||||
$("#currentPath").text("/"+window.fileShareName+"/"+window.currentPath.join("/")+"/")
|
||||
}
|
@ -139,7 +139,16 @@
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.filesharelist {
|
||||
#filesharelist {
|
||||
width: 295px;
|
||||
height: 200px;
|
||||
border: 1px dotted #000;
|
||||
}
|
||||
|
||||
table tbody {
|
||||
display: block;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
}
|
@ -26,7 +26,6 @@
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
</style>
|
||||
<script src="./jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<script type="module" defer>
|
||||
import initWASM from "/webrdp.js";
|
||||
async function wasm() {
|
||||
@ -35,8 +34,6 @@
|
||||
window.wasm = wasm;
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
filegateway_url="/filegw";
|
||||
function initFile(){}
|
||||
function jsExportedCredentials(key) {
|
||||
console.log("requested key", key)
|
||||
if (jsExportedCredentials[key]) {
|
||||
@ -47,36 +44,13 @@
|
||||
return "";
|
||||
}
|
||||
|
||||
function connect(user, password, event) {
|
||||
jsExportedCredentials.user = user;
|
||||
jsExportedCredentials.password = password;
|
||||
$.ajax({
|
||||
url: filegateway_url,
|
||||
method: "POST",
|
||||
data: {
|
||||
username: jsExportedCredentials.user,
|
||||
password: jsExportedCredentials.password,
|
||||
domain: jsExportedCredentials.domain
|
||||
},
|
||||
success: function(response) {
|
||||
var sessionId = response.sessionId;
|
||||
window.fileSessionId = sessionId;
|
||||
initFile();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert(error);
|
||||
}
|
||||
});
|
||||
window.wasm().then(() => {
|
||||
$('#login_container').hide();
|
||||
$('#rdp_container').show();
|
||||
}).catch((err) => {
|
||||
alert(err);
|
||||
});
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
function wrongCredential() {
|
||||
disconnectedAndRefresh("用户名或密码错误!")
|
||||
}
|
||||
|
||||
function disconnectedAndRefresh(msg) {
|
||||
alert(msg ?? "抱歉,与服务器连接断开。");
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
@ -123,113 +97,18 @@
|
||||
<div>分享:<select name="" id="">
|
||||
<option value="drive_c$">drive_c$</option>
|
||||
</select></div>
|
||||
<ul class="filesharelist">
|
||||
<li><a>Program Files</a></li>
|
||||
<li><a>Program Files (x86)</a></li>
|
||||
<li><a>Perflogs</a></li>
|
||||
<li><a>Windows</a></li>
|
||||
<li><a>Users</a></li>
|
||||
</ul>
|
||||
<div><button>新建文件夹</button></div>
|
||||
<div><input type="file" name="文件上传" id="fileupload"><input type="submit" name="上传" id="filesubmit"></div>
|
||||
<table id="filesharelist">
|
||||
</table>
|
||||
<div><button onclick="mkdir()">新建文件夹</button><button onclick="listFile()">刷新</button></div>
|
||||
<div><input type="file" name="文件上传" id="fileupload"><input type="submit" name="上传" id="filesubmit" onclick="uploadFile()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
function wrongCredential() {
|
||||
disconnectedAndRefresh("用户名或密码错误!")
|
||||
}
|
||||
|
||||
function disconnectedAndRefresh(msg) {
|
||||
alert(msg ?? "抱歉,与服务器连接断开。");
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" defer>
|
||||
|
||||
$("#clipboardbtn").attr("open1", 0);
|
||||
$("#clipboardbtn").click(
|
||||
function (e) {
|
||||
e.stopPropagation();
|
||||
if (($("#clipboardbtn")).attr("open1") == 0) {
|
||||
clipboard_open();
|
||||
} else {
|
||||
clipboard_close();
|
||||
}
|
||||
}
|
||||
);
|
||||
$(".clipboardback").click(function () {
|
||||
clipboard_close();
|
||||
})
|
||||
$(".clipboard").click(function () {
|
||||
event.stopPropagation();
|
||||
})
|
||||
function clipboard_open() {
|
||||
$("#clipboardbtn").attr("open1", 1);
|
||||
$("#clipboardbtn").html(">")
|
||||
$(".clipboard").toggleClass("clipboard-open");
|
||||
$(".clipboardback").toggleClass("clipboardback-open");
|
||||
$(".clipboardback").css("pointer-events", "auto");
|
||||
}
|
||||
|
||||
function clipboard_close() {
|
||||
$("#clipboardbtn").attr("open1", 0);
|
||||
$("#clipboardbtn").html("«")
|
||||
$(".clipboard").toggleClass("clipboard-open");
|
||||
$(".clipboardback").toggleClass("clipboardback-open");
|
||||
$(".clipboardback").css("pointer-events", "none");
|
||||
}
|
||||
|
||||
function setClipBoard(s) {
|
||||
$("#clipboardtxt").val(s);
|
||||
}
|
||||
|
||||
function getClipBoard() {
|
||||
return $("#clipboardtxt").val();
|
||||
}
|
||||
|
||||
|
||||
$("#filesharebtn").attr("open1", 0);
|
||||
$("#filesharebtn").click(
|
||||
function (e) {
|
||||
e.stopPropagation();
|
||||
if (($("#filesharebtn")).attr("open1") == 0) {
|
||||
fileshare_open();
|
||||
} else {
|
||||
fileshare_close();
|
||||
}
|
||||
}
|
||||
);
|
||||
$(".fileshareback").click(function () {
|
||||
fileshare_close();
|
||||
})
|
||||
$(".fileshare").click(function () {
|
||||
event.stopPropagation();
|
||||
})
|
||||
function fileshare_open() {
|
||||
$("#filesharebtn").attr("open1", 1);
|
||||
$("#filesharebtn").html("<")
|
||||
$(".fileshare").toggleClass("fileshare-open");
|
||||
$(".fileshareback").toggleClass("fileshareback-open");
|
||||
$(".fileshareback").css("pointer-events", "auto");
|
||||
}
|
||||
|
||||
function fileshare_close() {
|
||||
$("#filesharebtn").attr("open1", 0);
|
||||
$("#filesharebtn").html("»")
|
||||
$(".fileshare").toggleClass("fileshare-open");
|
||||
$(".fileshareback").toggleClass("fileshareback-open");
|
||||
$(".fileshareback").css("pointer-events", "none");
|
||||
}
|
||||
|
||||
// canvas 防止触屏滑动事件
|
||||
$("#canvas").on("touchmove", function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
<script src="./jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript" src="./ui.js"></script>
|
||||
<script type="text/javascript" src="./rdp.js"></script>
|
||||
<script type="text/javascript" src="./file.js"></script>
|
||||
|
||||
</script>
|
||||
</body>
|
32
webrdp/assets/rdp.js
Normal file
32
webrdp/assets/rdp.js
Normal file
@ -0,0 +1,32 @@
|
||||
function connect(user, password, event) {
|
||||
jsExportedCredentials.user = user;
|
||||
jsExportedCredentials.password = password;
|
||||
$.ajax({
|
||||
url: window.filegatewayUrl,
|
||||
method: "POST",
|
||||
data: {
|
||||
username: jsExportedCredentials.user,
|
||||
password: jsExportedCredentials.password,
|
||||
domain: jsExportedCredentials.domain
|
||||
},
|
||||
success: function (response) {
|
||||
var data = response;
|
||||
var sessionId = data.sessionId;
|
||||
window.fileSessionId = sessionId;
|
||||
initFile();
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
alert("文件服务登录失败!");
|
||||
}
|
||||
});
|
||||
window.wasm().then(() => {
|
||||
$('#login_container').hide();
|
||||
$('#rdp_container').show();
|
||||
}).catch((err) => {
|
||||
alert(err);
|
||||
});
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
79
webrdp/assets/ui.js
Normal file
79
webrdp/assets/ui.js
Normal file
@ -0,0 +1,79 @@
|
||||
$("#clipboardbtn").attr("open1", 0);
|
||||
$("#clipboardbtn").click(
|
||||
function (e) {
|
||||
e.stopPropagation();
|
||||
if (($("#clipboardbtn")).attr("open1") == 0) {
|
||||
clipboard_open();
|
||||
} else {
|
||||
clipboard_close();
|
||||
}
|
||||
}
|
||||
);
|
||||
$(".clipboardback").click(function () {
|
||||
clipboard_close();
|
||||
})
|
||||
$(".clipboard").click(function () {
|
||||
event.stopPropagation();
|
||||
})
|
||||
function clipboard_open() {
|
||||
$("#clipboardbtn").attr("open1", 1);
|
||||
$("#clipboardbtn").html(">")
|
||||
$(".clipboard").toggleClass("clipboard-open");
|
||||
$(".clipboardback").toggleClass("clipboardback-open");
|
||||
$(".clipboardback").css("pointer-events", "auto");
|
||||
}
|
||||
|
||||
function clipboard_close() {
|
||||
$("#clipboardbtn").attr("open1", 0);
|
||||
$("#clipboardbtn").html("«")
|
||||
$(".clipboard").toggleClass("clipboard-open");
|
||||
$(".clipboardback").toggleClass("clipboardback-open");
|
||||
$(".clipboardback").css("pointer-events", "none");
|
||||
}
|
||||
|
||||
function setClipBoard(s) {
|
||||
$("#clipboardtxt").val(s);
|
||||
}
|
||||
|
||||
function getClipBoard() {
|
||||
return $("#clipboardtxt").val();
|
||||
}
|
||||
|
||||
|
||||
$("#filesharebtn").attr("open1", 0);
|
||||
$("#filesharebtn").click(
|
||||
function (e) {
|
||||
e.stopPropagation();
|
||||
if (($("#filesharebtn")).attr("open1") == 0) {
|
||||
fileshare_open();
|
||||
} else {
|
||||
fileshare_close();
|
||||
}
|
||||
}
|
||||
);
|
||||
$(".fileshareback").click(function () {
|
||||
fileshare_close();
|
||||
})
|
||||
$(".fileshare").click(function () {
|
||||
event.stopPropagation();
|
||||
})
|
||||
function fileshare_open() {
|
||||
$("#filesharebtn").attr("open1", 1);
|
||||
$("#filesharebtn").html("<")
|
||||
$(".fileshare").toggleClass("fileshare-open");
|
||||
$(".fileshareback").toggleClass("fileshareback-open");
|
||||
$(".fileshareback").css("pointer-events", "auto");
|
||||
}
|
||||
|
||||
function fileshare_close() {
|
||||
$("#filesharebtn").attr("open1", 0);
|
||||
$("#filesharebtn").html("»")
|
||||
$(".fileshare").toggleClass("fileshare-open");
|
||||
$(".fileshareback").toggleClass("fileshareback-open");
|
||||
$(".fileshareback").css("pointer-events", "none");
|
||||
}
|
||||
|
||||
// canvas 防止触屏滑动事件
|
||||
$("#canvas").on("touchmove", function (e) {
|
||||
e.preventDefault();
|
||||
});
|
@ -1,20 +1,13 @@
|
||||
# 使用 Node.js 18 镜像作为基础
|
||||
FROM node:18-slim
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 将 package.json 和 package-lock.json 复制到容器中
|
||||
COPY package*.json ./
|
||||
|
||||
# 安装依赖
|
||||
RUN npm install
|
||||
|
||||
# 将应用程序代码复制到容器中
|
||||
COPY . .
|
||||
|
||||
# 暴露应用程序的端口
|
||||
EXPOSE 8081
|
||||
|
||||
# 运行应用程序
|
||||
CMD [ "node", "index.js" ]
|
||||
ENTRYPOINT [ "node", "index.js" ]
|
@ -60,6 +60,11 @@ async function handleClient(ws) {
|
||||
ws.send(data, { binary: true });
|
||||
});
|
||||
|
||||
upgradedStatus.secureSocket.on('error', (error) => {
|
||||
log("Upgraded to SSL. error:", error);
|
||||
ws.close();
|
||||
});
|
||||
|
||||
ws.on('message', (msg) => {
|
||||
log("Upgraded to SSL. Send socket:", msg);
|
||||
upgradedStatus.secureSocket.write(msg);
|
||||
|
Loading…
Reference in New Issue
Block a user