mirror of
https://github.com/linuxserver/kclient.git
synced 2026-02-19 16:56:06 +08:00
adding initial base logic
This commit is contained in:
commit
8a779ff930
136
index.js
Normal file
136
index.js
Normal file
@ -0,0 +1,136 @@
|
||||
// LinuxServer KasmVNC Client
|
||||
|
||||
//// Env variables ////
|
||||
var CUSTOM_USER = process.env.CUSTOM_USER || 'abc';
|
||||
var PASSWORD = process.env.PASSWORD || 'abc';
|
||||
var SUBFOLDER = process.env.SUBFOLDER || '/';
|
||||
var TITLE = process.env.TITLE || 'KasmVNC Client';
|
||||
var FM_HOME = process.env.FM_HOME || '/config';
|
||||
|
||||
//// Application Variables ////
|
||||
var socketIO = require('socket.io');
|
||||
var express = require('express');
|
||||
var ejs = require('ejs');
|
||||
var app = require('express')();
|
||||
var http = require('http').Server(app);
|
||||
var bodyParser = require('body-parser');
|
||||
var baseRouter = express.Router();
|
||||
var fsw = require('fs').promises;
|
||||
var fs = require('fs');
|
||||
|
||||
|
||||
//// Server Paths Main ////
|
||||
app.engine('html', require('ejs').renderFile);
|
||||
app.engine('json', require('ejs').renderFile);
|
||||
baseRouter.use('/public', express.static(__dirname + '/public'));
|
||||
baseRouter.use('/vnc', express.static("/usr/share/kasmvnc/www/"));
|
||||
baseRouter.get('/', function (req, res) {
|
||||
res.render(__dirname + '/public/index.html', {title: TITLE});
|
||||
});
|
||||
baseRouter.get('/favicon.ico', function (req, res) {
|
||||
res.sendFile(__dirname + '/public/favicon.ico');
|
||||
});
|
||||
baseRouter.get('/manifest.json', function (req, res) {
|
||||
res.render(__dirname + '/public/manifest.json', {title: TITLE});
|
||||
});
|
||||
|
||||
//// Web File Browser ////
|
||||
// Send landing page
|
||||
baseRouter.get('/files', function (req, res) {
|
||||
res.sendFile( __dirname + '/public/filebrowser.html');
|
||||
});
|
||||
// Websocket comms //
|
||||
io = socketIO(http, {path: SUBFOLDER + 'files/socket.io',maxHttpBufferSize: 200000000});
|
||||
io.on('connection', async function (socket) {
|
||||
let id = socket.id;
|
||||
|
||||
//// Functions ////
|
||||
|
||||
// Open default location
|
||||
async function checkAuth(password) {
|
||||
getFiles(FM_HOME);
|
||||
}
|
||||
|
||||
// Emit to user
|
||||
function send(command, data) {
|
||||
io.sockets.to(id).emit(command, data);
|
||||
}
|
||||
|
||||
// Get file list for directory
|
||||
async function getFiles(directory) {
|
||||
let items = await fsw.readdir(directory);
|
||||
if (items.length > 0) {
|
||||
let dirs = [];
|
||||
let files = [];
|
||||
for await (let item of items) {
|
||||
let fullPath = directory + '/' + item;
|
||||
if (fs.lstatSync(fullPath).isDirectory()) {
|
||||
dirs.push(item);
|
||||
} else {
|
||||
files.push(item);
|
||||
}
|
||||
}
|
||||
send('renderfiles', [dirs, files, directory]);
|
||||
} else {
|
||||
send('renderfiles', [[], [], directory]);
|
||||
}
|
||||
}
|
||||
|
||||
// Send file to client
|
||||
async function downloadFile(file) {
|
||||
let fileName = file.split('/').slice(-1)[0];
|
||||
let data = await fsw.readFile(file);
|
||||
send('sendfile', [data, fileName]);
|
||||
}
|
||||
|
||||
// Write client sent file
|
||||
async function uploadFile(res) {
|
||||
let directory = res[0];
|
||||
let filePath = res[1];
|
||||
let data = res[2];
|
||||
let render = res[3];
|
||||
let dirArr = filePath.split('/');
|
||||
let folder = filePath.replace(dirArr[dirArr.length - 1], '')
|
||||
await fsw.mkdir(folder, { recursive: true });
|
||||
await fsw.writeFile(filePath, Buffer.from(data));
|
||||
if (render) {
|
||||
getFiles(directory);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete files
|
||||
async function deleteFiles(res) {
|
||||
let item = res[0];
|
||||
let directory = res[1];
|
||||
item = item.replace("|","'");
|
||||
if (fs.lstatSync(item).isDirectory()) {
|
||||
await fsw.rm(item, {recursive: true});
|
||||
} else {
|
||||
await fsw.unlink(item);
|
||||
}
|
||||
getFiles(directory);
|
||||
}
|
||||
|
||||
// Create a folder
|
||||
async function createFolder(res) {
|
||||
let dir = res[0];
|
||||
let directory = res[1];
|
||||
if (!fs.existsSync(dir)){
|
||||
await fsw.mkdir(dir);
|
||||
}
|
||||
getFiles(directory);
|
||||
}
|
||||
|
||||
// Incoming socket requests
|
||||
socket.on('open', checkAuth);
|
||||
socket.on('getfiles', getFiles);
|
||||
socket.on('downloadfile', downloadFile);
|
||||
socket.on('uploadfile', uploadFile);
|
||||
socket.on('deletefiles', deleteFiles);
|
||||
socket.on('createfolder', createFolder);
|
||||
});
|
||||
|
||||
|
||||
// Spin up application on 6900
|
||||
app.use(SUBFOLDER, baseRouter);
|
||||
http.listen(6900);
|
||||
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "kclient",
|
||||
"version": "0.1.0",
|
||||
"description": "Kclient is a wrapper for KasmVNC to add functionality to a containerized environment",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"ejs": "^3.1.8",
|
||||
"express": "^4.18.2",
|
||||
"socket.io": "^4.6.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/linuxserver/kclient.git"
|
||||
},
|
||||
"keywords": [
|
||||
"VNC",
|
||||
"Webtop",
|
||||
"VDI",
|
||||
"Docker"
|
||||
],
|
||||
"author": "thelamer",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/linuxserver/kclient/issues"
|
||||
},
|
||||
"homepage": "https://github.com/linuxserver/kclient#readme"
|
||||
}
|
||||
72
public/css/filebrowser.css
Normal file
72
public/css/filebrowser.css
Normal file
@ -0,0 +1,72 @@
|
||||
html * {
|
||||
font-family: Poppins,Helvetica !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.directory, .file {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: rgb(9 2 2 / 0.6);
|
||||
border-radius: 5px;
|
||||
border-style: inset;
|
||||
border-color: rgb(255 255 255 / 0.6);
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
margin: 0px !important;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.fileTable {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 2px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr:hover, button:hover {
|
||||
background: rgba(255, 255, 255, 0.3)
|
||||
}
|
||||
|
||||
#dropzone {
|
||||
position: fixed; top: 0; left: 0;
|
||||
z-index: 9999999999;
|
||||
width: 100%; height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
transition: visibility 175ms, opacity 175ms;
|
||||
}
|
||||
|
||||
#loading {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 3px solid rgba(0,0,0,.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: black;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
-webkit-animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
@-webkit-keyframes spin {
|
||||
to { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
2
public/css/files.svg
Normal file
2
public/css/files.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><path d="m28.11 13.609c-2.7387 0-4.9494 2.2106-4.9494 4.9494v90.883c0 2.7387 2.2106 4.9494 4.9494 4.9494h71.809c2.7387 0 4.9204-2.2106 4.9204-4.9494v-90.883c0-2.7387-2.1817-4.9494-4.9204-4.9494zm7.9595 7.3517h55.861c3.0814 0 5.5572 2.5047 5.5572 5.5861v12.648c0 3.0814-2.4758 5.5572-5.5572 5.5572h-55.861c-3.0814 0-5.5282-2.4758-5.5282-5.5572v-12.648c0-3.0814 2.4468-5.5861 5.5282-5.5861zm21.071 9.0015c-2.3244 0-4.1968 1.592-4.1968 3.5601 0 1.9681 1.8724 3.5311 4.1968 3.5311h15.109c2.3244 0 4.1968-1.563 4.1968-3.5311 0-1.9681-1.8724-3.5601-4.1968-3.5601zm-21.071 22.142h55.861c3.0814 0 5.5572 2.5047 5.5572 5.5861v12.648c0 3.0814-2.4758 5.5572-5.5572 5.5572h-55.861c-3.0814 0-5.5282-2.4758-5.5282-5.5572v-12.648c0-3.0814 2.4468-5.5861 5.5282-5.5861zm21.071 9.0015c-2.3244 0-4.1968 1.592-4.1968 3.5601 0 1.9681 1.8724 3.5311 4.1968 3.5311h15.109c2.3244 0 4.1968-1.5631 4.1968-3.5311 0-1.9681-1.8724-3.5601-4.1968-3.5601zm-21.071 22.142h55.861c3.0814 0 5.5572 2.5047 5.5572 5.5861v12.648c0 3.0814-2.4758 5.5572-5.5572 5.5572h-55.861c-3.0814 0-5.5282-2.4758-5.5282-5.5572v-12.648c0-3.0814 2.4468-5.5861 5.5282-5.5861zm21.071 9.0015c-2.3244 0-4.1968 1.592-4.1968 3.5601 0 1.9681 1.8724 3.5311 4.1968 3.5311h15.109c2.3244 0 4.1968-1.563 4.1968-3.5311 0-1.9681-1.8724-3.5601-4.1968-3.5601z"/></svg>
|
||||
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
74
public/css/kclient.css
Normal file
74
public/css/kclient.css
Normal file
@ -0,0 +1,74 @@
|
||||
.vnc {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#files {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 20vw;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 60vw;
|
||||
height: 60vh;
|
||||
z-index: 2;
|
||||
background-color: rgb(9 2 2 / 0.6);
|
||||
border-radius: 10px;
|
||||
border-style: inset;
|
||||
border-color: rgb(255 255 255 / 0.6);
|
||||
}
|
||||
|
||||
#files_frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
background: DimGray;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
cursor: pointer;
|
||||
border-radius:50%;
|
||||
border-style: inset;
|
||||
border-color: rgb(255 255 255 / 0.6);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#lsbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: max-content;
|
||||
display: none;
|
||||
background-color: rgb(9 2 2 / 0.6);
|
||||
border-radius: 0 0 10px 10px;
|
||||
border-style: inset;
|
||||
border-color: rgb(255 255 255 / 0.6);
|
||||
}
|
||||
|
||||
.icons {
|
||||
margin: 5px;
|
||||
padding: 4px;
|
||||
height: 4vh;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(82deg) brightness(105%) contrast(105%);
|
||||
}
|
||||
|
||||
.icons:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
20
public/filebrowser.html
Normal file
20
public/filebrowser.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="public/css/filebrowser.css">
|
||||
<script type="text/javascript" src="public/js/jquery.min.js"></script>
|
||||
<script src="files/socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="public/js/filebrowser.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<span id="buttons">
|
||||
<input id="folderName" type="text" placeholder="Enter Directory Name"></input>
|
||||
<button onclick="createFolder()">Create Folder</button>
|
||||
<button onclick="$('#uploadInput').trigger( 'click' )">Upload Files</button>
|
||||
<input class="hidden" id="uploadInput" type='file' onchange="upload(this);" multiple>
|
||||
</span>
|
||||
<div id="filebrowser"></div>
|
||||
<div ondrop="dropFiles(event)" ondragover="allowDrop(event)" style="visibility:hidden;opacity:0" id="dropzone">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
29
public/index.html
Normal file
29
public/index.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title><%- title -%></title>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<link rel="apple-touch-icon" href="icon.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link href="public/css/kclient.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!--KasmVNC Iframe-->
|
||||
<iframe class="vnc" src="vnc/index.html?resize=remote&clipboard_up=true&clipboard_down=true&clipboard_seamless=true&show_control_bar=true"></iframe>
|
||||
<!--LSIO Function Bar-->
|
||||
<div id="lsbar">
|
||||
<img class="icons" title="File Manager" src="public/css/files.svg" onclick="toggle('#files')"/>
|
||||
</div>
|
||||
<!--File Browser-->
|
||||
<div id="files">
|
||||
<iframe id="files_frame" name="files_frame" src="files" frameborder="0"></iframe>
|
||||
<div class="close" onclick="closeToggle('#files')"></div>
|
||||
</div>
|
||||
<!--Main logic-->
|
||||
<script src="public/js/jquery.min.js"></script>
|
||||
<script src="public/js/kclient.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
262
public/js/filebrowser.js
Normal file
262
public/js/filebrowser.js
Normal file
@ -0,0 +1,262 @@
|
||||
var host = window.location.hostname;
|
||||
var port = window.location.port;
|
||||
var protocol = window.location.protocol;
|
||||
var path = window.location.pathname;
|
||||
var socket = io(protocol + '//' + host + ':' + port, { path: path + '/socket.io'});
|
||||
|
||||
// Open default folder on connect
|
||||
socket.on('connect',function(){
|
||||
$('#filebrowser').empty();
|
||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
||||
socket.emit('open', '');
|
||||
});
|
||||
|
||||
// Get file list
|
||||
function getFiles(directory) {
|
||||
directory = directory.replace("//","/");
|
||||
directory = directory.replace("|","'");
|
||||
let directoryClean = directory.replace("'","|");
|
||||
if ((directory !== '/') && (directory.endsWith('/'))) {
|
||||
directory = directory.slice(0, -1);
|
||||
}
|
||||
$('#filebrowser').empty();
|
||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
||||
socket.emit('getfiles', directory);
|
||||
}
|
||||
|
||||
// Render file list
|
||||
async function renderFiles(data) {
|
||||
let dirs = data[0];
|
||||
let files = data[1];
|
||||
let directory = data[2];
|
||||
let baseName = directory.split('/').slice(-1)[0];
|
||||
let parentFolder = directory.replace(baseName,'');
|
||||
let parentLink = $('<td>').addClass('directory').attr('onclick', 'getFiles(\'' + parentFolder + '\');').text('..');
|
||||
let directoryClean = directory.replace("'","|");
|
||||
if (directoryClean == '/') {
|
||||
directoryClean = '';
|
||||
}
|
||||
let table = $('<table>').addClass('fileTable');
|
||||
let tableHeader = $('<tr>');
|
||||
for await (name of ['Name', 'Type', 'Delete (NO WARNING)']) {
|
||||
tableHeader.append($('<th>').text(name));
|
||||
}
|
||||
let parentRow = $('<tr>');
|
||||
for await (item of [parentLink, $('<td>').text('Parent'), $('<td>')]) {
|
||||
parentRow.append(item);
|
||||
}
|
||||
table.append(tableHeader,parentRow);
|
||||
$('#filebrowser').empty();
|
||||
$('#filebrowser').data('directory', directory);
|
||||
$('#filebrowser').append($('<div>').text(directory));
|
||||
$('#filebrowser').append(table);
|
||||
if (dirs.length > 0) {
|
||||
for await (let dir of dirs) {
|
||||
let tableRow = $('<tr>');
|
||||
let dirClean = dir.replace("'","|");
|
||||
let link = $('<td>').addClass('directory').attr('onclick', 'getFiles(\'' + directoryClean + '/' + dirClean + '\');').text(dir);
|
||||
let type = $('<td>').text('Dir');
|
||||
let del = $('<td>').append($('<button>').addClass('deleteButton').attr('onclick', 'deleter(\'' + directoryClean + '/' + dirClean + '\');').text('Delete'));
|
||||
for await (item of [link, type, del]) {
|
||||
tableRow.append(item);
|
||||
}
|
||||
table.append(tableRow);
|
||||
}
|
||||
}
|
||||
if (files.length > 0) {
|
||||
for await (let file of files) {
|
||||
let tableRow = $('<tr>');
|
||||
let fileClean = file.replace("'","|");
|
||||
let link = $('<td>').addClass('file').attr('onclick', 'downloadFile(\'' + directoryClean + '/' + fileClean + '\');').text(file);
|
||||
let type = $('<td>').text('File');
|
||||
let del = $('<td>').append($('<button>').addClass('deleteButton').attr('onclick', 'deleter(\'' + directoryClean + '/' + fileClean + '\');').text('Delete'));
|
||||
for await (item of [link, type, del]) {
|
||||
tableRow.append(item);
|
||||
}
|
||||
table.append(tableRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Download a file
|
||||
function downloadFile(file) {
|
||||
file = file.replace("|","'");
|
||||
socket.emit('downloadfile', file);
|
||||
}
|
||||
|
||||
// Send buffer to download blob
|
||||
function sendFile(res) {
|
||||
let data = res[0];
|
||||
let fileName = res[1];
|
||||
let blob = new Blob([data], { type: "application/octetstream" });
|
||||
let url = window.URL || window.webkitURL;
|
||||
link = url.createObjectURL(blob);
|
||||
let a = $("<a />");
|
||||
a.attr("download", fileName);
|
||||
a.attr("href", link);
|
||||
$("body").append(a);
|
||||
a[0].click();
|
||||
$("body").remove(a);
|
||||
}
|
||||
|
||||
// Upload files to current directory
|
||||
async function upload(input) {
|
||||
let directory = $('#filebrowser').data('directory');
|
||||
if (directory == '/') {
|
||||
directoryUp = '';
|
||||
} else {
|
||||
directoryUp = directory;
|
||||
}
|
||||
if (input.files && input.files[0]) {
|
||||
$('#filebrowser').empty();
|
||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
||||
for await (let file of input.files) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = async function(e) {
|
||||
let fileName = file.name;
|
||||
if (e.total < 200000000) {
|
||||
let data = e.target.result;
|
||||
$('#filebrowser').append($('<div>').text('Uploading ' + fileName));
|
||||
if (file == input.files[input.files.length - 1]) {
|
||||
socket.emit('uploadfile', [directory, directoryUp + '/' + fileName, data, true]);
|
||||
} else {
|
||||
socket.emit('uploadfile', [directory, directoryUp + '/' + fileName, data, false]);
|
||||
}
|
||||
} else {
|
||||
$('#filebrowser').append($('<div>').text('File too big ' + fileName));
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
socket.emit('getfiles', directory);
|
||||
}
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete file/folder
|
||||
function deleter(item) {
|
||||
let directory = $('#filebrowser').data('directory');
|
||||
$('#filebrowser').empty();
|
||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
||||
socket.emit('deletefiles', [item, directory]);
|
||||
}
|
||||
|
||||
// Delete file/folder
|
||||
function createFolder() {
|
||||
let directory = $('#filebrowser').data('directory');
|
||||
if (directory == '/') {
|
||||
directoryUp = '';
|
||||
} else {
|
||||
directoryUp = directory;
|
||||
}
|
||||
let folderName = $('#folderName').val();
|
||||
$('#folderName').val('');
|
||||
if ((folderName.length == 0) || (folderName.includes('/'))) {
|
||||
alert('Bad or Null Directory Name');
|
||||
return '';
|
||||
}
|
||||
$('#filebrowser').empty();
|
||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
||||
socket.emit('createfolder', [directoryUp + '/' + folderName, directory]);
|
||||
}
|
||||
|
||||
// Handle drag and drop
|
||||
async function dropFiles(ev) {
|
||||
ev.preventDefault();
|
||||
$('#filebrowser').empty();
|
||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
||||
$('#dropzone').css({'visibility':'hidden','opacity':0});
|
||||
let directory = $('#filebrowser').data('directory');
|
||||
if (directory == '/') {
|
||||
directoryUp = '';
|
||||
} else {
|
||||
directoryUp = directory;
|
||||
}
|
||||
let items = await getAllFileEntries(event.dataTransfer.items);
|
||||
for await (let item of items) {
|
||||
let fullPath = item.fullPath;
|
||||
item.file(async function(file) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = async function(e) {
|
||||
let fileName = file.name;
|
||||
if (e.total < 200000000) {
|
||||
let data = e.target.result;
|
||||
$('#filebrowser').append($('<div>').text('Uploading ' + fileName));
|
||||
if (item == items[items.length - 1]) {
|
||||
socket.emit('uploadfile', [directory, directoryUp + '/' + fullPath, data, true]);
|
||||
} else {
|
||||
socket.emit('uploadfile', [directory, directoryUp + '/' + fullPath, data, false]);
|
||||
}
|
||||
} else {
|
||||
$('#filebrowser').append($('<div>').text('File too big ' + fileName));
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
socket.emit('getfiles', directory);
|
||||
}
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Drop handler function to get all files
|
||||
async function getAllFileEntries(dataTransferItemList) {
|
||||
let fileEntries = [];
|
||||
// Use BFS to traverse entire directory/file structure
|
||||
let queue = [];
|
||||
// Unfortunately dataTransferItemList is not iterable i.e. no forEach
|
||||
for (let i = 0; i < dataTransferItemList.length; i++) {
|
||||
queue.push(dataTransferItemList[i].webkitGetAsEntry());
|
||||
}
|
||||
while (queue.length > 0) {
|
||||
let entry = queue.shift();
|
||||
if (entry.isFile) {
|
||||
fileEntries.push(entry);
|
||||
} else if (entry.isDirectory) {
|
||||
let reader = entry.createReader();
|
||||
queue.push(...await readAllDirectoryEntries(reader));
|
||||
}
|
||||
}
|
||||
return fileEntries;
|
||||
}
|
||||
// Get all the entries (files or sub-directories) in a directory by calling readEntries until it returns empty array
|
||||
async function readAllDirectoryEntries(directoryReader) {
|
||||
let entries = [];
|
||||
let readEntries = await readEntriesPromise(directoryReader);
|
||||
while (readEntries.length > 0) {
|
||||
entries.push(...readEntries);
|
||||
readEntries = await readEntriesPromise(directoryReader);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
// Wrap readEntries in a promise to make working with readEntries easier
|
||||
async function readEntriesPromise(directoryReader) {
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
directoryReader.readEntries(resolve, reject);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
var lastTarget;
|
||||
// Change style when hover files
|
||||
window.addEventListener('dragenter', function(ev) {
|
||||
lastTarget = ev.target;
|
||||
$('#dropzone').css({'visibility':'','opacity':1});
|
||||
});
|
||||
|
||||
// Change style when leave hover files
|
||||
window.addEventListener("dragleave", function(ev) {
|
||||
if(ev.target == lastTarget || ev.target == document) {
|
||||
$('#dropzone').css({'visibility':'hidden','opacity':0});
|
||||
}
|
||||
});
|
||||
|
||||
// Disabled default drag and drop
|
||||
function allowDrop(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
// Incoming socket requests
|
||||
socket.on('renderfiles', renderFiles);
|
||||
socket.on('sendfile', sendFile);
|
||||
2
public/js/jquery.min.js
vendored
Normal file
2
public/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
60
public/js/kclient.js
Normal file
60
public/js/kclient.js
Normal file
@ -0,0 +1,60 @@
|
||||
// Parse messages from KasmVNC
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
|
||||
eventer(messageEvent,function(e) {
|
||||
if (event.data && event.data.action) {
|
||||
switch (event.data.action) {
|
||||
case 'control_open':
|
||||
openToggle('#lsbar');
|
||||
break;
|
||||
case 'control_close':
|
||||
closeToggle('#lsbar');
|
||||
break;
|
||||
case 'fullscreen':
|
||||
fullscreen();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},false);
|
||||
|
||||
|
||||
// Handle Toggle divs
|
||||
function openToggle(id) {
|
||||
if ($(id).is(":hidden")) {
|
||||
$(id).slideToggle(300);
|
||||
}
|
||||
}
|
||||
function closeToggle(id) {
|
||||
if ($(id).is(":visible")) {
|
||||
$(id).slideToggle(300);
|
||||
}
|
||||
}
|
||||
function toggle(id) {
|
||||
$(id).slideToggle(300);
|
||||
}
|
||||
|
||||
// Fullscreen handler
|
||||
function fullscreen() {
|
||||
if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
} else {
|
||||
if (document.documentElement.requestFullscreen) {
|
||||
document.documentElement.requestFullscreen();
|
||||
} else if (document.documentElement.mozRequestFullScreen) {
|
||||
document.documentElement.mozRequestFullScreen();
|
||||
} else if (document.documentElement.webkitRequestFullscreen) {
|
||||
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||
} else if (document.body.msRequestFullscreen) {
|
||||
document.body.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
public/manifest.json
Normal file
17
public/manifest.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "<%- title -%>",
|
||||
"short_name": "<%- title -%>",
|
||||
"manifest_version": 2,
|
||||
"version": "1.0",
|
||||
"display": "fullscreen",
|
||||
"background_color": "#000000",
|
||||
"theme_color": "#000000",
|
||||
"icons": [
|
||||
{
|
||||
"src": "public/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "180x180"
|
||||
}
|
||||
],
|
||||
"start_url": "/"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user