mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-05-14 05:41:29 +00:00
* Fix filer UI navigation for URL-sensitive object prefixes * Fix filer UI navigation for URL-sensitive object prefixes * Clarify filer UI path escaping test name Rename the legacy filer UI path test to describe the actual behavior being checked. The printpath helper preserves timestamp characters that are valid in URL path components, while the PR fix is focused on query-string escaping for path and cursor parameters.
365 lines
12 KiB
HTML
365 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>SeaweedFS Filer {{ .Version }}</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="/seaweedfsstatic/bootstrap/3.3.1/css/bootstrap.min.css">
|
|
<style>
|
|
body {
|
|
padding-bottom: 128px;
|
|
}
|
|
|
|
#drop-area {
|
|
border: 1px transparent;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
#drop-area.highlight {
|
|
border-color: purple;
|
|
border: 2px dashed #ccc;
|
|
}
|
|
|
|
.button {
|
|
display: inline-block;
|
|
padding: 2px;
|
|
background: #ccc;
|
|
cursor: pointer;
|
|
border-radius: 2px;
|
|
border: 1px solid #ccc;
|
|
float: right;
|
|
margin-left: 2px;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
label {
|
|
font-weight: normal;
|
|
}
|
|
|
|
.button:hover {
|
|
background: #ddd;
|
|
}
|
|
|
|
#fileElem {
|
|
display: none;
|
|
}
|
|
|
|
td, th {
|
|
vertical-align: bottom;
|
|
}
|
|
|
|
.table-hover > tbody > tr:hover > * > div.operations {
|
|
display: block;
|
|
}
|
|
|
|
.table > tbody > tr {
|
|
height: 39px;
|
|
}
|
|
|
|
div.operations {
|
|
display: none;
|
|
}
|
|
|
|
.footer {
|
|
position: absolute;
|
|
bottom: 0px;
|
|
right: 5%;
|
|
min-width: 25%;
|
|
border-left: 1px solid #ccc;
|
|
border-right: 1px solid #ccc;
|
|
}
|
|
|
|
.add-files {
|
|
font-size: 46px;
|
|
text-align: center;
|
|
border: 1px dashed #999;
|
|
padding-bottom: 9px;
|
|
margin: 0 2px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="page-header">
|
|
<h1>
|
|
<a href="https://github.com/seaweedfs/seaweedfs"><img src="/seaweedfsstatic/seaweed50x50.png"></img></a>
|
|
SeaweedFS Filer <small>{{ .Version }}</small>
|
|
</h1>
|
|
</div>
|
|
<div class="row">
|
|
<div>
|
|
<div class="btn-group btn-group-sm pull-right" role="group" style="margin-top:3px;">
|
|
<label class="btn btn-default" onclick="handleCreateDir()">
|
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> New Folder
|
|
</label>
|
|
<label class="btn btn-default" for="fileElem">
|
|
<span class="glyphicon glyphicon-cloud-upload" aria-hidden="true"></span> Upload
|
|
</label>
|
|
</div>
|
|
<ol class="breadcrumb">
|
|
{{ range $entry := .Breadcrumbs }}
|
|
<li><a href="{{ printpath $entry.Link }}">
|
|
{{ $entry.Name }}
|
|
</a></li>
|
|
{{ end }}
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" id="drop-area">
|
|
<form class="upload-form">
|
|
<input type="file" id="fileElem" multiple onchange="handleFiles(this.files)">
|
|
|
|
{{ if .EmptyFolder }}
|
|
<div class="row add-files">
|
|
+
|
|
</div>
|
|
{{ else }}
|
|
<table width="100%" class="table table-hover">
|
|
{{ $path := .Path }}
|
|
{{ $showDirDel := .ShowDirectoryDelete }}
|
|
{{ range $entry_index, $entry := .Entries }}
|
|
<tr>
|
|
<td>
|
|
{{ if $entry.IsDirectory }}
|
|
<span class="glyphicon glyphicon-folder-open" aria-hidden="true"></span>
|
|
<a href="{{ printpath $path "/" $entry.Name "/"}}" >
|
|
{{ $entry.Name }}
|
|
</a>
|
|
{{ else }}
|
|
<a href="{{ printpath $path "/" $entry.Name }}" >
|
|
{{ $entry.Name }}
|
|
</a>
|
|
{{ end }}
|
|
</td>
|
|
<td align="right" nowrap>
|
|
{{ if not $entry.IsDirectory }}
|
|
{{ $entry.Mime }}
|
|
{{ end }}
|
|
</td>
|
|
<td align="right" nowrap>
|
|
{{ if not $entry.IsDirectory }}
|
|
{{ $entry.Size | humanizeBytes }}
|
|
{{ end }}
|
|
</td>
|
|
<td align="right" nowrap>
|
|
{{ $entry.Timestamp.Format "2006-01-02 15:04" }}
|
|
</td>
|
|
<td style="width:75px">
|
|
<div class="btn-group btn-group-xs pull-right operations" role="group">
|
|
<label class="btn" onclick="handleRename('{{ $entry.Name }}', '{{ printpath $path "/" }}')">
|
|
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
|
|
</label>
|
|
{{ if and $entry.IsDirectory $showDirDel }}
|
|
<label class="btn" onclick="handleDelete('{{ printpath $path "/" $entry.Name "/" }}')">
|
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
|
</label>
|
|
{{ end }}
|
|
{{ if not $entry.IsDirectory }}
|
|
<label class="btn" onclick="handleDelete('{{ printpath $path "/" $entry.Name }}')">
|
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
|
</label>
|
|
{{ end }}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{ end }}
|
|
</table>
|
|
{{ end }}
|
|
</form>
|
|
</div>
|
|
|
|
{{ if .ShouldDisplayLoadMore }}
|
|
<div class="row">
|
|
<a href="{{ printpath .Path }}?limit={{ .Limit }}&lastFileName={{ queryEscape .LastFileName }}" >
|
|
Load more
|
|
</a>
|
|
</div>
|
|
{{ end }}
|
|
|
|
<br/>
|
|
<br/>
|
|
<div id="progress-area" class="footer" style="display: none;">
|
|
</div>
|
|
</div>
|
|
</body>
|
|
<script type="text/javascript">
|
|
// ************************ Drag and drop ***************** //
|
|
let dropArea = document.getElementById("drop-area");
|
|
let progressArea = document.getElementById("progress-area");
|
|
|
|
// Prevent default drag behaviors
|
|
;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
dropArea.addEventListener(eventName, preventDefaults, false);
|
|
document.body.addEventListener(eventName, preventDefaults, false);
|
|
});
|
|
|
|
// Highlight drop area when item is dragged over it
|
|
;['dragenter', 'dragover'].forEach(eventName => {
|
|
dropArea.addEventListener(eventName, highlight, false);
|
|
});
|
|
|
|
;['dragleave', 'drop'].forEach(eventName => {
|
|
dropArea.addEventListener(eventName, unhighlight, false);
|
|
});
|
|
|
|
// Handle dropped files
|
|
dropArea.addEventListener('drop', handleDrop, false);
|
|
|
|
function preventDefaults(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
|
|
function highlight(e) {
|
|
dropArea.classList.add('highlight');
|
|
}
|
|
|
|
function unhighlight(e) {
|
|
dropArea.classList.remove('highlight');
|
|
}
|
|
|
|
function handleDrop(e) {
|
|
var dt = e.dataTransfer;
|
|
var files = dt.files;
|
|
|
|
handleFiles(files);
|
|
}
|
|
|
|
function reloadPage() {
|
|
window.location.reload(true);
|
|
}
|
|
|
|
var uploadList = {};
|
|
|
|
function handleFiles(files) {
|
|
files = [...files];
|
|
files.forEach(startUpload);
|
|
renderProgress();
|
|
files.forEach(uploadFile);
|
|
}
|
|
|
|
function startUpload(file, i) {
|
|
uploadList[file.name] = {'name': file.name, 'percent': 0, 'finish': false};
|
|
}
|
|
|
|
function renderProgress() {
|
|
var values = Object.values(uploadList);
|
|
var html = '<table class="table">\n<tr><th>Uploading</th><\/tr>\n';
|
|
for (let i of values) {
|
|
var progressBarClass = 'progress-bar-striped active';
|
|
if (i.percent >= 100) {
|
|
progressBarClass = 'progress-bar-success';
|
|
}
|
|
html += '<tr>\n<td>\n';
|
|
html += '<div class="progress" style="margin-bottom: 2px;">\n';
|
|
html += '<div class="progress-bar ' + progressBarClass + '" role="progressbar" aria-valuenow="' + '100" aria-valuemin="0" aria-valuemax="100" style="width:' + i.percent + '%;">';
|
|
html += '<span style="margin-right: 10px;">' + i.name + '</span>' + i.percent + '%<\/div>';
|
|
html += '<\/div>\n<\/td>\n<\/tr>\n';
|
|
}
|
|
html += '<\/table>\n';
|
|
progressArea.innerHTML = html;
|
|
if (values.length > 0) {
|
|
progressArea.attributes.style.value = '';
|
|
}
|
|
}
|
|
|
|
function reportProgress(file, percent) {
|
|
var item = uploadList[file]
|
|
item.percent = percent;
|
|
renderProgress();
|
|
}
|
|
|
|
function finishUpload(file) {
|
|
uploadList[file]['finish'] = true;
|
|
renderProgress();
|
|
var allFinish = true;
|
|
for (let i of Object.values(uploadList)) {
|
|
if (!i.finish) {
|
|
allFinish = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allFinish) {
|
|
console.log('All Finish');
|
|
reloadPage();
|
|
}
|
|
}
|
|
|
|
function uploadFile(file, i) {
|
|
var url = window.location.href;
|
|
var xhr = new XMLHttpRequest();
|
|
var fileName = file.name;
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState == XMLHttpRequest.DONE) {
|
|
finishUpload(fileName)
|
|
}
|
|
}
|
|
xhr.upload.addEventListener('progress', function(e) {
|
|
if (e.lengthComputable) {
|
|
var percent = Math.ceil((e.loaded / e.total) * 100);
|
|
reportProgress(fileName, percent)
|
|
}
|
|
});
|
|
xhr.upload.addEventListener('loadend', function(e) {
|
|
finishUpload(fileName);
|
|
});
|
|
var formData = new FormData();
|
|
xhr.open('POST', url, true);
|
|
formData.append('file', file);
|
|
xhr.send(formData);
|
|
}
|
|
|
|
function handleCreateDir() {
|
|
var dirName = prompt('Folder Name:', '');
|
|
dirName = dirName.trim();
|
|
if (dirName == null || dirName == '') {
|
|
return;
|
|
}
|
|
var baseUrl = window.location.origin + window.location.pathname;
|
|
if (!baseUrl.endsWith('/')) {
|
|
baseUrl += '/';
|
|
}
|
|
var url = baseUrl + encodeURIComponent(dirName);
|
|
if (!url.endsWith('/')) {
|
|
url += '/';
|
|
}
|
|
url += window.location.search;
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('POST', url, false);
|
|
xhr.setRequestHeader('Content-Type', '');
|
|
xhr.send();
|
|
reloadPage();
|
|
}
|
|
|
|
function handleRename(originName, basePath) {
|
|
var newName = prompt('New Name:', originName);
|
|
if (newName == null || newName == '') {
|
|
return;
|
|
}
|
|
var url = basePath + encodeURIComponent(newName);
|
|
var originPath = basePath + originName;
|
|
url += '?mv.from=' + encodeURIComponent(originPath);
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('POST', url, false);
|
|
xhr.setRequestHeader('Content-Type', '');
|
|
xhr.send();
|
|
reloadPage();
|
|
}
|
|
|
|
function handleDelete(path) {
|
|
if (!confirm('Are you sure to delete ' + path + '?')) {
|
|
return;
|
|
}
|
|
var url = path;
|
|
if (url.endsWith('/')) {
|
|
url += '?recursive=true';
|
|
}
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('DELETE', url, false);
|
|
xhr.send();
|
|
reloadPage();
|
|
}
|
|
</script>
|
|
</html>
|