mirror of
https://github.com/gmemstr/sliproad.git
synced 2024-09-20 00:21:15 +01:00
Begin work on API, decouple frontend to be more dynamic.
This commit is contained in:
parent
b07c20d43a
commit
a2a1e0e573
|
@ -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
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta content="utf-8" http-equiv="encoding">
|
||||
<title>Home | PiNas</title>
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
</head>
|
||||
|
|
|
@ -1,51 +1,40 @@
|
|||
{{ $path := .Path }}
|
||||
{{ $prev := .Previous }}
|
||||
{{ $prefix := .Prefix }}
|
||||
{{ $singleprefix := .SinglePrefix }}
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ $path }} | PiNas</title>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta content="utf-8" http-equiv="encoding">
|
||||
<title>| PiNas</title>
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
<script type="text/javascript" src="/assets/fileListing.js"></script>
|
||||
<script type="text/javascript" src="/assets/app.js"></script>
|
||||
</head>
|
||||
<body ondragenter="dragEnter(event)" ondragexit="dragExit(event)">
|
||||
<body>
|
||||
<div id="uploadoverlay" class="fileupload hidden">
|
||||
<p><img src="/assets/svgs/newfile.svg" alt="">Upload</p>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<a href="/" class="back-button">
|
||||
<img src="/assets/svgs/back.svg" alt="">
|
||||
<img src="/assets/svgs/back.svg" alt="" data-dismiss-context="false">
|
||||
</a>
|
||||
<h1 style="display: inline-block">PiNas</h1>
|
||||
</header>
|
||||
<main>
|
||||
<div>
|
||||
<h1>{{$prefix}}/{{$path}}</h1>
|
||||
<h1 id="path-header"></h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
{{ if eq $prefix "files" }}
|
||||
<div class="context-menu hidden" id="context" data-dismiss-context="false">
|
||||
<ul class="context-actions">
|
||||
<li>Archive</li>
|
||||
<li>Get MD5</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content" id="filelist">
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
<input id="path" type="value" value="{{$path}}" name="path">
|
||||
<input id="path" type="value" name="path">
|
||||
<input type="file" data-multiple-caption="{count} files selected" multiple name="file">
|
||||
<input type="submit" value="Upload">
|
||||
</form>
|
||||
{{end}}
|
||||
<p class="directory"><a href="/{{$prefix}}/{{$prev}}">..</a></p>
|
||||
{{range $file := .Files}}
|
||||
{{ if $file.IsDir }}
|
||||
<p class="directory"><a href="/{{$prefix}}/{{$path}}/{{$file.Name}}">
|
||||
<img src="/assets/svgs/directory.svg" alt="">
|
||||
{{$file.Name}}</a>
|
||||
</p>
|
||||
{{ end }}
|
||||
{{ if not $file.IsDir }}
|
||||
<p class="file"><a href="/{{$singleprefix}}/{{$path}}/{{$file.Name}}">
|
||||
<img src="/assets/svgs/file.svg" alt="">
|
||||
{{$file.Name}}</a>
|
||||
</p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<p class="directory"><a href="#" id="previous-dir">..</a></p>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
|
88
assets/web/static/app.js
Normal file
88
assets/web/static/app.js
Normal file
|
@ -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);
|
||||
|
|
@ -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
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue