feat: close buttons as a red cross in a square; static files revalidated by content, so a release reaches the browser at once
This commit is contained in:
1 parent
55db8f03b3
commit
8eb2bbd35d
3 files changed
+38
-3
No files matched your search
@@ -1,7 +1,9 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/hex"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
@@ -184,7 +186,7 @@ func (s *Server) Handler() http.Handler {
|
|||||||
|
|
||||||
// Static assets (xterm.js etc.) from the embedded FS.
|
// Static assets (xterm.js etc.) from the embedded FS.
|
||||||
staticFS, _ := fs.Sub(webFS, "web/static")
|
staticFS, _ := fs.Sub(webFS, "web/static")
|
||||||
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
|
mux.Handle("GET /static/", http.StripPrefix("/static/", staticHandler(staticFS)))
|
||||||
|
|
||||||
// Public: install script + agent binary downloads.
|
// Public: install script + agent binary downloads.
|
||||||
mux.HandleFunc("GET /install.sh", s.handleInstallScript)
|
mux.HandleFunc("GET /install.sh", s.handleInstallScript)
|
||||||
@@ -333,3 +335,33 @@ func clientIP(r *http.Request) string {
|
|||||||
}
|
}
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// staticHandler serves the embedded assets with a content hash as ETag. The
|
||||||
|
// browser asks each time whether a file changed (no-cache) and gets a 304
|
||||||
|
// when it did not, so a new release reaches it at once without reloading
|
||||||
|
// what stayed the same.
|
||||||
|
func staticHandler(fsys fs.FS) http.Handler {
|
||||||
|
files := http.FileServer(http.FS(fsys))
|
||||||
|
var mu sync.Mutex
|
||||||
|
etags := map[string]string{}
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
name := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
|
mu.Lock()
|
||||||
|
tag, ok := etags[name]
|
||||||
|
mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
if data, err := fs.ReadFile(fsys, name); err == nil {
|
||||||
|
sum := sha256.Sum256(data)
|
||||||
|
tag = `"` + hex.EncodeToString(sum[:8]) + `"`
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
etags[name] = tag
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
if tag != "" {
|
||||||
|
w.Header().Set("ETag", tag)
|
||||||
|
}
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
files.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -65,6 +65,9 @@ button:hover, .btn:hover { background: var(--accent-hover); text-decoration: non
|
|||||||
/* Close buttons: red text and border on the ghost button. */
|
/* Close buttons: red text and border on the ghost button. */
|
||||||
.btn.close, .btn.ghost.close { background: transparent; border: 1px solid rgba(255, 92, 92, .6); color: var(--danger); }
|
.btn.close, .btn.ghost.close { background: transparent; border: 1px solid rgba(255, 92, 92, .6); color: var(--danger); }
|
||||||
.btn.close:hover, .btn.ghost.close:hover { background: #3a1d1d; border-color: var(--danger); }
|
.btn.close:hover, .btn.ghost.close:hover { background: #3a1d1d; border-color: var(--danger); }
|
||||||
|
/* A close button that is just a cross: square, the height of the buttons by it. */
|
||||||
|
.btn.close.x { width: 32px; height: 32px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
|
||||||
|
.btn.small.close.x { width: 28px; height: 28px; }
|
||||||
.btn.small { padding: 6px 10px; font-size: 13px; }
|
.btn.small { padding: 6px 10px; font-size: 13px; }
|
||||||
.btn.icon { width: 30px; height: 30px; padding: 0; display: inline-flex;
|
.btn.icon { width: 30px; height: 30px; padding: 0; display: inline-flex;
|
||||||
align-items: center; justify-content: center; font-size: 14px; line-height: 1; text-decoration: none; }
|
align-items: center; justify-content: center; font-size: 14px; line-height: 1; text-decoration: none; }
|
||||||
|
|||||||
@@ -226,7 +226,7 @@
|
|||||||
<button class="btn ghost small" id="vtext" hidden>Редактировать</button>
|
<button class="btn ghost small" id="vtext" hidden>Редактировать</button>
|
||||||
<button class="btn ghost small" id="vhex" hidden>Редактировать бинарно</button>
|
<button class="btn ghost small" id="vhex" hidden>Редактировать бинарно</button>
|
||||||
<button class="btn small" id="save" hidden>Сохранить</button>
|
<button class="btn small" id="save" hidden>Сохранить</button>
|
||||||
<button class="btn ghost small close" id="closev">Закрыть</button>
|
<button class="btn ghost small icon close x" id="closev" title="Закрыть" aria-label="Закрыть"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
|
||||||
<div class="vmsg muted" id="vmsg"></div>
|
<div class="vmsg muted" id="vmsg"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="vbody">
|
<div class="vbody">
|
||||||
@@ -261,7 +261,7 @@
|
|||||||
<dialog id="updlg">
|
<dialog id="updlg">
|
||||||
<div class="dlg-h">
|
<div class="dlg-h">
|
||||||
<strong style="flex:1">Загрузка файлов</strong>
|
<strong style="flex:1">Загрузка файлов</strong>
|
||||||
<button class="btn ghost small close" id="updlg-close">Закрыть</button>
|
<button class="btn ghost small icon close x" id="updlg-close" title="Закрыть" aria-label="Закрыть"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="dlg-b" id="updlg-list"></div>
|
<div class="dlg-b" id="updlg-list"></div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|||||||
Reference in new issue
Block a user