diff --git a/README.md b/README.md index 9124c1c..6178b4b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ you can also optionally use the `build-pi.sh` to build it for a raspberry pi (te then navigate to `localhost:3000` +## api + +initially the heavy lifting was done by the server, but the need for a better frontend was clear. + ## credits svg icons via https://iconsvg.xyz diff --git a/assets/web/index.html b/assets/web/index.html index 21f5f54..09fdf5b 100644 --- a/assets/web/index.html +++ b/assets/web/index.html @@ -1,5 +1,8 @@ + + + Home | PiNas diff --git a/assets/web/listing.html b/assets/web/listing.html index 078e2b2..f63b36c 100644 --- a/assets/web/listing.html +++ b/assets/web/listing.html @@ -1,51 +1,40 @@ -{{ $path := .Path }} -{{ $prev := .Previous }} -{{ $prefix := .Prefix }} -{{ $singleprefix := .SinglePrefix }} - {{ $path }} | PiNas + + + + | PiNas - + - +
- +

PiNas

-

{{$prefix}}/{{$path}}

+

-
- {{ if eq $prefix "files" }} + +
- +
- {{end}} -

..

- {{range $file := .Files}} - {{ if $file.IsDir }} -

- - {{$file.Name}} -

- {{ end }} - {{ if not $file.IsDir }} -

- - {{$file.Name}} -

- {{end}} - {{end}} +

..

diff --git a/assets/web/static/app.js b/assets/web/static/app.js new file mode 100644 index 0000000..0af65cf --- /dev/null +++ b/assets/web/static/app.js @@ -0,0 +1,88 @@ +const $ = element => document.getElementById(element); + +const init = event => { + var path = window.location.pathname; + var pathSplit = path.split("/"); + var path = pathSplit.splice(1).join("/"); + document.title = path + " | PiNAS"; + + getFiles(pathSplit[0], path, json => { + console.log(json); + document.getElementById("previous-dir").href = "/" + json.Prefix + "/" + json.Previous; + buildList(json); + }); + + $("path-header").innerText = path; + + document.querySelector("body").addEventListener('contextmenu', function(ev) { + if (ev.target.localName == "a") { + ev.preventDefault(); + + var d = document.getElementById('context'); + d.classList.remove("hidden"); + d.style.position = "absolute"; + d.style.left = ev.clientX + 'px'; + d.style.top = ev.clientY + 'px'; + + } + return false; + }, false); + + document.querySelector("body").addEventListener('click', function(ev) { + let shouldDismiss = ev.target.dataset.dismissContext == undefined && ev.target.parentElement.classList.contains("context-actions") == false && ev.target.localName != 'a'; + + if (ev.which == 1 && shouldDismiss) { + ev.preventDefault(); + + var d = document.getElementById('context'); + d.classList.add("hidden"); + return false; + } + }, false); +} + +const getFiles = (prefix, path, callback) => { + fetch('/api/' + prefix + '/' + path) + .then(function (response) { + return response.json(); + }) + .then(function (jsonResult) { + callback(jsonResult); + }); +} + +const buildList = data => { + for (var i = 0; i < data.Files.length; i++) { + let fileItem = document.createElement('p'); + let fileLink = document.createElement('a'); + if (data.Files[i].IsDirectory == true) { + fileItem.classList.add("directory"); + fileLink.href = "/" + data.Prefix + "/" + data.Path + "/" + data.Files[i].Name; + } + else { + fileItem.classList.add("file"); + fileLink.href = "/" + data.SinglePrefix + "/" + data.Path + "/" + data.Files[i].Name; + } + + fileLink.innerText = data.Files[i].Name; + fileItem.appendChild(fileLink); + document.getElementById("filelist").appendChild(fileItem); + } +} + +const upload = (file, path) => { + var formData = new FormData(); + formData.append("path", path); + formData.append("file", file); + fetch('/upload', { // Your POST endpoint + method: 'POST', + body: formData + }).then( + success => console.log(success) // Handle the success response object + ).catch( + error => console.log(error) // Handle the error response object + ); +}; + +window.addEventListener('load', init); + diff --git a/assets/web/static/fileListing.js b/assets/web/static/fileListing.js deleted file mode 100644 index d73cf61..0000000 --- a/assets/web/static/fileListing.js +++ /dev/null @@ -1,40 +0,0 @@ -function dragEnter(e) { - document.getElementById("uploadoverlay").classList.remove("hidden"); -} - -function dragExit(e) { - document.getElementById("uploadoverlay").classList.add("hidden"); -} -document.addEventListener("drop", function(e) { - var files = e.target.files || e.dataTransfer.files - console.log(files) - document.getElementById("uploadoverlay").classList.add("hidden"); - for (var i = 0; i <= files.length; i++) { - upload(files[i]); - } -}) - -window.addEventListener("dragover",function(e){ - e = e || event; - e.preventDefault(); -},false); -window.addEventListener("drop",function(e){ - e = e || event; - e.preventDefault(); -},false); - - -var upload = (file) => { - var path = document.getElementById("path").value; - var formData = new FormData(); - formData.append("path", path); - formData.append("file", file); - fetch('/upload', { // Your POST endpoint - method: 'POST', - body: formData - }).then( - success => console.log(success) // Handle the success response object - ).catch( - error => console.log(error) // Handle the error response object - ); -}; \ No newline at end of file diff --git a/assets/web/static/styles.css b/assets/web/static/styles.css index 7054f0a..8afc72d 100644 --- a/assets/web/static/styles.css +++ b/assets/web/static/styles.css @@ -81,3 +81,19 @@ h1, h2, h3 { a.back-button { display: inline-block; } + +.context-menu { + background-color: #ededed; + padding: 10px; +} + +.context-actions { + list-style: none; + padding: 0; + margin: 0; +} + +.context-actions li { + border-bottom: 1px solid black; + cursor: pointer; +} \ No newline at end of file diff --git a/files/files.go b/files/files.go index 1faceb3..2394c2a 100644 --- a/files/files.go +++ b/files/files.go @@ -1,11 +1,12 @@ package files import ( + "crypto/md5" + "encoding/hex" "encoding/json" "fmt" "github.com/gmemstr/nas/common" "github.com/gorilla/mux" - "html/template" "io" "io/ioutil" "net/http" @@ -20,12 +21,17 @@ type Config struct { type Directory struct { Path string - Files []os.FileInfo + Files []FileInfo Previous string Prefix string SinglePrefix string } +type FileInfo struct { + IsDirectory bool + Name string +} + // Lists out directory using template. func Listing(tier string) common.Handler { @@ -62,6 +68,15 @@ func Listing(tier string) common.Handler { } fileDir, err := ioutil.ReadDir(path) + var fileList []FileInfo; + + for _, file := range fileDir { + info := FileInfo{ + IsDirectory: file.IsDir(), + Name: file.Name(), + } + fileList = append(fileList, info) + } path = strings.Replace(path, storage, "", -1) // Figure out what our previous location was. @@ -71,19 +86,15 @@ func Listing(tier string) common.Handler { directory := Directory{ Path: path, - Files: fileDir, + Files: fileList, Previous: previousPath, Prefix: prefix, SinglePrefix: singleprefix, } - t, err := template.ParseFiles("assets/web/listing.html") - if err != nil { - panic(err) - } - - t.Execute(w, directory) - return nil + resultJson, err := json.Marshal(directory); + w.Write(resultJson); + return nil; } } @@ -155,3 +166,50 @@ func UploadFile() common.Handler { } } + +func Md5File(tier string) common.Handler { + + return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError { + vars := mux.Vars(r) + id := vars["file"] + + var returnMD5String string + + d, err := ioutil.ReadFile("assets/config/config.json") + var config Config; + err = json.Unmarshal(d, &config) + if err != nil { + panic(err) + } + + // Default to hot storage + storage := config.HotStorage + + if err != nil { + fmt.Println(err) + return nil + } + file, err := os.Open(storage + "/" + id) + if err != nil { + panic(err) + } + defer file.Close() + + hash := md5.New() + if _, err := io.Copy(hash, file); err != nil { + panic(err) + } + + //Get the 16 bytes hash + hashInBytes := hash.Sum(nil)[:16] + + //Convert the bytes to a string + returnMD5String = hex.EncodeToString(hashInBytes) + + w.Write([]byte(returnMD5String)) + + + return nil + } + +} diff --git a/router/router.go b/router/router.go index 250c7bd..910c044 100644 --- a/router/router.go +++ b/router/router.go @@ -48,29 +48,48 @@ func Init() *mux.Router { r.Handle("/", Handle( rootHandler(), )).Methods("GET") + r.Handle(`/files/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( + fileList(), + )).Methods("GET") + r.Handle(`/files/`, Handle( + fileList(), + )).Methods("GET") + r.Handle(`/archive/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( + fileList(), + )).Methods("GET") + r.Handle(`/archive/`, Handle( + fileList(), + )).Methods("GET") - r.Handle("/files/", Handle( + + r.Handle("/api/files/", Handle( files.Listing("hot"), )).Methods("GET") - r.Handle(`/files/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( + r.Handle(`/api/files/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( files.Listing("hot"), )).Methods("GET") r.Handle(`/file/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( files.ViewFile("hot"), )).Methods("GET") - r.Handle("/upload", Handle( + r.Handle("/api/upload", Handle( files.UploadFile(), )).Methods("POST") + r.Handle(`/api/filesmd5/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( + files.Md5File("hot"), + )).Methods("GET") - r.Handle("/archive/", Handle( + r.Handle("/api/archive/", Handle( files.Listing("cold"), )).Methods("GET") - r.Handle(`/archive/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( + r.Handle(`/api/archive/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( files.Listing("cold"), )).Methods("GET") r.Handle(`/archived/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( files.ViewFile("cold"), )).Methods("GET") + r.Handle(`/api/archivemd5/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle( + files.Md5File("cold"), + )).Methods("GET") return r } @@ -84,6 +103,7 @@ func rootHandler() common.Handler { case "/": w.Header().Set("Content-Type", "text/html") file = "assets/web/index.html" + default: return &common.HTTPError{ Message: fmt.Sprintf("%s: Not Found", r.URL.Path), @@ -95,8 +115,11 @@ func rootHandler() common.Handler { } } -func adminHandler() common.Handler { +func fileList() common.Handler { return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError { - return common.ReadAndServeFile("assets/web/admin.html", w) + w.Header().Set("Content-Type", "text/html") + file := "assets/web/listing.html" + + return common.ReadAndServeFile(file, w) } } \ No newline at end of file