Commit c3417a07 authored by Matt Holt's avatar Matt Holt

Merge pull request #772 from mholt/fix-browse

Make Browse Great Again ★★★
parents da016f8d 72bc6932
...@@ -3,6 +3,7 @@ package setup ...@@ -3,6 +3,7 @@ package setup
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"text/template" "text/template"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
...@@ -17,7 +18,6 @@ func Browse(c *Controller) (middleware.Middleware, error) { ...@@ -17,7 +18,6 @@ func Browse(c *Controller) (middleware.Middleware, error) {
} }
browse := browse.Browse{ browse := browse.Browse{
Root: c.Root,
Configs: configs, Configs: configs,
IgnoreIndexes: false, IgnoreIndexes: false,
} }
...@@ -50,6 +50,16 @@ func browseParse(c *Controller) ([]browse.Config, error) { ...@@ -50,6 +50,16 @@ func browseParse(c *Controller) ([]browse.Config, error) {
} else { } else {
bc.PathScope = "/" bc.PathScope = "/"
} }
bc.Root = http.Dir(c.Root)
theRoot, err := bc.Root.Open("/") // catch a missing path early
if err != nil {
return configs, err
}
defer theRoot.Close()
_, err = theRoot.Readdir(-1)
if err != nil {
return configs, err
}
// Second argument would be the template file to use // Second argument would be the template file to use
var tplText string var tplText string
...@@ -85,7 +95,6 @@ const defaultTemplate = `<!DOCTYPE html> ...@@ -85,7 +95,6 @@ const defaultTemplate = `<!DOCTYPE html>
<html> <html>
<head> <head>
<title>{{.Name}}</title> <title>{{.Name}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<style> <style>
* { padding: 0; margin: 0; } * { padding: 0; margin: 0; }
...@@ -106,7 +115,7 @@ h1 a:hover { ...@@ -106,7 +115,7 @@ h1 a:hover {
} }
header, header,
.content { #summary {
padding-left: 5%; padding-left: 5%;
padding-right: 5%; padding-right: 5%;
} }
...@@ -306,43 +315,49 @@ footer { ...@@ -306,43 +315,49 @@ footer {
</header> </header>
<main> <main>
<div class="meta"> <div class="meta">
<div class="content"> <div id="summary">
<span class="meta-item"><b>{{.NumDirs}}</b> director{{if eq 1 .NumDirs}}y{{else}}ies{{end}}</span> <span class="meta-item"><b>{{.NumDirs}}</b> director{{if eq 1 .NumDirs}}y{{else}}ies{{end}}</span>
<span class="meta-item"><b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}</span> <span class="meta-item"><b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}</span>
{{- if ne 0 .ItemsLimitedTo}}
<span class="meta-item">(of which only <b>{{.ItemsLimitedTo}}</b> are displayed)</span>
{{- end}}
</div> </div>
</div> </div>
<div class="listing"> <div class="listing">
<table> <table aria-describedby="summary">
<thead>
<tr> <tr>
<th> <th>
{{if and (eq .Sort "name") (ne .Order "desc")}} {{- if and (eq .Sort "name") (ne .Order "desc")}}
<a href="?sort=name&order=desc">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a> <a href="?sort=name&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
{{else if and (eq .Sort "name") (ne .Order "asc")}} {{- else if and (eq .Sort "name") (ne .Order "asc")}}
<a href="?sort=name&order=asc">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a> <a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
{{else}} {{- else}}
<a href="?sort=name&order=asc">Name</a> <a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name</a>
{{end}} {{- end}}
</th> </th>
<th> <th>
{{if and (eq .Sort "size") (ne .Order "desc")}} {{- if and (eq .Sort "size") (ne .Order "desc")}}
<a href="?sort=size&order=desc">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a> <a href="?sort=size&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a>
{{else if and (eq .Sort "size") (ne .Order "asc")}} {{- else if and (eq .Sort "size") (ne .Order "asc")}}
<a href="?sort=size&order=asc">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a> <a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a>
{{else}} {{- else}}
<a href="?sort=size&order=asc">Size</a> <a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size</a>
{{end}} {{- end}}
</th> </th>
<th class="hideable"> <th class="hideable">
{{if and (eq .Sort "time") (ne .Order "desc")}} {{- if and (eq .Sort "time") (ne .Order "desc")}}
<a href="?sort=time&order=desc">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a> <a href="?sort=time&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a>
{{else if and (eq .Sort "time") (ne .Order "asc")}} {{- else if and (eq .Sort "time") (ne .Order "asc")}}
<a href="?sort=time&order=asc">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a> <a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a>
{{else}} {{- else}}
<a href="?sort=time&order=asc">Modified</a> <a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified</a>
{{end}} {{- end}}
</th> </th>
</tr> </tr>
{{if .CanGoUp}} </thead>
<tbody>
{{- if .CanGoUp}}
<tr> <tr>
<td> <td>
<a href=".."> <a href="..">
...@@ -350,30 +365,46 @@ footer { ...@@ -350,30 +365,46 @@ footer {
</a> </a>
</td> </td>
<td>&mdash;</td> <td>&mdash;</td>
<td>&mdash;</td> <td class="hideable">&mdash;</td>
</tr> </tr>
{{end}} {{- end}}
{{range .Items}} {{- range .Items}}
<tr> <tr>
<td> <td>
<a href="{{.URL}}"> <a href="{{.URL}}">
{{if .IsDir}} {{- if .IsDir}}
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 35.678803 28.527945"><use xlink:href="#folder"></use></svg> <svg width="1.5em" height="1em" version="1.1" viewBox="0 0 35.678803 28.527945"><use xlink:href="#folder"></use></svg>
{{else}} {{- else}}
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 26.604381 29.144726"><use xlink:href="#file"></use></svg> <svg width="1.5em" height="1em" version="1.1" viewBox="0 0 26.604381 29.144726"><use xlink:href="#file"></use></svg>
{{end}} {{- end}}
<span class="name">{{.Name}}</span> <span class="name">{{.Name}}</span>
</a> </a>
</td> </td>
<td>{{.HumanSize}}</td> {{- if .IsDir}}
<td class="hideable">{{.HumanModTime "01/02/2006 03:04:05 PM"}}</td> <td data-order="-1">&mdash;</td>
{{- else}}
<td data-order="{{.Size}}">{{.HumanSize}}</td>
{{- end}}
<td class="hideable"><time datetime="{{.HumanModTime "2006-01-02 15:04:05-0700"}}">{{.HumanModTime "01/02/2006 03:04:05 PM"}}</time></td>
</tr> </tr>
{{end}} {{- end}}
</tbody>
</table> </table>
</div> </div>
</main> </main>
<footer> <footer>
Served with <a href="https://caddyserver.com">Caddy</a> Served with <a href="https://caddyserver.com">Caddy</a>
</footer> </footer>
<script type="text/javascript">
function localizeDatetime(e, index, ar) {
if (e.textContent === undefined) {
return;
}
var d = new Date(e.getAttribute('datetime'));
e.textContent = d.toLocaleString();
}
var timeList = Array.prototype.slice.call(document.getElementsByTagName("time"));
timeList.forEach(localizeDatetime);
</script>
</body> </body>
</html>` </html>`
This diff is collapsed.
...@@ -114,10 +114,10 @@ func TestBrowseHTTPMethods(t *testing.T) { ...@@ -114,10 +114,10 @@ func TestBrowseHTTPMethods(t *testing.T) {
Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
return http.StatusTeapot, nil // not t.Fatalf, or we will not see what other methods yield return http.StatusTeapot, nil // not t.Fatalf, or we will not see what other methods yield
}), }),
Root: "./testdata",
Configs: []Config{ Configs: []Config{
{ {
PathScope: "/photos", PathScope: "/photos",
Root: http.Dir("./testdata"),
Template: tmpl, Template: tmpl,
}, },
}, },
...@@ -153,10 +153,10 @@ func TestBrowseTemplate(t *testing.T) { ...@@ -153,10 +153,10 @@ func TestBrowseTemplate(t *testing.T) {
t.Fatalf("Next shouldn't be called") t.Fatalf("Next shouldn't be called")
return 0, nil return 0, nil
}), }),
Root: "./testdata",
Configs: []Config{ Configs: []Config{
{ {
PathScope: "/photos", PathScope: "/photos",
Root: http.Dir("./testdata"),
Template: tmpl, Template: tmpl,
}, },
}, },
...@@ -208,16 +208,16 @@ func TestBrowseJson(t *testing.T) { ...@@ -208,16 +208,16 @@ func TestBrowseJson(t *testing.T) {
t.Fatalf("Next shouldn't be called") t.Fatalf("Next shouldn't be called")
return 0, nil return 0, nil
}), }),
Root: "./testdata",
Configs: []Config{ Configs: []Config{
{ {
PathScope: "/photos/", PathScope: "/photos/",
Root: http.Dir("./testdata"),
}, },
}, },
} }
//Getting the listing from the ./testdata/photos, the listing returned will be used to validate test results //Getting the listing from the ./testdata/photos, the listing returned will be used to validate test results
testDataPath := b.Root + "/photos/" testDataPath := filepath.Join("./testdata", "photos")
file, err := os.Open(testDataPath) file, err := os.Open(testDataPath)
if err != nil { if err != nil {
if os.IsPermission(err) { if os.IsPermission(err) {
...@@ -238,7 +238,7 @@ func TestBrowseJson(t *testing.T) { ...@@ -238,7 +238,7 @@ func TestBrowseJson(t *testing.T) {
// Tests fail in CI environment because all file mod times are the same for // Tests fail in CI environment because all file mod times are the same for
// some reason, making the sorting unpredictable. To hack around this, // some reason, making the sorting unpredictable. To hack around this,
// we ensure here that each file has a different mod time. // we ensure here that each file has a different mod time.
chTime := f.ModTime().Add(-(time.Duration(i) * time.Second)) chTime := f.ModTime().UTC().Add(-(time.Duration(i) * time.Second))
if err := os.Chtimes(filepath.Join(testDataPath, name), chTime, chTime); err != nil { if err := os.Chtimes(filepath.Join(testDataPath, name), chTime, chTime); err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -315,7 +315,7 @@ func TestBrowseJson(t *testing.T) { ...@@ -315,7 +315,7 @@ func TestBrowseJson(t *testing.T) {
code, err := b.ServeHTTP(rec, req) code, err := b.ServeHTTP(rec, req)
if code != http.StatusOK { if code != http.StatusOK {
t.Fatalf("Wrong status, expected %d, got %d", http.StatusOK, code) t.Fatalf("In test %d: Wrong status, expected %d, got %d", i, http.StatusOK, code)
} }
if rec.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" { if rec.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" {
t.Fatalf("Expected Content type to be application/json; charset=utf-8, but got %s ", rec.HeaderMap.Get("Content-Type")) t.Fatalf("Expected Content type to be application/json; charset=utf-8, but got %s ", rec.HeaderMap.Get("Content-Type"))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment