commit 38cc8a171f2315885ed2a64e7524ade682e8d1f0 Author: Ben Date: Sat Sep 2 22:25:45 2023 +0000 first commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a383ac8 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +# From https://github.com/DarkDean89/luci-app-filebrowser +# From https://github.com/stuarthua/oh-my-openwrt/tree/master/stuart/luci-app-fileassistant +# This is free software, licensed under the Apache License, Version 2.0 . + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI support for Fileassistant +LUCI_PKGARCH:=all + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/htdocs/luci-static/resources/fileassistant/fb.css b/htdocs/luci-static/resources/fileassistant/fb.css new file mode 100644 index 0000000..96cbbb3 --- /dev/null +++ b/htdocs/luci-static/resources/fileassistant/fb.css @@ -0,0 +1,68 @@ +.fb-container { + margin-top: 1rem; +} +.fb-container .cbi-button { + height: 1.8rem; +} +.fb-container .cbi-input-text { + margin-bottom: 1rem; + width: 100%; +} +.fb-container .panel-title { + padding-bottom: 0; + width: 50%; + border-bottom: none; +} +.fb-container .panel-container { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 1rem; + border-bottom: 1px solid #eee; +} +.fb-container .upload-container { + display: none; + margin: 1rem 0; +} +.fb-container .upload-file { + margin-right: 2rem; +} +.fb-container .cbi-value-field { + text-align: left; +} +.fb-container .parent-icon strong { + margin-left: 1rem; +} +.fb-container td[class$="-icon"] { + cursor: pointer; +} +.fb-container .file-icon, .fb-container .folder-icon, .fb-container .link-icon { + position: relative; +} +.fb-container .file-icon:before, .fb-container .folder-icon:before, .fb-container .link-icon:before { + display: inline-block; + width: 1.5rem; + height: 1.5rem; + content: ''; + background-size: contain; + margin: 0 0.5rem 0 1rem; + vertical-align: middle; +} +.fb-container .file-icon:before { + background-image: url(file-icon.png); +} +.fb-container .folder-icon:before { + background-image: url(folder-icon.png); +} +.fb-container .link-icon:before { + background-image: url(link-icon.png); +} +@media screen and (max-width: 480px) { + .fb-container .upload-file { + width: 14.6rem; + } + .fb-container .cbi-value-owner, + .fb-container .cbi-value-perm { + display: none; + } +} diff --git a/htdocs/luci-static/resources/fileassistant/fb.js b/htdocs/luci-static/resources/fileassistant/fb.js new file mode 100644 index 0000000..10c0d47 --- /dev/null +++ b/htdocs/luci-static/resources/fileassistant/fb.js @@ -0,0 +1,288 @@ +String.prototype.replaceAll = function(search, replacement) { + var target = this; + return target.replace(new RegExp(search, 'g'), replacement); +}; +(function () { + var iwxhr = new XHR(); + var listElem = document.getElementById("list-content"); + listElem.onclick = handleClick; + var currentPath; + var pathElem = document.getElementById("current-path"); + pathElem.onblur = function () { + update_list(this.value.trim()); + }; + pathElem.onkeyup = function (evt) { + if (evt.keyCode == 13) { + this.blur(); + } + }; + function removePath(filename, isdir) { + var c = confirm('Are you sure you want to delete ' + filename + ' ?'); + if (c) { + iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/delete', + { + path: concatPath(currentPath, filename), + isdir: isdir + }, + function (x, res) { + if (res.ec === 0) { + refresh_list(res.data, currentPath); + } + }); + } + } + + function installPath(filename, isdir) { + if (isdir === "1") { + alert('This is a Directory,Please Choose ipk file to install!'); + return; + } + var isipk = isIPK(filename); + if (isipk === 0) { + alert('Only allow installation ipk format!'); + return; + } + var c = confirm('Are you sure you want to install ' + filename + ' ?'); + if (c) { + iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/install', + { + filepath: concatPath(currentPath, filename), + isdir: isdir + }, + function (x, res) { + if (res.ec === 0) { + location.reload(); + alert('Successful installation!'); + } else { + alert('installation failed,Please check the file format!'); + } + }); + } + } + + function isIPK(filename) { + var index= filename.lastIndexOf("."); + var ext = filename.substr(index+1); + if (ext === 'ipk') { + return 1; + } else { + return 0; + } + } + + function renamePath(filename) { + var newname = prompt('Please enter a new filename:', filename); + if (newname) { + newname = newname.trim(); + if (newname != filename) { + var newpath = concatPath(currentPath, newname); + iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/rename', + { + filepath: concatPath(currentPath, filename), + newpath: newpath + }, + function (x, res) { + if (res.ec === 0) { + refresh_list(res.data, currentPath); + } + } + ); + } + } + } + + function openpath(filename, dirname) { + dirname = dirname || currentPath; + window.open('/cgi-bin/luci/admin/nas/fileassistant/open?path=' + + encodeURIComponent(dirname) + '&filename=' + + encodeURIComponent(filename)); + } + + function getFileElem(elem) { + if (elem.className.indexOf('-icon') > -1) { + return elem; + } + else if (elem.parentNode.className.indexOf('-icon') > -1) { + return elem.parentNode; + } + } + + function concatPath(path, filename) { + if (path === '/') { + return path + filename; + } + else { + return path.replace(/\/$/, '') + '/' + filename; + } + } + + function handleClick(evt) { + var targetElem = evt.target; + var infoElem; + if (targetElem.className.indexOf('cbi-button-remove') > -1) { + infoElem = targetElem.parentNode.parentNode; + removePath(infoElem.dataset['filename'] , infoElem.dataset['isdir']) + } + else if (targetElem.className.indexOf('cbi-button-install') > -1) { + infoElem = targetElem.parentNode.parentNode; + installPath(infoElem.dataset['filename'] , infoElem.dataset['isdir']) + } + else if (targetElem.className.indexOf('cbi-button-edit') > -1) { + renamePath(targetElem.parentNode.parentNode.dataset['filename']); + } + else if (targetElem = getFileElem(targetElem)) { + if (targetElem.className.indexOf('parent-icon') > -1) { + update_list(currentPath.replace(/\/[^/]+($|\/$)/, '')); + } + else if (targetElem.className.indexOf('file-icon') > -1) { + openpath(targetElem.parentNode.dataset['filename']); + } + else if (targetElem.className.indexOf('link-icon') > -1) { + infoElem = targetElem.parentNode; + var filepath = infoElem.dataset['linktarget']; + if (filepath) { + if (infoElem.dataset['isdir'] === "1") { + update_list(filepath); + } + else { + var lastSlash = filepath.lastIndexOf('/'); + openpath(filepath.substring(lastSlash + 1), filepath.substring(0, lastSlash)); + } + } + } + else if (targetElem.className.indexOf('folder-icon') > -1) { + update_list(concatPath(currentPath, targetElem.parentNode.dataset['filename'])) + } + } + } + function refresh_list(filenames, path) { + var listHtml = ''; + if (path !== '/') { + listHtml += ''; + } + if (filenames) { + for (var i = 0; i < filenames.length; i++) { + var line = filenames[i]; + if (line) { + var f = line.match(/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+([\S\s]+)/); + var isLink = f[1][0] === 'z' || f[1][0] === 'l' || f[1][0] === 'x'; + var o = { + displayname: f[9], + filename: isLink ? f[9].split(' -> ')[0] : f[9], + perms: f[1], + date: f[7] + ' ' + f[6] + ' ' + f[8], + size: f[5], + owner: f[3], + icon: (f[1][0] === 'd') ? "folder-icon" : (isLink ? "link-icon" : "file-icon") + }; + + var install_btn = ''; + var index= o.filename.lastIndexOf("."); + var ext = o.filename.substr(index+1); + if (ext === 'ipk') { + install_btn = ''; + } + + listHtml += '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + } + } + } + listHtml += "
..
' + + '' + o.displayname + '' + + ''+o.owner+''+o.date+''+o.size+''+o.perms+'\ + \ + ' + + install_btn + + '
"; + listElem.innerHTML = listHtml; + } + function update_list(path, opt) { + opt = opt || {}; + path = concatPath(path, ''); + if (currentPath != path) { + iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/list', + {path: path}, + function (x, res) { + if (res.ec === 0) { + refresh_list(res.data, path); + } + else { + refresh_list([], path); + } + } + ); + if (!opt.popState) { + history.pushState({path: path}, null, '?path=' + path); + } + currentPath = path; + pathElem.value = currentPath; + } + }; + + var uploadToggle = document.getElementById('upload-toggle'); + var uploadContainer = document.getElementById('upload-container'); + var isUploadHide = true; + uploadToggle.onclick = function() { + if (isUploadHide) { + uploadContainer.style.display = 'inline-flex'; + } + else { + uploadContainer.style.display = 'none'; + } + isUploadHide = !isUploadHide; + }; + var uploadBtn = uploadContainer.getElementsByClassName('cbi-input-apply')[0]; + uploadBtn.onclick = function (evt) { + var uploadinput = document.getElementById('upload-file'); + var fullPath = uploadinput.value; + if (!fullPath) { + evt.preventDefault(); + } + else { + var formData = new FormData(); + var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/')); + formData.append('upload-filename', fullPath.substring(startIndex + 1)); + formData.append('upload-dir', concatPath(currentPath, '')); + formData.append('upload-file', uploadinput.files[0]); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/cgi-bin/luci/admin/nas/fileassistant/upload", true); + xhr.onload = function() { + if (xhr.status == 200) { + var res = JSON.parse(xhr.responseText); + refresh_list(res.data, currentPath); + uploadinput.value = ''; + } + else { + alert('Upload failed, please try again later...'); + } + }; + xhr.send(formData); + } + }; + + document.addEventListener('DOMContentLoaded', function(evt) { + var initPath = '/'; + if (/path=([/\w]+)/.test(location.search)) { + initPath = RegExp.$1; + } + update_list(initPath, {popState: true}); + }); + window.addEventListener('popstate', function (evt) { + var path = '/'; + if (evt.state && evt.state.path) { + path = evt.state.path; + } + update_list(path, {popState: true}); + }); + +})(); diff --git a/htdocs/luci-static/resources/fileassistant/file-icon.png b/htdocs/luci-static/resources/fileassistant/file-icon.png new file mode 100644 index 0000000..f156dc1 Binary files /dev/null and b/htdocs/luci-static/resources/fileassistant/file-icon.png differ diff --git a/htdocs/luci-static/resources/fileassistant/folder-icon.png b/htdocs/luci-static/resources/fileassistant/folder-icon.png new file mode 100644 index 0000000..1370df3 Binary files /dev/null and b/htdocs/luci-static/resources/fileassistant/folder-icon.png differ diff --git a/htdocs/luci-static/resources/fileassistant/link-icon.png b/htdocs/luci-static/resources/fileassistant/link-icon.png new file mode 100644 index 0000000..03cc82c Binary files /dev/null and b/htdocs/luci-static/resources/fileassistant/link-icon.png differ diff --git a/luasrc/controller/fileassistant.lua b/luasrc/controller/fileassistant.lua new file mode 100644 index 0000000..caea6e7 --- /dev/null +++ b/luasrc/controller/fileassistant.lua @@ -0,0 +1,232 @@ +module("luci.controller.fileassistant", package.seeall) + +function index() + entry({"admin", "nas"}, firstchild(), _("NAS") , 45).dependent = false + + entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false + + local page + page = entry({"admin", "nas", "fileassistant"}, template("fileassistant"), _("File Assistant"), 1) + page.i18n = "base" + page.dependent = true + page.acl_depends = { "luci-app-fileassistant" } + + page = entry({"admin", "nas", "fileassistant", "list"}, call("fileassistant_list"), nil) + page.leaf = true + + page = entry({"admin", "nas", "fileassistant", "open"}, call("fileassistant_open"), nil) + page.leaf = true + + page = entry({"admin", "nas", "fileassistant", "delete"}, call("fileassistant_delete"), nil) + page.leaf = true + + page = entry({"admin", "nas", "fileassistant", "rename"}, call("fileassistant_rename"), nil) + page.leaf = true + + page = entry({"admin", "nas", "fileassistant", "upload"}, call("fileassistant_upload"), nil) + page.leaf = true + + page = entry({"admin", "nas", "fileassistant", "install"}, call("fileassistant_install"), nil) + page.leaf = true + +end + +function list_response(path, success) + luci.http.prepare_content("application/json") + local result + if success then + local rv = scandir(path) + result = { + ec = 0, + data = rv + } + else + result = { + ec = 1 + } + end + luci.http.write_json(result) +end + +function fileassistant_list() + local path = luci.http.formvalue("path") + list_response(path, true) +end + +function fileassistant_open() + local path = luci.http.formvalue("path") + local filename = luci.http.formvalue("filename") + local io = require "io" + local mime = to_mime(filename) + + file = path..filename + + local download_fpi = io.open(file, "r") + luci.http.header('Content-Disposition', 'inline; filename="'..filename..'"' ) + luci.http.prepare_content(mime) + luci.ltn12.pump.all(luci.ltn12.source.file(download_fpi), luci.http.write) +end + +function fileassistant_delete() + local path = luci.http.formvalue("path") + local isdir = luci.http.formvalue("isdir") + path = path:gsub("<>", "/") + path = path:gsub(" ", "\ ") + local success + if isdir then + success = os.execute('rm -r "'..path..'"') + else + success = os.remove(path) + end + list_response(nixio.fs.dirname(path), success) +end + +function fileassistant_rename() + local filepath = luci.http.formvalue("filepath") + local newpath = luci.http.formvalue("newpath") + local success = os.execute('mv "'..filepath..'" "'..newpath..'"') + list_response(nixio.fs.dirname(filepath), success) +end + +function fileassistant_install() + local filepath = luci.http.formvalue("filepath") + local isdir = luci.http.formvalue("isdir") + local ext = filepath:match(".+%.(%w+)$") + filepath = filepath:gsub("<>", "/") + filepath = filepath:gsub(" ", "\ ") + local success + if isdir == "1" then + success = false + elseif ext == "ipk" then + success = installIPK(filepath) + else + success = false + end + list_response(nixio.fs.dirname(filepath), success) +end + +function installIPK(filepath) + luci.sys.exec('opkg --force-depends install "'..filepath..'"') + luci.sys.exec('rm -rf /tmp/luci-*') + return true; +end + +function fileassistant_upload() + local filecontent = luci.http.formvalue("upload-file") + local filename = luci.http.formvalue("upload-filename") + local uploaddir = luci.http.formvalue("upload-dir") + local filepath = uploaddir..filename + + local fp + luci.http.setfilehandler( + function(meta, chunk, eof) + if not fp and meta and meta.name == "upload-file" then + fp = io.open(filepath, "w") + end + if fp and chunk then + fp:write(chunk) + end + if fp and eof then + fp:close() + end + end + ) + + list_response(uploaddir, true) +end + +function scandir(directory) + local i, t, popen = 0, {}, io.popen + + local pfile = popen("ls -lh \""..directory.."\" | egrep '^d' ; ls -lh \""..directory.."\" | egrep -v '^d|^l'") + for fileinfo in pfile:lines() do + i = i + 1 + t[i] = fileinfo + end + pfile:close() + pfile = popen("ls -lh \""..directory.."\" | egrep '^l' ;") + for fileinfo in pfile:lines() do + i = i + 1 + linkindex, _, linkpath = string.find(fileinfo, "->%s+(.+)$") + local finalpath; + if string.sub(linkpath, 1, 1) == "/" then + finalpath = linkpath + else + finalpath = nixio.fs.realpath(directory..linkpath) + end + local linktype; + if not finalpath then + finalpath = linkpath; + linktype = 'x' + elseif nixio.fs.stat(finalpath, "type") == "dir" then + linktype = 'z' + else + linktype = 'l' + end + fileinfo = string.sub(fileinfo, 2, linkindex - 1) + fileinfo = linktype..fileinfo.."-> "..finalpath + t[i] = fileinfo + end + pfile:close() + return t +end + +MIME_TYPES = { + ["txt"] = "text/plain"; + ["conf"] = "text/plain"; + ["ovpn"] = "text/plain"; + ["log"] = "text/plain"; + ["js"] = "text/javascript"; + ["json"] = "application/json"; + ["css"] = "text/css"; + ["htm"] = "text/html"; + ["html"] = "text/html"; + ["patch"] = "text/x-patch"; + ["c"] = "text/x-csrc"; + ["h"] = "text/x-chdr"; + ["o"] = "text/x-object"; + ["ko"] = "text/x-object"; + + ["bmp"] = "image/bmp"; + ["gif"] = "image/gif"; + ["png"] = "image/png"; + ["jpg"] = "image/jpeg"; + ["jpeg"] = "image/jpeg"; + ["svg"] = "image/svg+xml"; + + ["zip"] = "application/zip"; + ["pdf"] = "application/pdf"; + ["xml"] = "application/xml"; + ["xsl"] = "application/xml"; + ["doc"] = "application/msword"; + ["ppt"] = "application/vnd.ms-powerpoint"; + ["xls"] = "application/vnd.ms-excel"; + ["odt"] = "application/vnd.oasis.opendocument.text"; + ["odp"] = "application/vnd.oasis.opendocument.presentation"; + ["pl"] = "application/x-perl"; + ["sh"] = "application/x-shellscript"; + ["php"] = "application/x-php"; + ["deb"] = "application/x-deb"; + ["iso"] = "application/x-cd-image"; + ["tgz"] = "application/x-compressed-tar"; + + ["mp3"] = "audio/mpeg"; + ["ogg"] = "audio/x-vorbis+ogg"; + ["wav"] = "audio/x-wav"; + + ["mpg"] = "video/mpeg"; + ["mpeg"] = "video/mpeg"; + ["avi"] = "video/x-msvideo"; +} + +function to_mime(filename) + if type(filename) == "string" then + local ext = filename:match("[^%.]+$") + + if ext and MIME_TYPES[ext:lower()] then + return MIME_TYPES[ext:lower()] + end + end + + return "application/octet-stream" +end diff --git a/luasrc/view/fileassistant.htm b/luasrc/view/fileassistant.htm new file mode 100644 index 0000000..2211bc3 --- /dev/null +++ b/luasrc/view/fileassistant.htm @@ -0,0 +1,20 @@ +<%+header%> + + +

File Assistant

+
+ +
+
File List
+ +
+
+ + +
+
+
+ + + +<%+footer%> diff --git a/root/usr/share/rpcd/acl.d/luci-app-fileassistant.json b/root/usr/share/rpcd/acl.d/luci-app-fileassistant.json new file mode 100644 index 0000000..8db4ea3 --- /dev/null +++ b/root/usr/share/rpcd/acl.d/luci-app-fileassistant.json @@ -0,0 +1,11 @@ +{ + "luci-app-fileassistant": { + "description": "Grant UCI access for luci-app-fileassistant", + "read": { + "uci": [ "fileassistant" ] + }, + "write": { + "uci": [ "fileassistant" ] + } + } +}