Implement new frontend, "/" at end of provider.

Started work on a new basic frontend, with some magic code from
@tomhodgins (Tom Hodgins) for templating and processing data, which was
the primary purpose for considering a full-fledged JavaScript
framework. Fairly simple right now, with minimal "bling", and seems
"functional enough". Might need to consider options for using less or
no JS down the line if there is demand.

As part of this frontend, actually implemented route logic for fetching
a sorted list of providers (might be worth investigating whether
storing this final result in memory is worth the performance to memory
trade-off).

Also fixed a bug where adding "/" to the end of a provider name without
a file path would result in a 404. This was addressed with some Regex
filtering on the path that should be able to handle that matching.
This commit is contained in:
Gabriel Simmer 2020-03-22 00:18:19 +00:00
parent 49dba732d3
commit 292e24978d
No known key found for this signature in database
GPG key ID: 33BA4D83B160A0A9
6 changed files with 165 additions and 5 deletions

1
.gitignore vendored
View file

@ -22,4 +22,3 @@ nas
*.db
.lock
assets/web/*

48
assets/web/css/styles.css Normal file
View file

@ -0,0 +1,48 @@
* {
font-family: sans-serif;
}
body {
}
.grid-lg, .grid-sm {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 2fr);
grid-column-gap: 5px;
grid-row-gap: 5px;
}
.grid-lg a {
display: flex;
padding: 10vh;
justify-content: center;
background-color: black;
font-size: 32px;
font-weight: bold;
text-decoration: none;
transition: background-color 0.5s;
}
.grid-sm a {
display: flex;
padding: 2vh;
justify-content: center;
background-color: black;
font-size: 24px;
font-weight: bold;
text-decoration: none;
transition: background-color 0.5s;
}
.grid-lg a:visited, .grid-lg a,
.grid-sm a:visited, .grid-sm a {
color: white;
}
.grid-lg a:hover,
.grid-sm a:hover {
color: white;
background-color: darkgray;
transition: background-color 0.5s;
}

19
assets/web/index.html Normal file
View file

@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>NAS</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>NAS</h1>
<main id="main">
</main>
<script src="/javascript/app.js"></script>
</body>
</html>

View file

@ -0,0 +1,80 @@
// Register our router, and fire it off initially in case user is being linked a dir.
window.addEventListener("hashchange", router, false);
router()
function getFileListing(provider, path = "") {
fetch(`/api/files/${provider}${path}`)
.then((response) => {
return response.json()
})
.then((data) => {
let files = data["Files"]
html`
<div class="grid-sm">
${files.map(file =>
`<a href="${!file.IsDirectory ? `/api/files/${provider}${path}/${file.Name}` : `#${provider + "/" + file.Name}`}">
${file.Name}${file.IsDirectory ? '/' : ''}
</a>
`
).join('')}
</div>
`
})
}
function getProviders() {
fetch(`/api/providers`)
.then((response) => {
return response.json()
})
.then((data) => {
let providers = data
html`
<div class="grid-lg">
${providers.map(provider =>
`<a href=#${provider}>
${provider}
</a>
`
).join('')}
</div>
`
})
}
function router(event = null) {
let hash = location.hash.replace("#", "")
// If hash is empty, "redirect" to index.
if (hash === "") {
getProviders()
return
}
let path = hash.split("/")
let provider = path.shift()
console.log(path, provider)
getFileListing(provider, "/" + path)
}
// Tagged template function for parsing a string of text as HTML objects
// <3 @innovati for this brilliance.
function html(strings, ...things) {
// Our "body", where we'll render stuff.
const body = document.getElementById("main")
let x = document.createRange().createContextualFragment(
strings.reduce(
(markup, string, index) => {
markup += string
if (things[index]) {
markup += things[index]
}
return markup
},
''
)
)
body.innerHTML = ""
body.append(x)
}

View file

@ -6,6 +6,8 @@ import (
"github.com/gmemstr/nas/files"
"github.com/gorilla/mux"
"net/http"
"sort"
"strings"
)
func HandleProvider() common.Handler {
@ -14,6 +16,7 @@ func HandleProvider() common.Handler {
vars := mux.Vars(r)
if r.Method == "GET" {
providerCodename := vars["provider"]
providerCodename = strings.Replace(providerCodename, "/", "", -1)
provider := *files.Providers[providerCodename]
fileList := provider.GetDirectory("")
@ -44,6 +47,16 @@ func HandleProvider() common.Handler {
func ListProviders() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
var providers []string
for v, _ := range files.ProviderConfig {
providers = append(providers, v)
}
sort.Strings(providers)
data, err := json.Marshal(providers)
if err != nil {
return nil
}
w.Write(data)
return nil
}
}

View file

@ -35,11 +35,12 @@ func Init() *mux.Router {
r := mux.NewRouter()
// "Static" paths
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("assets/web/static"))))
r.PathPrefix("/javascript/").Handler(http.StripPrefix("/javascript/", http.FileServer(http.Dir("assets/web/javascript"))))
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("assets/web/css"))))
// Paths that require specific handlers
r.Handle("/", Handle(
auth.RequireAuthorization(1),
//auth.RequireAuthorization(1),
rootHandler(),
)).Methods("GET")
@ -48,11 +49,11 @@ func Init() *mux.Router {
)).Methods("POST", "GET")
r.Handle("/api/providers", Handle(
auth.RequireAuthorization(1),
//auth.RequireAuthorization(1),
ListProviders(),
)).Methods("GET")
r.Handle(`/api/files/{provider}`, Handle(
r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, Handle(
//auth.RequireAuthorization(1),
HandleProvider(),
)).Methods("GET")