Sybren A. Stüvel
7b028df8ac
Vue Router generates URLs for which there are no static files on the filesystem (like `/jobs/{job ID}`). To make this work, the webapp's `index.html` has to be served for such requests. The client-side JavaScript then figures out how things fit together, and can even render a nice 404 page if necessary. This shouldn't happen for non-webapp URLs, though. Because of this, the entire webapp (including the "serve `index.html` if file not found logic) is moved to a `/app/` base URL. `make flamenco-manager` now also builds the webapp and embeds the static files into the binary. `make flamenco-manager_race` does NOT rebuild the static web files, to help speed up of debug cycles. Run `make webapp-static` to rebuild the webapp itself, if necessary, or run a separate web development server with `yarn --cwd web/app run dev --host`.
71 lines
1.8 KiB
Go
71 lines
1.8 KiB
Go
package web
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import (
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
//go:embed static
|
|
var webStaticFS embed.FS
|
|
|
|
// WebAppHandler returns a HTTP handler to serve the static files of the Flamenco Manager web app.
|
|
func WebAppHandler() (http.Handler, error) {
|
|
// Strip the 'static/' directory off of the embedded filesystem.
|
|
fs, err := fs.Sub(webStaticFS, "static")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to wrap embedded filesystem: %w", err)
|
|
}
|
|
|
|
// Serve `index.html` from the root directory if the requested file cannot be
|
|
// found.
|
|
wrappedFS := WrapFS(fs, "index.html")
|
|
|
|
return http.FileServer(http.FS(wrappedFS)), nil
|
|
}
|
|
|
|
// FSWrapper wraps a filesystem and falls back to serving a specific file when
|
|
// the requested file cannot be found.
|
|
//
|
|
// This is necesasry for compatibility with Vue Router, as that generates URL
|
|
// paths to files that don't exist on the filesystem, like
|
|
// `/workers/c441766a-5d28-47cb-9589-b0caa4269065`. Serving `/index.html` in
|
|
// such cases makes Vue Router understand what's going on again.
|
|
type FSWrapper struct {
|
|
fs fs.FS
|
|
fallback string
|
|
}
|
|
|
|
func (w *FSWrapper) Open(name string) (fs.File, error) {
|
|
file, err := w.fs.Open(name)
|
|
|
|
switch {
|
|
case err == nil:
|
|
return file, nil
|
|
case errors.Is(err, fs.ErrNotExist):
|
|
fallbackFile, fallbackErr := w.fs.Open(w.fallback)
|
|
if fallbackErr != nil {
|
|
log.Error().
|
|
Str("name", name).
|
|
Str("fallback", w.fallback).
|
|
Err(err).
|
|
Str("fallbackErr", fallbackErr.Error()).
|
|
Msg("static web server: error opening fallback file")
|
|
return file, err
|
|
}
|
|
return fallbackFile, nil
|
|
}
|
|
|
|
return file, err
|
|
}
|
|
|
|
func WrapFS(fs fs.FS, fallback string) *FSWrapper {
|
|
return &FSWrapper{fs: fs, fallback: fallback}
|
|
}
|