feat: filegw frontend

This commit is contained in:
qwqVictor 2024-05-08 22:56:40 +08:00
parent 93b34dcc85
commit 12058ed403
11 changed files with 291 additions and 159 deletions

View File

@ -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
View 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

View File

@ -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 });

View File

@ -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
View 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("/")+"/")
}

View File

@ -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;
}

View File

@ -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
View 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
View 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();
});

View File

@ -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" ]

View File

@ -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);