mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-09 17:13:02 +03:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b48a7473fc | ||
|
|
a84c3b353d | ||
|
|
e9383d71ed | ||
|
|
8258dabe4a | ||
|
|
0e236b61f6 | ||
|
|
09788ed031 | ||
|
|
46ebe978ae | ||
|
|
e01f2030e1 | ||
|
|
8d03ec151a |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.15.1] - 2022-06-11
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Cannot upload ([#32](https://github.com/sigoden/duf/issues/32))
|
||||||
|
|
||||||
|
## [0.15.0] - 2022-06-10
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Encode webdav href as uri ([#28](https://github.com/sigoden/duf/issues/28))
|
||||||
|
- Query dir param
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add basic dark theme ([#29](https://github.com/sigoden/duf/issues/29))
|
||||||
|
- Add empty state placeholder to page([#30](https://github.com/sigoden/duf/issues/30))
|
||||||
|
|
||||||
## [0.14.0] - 2022-06-07
|
## [0.14.0] - 2022-06-07
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -286,7 +286,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "duf"
|
name = "duf"
|
||||||
version = "0.14.0"
|
version = "0.15.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-walkdir",
|
"async-walkdir",
|
||||||
"async_zip",
|
"async_zip",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "duf"
|
name = "duf"
|
||||||
version = "0.14.0"
|
version = "0.15.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["sigoden <sigoden@gmail.com>"]
|
authors = ["sigoden <sigoden@gmail.com>"]
|
||||||
description = "Duf is a simple file server."
|
description = "Duf is a simple file server."
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 9.1 KiB |
@@ -97,6 +97,11 @@ body {
|
|||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-folder {
|
||||||
|
padding-top: 1rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.uploaders-table th,
|
.uploaders-table th,
|
||||||
.paths-table th {
|
.paths-table th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -173,3 +178,39 @@ body {
|
|||||||
.uploader {
|
.uploader {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* dark theme */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
.breadcrumb>b,
|
||||||
|
.searchbar #search {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploaders-table th,
|
||||||
|
.paths-table th {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg,
|
||||||
|
.path svg {
|
||||||
|
fill: #d0e6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar {
|
||||||
|
background-color: #111;
|
||||||
|
border-color: #fff6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar svg {
|
||||||
|
fill: #fff6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path a {
|
||||||
|
color: #3191ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
|
<div class="empty-folder hidden"></div>
|
||||||
<table class="uploaders-table hidden">
|
<table class="uploaders-table hidden">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -7,11 +7,29 @@
|
|||||||
* @property {number} size
|
* @property {number} size
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/901144/3642588
|
||||||
|
const params = new Proxy(new URLSearchParams(window.location.search), {
|
||||||
|
get: (searchParams, prop) => searchParams.get(prop),
|
||||||
|
});
|
||||||
|
|
||||||
|
const dirEmptyNote = params.q ? 'No results' : DATA.dir_exists ? 'Empty folder' : 'Folder will be created when a file is uploaded';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type Element
|
* @type Element
|
||||||
*/
|
*/
|
||||||
let $pathsTable, $pathsTableBody, $uploadersTable;
|
let $pathsTable;
|
||||||
|
/**
|
||||||
|
* @type Element
|
||||||
|
*/
|
||||||
|
let $pathsTableBody;
|
||||||
|
/**
|
||||||
|
* @type Element
|
||||||
|
*/
|
||||||
|
let $uploadersTable;
|
||||||
|
/**
|
||||||
|
* @type Element
|
||||||
|
*/
|
||||||
|
let $emptyFolder;
|
||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
@@ -53,6 +71,7 @@ class Uploader {
|
|||||||
<td class="cell-status" id="uploadStatus${idx}"></td>
|
<td class="cell-status" id="uploadStatus${idx}"></td>
|
||||||
</tr>`);
|
</tr>`);
|
||||||
$uploadersTable.classList.remove("hidden");
|
$uploadersTable.classList.remove("hidden");
|
||||||
|
$emptyFolder.classList.add("hidden");
|
||||||
this.$uploadStatus = document.getElementById(`uploadStatus${idx}`);
|
this.$uploadStatus = document.getElementById(`uploadStatus${idx}`);
|
||||||
|
|
||||||
const ajax = new XMLHttpRequest();
|
const ajax = new XMLHttpRequest();
|
||||||
@@ -180,6 +199,8 @@ async function deletePath(index) {
|
|||||||
DATA.paths[index] = null;
|
DATA.paths[index] = null;
|
||||||
if (!DATA.paths.find(v => !!v)) {
|
if (!DATA.paths.find(v => !!v)) {
|
||||||
$pathsTable.classList.add("hidden");
|
$pathsTable.classList.add("hidden");
|
||||||
|
$emptyFolder.textContent = dirEmptyNote;
|
||||||
|
$emptyFolder.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(await res.text())
|
throw new Error(await res.text())
|
||||||
@@ -274,6 +295,11 @@ function ready() {
|
|||||||
$pathsTable = document.querySelector(".paths-table")
|
$pathsTable = document.querySelector(".paths-table")
|
||||||
$pathsTableBody = document.querySelector(".paths-table tbody");
|
$pathsTableBody = document.querySelector(".paths-table tbody");
|
||||||
$uploadersTable = document.querySelector(".uploaders-table");
|
$uploadersTable = document.querySelector(".uploaders-table");
|
||||||
|
$emptyFolder = document.querySelector(".empty-folder");
|
||||||
|
|
||||||
|
if (params.q) {
|
||||||
|
document.getElementById('search').value = params.q;
|
||||||
|
}
|
||||||
|
|
||||||
addBreadcrumb(DATA.breadcrumb);
|
addBreadcrumb(DATA.breadcrumb);
|
||||||
if (Array.isArray(DATA.paths)) {
|
if (Array.isArray(DATA.paths)) {
|
||||||
@@ -284,6 +310,10 @@ function ready() {
|
|||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
addPath(DATA.paths[i], i);
|
addPath(DATA.paths[i], i);
|
||||||
}
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
$emptyFolder.textContent = dirEmptyNote;
|
||||||
|
$emptyFolder.classList.remove("hidden");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (DATA.allow_upload) {
|
if (DATA.allow_upload) {
|
||||||
dropzone();
|
dropzone();
|
||||||
|
|||||||
@@ -191,8 +191,8 @@ impl InnerService {
|
|||||||
self.handle_render_index(path, headers, &mut res).await?;
|
self.handle_render_index(path, headers, &mut res).await?;
|
||||||
} else if query == "zip" {
|
} else if query == "zip" {
|
||||||
self.handle_zip_dir(path, &mut res).await?;
|
self.handle_zip_dir(path, &mut res).await?;
|
||||||
} else if query.starts_with("q=") {
|
} else if let Some(q) = query.strip_prefix("q=") {
|
||||||
self.handle_query_dir(path, &query[3..], &mut res).await?;
|
self.handle_query_dir(path, q, &mut res).await?;
|
||||||
} else {
|
} else {
|
||||||
self.handle_ls_dir(path, true, &mut res).await?;
|
self.handle_ls_dir(path, true, &mut res).await?;
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ impl InnerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.send_index(path, paths, res)
|
self.send_index(path, paths, res, exist)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_query_dir(
|
async fn handle_query_dir(
|
||||||
@@ -354,7 +354,7 @@ impl InnerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.send_index(path, paths, res)
|
self.send_index(path, paths, res, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_zip_dir(&self, path: &Path, res: &mut Response) -> BoxResult<()> {
|
async fn handle_zip_dir(&self, path: &Path, res: &mut Response) -> BoxResult<()> {
|
||||||
@@ -645,6 +645,7 @@ impl InnerService {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
mut paths: Vec<PathItem>,
|
mut paths: Vec<PathItem>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
|
exist: bool,
|
||||||
) -> BoxResult<()> {
|
) -> BoxResult<()> {
|
||||||
paths.sort_unstable();
|
paths.sort_unstable();
|
||||||
let rel_path = match self.args.path.parent() {
|
let rel_path = match self.args.path.parent() {
|
||||||
@@ -656,6 +657,7 @@ impl InnerService {
|
|||||||
paths,
|
paths,
|
||||||
allow_upload: self.args.allow_upload,
|
allow_upload: self.args.allow_upload,
|
||||||
allow_delete: self.args.allow_delete,
|
allow_delete: self.args.allow_delete,
|
||||||
|
dir_exists: exist,
|
||||||
};
|
};
|
||||||
let data = serde_json::to_string(&data).unwrap();
|
let data = serde_json::to_string(&data).unwrap();
|
||||||
let output = INDEX_HTML.replace(
|
let output = INDEX_HTML.replace(
|
||||||
@@ -802,6 +804,7 @@ struct IndexData {
|
|||||||
paths: Vec<PathItem>,
|
paths: Vec<PathItem>,
|
||||||
allow_upload: bool,
|
allow_upload: bool,
|
||||||
allow_delete: bool,
|
allow_delete: bool,
|
||||||
|
dir_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
@@ -816,10 +819,12 @@ struct PathItem {
|
|||||||
impl PathItem {
|
impl PathItem {
|
||||||
pub fn to_dav_xml(&self, prefix: &str) -> String {
|
pub fn to_dav_xml(&self, prefix: &str) -> String {
|
||||||
let mtime = Utc.timestamp_millis(self.mtime as i64).to_rfc2822();
|
let mtime = Utc.timestamp_millis(self.mtime as i64).to_rfc2822();
|
||||||
|
let href = encode_uri(&format!("{}{}", prefix, &self.name));
|
||||||
|
let displayname = escape_str_pcdata(&self.base_name);
|
||||||
match self.path_type {
|
match self.path_type {
|
||||||
PathType::Dir | PathType::SymlinkDir => format!(
|
PathType::Dir | PathType::SymlinkDir => format!(
|
||||||
r#"<D:response>
|
r#"<D:response>
|
||||||
<D:href>{}{}</D:href>
|
<D:href>{}</D:href>
|
||||||
<D:propstat>
|
<D:propstat>
|
||||||
<D:prop>
|
<D:prop>
|
||||||
<D:displayname>{}</D:displayname>
|
<D:displayname>{}</D:displayname>
|
||||||
@@ -829,14 +834,11 @@ impl PathItem {
|
|||||||
<D:status>HTTP/1.1 200 OK</D:status>
|
<D:status>HTTP/1.1 200 OK</D:status>
|
||||||
</D:propstat>
|
</D:propstat>
|
||||||
</D:response>"#,
|
</D:response>"#,
|
||||||
escape_str_pcdata(prefix),
|
href, displayname, mtime
|
||||||
escape_str_pcdata(&self.name),
|
|
||||||
escape_str_pcdata(&self.base_name),
|
|
||||||
mtime
|
|
||||||
),
|
),
|
||||||
PathType::File | PathType::SymlinkFile => format!(
|
PathType::File | PathType::SymlinkFile => format!(
|
||||||
r#"<D:response>
|
r#"<D:response>
|
||||||
<D:href>{}{}</D:href>
|
<D:href>{}</D:href>
|
||||||
<D:propstat>
|
<D:propstat>
|
||||||
<D:prop>
|
<D:prop>
|
||||||
<D:displayname>{}</D:displayname>
|
<D:displayname>{}</D:displayname>
|
||||||
@@ -847,9 +849,8 @@ impl PathItem {
|
|||||||
<D:status>HTTP/1.1 200 OK</D:status>
|
<D:status>HTTP/1.1 200 OK</D:status>
|
||||||
</D:propstat>
|
</D:propstat>
|
||||||
</D:response>"#,
|
</D:response>"#,
|
||||||
escape_str_pcdata(prefix),
|
href,
|
||||||
escape_str_pcdata(&self.name),
|
displayname,
|
||||||
escape_str_pcdata(&self.base_name),
|
|
||||||
self.size.unwrap_or_default(),
|
self.size.unwrap_or_default(),
|
||||||
mtime
|
mtime
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user