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`
|
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
|
## credits
|
||||||
|
|
||||||
svg icons via https://iconsvg.xyz
|
svg icons via https://iconsvg.xyz
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<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>
|
<title>Home | PiNas</title>
|
||||||
<link rel="stylesheet" href="/assets/styles.css">
|
<link rel="stylesheet" href="/assets/styles.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,51 +1,40 @@
|
||||||
{{ $path := .Path }}
|
|
||||||
{{ $prev := .Previous }}
|
|
||||||
{{ $prefix := .Prefix }}
|
|
||||||
{{ $singleprefix := .SinglePrefix }}
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<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">
|
<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>
|
</head>
|
||||||
<body ondragenter="dragEnter(event)" ondragexit="dragExit(event)">
|
<body>
|
||||||
<div id="uploadoverlay" class="fileupload hidden">
|
<div id="uploadoverlay" class="fileupload hidden">
|
||||||
<p><img src="/assets/svgs/newfile.svg" alt="">Upload</p>
|
<p><img src="/assets/svgs/newfile.svg" alt="">Upload</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<a href="/" class="back-button">
|
<a href="/" class="back-button">
|
||||||
<img src="/assets/svgs/back.svg" alt="">
|
<img src="/assets/svgs/back.svg" alt="" data-dismiss-context="false">
|
||||||
</a>
|
</a>
|
||||||
<h1 style="display: inline-block">PiNas</h1>
|
<h1 style="display: inline-block">PiNas</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div>
|
<div>
|
||||||
<h1>{{$prefix}}/{{$path}}</h1>
|
<h1 id="path-header"></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="context-menu hidden" id="context" data-dismiss-context="false">
|
||||||
{{ if eq $prefix "files" }}
|
<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">
|
<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="file" data-multiple-caption="{count} files selected" multiple name="file">
|
||||||
<input type="submit" value="Upload">
|
<input type="submit" value="Upload">
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
<p class="directory"><a href="#" id="previous-dir">..</a></p>
|
||||||
<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}}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</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 {
|
a.back-button {
|
||||||
display: inline-block;
|
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
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gmemstr/nas/common"
|
"github.com/gmemstr/nas/common"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"html/template"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -20,12 +21,17 @@ type Config struct {
|
||||||
|
|
||||||
type Directory struct {
|
type Directory struct {
|
||||||
Path string
|
Path string
|
||||||
Files []os.FileInfo
|
Files []FileInfo
|
||||||
Previous string
|
Previous string
|
||||||
Prefix string
|
Prefix string
|
||||||
SinglePrefix string
|
SinglePrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
IsDirectory bool
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
// Lists out directory using template.
|
// Lists out directory using template.
|
||||||
func Listing(tier string) common.Handler {
|
func Listing(tier string) common.Handler {
|
||||||
|
|
||||||
|
@ -62,6 +68,15 @@ func Listing(tier string) common.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileDir, err := ioutil.ReadDir(path)
|
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)
|
path = strings.Replace(path, storage, "", -1)
|
||||||
|
|
||||||
// Figure out what our previous location was.
|
// Figure out what our previous location was.
|
||||||
|
@ -71,19 +86,15 @@ func Listing(tier string) common.Handler {
|
||||||
|
|
||||||
directory := Directory{
|
directory := Directory{
|
||||||
Path: path,
|
Path: path,
|
||||||
Files: fileDir,
|
Files: fileList,
|
||||||
Previous: previousPath,
|
Previous: previousPath,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
SinglePrefix: singleprefix,
|
SinglePrefix: singleprefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := template.ParseFiles("assets/web/listing.html")
|
resultJson, err := json.Marshal(directory);
|
||||||
if err != nil {
|
w.Write(resultJson);
|
||||||
panic(err)
|
return nil;
|
||||||
}
|
|
||||||
|
|
||||||
t.Execute(w, directory)
|
|
||||||
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(
|
r.Handle("/", Handle(
|
||||||
rootHandler(),
|
rootHandler(),
|
||||||
)).Methods("GET")
|
)).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"),
|
files.Listing("hot"),
|
||||||
)).Methods("GET")
|
)).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"),
|
files.Listing("hot"),
|
||||||
)).Methods("GET")
|
)).Methods("GET")
|
||||||
r.Handle(`/file/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle(
|
r.Handle(`/file/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle(
|
||||||
files.ViewFile("hot"),
|
files.ViewFile("hot"),
|
||||||
)).Methods("GET")
|
)).Methods("GET")
|
||||||
r.Handle("/upload", Handle(
|
r.Handle("/api/upload", Handle(
|
||||||
files.UploadFile(),
|
files.UploadFile(),
|
||||||
)).Methods("POST")
|
)).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"),
|
files.Listing("cold"),
|
||||||
)).Methods("GET")
|
)).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"),
|
files.Listing("cold"),
|
||||||
)).Methods("GET")
|
)).Methods("GET")
|
||||||
r.Handle(`/archived/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle(
|
r.Handle(`/archived/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle(
|
||||||
files.ViewFile("cold"),
|
files.ViewFile("cold"),
|
||||||
)).Methods("GET")
|
)).Methods("GET")
|
||||||
|
r.Handle(`/api/archivemd5/{file:[a-zA-Z0-9=\-\/\s.,&_+]+}`, Handle(
|
||||||
|
files.Md5File("cold"),
|
||||||
|
)).Methods("GET")
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -84,6 +103,7 @@ func rootHandler() common.Handler {
|
||||||
case "/":
|
case "/":
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
file = "assets/web/index.html"
|
file = "assets/web/index.html"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return &common.HTTPError{
|
return &common.HTTPError{
|
||||||
Message: fmt.Sprintf("%s: Not Found", r.URL.Path),
|
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 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