feat: support delete operation
parent
78e7daf7cc
commit
dd888f1ae8
|
@ -34,6 +34,7 @@ fn app() -> clap::Command<'static> {
|
||||||
.long("readonly")
|
.long("readonly")
|
||||||
.help("Only serve static files, no operations like upload and delete");
|
.help("Only serve static files, no operations like upload and delete");
|
||||||
|
|
||||||
|
|
||||||
let arg_auth = Arg::new("auth")
|
let arg_auth = Arg::new("auth")
|
||||||
.short('a')
|
.short('a')
|
||||||
.long("auth")
|
.long("auth")
|
||||||
|
|
|
@ -59,14 +59,26 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
.main .cell-name {
|
.main .cell-name {
|
||||||
max-width: 300px;
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .cell-mtime {
|
||||||
|
width: 150px;
|
||||||
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main .cell-size {
|
.main .cell-size {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 80px;
|
width: 100px;
|
||||||
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main .cell-actions {
|
||||||
|
width: 100px;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.path svg {
|
.path svg {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
fill: rgba(3,47,98,0.5);
|
fill: rgba(3,47,98,0.5);
|
||||||
|
@ -98,5 +110,5 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploader {
|
.uploader {
|
||||||
padding-right: 2em;
|
padding-right: 1em;
|
||||||
}
|
}
|
|
@ -103,17 +103,41 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFile(file) {
|
function addPath(file, index) {
|
||||||
|
const actionTd = file.name === ".." || readonly ? "" : `
|
||||||
|
<td class="cell-actions">
|
||||||
|
<div onclick="deletePath(${index})" class="delete-btn" id="deleteBtn${index}" title="Delete ${file.name}">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 40 40"><path d="M28,40H11.8c-3.3,0-5.9-2.7-5.9-5.9V16c0-0.6,0.4-1,1-1s1,0.4,1,1v18.1c0,2.2,1.8,3.9,3.9,3.9H28c2.2,0,3.9-1.8,3.9-3.9V16 c0-0.6,0.4-1,1-1s1,0.4,1,1v18.1C33.9,37.3,31.2,40,28,40z"/></g><g><path d="M33.3,4.9h-7.6C25.2,2.1,22.8,0,19.9,0s-5.3,2.1-5.8,4.9H6.5c-2.3,0-4.1,1.8-4.1,4.1S4.2,13,6.5,13h26.9 c2.3,0,4.1-1.8,4.1-4.1S35.6,4.9,33.3,4.9z M19.9,2c1.8,0,3.3,1.2,3.7,2.9h-7.5C16.6,3.2,18.1,2,19.9,2z M33.3,11H6.5 c-1.1,0-2.1-0.9-2.1-2.1c0-1.1,0.9-2.1,2.1-2.1h26.9c1.1,0,2.1,0.9,2.1,2.1C35.4,10.1,34.5,11,33.3,11z"/></g><g><path d="M12.9,35.1c-0.6,0-1-0.4-1-1V17.4c0-0.6,0.4-1,1-1s1,0.4,1,1v16.7C13.9,34.6,13.4,35.1,12.9,35.1z"/></g><g><path d="M26.9,35.1c-0.6,0-1-0.4-1-1V17.4c0-0.6,0.4-1,1-1s1,0.4,1,1v16.7C27.9,34.6,27.4,35.1,26.9,35.1z"/></g><g><path d="M19.9,35.1c-0.6,0-1-0.4-1-1V17.4c0-0.6,0.4-1,1-1s1,0.4,1,1v16.7C20.9,34.6,20.4,35.1,19.9,35.1z"/></svg>
|
||||||
|
</div>
|
||||||
|
</td>`
|
||||||
|
|
||||||
$tbody.insertAdjacentHTML("beforeend", `
|
$tbody.insertAdjacentHTML("beforeend", `
|
||||||
<tr>
|
<tr id="addPath${index}">
|
||||||
<td class="path cell-name">
|
<td class="path cell-name">
|
||||||
<div>${getSvg(file.path_type)}</div>
|
<div>${getSvg(file.path_type)}</div>
|
||||||
<a href="${encodeURI(file.path)}" title="${file.name}">${file.name}</a>
|
<a href="${encodeURI(file.path)}" title="${file.name}">${file.name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="cell-mtime">${formatMtime(file.mtime)}</td>
|
<td class="cell-mtime">${formatMtime(file.mtime)}</td>
|
||||||
<td class="cell-size">${formatSize(file.size)}</td>
|
<td class="cell-size">${formatSize(file.size)}</td>
|
||||||
</tr>
|
${actionTd}
|
||||||
`)
|
</tr>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePath(index) {
|
||||||
|
const file = paths[index];
|
||||||
|
if (!file) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(encodeURI(file.path), {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
const text = await res.text();
|
||||||
|
throw new Error(text);
|
||||||
|
}
|
||||||
|
document.getElementById(`addPath${index}`).remove();
|
||||||
|
} catch (err) {
|
||||||
|
alert(`Failed to delete ${file.name}, ${err.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUploadControl() {
|
function addUploadControl() {
|
||||||
|
@ -164,11 +188,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
addBreadcrumb(breadcrumb);
|
addBreadcrumb(breadcrumb);
|
||||||
paths.forEach(file => addFile(file));
|
paths.forEach((file, index) => addPath(file, index));
|
||||||
if (readonly) {
|
if (!readonly) {
|
||||||
addUploadControl();
|
addUploadControl();
|
||||||
|
document.querySelector(".main thead tr").insertAdjacentHTML("beforeend", `<th class="cell-actions">Actions</th>`);
|
||||||
document.getElementById("file").addEventListener("change", e => {
|
document.getElementById("file").addEventListener("change", e => {
|
||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
|
|
@ -60,6 +60,12 @@ impl InnerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle(self: Arc<Self>, req: Request) -> Result<Response, hyper::Error> {
|
pub async fn handle(self: Arc<Self>, req: Request) -> Result<Response, hyper::Error> {
|
||||||
|
if !self.auth_guard(&req).unwrap_or_default() {
|
||||||
|
let mut res = status_code!(StatusCode::UNAUTHORIZED);
|
||||||
|
res.headers_mut().insert("WWW-Authenticate" , HeaderValue::from_static("Basic"));
|
||||||
|
return Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
let res = if req.method() == Method::GET {
|
let res = if req.method() == Method::GET {
|
||||||
self.handle_static(req).await
|
self.handle_static(req).await
|
||||||
} else if req.method() == Method::PUT {
|
} else if req.method() == Method::PUT {
|
||||||
|
@ -67,6 +73,8 @@ impl InnerService {
|
||||||
return Ok(status_code!(StatusCode::FORBIDDEN));
|
return Ok(status_code!(StatusCode::FORBIDDEN));
|
||||||
}
|
}
|
||||||
self.handle_upload(req).await
|
self.handle_upload(req).await
|
||||||
|
} else if req.method() == Method::DELETE {
|
||||||
|
self.handle_delete(req).await
|
||||||
} else {
|
} else {
|
||||||
return Ok(status_code!(StatusCode::NOT_FOUND));
|
return Ok(status_code!(StatusCode::NOT_FOUND));
|
||||||
};
|
};
|
||||||
|
@ -74,11 +82,6 @@ impl InnerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_static(&self, req: Request) -> BoxResult<Response> {
|
async fn handle_static(&self, req: Request) -> BoxResult<Response> {
|
||||||
if !self.auth_guard(&req).unwrap_or_default() {
|
|
||||||
let mut res = status_code!(StatusCode::UNAUTHORIZED);
|
|
||||||
res.headers_mut().insert("WWW-Authenticate" , HeaderValue::from_static("Basic"));
|
|
||||||
return Ok(res)
|
|
||||||
}
|
|
||||||
let path = match self.get_file_path(req.uri().path())? {
|
let path = match self.get_file_path(req.uri().path())? {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => return Ok(status_code!(StatusCode::FORBIDDEN)),
|
None => return Ok(status_code!(StatusCode::FORBIDDEN)),
|
||||||
|
@ -96,11 +99,6 @@ impl InnerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_upload(&self, mut req: Request) -> BoxResult<Response> {
|
async fn handle_upload(&self, mut req: Request) -> BoxResult<Response> {
|
||||||
if !self.auth_guard(&req).unwrap_or_default() {
|
|
||||||
let mut res = status_code!(StatusCode::UNAUTHORIZED);
|
|
||||||
res.headers_mut().insert("WWW-Authenticate" , HeaderValue::from_static("Basic"));
|
|
||||||
return Ok(res)
|
|
||||||
}
|
|
||||||
let path = match self.get_file_path(req.uri().path())? {
|
let path = match self.get_file_path(req.uri().path())? {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => return Ok(status_code!(StatusCode::FORBIDDEN)),
|
None => return Ok(status_code!(StatusCode::FORBIDDEN)),
|
||||||
|
@ -125,6 +123,22 @@ impl InnerService {
|
||||||
return Ok(status_code!(StatusCode::OK));
|
return Ok(status_code!(StatusCode::OK));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_delete(&self, req: Request) -> BoxResult<Response> {
|
||||||
|
let path = match self.get_file_path(req.uri().path())? {
|
||||||
|
Some(path) => path,
|
||||||
|
None => return Ok(status_code!(StatusCode::FORBIDDEN)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let meta = fs::metadata(&path).await?;
|
||||||
|
if meta.is_file() {
|
||||||
|
fs::remove_file(path).await?;
|
||||||
|
} else {
|
||||||
|
fs::remove_dir_all(path).await?;
|
||||||
|
}
|
||||||
|
Ok(status_code!(StatusCode::OK))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_send_dir(&self, path: &Path) -> BoxResult<Response> {
|
async fn handle_send_dir(&self, path: &Path) -> BoxResult<Response> {
|
||||||
let base_path = &self.args.path;
|
let base_path = &self.args.path;
|
||||||
let mut rd = fs::read_dir(path).await?;
|
let mut rd = fs::read_dir(path).await?;
|
||||||
|
@ -179,7 +193,7 @@ impl InnerService {
|
||||||
|
|
||||||
paths.sort_unstable();
|
paths.sort_unstable();
|
||||||
let breadcrumb = self.get_breadcrumb(path);
|
let breadcrumb = self.get_breadcrumb(path);
|
||||||
let data = SendDirData { breadcrumb, paths, readonly: !self.args.readonly };
|
let data = SendDirData { breadcrumb, paths, readonly: self.args.readonly };
|
||||||
let data = serde_json::to_string(&data).unwrap();
|
let data = serde_json::to_string(&data).unwrap();
|
||||||
|
|
||||||
let mut output =
|
let mut output =
|
||||||
|
|
Loading…
Reference in New Issue