Fix delete color and contrast
This commit is contained in:
@@ -61,12 +61,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.handler.S
|
|||||||
func (s *Server) routes() http.Handler {
|
func (s *Server) routes() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("GET /{$}", s.handleIndex)
|
mux.HandleFunc("GET /{$}", s.handleIndex)
|
||||||
mux.HandleFunc("POST /api/upload", s.handleUpload)
|
mux.HandleFunc("POST /upload", s.handleUpload)
|
||||||
mux.HandleFunc("GET /api/limits", s.handleLimits)
|
mux.HandleFunc("GET /api/limits", s.handleLimits)
|
||||||
mux.HandleFunc("GET /admin", s.handleAdmin)
|
mux.HandleFunc("GET /admin", s.handleAdmin)
|
||||||
mux.HandleFunc("GET /d/{id}", s.handleDownload)
|
mux.HandleFunc("GET /d/{id}", s.handleDownload)
|
||||||
mux.HandleFunc("GET /i/{id}", s.handleInfo)
|
mux.HandleFunc("GET /i/{id}", s.handleInfo)
|
||||||
mux.HandleFunc("POST /api/d/{id}/delete", s.handleDelete)
|
mux.HandleFunc("POST /d/{id}/delete", s.handleDelete)
|
||||||
mux.HandleFunc("GET /login", s.handleLoginPage)
|
mux.HandleFunc("GET /login", s.handleLoginPage)
|
||||||
mux.HandleFunc("POST /login", s.handleLogin)
|
mux.HandleFunc("POST /login", s.handleLogin)
|
||||||
mux.HandleFunc("POST /logout", s.handleLogout)
|
mux.HandleFunc("POST /logout", s.handleLogout)
|
||||||
@@ -104,10 +104,10 @@ func (s *Server) staticHandler() http.Handler {
|
|||||||
// appCSP locks the application pages down to their own origin. The frontend has
|
// appCSP locks the application pages down to their own origin. The frontend has
|
||||||
// no inline script and no third-party anything, so this can be strict.
|
// no inline script and no third-party anything, so this can be strict.
|
||||||
//
|
//
|
||||||
// connect-src is not optional here: the upload page talks to /api/upload and
|
// connect-src is not optional here: the upload page posts to /upload over
|
||||||
// /api/limits over XMLHttpRequest, and every fetch-directive left unlisted
|
// XMLHttpRequest to draw a progress bar, and every fetch-directive left
|
||||||
// falls back to default-src, so omitting it makes the browser block every
|
// unlisted falls back to default-src, so omitting it makes the browser block
|
||||||
// upload before it reaches the network. See TestAppCSPAllowsWhatThePageDoes.
|
// every upload before it reaches the network. See TestAppCSPAllowsWhatThePageDoes.
|
||||||
const appCSP = "default-src 'none'; script-src 'self'; style-src 'self'; " +
|
const appCSP = "default-src 'none'; script-src 'self'; style-src 'self'; " +
|
||||||
"img-src 'self' data:; connect-src 'self'; form-action 'self'; " +
|
"img-src 'self' data:; connect-src 'self'; form-action 'self'; " +
|
||||||
"base-uri 'none'; frame-ancestors 'none'"
|
"base-uri 'none'; frame-ancestors 'none'"
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func (h *harness) upload(t *testing.T, body []byte, headers map[string]string) *
|
|||||||
|
|
||||||
func (h *harness) uploadReader(t *testing.T, body io.Reader, headers map[string]string) *http.Response {
|
func (h *harness) uploadReader(t *testing.T, body io.Reader, headers map[string]string) *http.Response {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
req, err := http.NewRequest("POST", h.ts.URL+"/api/upload", body)
|
req, err := http.NewRequest("POST", h.ts.URL+"/upload", body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -416,7 +416,7 @@ func TestPathTraversalIsRejected(t *testing.T) {
|
|||||||
|
|
||||||
func TestReservedNamesAreRejected(t *testing.T) {
|
func TestReservedNamesAreRejected(t *testing.T) {
|
||||||
h := newHarness(t, nil)
|
h := newHarness(t, nil)
|
||||||
for _, name := range []string{"api", "static", "d", "i", "robots.txt"} {
|
for _, name := range []string{"api", "static", "d", "i", "upload", "robots.txt"} {
|
||||||
resp := h.upload(t, []byte("x"), map[string]string{
|
resp := h.upload(t, []byte("x"), map[string]string{
|
||||||
"Vanity": name,
|
"Vanity": name,
|
||||||
"Authorization": "Bearer " + h.token,
|
"Authorization": "Bearer " + h.token,
|
||||||
@@ -434,7 +434,7 @@ func TestDeleteRequiresTheRightToken(t *testing.T) {
|
|||||||
|
|
||||||
del := func(token string) int {
|
del := func(token string) int {
|
||||||
form := strings.NewReader("token=" + token)
|
form := strings.NewReader("token=" + token)
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/d/"+res.ID+"/delete", form)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/d/"+res.ID+"/delete", form)
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
resp, err := h.ts.Client().Do(req)
|
resp, err := h.ts.Client().Do(req)
|
||||||
@@ -465,7 +465,7 @@ func TestAdminMayDeleteAnything(t *testing.T) {
|
|||||||
h := newHarness(t, nil)
|
h := newHarness(t, nil)
|
||||||
res := decode[uploadResult](t, h.upload(t, []byte("someone else's"), nil))
|
res := decode[uploadResult](t, h.upload(t, []byte("someone else's"), nil))
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/d/"+res.ID+"/delete", nil)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/d/"+res.ID+"/delete", nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+h.admin)
|
req.Header.Set("Authorization", "Bearer "+h.admin)
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
resp, err := h.ts.Client().Do(req)
|
resp, err := h.ts.Client().Do(req)
|
||||||
@@ -494,7 +494,7 @@ func TestMultipartUpload(t *testing.T) {
|
|||||||
fw.Write([]byte("hello from a browser"))
|
fw.Write([]byte("hello from a browser"))
|
||||||
mw.Close()
|
mw.Close()
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", &body)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", &body)
|
||||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
resp, err := h.ts.Client().Do(req)
|
resp, err := h.ts.Client().Do(req)
|
||||||
@@ -523,7 +523,7 @@ func TestFormPostRendersHTML(t *testing.T) {
|
|||||||
fw.Write([]byte("data"))
|
fw.Write([]byte("data"))
|
||||||
mw.Close()
|
mw.Close()
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", &body)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", &body)
|
||||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
req.Header.Set("Accept", "text/html,application/xhtml+xml")
|
req.Header.Set("Accept", "text/html,application/xhtml+xml")
|
||||||
resp, err := h.ts.Client().Do(req)
|
resp, err := h.ts.Client().Do(req)
|
||||||
@@ -693,7 +693,7 @@ func TestLoginStoresTheToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The session alone is now enough to claim a custom name.
|
// The session alone is now enough to claim a custom name.
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", strings.NewReader("two"))
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", strings.NewReader("two"))
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.Header.Set("Vanity", "session-upload")
|
req.Header.Set("Vanity", "session-upload")
|
||||||
req.AddCookie(cookie)
|
req.AddCookie(cookie)
|
||||||
@@ -885,7 +885,7 @@ func TestCookieDoesNotShadowTheDeleteToken(t *testing.T) {
|
|||||||
res := decode[uploadResult](t, h.upload(t, []byte("x"), nil))
|
res := decode[uploadResult](t, h.upload(t, []byte("x"), nil))
|
||||||
|
|
||||||
form := strings.NewReader("token=" + res.DeleteToken)
|
form := strings.NewReader("token=" + res.DeleteToken)
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/d/"+res.ID+"/delete", form)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/d/"+res.ID+"/delete", form)
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: h.token})
|
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: h.token})
|
||||||
@@ -929,7 +929,7 @@ func (h *harness) formUploadWith(t *testing.T, cookie *http.Cookie, fields map[s
|
|||||||
fw.Write([]byte(content))
|
fw.Write([]byte(content))
|
||||||
mw.Close()
|
mw.Close()
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", &body)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", &body)
|
||||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
if cookie != nil {
|
if cookie != nil {
|
||||||
@@ -1050,7 +1050,7 @@ func TestResultPageOffersBothLinksAndAWayBack(t *testing.T) {
|
|||||||
fw.Write([]byte("data"))
|
fw.Write([]byte("data"))
|
||||||
mw.Close()
|
mw.Close()
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", &body)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", &body)
|
||||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
req.Header.Set("Accept", "text/html")
|
req.Header.Set("Accept", "text/html")
|
||||||
resp, err := h.ts.Client().Do(req)
|
resp, err := h.ts.Client().Do(req)
|
||||||
@@ -1223,7 +1223,7 @@ func TestAdminDeleteReturnsToTheTable(t *testing.T) {
|
|||||||
client.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
|
client.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
|
||||||
|
|
||||||
form := strings.NewReader("from=admin")
|
form := strings.NewReader("from=admin")
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/d/"+res.ID+"/delete", form)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/d/"+res.ID+"/delete", form)
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Set("Accept", "text/html")
|
req.Header.Set("Accept", "text/html")
|
||||||
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: h.admin})
|
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: h.admin})
|
||||||
@@ -1250,7 +1250,7 @@ func TestAdminDeleteStillRequiresAdmin(t *testing.T) {
|
|||||||
res := decode[uploadResult](t, h.upload(t, []byte("not yours"), nil))
|
res := decode[uploadResult](t, h.upload(t, []byte("not yours"), nil))
|
||||||
|
|
||||||
form := strings.NewReader("from=admin")
|
form := strings.NewReader("from=admin")
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/d/"+res.ID+"/delete", form)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/d/"+res.ID+"/delete", form)
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: h.token})
|
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: h.token})
|
||||||
@@ -1305,7 +1305,7 @@ func TestMultipartHonoursTheHeaderForm(t *testing.T) {
|
|||||||
fw.Write([]byte("payload"))
|
fw.Write([]byte("payload"))
|
||||||
mw.Close()
|
mw.Close()
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", &body)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", &body)
|
||||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.Header.Set("Authorization", "Bearer "+h.token)
|
req.Header.Set("Authorization", "Bearer "+h.token)
|
||||||
@@ -1339,7 +1339,7 @@ func TestFormFieldsOverrideTheHeaders(t *testing.T) {
|
|||||||
fw.Write([]byte("payload"))
|
fw.Write([]byte("payload"))
|
||||||
mw.Close()
|
mw.Close()
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", &body)
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", &body)
|
||||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.Header.Set("Authorization", "Bearer "+h.token)
|
req.Header.Set("Authorization", "Bearer "+h.token)
|
||||||
@@ -1399,7 +1399,7 @@ func TestInfoPageAcceptsTheDeleteToken(t *testing.T) {
|
|||||||
t.Fatal("the info page leaks the delete token to anyone holding the link")
|
t.Fatal("the info page leaks the delete token to anyone holding the link")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := h.postForm(t, "/api/d/"+res.ID+"/delete",
|
resp := h.postForm(t, "/d/"+res.ID+"/delete",
|
||||||
url.Values{"from": {"info"}, "token": {res.DeleteToken}}, nil)
|
url.Values{"from": {"info"}, "token": {res.DeleteToken}}, nil)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -1417,7 +1417,7 @@ func TestWrongDeleteTokenReturnsToTheInfoPage(t *testing.T) {
|
|||||||
res := decode[uploadResult](t, h.upload(t, []byte("x"), map[string]string{
|
res := decode[uploadResult](t, h.upload(t, []byte("x"), map[string]string{
|
||||||
"Content-Disposition": `attachment; filename="keepme.bin"`}))
|
"Content-Disposition": `attachment; filename="keepme.bin"`}))
|
||||||
|
|
||||||
resp := h.postForm(t, "/api/d/"+res.ID+"/delete",
|
resp := h.postForm(t, "/d/"+res.ID+"/delete",
|
||||||
url.Values{"from": {"info"}, "token": {"wrong"}}, nil)
|
url.Values{"from": {"info"}, "token": {"wrong"}}, nil)
|
||||||
raw, _ := io.ReadAll(resp.Body)
|
raw, _ := io.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
@@ -1473,7 +1473,7 @@ func TestInfoPageOffersADirectButtonToAnOwner(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// And that button actually works with no token field at all.
|
// And that button actually works with no token field at all.
|
||||||
resp := h.postForm(t, "/api/d/"+res.ID+"/delete",
|
resp := h.postForm(t, "/d/"+res.ID+"/delete",
|
||||||
url.Values{"from": {"info"}}, &http.Cookie{Name: tokenCookie, Value: h.token})
|
url.Values{"from": {"info"}}, &http.Cookie{Name: tokenCookie, Value: h.token})
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -1494,7 +1494,7 @@ func TestFailedDeletesAreThrottledAndSuccessIsNot(t *testing.T) {
|
|||||||
if last != nil {
|
if last != nil {
|
||||||
last.Body.Close()
|
last.Body.Close()
|
||||||
}
|
}
|
||||||
last = h.postForm(t, "/api/d/"+res.ID+"/delete",
|
last = h.postForm(t, "/d/"+res.ID+"/delete",
|
||||||
url.Values{"from": {"info"}, "token": {"guess"}}, nil)
|
url.Values{"from": {"info"}, "token": {"guess"}}, nil)
|
||||||
}
|
}
|
||||||
if last.StatusCode != http.StatusTooManyRequests {
|
if last.StatusCode != http.StatusTooManyRequests {
|
||||||
@@ -1503,7 +1503,7 @@ func TestFailedDeletesAreThrottledAndSuccessIsNot(t *testing.T) {
|
|||||||
last.Body.Close()
|
last.Body.Close()
|
||||||
|
|
||||||
// The real token still works, having consumed nothing from the bucket.
|
// The real token still works, having consumed nothing from the bucket.
|
||||||
resp := h.postForm(t, "/api/d/"+res.ID+"/delete",
|
resp := h.postForm(t, "/d/"+res.ID+"/delete",
|
||||||
url.Values{"from": {"info"}, "token": {res.DeleteToken}}, nil)
|
url.Values{"from": {"info"}, "token": {res.DeleteToken}}, nil)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -1520,7 +1520,7 @@ func TestFailedDeletesAreThrottledAndSuccessIsNot(t *testing.T) {
|
|||||||
func TestCrossOriginPostsAreRejected(t *testing.T) {
|
func TestCrossOriginPostsAreRejected(t *testing.T) {
|
||||||
h := newHarness(t, nil)
|
h := newHarness(t, nil)
|
||||||
|
|
||||||
paths := []string{"/login", "/logout", "/api/upload", "/api/d/anything/delete"}
|
paths := []string{"/login", "/logout", "/upload", "/d/anything/delete"}
|
||||||
hostile := []map[string]string{
|
hostile := []map[string]string{
|
||||||
{"Origin": "https://evil.example.com"},
|
{"Origin": "https://evil.example.com"},
|
||||||
{"Sec-Fetch-Site": "cross-site"},
|
{"Sec-Fetch-Site": "cross-site"},
|
||||||
@@ -1746,7 +1746,7 @@ func TestChosenPassphraseWorksEndToEnd(t *testing.T) {
|
|||||||
t.Fatal("no session was started")
|
t.Fatal("no session was started")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", strings.NewReader("x"))
|
req, _ := http.NewRequest("POST", h.ts.URL+"/upload", strings.NewReader("x"))
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.Header.Set("Vanity", "chosen-upload")
|
req.Header.Set("Vanity", "chosen-upload")
|
||||||
req.AddCookie(cookie)
|
req.AddCookie(cookie)
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ func (s *Server) respondUploaded(w http.ResponseWriter, r *http.Request, m *stor
|
|||||||
URL: url,
|
URL: url,
|
||||||
InfoURL: s.absBase(r) + "i/" + m.ID,
|
InfoURL: s.absBase(r) + "i/" + m.ID,
|
||||||
DeleteToken: secret,
|
DeleteToken: secret,
|
||||||
DeleteURL: s.absBase(r) + "api/d/" + m.ID + "/delete",
|
DeleteURL: s.absBase(r) + "d/" + m.ID + "/delete",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var vanityRe = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{1,63}$`)
|
|||||||
// allowed into the object namespace.
|
// allowed into the object namespace.
|
||||||
var reserved = map[string]bool{
|
var reserved = map[string]bool{
|
||||||
"d": true, "i": true, "api": true, "static": true, "admin": true,
|
"d": true, "i": true, "api": true, "static": true, "admin": true,
|
||||||
"login": true, "logout": true,
|
"login": true, "logout": true, "upload": true,
|
||||||
"favicon.ico": true, "robots.txt": true, "index.html": true,
|
"favicon.ico": true, "robots.txt": true, "index.html": true,
|
||||||
"sitemap.xml": true, "tokens.json": true, "objects": true,
|
"sitemap.xml": true, "tokens.json": true, "objects": true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
--line: #dcdcd5;
|
--line: #dcdcd5;
|
||||||
--accent: #2f6f4f;
|
--accent: #2f6f4f;
|
||||||
--accent-fg: #ffffff;
|
--accent-fg: #ffffff;
|
||||||
|
/* Red reads as text and as a button surface, and one value cannot do both:
|
||||||
|
see the pair below, kept in step per theme. */
|
||||||
--danger: #9b2c2c;
|
--danger: #9b2c2c;
|
||||||
|
--danger-bg: #991b1b;
|
||||||
|
--danger-fg: #ffffff;
|
||||||
--warn-bg: #fdf6e3;
|
--warn-bg: #fdf6e3;
|
||||||
--radius: 10px;
|
--radius: 10px;
|
||||||
}
|
}
|
||||||
@@ -23,6 +27,8 @@
|
|||||||
--accent: #5fae86;
|
--accent: #5fae86;
|
||||||
--accent-fg: #10241a;
|
--accent-fg: #10241a;
|
||||||
--danger: #e07070;
|
--danger: #e07070;
|
||||||
|
--danger-bg: #d32f2f;
|
||||||
|
--danger-fg: #ffffff;
|
||||||
--warn-bg: #2a2418;
|
--warn-bg: #2a2418;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +118,7 @@ button, .button {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
button:disabled { opacity: .55; cursor: default; }
|
button:disabled { opacity: .55; cursor: default; }
|
||||||
button.danger { background: var(--danger); color: #fff; }
|
button.danger { background: var(--danger-bg); color: var(--danger-fg); }
|
||||||
|
|
||||||
.progress .bar {
|
.progress .bar {
|
||||||
height: .5rem;
|
height: .5rem;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<td data-label="Uploaded" class="nowrap" title="{{.Created}}">{{.CreatedAgo}}</td>
|
<td data-label="Uploaded" class="nowrap" title="{{.Created}}">{{.CreatedAgo}}</td>
|
||||||
<td data-label="Expires" class="nowrap" title="{{.Expires}}">{{.ExpiresIn}}</td>
|
<td data-label="Expires" class="nowrap" title="{{.Expires}}">{{.ExpiresIn}}</td>
|
||||||
<td class="actions-cell">
|
<td class="actions-cell">
|
||||||
<form method="post" action="{{$.Base}}api/d/{{.ID}}/delete">
|
<form method="post" action="{{$.Base}}d/{{.ID}}/delete">
|
||||||
<input type="hidden" name="from" value="admin">
|
<input type="hidden" name="from" value="admin">
|
||||||
<button type="submit" class="danger small"
|
<button type="submit" class="danger small"
|
||||||
data-confirm="Delete {{.Filename}}? This cannot be undone.">Delete</button>
|
data-confirm="Delete {{.Filename}}? This cannot be undone.">Delete</button>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<p class="notice">Your login is no longer valid, that token has been removed. You have been logged out.</p>
|
<p class="notice">Your login is no longer valid, that token has been removed. You have been logged out.</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<form id="upload" class="card" method="post" action="{{.Base}}api/upload"
|
<form id="upload" class="card" method="post" action="{{.Base}}upload"
|
||||||
enctype="multipart/form-data" data-max-size="{{.MaxSizeBytes}}">
|
enctype="multipart/form-data" data-max-size="{{.MaxSizeBytes}}">
|
||||||
|
|
||||||
<!-- Field order is load-bearing: the server streams this body rather than
|
<!-- Field order is load-bearing: the server streams this body rather than
|
||||||
@@ -68,6 +68,6 @@
|
|||||||
-H 'Authorization: Bearer <token>' \
|
-H 'Authorization: Bearer <token>' \
|
||||||
-H 'Vanity: my-file' \
|
-H 'Vanity: my-file' \
|
||||||
-H 'Expiry: 3d' \
|
-H 'Expiry: 3d' \
|
||||||
{{.AbsBase}}api/upload</code></pre>
|
{{.AbsBase}}upload</code></pre>
|
||||||
</section>
|
</section>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
{{if .CanDelete}}
|
{{if .CanDelete}}
|
||||||
<p class="hint">Your token can remove this file.</p>
|
<p class="hint">Your token can remove this file.</p>
|
||||||
<form method="post" action="{{.Base}}api/d/{{.Meta.ID}}/delete">
|
<form method="post" action="{{.Base}}d/{{.Meta.ID}}/delete">
|
||||||
<input type="hidden" name="from" value="info">
|
<input type="hidden" name="from" value="info">
|
||||||
<button type="submit" class="danger"
|
<button type="submit" class="danger"
|
||||||
data-confirm="Delete {{.Meta.Filename}}? This cannot be undone.">Delete this file</button>
|
data-confirm="Delete {{.Meta.Filename}}? This cannot be undone.">Delete this file</button>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
Paste the delete token you were given when this file was uploaded.
|
Paste the delete token you were given when this file was uploaded.
|
||||||
It is the only way to remove a file early without an owning token.
|
It is the only way to remove a file early without an owning token.
|
||||||
</p>
|
</p>
|
||||||
<form method="post" action="{{.Base}}api/d/{{.Meta.ID}}/delete">
|
<form method="post" action="{{.Base}}d/{{.Meta.ID}}/delete">
|
||||||
<input type="hidden" name="from" value="info">
|
<input type="hidden" name="from" value="info">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Delete token</span>
|
<span>Delete token</span>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
expires.
|
expires.
|
||||||
</p>
|
</p>
|
||||||
<p class="mono wrap">{{.DeleteToken}}</p>
|
<p class="mono wrap">{{.DeleteToken}}</p>
|
||||||
<form method="post" action="{{.Base}}api/d/{{.Meta.ID}}/delete">
|
<form method="post" action="{{.Base}}d/{{.Meta.ID}}/delete">
|
||||||
<input type="hidden" name="token" value="{{.DeleteToken}}">
|
<input type="hidden" name="token" value="{{.DeleteToken}}">
|
||||||
<button type="submit" class="danger"
|
<button type="submit" class="danger"
|
||||||
data-confirm="Delete {{.Meta.Filename}}? This cannot be undone.">Delete it now</button>
|
data-confirm="Delete {{.Meta.Filename}}? This cannot be undone.">Delete it now</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user