exception<NullPointerException> { call, _ ->
call.respondHtml(HttpStatusCode.NotFound, call.error404())
}
+ exception<NoSuchElementException> { call, _ ->
+ call.respondHtml(HttpStatusCode.NotFound, call.error404())
+ }
exception<IOException> { call, _ ->
call.respondHtml(HttpStatusCode.NotFound, call.error404())
}
get<Root.Admin.Vfs.Inline>()
get<Root.Admin.Vfs.Download>()
get<Root.Admin.Vfs.View>()
+ get<Root.Admin.Vfs.CopyPage>()
+ post<Root.Admin.Vfs.CopyPost, _>()
postMultipart<Root.Admin.Vfs.Upload, _>()
postMultipart<Root.Admin.Vfs.Overwrite, _>()
get<Root.Admin.Vfs.DeleteConfirmPage>()
td {
ul {
li {
- a(href = href(Root.Admin.Vfs.Download(path.elements))) {
+ a(classes = "button", href = href(Root.Admin.Vfs.Download(path.elements))) {
+"Download"
}
}
}
}
li {
- a(href = href(Root.Admin.Vfs.DeleteConfirmPage(path.elements))) {
+ a(classes = "button", href = href(Root.Admin.Vfs.CopyPage(path.elements))) {
+ +"Make Copy"
+ }
+ }
+ li {
+ a(classes = "button evil", href = href(Root.Admin.Vfs.DeleteConfirmPage(path.elements))) {
+"Delete"
}
}
if (!result) respond(HttpStatusCode.NotFound)
}
+private suspend fun fileTreeForCopy(path: StoragePath): TreeNode.DirNode? {
+ return coroutineScope {
+ FileStorage.instance.listDir(path)?.map { entry ->
+ async {
+ fileTreeForCopy(path / entry.name)?.let { entry.name to it }
+ }
+ }?.awaitAll()
+ ?.filterNotNull()
+ ?.toMap()
+ ?.let { TreeNode.DirNode(it) }
+ }
+}
+
+context(ApplicationCall)
+private fun UL.renderForCopy(fromPath: StoragePath, intoPath: StoragePath, node: TreeNode.DirNode) {
+ li {
+ form(method = FormMethod.post, action = href(Root.Admin.Vfs.CopyPost(intoPath.elements))) {
+ installCsrfToken()
+ hiddenInput(name = "from") { value = fromPath.toString() }
+ submitInput { value = "Copy Into /$intoPath" }
+ }
+ ul {
+ for ((childName, childNode) in node.children)
+ if (childNode is TreeNode.DirNode)
+ renderForCopy(fromPath, intoPath / childName, childNode)
+ }
+ }
+}
+
+suspend fun ApplicationCall.adminShowCopyFile(from: StoragePath): HTML.() -> Unit {
+ if (FileStorage.instance.statFile(from) == null)
+ throw NoSuchElementException("File does not exist")
+
+ val tree = fileTreeForCopy(StoragePath.Root)!!
+
+ return adminPage("Copy File /$from") {
+ main {
+ h1 { +"Choose Destination for /$from" }
+ ul {
+ li {
+ form(method = FormMethod.get, action = href(Root.Admin.Vfs.View(from.elements))) {
+ submitInput { value = "Cancel Copy" }
+ }
+ }
+ renderForCopy(from, StoragePath.Root, tree)
+ }
+ }
+ }
+}
+
+suspend fun ApplicationCall.adminDoCopyFile(from: StoragePath, into: StoragePath) {
+ val name = from.elements.last()
+ val dest = into / name
+
+ if (FileStorage.instance.copyFile(from, dest))
+ redirectHref(Root.Admin.Vfs.View(dest.elements))
+ else
+ respond(HttpStatusCode.Conflict)
+}
+
suspend fun ApplicationCall.adminUploadFile(path: StoragePath, part: PartData.FileItem) {
val name = part.originalFileName ?: throw MissingRequestParameterException("originalFileName")
val filePath = path / name
}
}
- form(method = FormMethod.get, action = href(Root.Admin.Vfs.View(path.elements))) {
- submitInput { value = "No, take me back" }
- }
- +Entities.nbsp
- form(method = FormMethod.post, action = href(Root.Admin.Vfs.DeleteConfirmPost(path.elements))) {
- installCsrfToken()
- submitInput(classes = "evil") { value = "Yes, delete it" }
+ br
+
+ div {
+ style = "text-align:center"
+ form(method = FormMethod.get, action = href(Root.Admin.Vfs.View(path.elements))) {
+ submitInput { value = "No, take me back" }
+ }
+ +Entities.nbsp
+ form(method = FormMethod.post, action = href(Root.Admin.Vfs.DeleteConfirmPost(path.elements))) {
+ installCsrfToken()
+ submitInput(classes = "evil") { value = "Yes, delete it" }
+ }
}
}
})
}
}
- form(method = FormMethod.get, action = href(Root.Admin.Vfs.View(path.elements))) {
- submitInput { value = "No, take me back" }
- }
- +Entities.nbsp
- form(method = FormMethod.post, action = href(Root.Admin.Vfs.RmDirConfirmPost(path.elements))) {
- installCsrfToken()
- submitInput(classes = "evil") { value = "Yes, delete it" }
+ br
+
+ div {
+ style = "text-align:center"
+ form(method = FormMethod.get, action = href(Root.Admin.Vfs.View(path.elements))) {
+ submitInput { value = "No, take me back" }
+ }
+ +Entities.nbsp
+ form(method = FormMethod.post, action = href(Root.Admin.Vfs.RmDirConfirmPost(path.elements))) {
+ installCsrfToken()
+ submitInput(classes = "evil") { value = "Yes, delete it" }
+ }
}
}
})
}
}
+ @Resource("copy/{path...}")
+ class CopyPage(val path: List<String>, val vfs: Vfs = Vfs()) : ResourceHandler {
+ override suspend fun PipelineContext<Unit, ApplicationCall>.handleCall() {
+ with(vfs) { filterCall() }
+
+ call.respondHtml(HttpStatusCode.OK, call.adminShowCopyFile(StoragePath(path)))
+ }
+ }
+
+ @Resource("copy/{path...}")
+ class CopyPost(val path: List<String>, val vfs: Vfs = Vfs()) : ResourceReceiver<AdminVfsCopyFilePayload> {
+ override suspend fun PipelineContext<Unit, ApplicationCall>.handleCall(payload: AdminVfsCopyFilePayload) {
+ with(vfs) { filterCall() }
+
+ call.adminDoCopyFile(StoragePath(payload.from), StoragePath(path))
+ }
+ }
+
@Resource("upload/{path...}")
class Upload(val path: List<String>, val vfs: Vfs = Vfs()) : ResourceReceiver<CsrfProtectedMultiPartPayload> {
override suspend fun PipelineContext<Unit, ApplicationCall>.handleCall(payload: CsrfProtectedMultiPartPayload) {
display: none;
}
-label:has(> input[type=file]) {
+a.button, label:has(> input[type=file]) {
border: 1px solid #ec6;
background-color: #541;
+ color: #fd7;
+
+ text-shadow: 0 0 0.25em #fd7;
+ text-decoration: none;
display: inline-block;
vertical-align: middle;
cursor: pointer;
}
-label:has(> input[type=file]):hover {
+a.button:hover, label:has(> input[type=file]):hover {
background-color: #a82;
}
+a.button.evil {
+ border: 1px solid #d66;
+ background-color: #411;
+ color: #e77;
+
+ text-shadow: 0 0 0.25em #b44;
+ text-decoration: none;
+}
+
+a.button.evil:hover {
+ background-color: #922;
+}
+
label:has(> input[type=file]) ~ input[type=submit] {
display: none;
}
}
input[type=submit].evil {
- background-color: #922;
+ background-color: #811;
}
input[type=submit]:hover {
}
input[type=submit].evil:hover {
- background-color: #b44;
+ background-color: #a33;
}
input[type=submit]:active {
}
input[type=submit].evil:active {
- background-color: #e77;
+ background-color: #d66;
}