Implement frontend customizations
refs #71 Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
d3ca12fa35
commit
128ce071cb
12 changed files with 198 additions and 53 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,9 +1,10 @@
|
||||||
|
customize.yaml
|
||||||
|
frontend/api.html
|
||||||
frontend/app.css
|
frontend/app.css
|
||||||
frontend/app.js
|
frontend/app.js
|
||||||
frontend/app.js.LICENSE.txt
|
frontend/app.js.LICENSE.txt
|
||||||
frontend/css
|
frontend/css
|
||||||
frontend/js
|
frontend/js
|
||||||
frontend/api.html
|
|
||||||
frontend/locale/*.untranslated.json
|
frontend/locale/*.untranslated.json
|
||||||
frontend/webfonts
|
frontend/webfonts
|
||||||
frontend/*.woff2
|
frontend/*.woff2
|
||||||
|
|
39
README.md
39
README.md
|
@ -27,6 +27,45 @@ For a better setup you can choose the backend which is used to store the secrets
|
||||||
- Common options
|
- Common options
|
||||||
- `SECRET_EXPIRY` - Expiry of the keys in seconds (Default `0` = no expiry)
|
- `SECRET_EXPIRY` - Expiry of the keys in seconds (Default `0` = no expiry)
|
||||||
|
|
||||||
|
### Customization
|
||||||
|
|
||||||
|
In order to be adjustable to your needs there are some ways to customize your OTS setup. All of those require you to create a YAML file containing the definitions of your customizations and to load this file through the `--customize=path/to/customize.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Override the app-icon, present a path to the image to use, if unset
|
||||||
|
# or empty the default FontAwesome icon will be displayed. Recommended
|
||||||
|
# is a height of 30px.
|
||||||
|
appIcon: ''
|
||||||
|
|
||||||
|
# Override the app-title, if unset or empty the default app-title
|
||||||
|
# "OTS - One Time Secret" will be used
|
||||||
|
appTitle: ''
|
||||||
|
|
||||||
|
# Disable display of the app-title (for example if you included the
|
||||||
|
# title within the appIcon)
|
||||||
|
disableAppTitle: false
|
||||||
|
|
||||||
|
# Disable the footer linking back to the project. If you disable it
|
||||||
|
# please consider a donation to support the project.
|
||||||
|
disablePoweredBy: false
|
||||||
|
|
||||||
|
# Disable the switcher for dark / light theme in the top right corner
|
||||||
|
# for example if your custom theme does not support two themes.
|
||||||
|
disableThemeSwitcher: false
|
||||||
|
|
||||||
|
# Custom path to override embedded resources. You can override any
|
||||||
|
# file present in the `frontend` directory (which is baked into the
|
||||||
|
# binary during compile-time). You also can add new files (for
|
||||||
|
# example the appIcon given above). Those files are available at the
|
||||||
|
# root of the application (i.e. an app.png would be served at
|
||||||
|
# https://ots.example.com/app.png).
|
||||||
|
overlayFSPath: /path/to/ots-customization
|
||||||
|
```
|
||||||
|
|
||||||
|
To override the styling of the application have a look at the [`src/style.scss`](./src/style.scss) file how the theme of the application is built and present the compiled `app.css` in the `overlayFSPath`.
|
||||||
|
|
||||||
|
After modifying files in the `overlayFSPath` make sure to restart the application as otherwise the file integrity hashes are no longer matching and your resources will be blocked by the browsers.
|
||||||
|
|
||||||
## Creating secrets through CLI / scripts
|
## Creating secrets through CLI / scripts
|
||||||
|
|
||||||
As `ots` is designed to never let the server know the secret you are sharing you should not just send the plain secret to it though it is possible.
|
As `ots` is designed to never let the server know the secret you are sharing you should not just send the plain secret to it though it is possible.
|
||||||
|
|
|
@ -9,6 +9,7 @@ esbuild.build({
|
||||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'dev'),
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'dev'),
|
||||||
},
|
},
|
||||||
entryPoints: ['src/main.js'],
|
entryPoints: ['src/main.js'],
|
||||||
|
legalComments: 'none',
|
||||||
loader: {
|
loader: {
|
||||||
'.woff2': 'file',
|
'.woff2': 'file',
|
||||||
},
|
},
|
||||||
|
|
51
customize.go
Normal file
51
customize.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
customize struct {
|
||||||
|
AppIcon string `json:"appIcon,omitempty" yaml:"appIcon"`
|
||||||
|
AppTitle string `json:"appTitle,omitempty" yaml:"appTitle"`
|
||||||
|
DisableAppTitle bool `json:"disableAppTitle,omitempty" yaml:"disableAppTitle"`
|
||||||
|
DisablePoweredBy bool `json:"disablePoweredBy,omitempty" yaml:"disablePoweredBy"`
|
||||||
|
DisableThemeSwitcher bool `json:"disableThemeSwitcher,omitempty" yaml:"disableThemeSwitcher"`
|
||||||
|
OverlayFSPath string `json:"-" yaml:"overlayFSPath"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadCustomize(filename string) (customize, error) {
|
||||||
|
if filename == "" {
|
||||||
|
// None given, take a shortcut
|
||||||
|
return customize{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cust customize
|
||||||
|
|
||||||
|
cf, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
logrus.Warn("customize file given but not found")
|
||||||
|
return cust, nil
|
||||||
|
}
|
||||||
|
return cust, errors.Wrap(err, "opening customize file")
|
||||||
|
}
|
||||||
|
defer cf.Close()
|
||||||
|
|
||||||
|
return cust, errors.Wrap(
|
||||||
|
yaml.NewDecoder(cf).Decode(&cust),
|
||||||
|
"decoding customize file",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c customize) ToJSON() (string, error) {
|
||||||
|
j, err := json.Marshal(c)
|
||||||
|
return string(j), errors.Wrap(err, "marshalling JSON")
|
||||||
|
}
|
|
@ -49,6 +49,7 @@
|
||||||
|
|
||||||
// Template variable from Golang process
|
// Template variable from Golang process
|
||||||
const version = "{{ .Version }}"
|
const version = "{{ .Version }}"
|
||||||
|
window.OTSCustomize = JSON.parse('{{ .Customize.ToJSON }}')
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -3,7 +3,7 @@ module github.com/Luzifer/ots
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Luzifer/go_helpers/v2 v2.17.1
|
github.com/Luzifer/go_helpers/v2 v2.18.0
|
||||||
github.com/Luzifer/rconfig/v2 v2.4.0
|
github.com/Luzifer/rconfig/v2 v2.4.0
|
||||||
github.com/gofrs/uuid/v3 v3.1.2
|
github.com/gofrs/uuid/v3 v3.1.2
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,5 +1,5 @@
|
||||||
github.com/Luzifer/go_helpers/v2 v2.17.1 h1:SJjfkkJ14d1i8zpKzZ3619YSGijxp2jfc6Qk6iT5YsY=
|
github.com/Luzifer/go_helpers/v2 v2.18.0 h1:zDLNPKAxyFLMcwCN2Z/0SVpU3hTTqdYWXnCyviI8IBM=
|
||||||
github.com/Luzifer/go_helpers/v2 v2.17.1/go.mod h1:C5EkTBawA4sJt0CHoAoblgGPwTjW9blXZ/Et6RiEu6Q=
|
github.com/Luzifer/go_helpers/v2 v2.18.0/go.mod h1:C5EkTBawA4sJt0CHoAoblgGPwTjW9blXZ/Et6RiEu6Q=
|
||||||
github.com/Luzifer/rconfig/v2 v2.4.0 h1:MAdymTlExAZ8mx5VG8xOFAtFQSpWBipKYQHPOmYTn9o=
|
github.com/Luzifer/rconfig/v2 v2.4.0 h1:MAdymTlExAZ8mx5VG8xOFAtFQSpWBipKYQHPOmYTn9o=
|
||||||
github.com/Luzifer/rconfig/v2 v2.4.0/go.mod h1:hWF3ZVSusbYlg5bEvCwalEyUSY+0JPJWUiIu7rBmav8=
|
github.com/Luzifer/rconfig/v2 v2.4.0/go.mod h1:hWF3ZVSusbYlg5bEvCwalEyUSY+0JPJWUiIu7rBmav8=
|
||||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||||
|
|
119
main.go
119
main.go
|
@ -2,24 +2,26 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"io/fs"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
file_helpers "github.com/Luzifer/go_helpers/v2/file"
|
||||||
http_helpers "github.com/Luzifer/go_helpers/v2/http"
|
http_helpers "github.com/Luzifer/go_helpers/v2/http"
|
||||||
"github.com/Luzifer/rconfig/v2"
|
"github.com/Luzifer/rconfig/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg struct {
|
cfg struct {
|
||||||
|
Customize string `flag:"customize" default:"" description:"Customize-File to load"`
|
||||||
Listen string `flag:"listen" default:":3000" description:"IP/Port to listen on"`
|
Listen string `flag:"listen" default:":3000" description:"IP/Port to listen on"`
|
||||||
LogLevel string `flag:"log-level" default:"info" description:"Set log level (debug, info, warning, error)"`
|
LogLevel string `flag:"log-level" default:"info" description:"Set log level (debug, info, warning, error)"`
|
||||||
SecretExpiry int64 `flag:"secret-expiry" default:"0" description:"Maximum expiry of the stored secrets in seconds"`
|
SecretExpiry int64 `flag:"secret-expiry" default:"0" description:"Maximum expiry of the stored secrets in seconds"`
|
||||||
|
@ -27,34 +29,77 @@ var (
|
||||||
VersionAndExit bool `flag:"version" default:"false" description:"Print version information and exit"`
|
VersionAndExit bool `flag:"version" default:"false" description:"Print version information and exit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
product = "ots"
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
||||||
|
cspHeader = strings.Join([]string{
|
||||||
|
"default-src 'none'",
|
||||||
|
"connect-src 'self'",
|
||||||
|
"font-src 'self'",
|
||||||
|
"img-src 'self'",
|
||||||
|
"script-src 'self' 'unsafe-inline'",
|
||||||
|
"style-src 'self' 'unsafe-inline'",
|
||||||
|
}, ";")
|
||||||
|
|
||||||
|
assets file_helpers.FSStack
|
||||||
|
cust customize
|
||||||
|
indexTpl *template.Template
|
||||||
|
|
||||||
version = "dev"
|
version = "dev"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed frontend/*
|
//go:embed frontend/*
|
||||||
var assets embed.FS
|
var embeddedAssets embed.FS
|
||||||
|
|
||||||
func init() {
|
func initApp() error {
|
||||||
|
rconfig.AutoEnv(true)
|
||||||
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
||||||
log.Fatalf("Error parsing CLI arguments: %s", err)
|
return errors.Wrap(err, "parsing cli options")
|
||||||
}
|
}
|
||||||
|
|
||||||
if l, err := log.ParseLevel(cfg.LogLevel); err == nil {
|
l, err := logrus.ParseLevel(cfg.LogLevel)
|
||||||
log.SetLevel(l)
|
if err != nil {
|
||||||
} else {
|
return errors.Wrap(err, "parsing log-level")
|
||||||
log.Fatalf("Invalid log level: %s", err)
|
}
|
||||||
|
logrus.SetLevel(l)
|
||||||
|
|
||||||
|
if cust, err = loadCustomize(cfg.Customize); err != nil {
|
||||||
|
return errors.Wrap(err, "loading customizations")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.VersionAndExit {
|
frontendFS, err := fs.Sub(embeddedAssets, "frontend")
|
||||||
fmt.Printf("%s %s\n", product, version)
|
if err != nil {
|
||||||
os.Exit(0)
|
return errors.Wrap(err, "creating sub-fs for assets")
|
||||||
}
|
}
|
||||||
|
assets = append(assets, frontendFS)
|
||||||
|
|
||||||
|
if cust.OverlayFSPath != "" {
|
||||||
|
assets = append(file_helpers.FSStack{os.DirFS(cust.OverlayFSPath)}, assets...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
var err error
|
||||||
|
if err = initApp(); err != nil {
|
||||||
|
logrus.WithError(err).Fatal("initializing app")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.VersionAndExit {
|
||||||
|
logrus.WithField("version", version).Info("ots")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize index template in order not to parse it multiple times
|
||||||
|
source, err := assets.ReadFile("index.html")
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("frontend folder should contain index.html Go template")
|
||||||
|
}
|
||||||
|
indexTpl = template.Must(template.New("index.html").Funcs(tplFuncs).Parse(string(source)))
|
||||||
|
|
||||||
|
// Initialize storage
|
||||||
store, err := getStorageByType(cfg.StorageType)
|
store, err := getStorageByType(cfg.StorageType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not initialize storage: %s", err)
|
logrus.WithError(err).Fatal("initializing storage")
|
||||||
}
|
}
|
||||||
api := newAPI(store)
|
api := newAPI(store)
|
||||||
|
|
||||||
|
@ -66,11 +111,21 @@ func main() {
|
||||||
r.HandleFunc("/", handleIndex)
|
r.HandleFunc("/", handleIndex)
|
||||||
r.PathPrefix("/").HandlerFunc(assetDelivery)
|
r.PathPrefix("/").HandlerFunc(assetDelivery)
|
||||||
|
|
||||||
log.Fatalf("HTTP server quit: %s", http.ListenAndServe(cfg.Listen, http_helpers.NewHTTPLogHandler(r)))
|
logrus.WithField("version", version).Info("ots started")
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: cfg.Listen,
|
||||||
|
Handler: http_helpers.NewHTTPLogHandlerWithLogger(r, logrus.StandardLogger()),
|
||||||
|
ReadHeaderTimeout: time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = server.ListenAndServe(); err != nil {
|
||||||
|
logrus.WithError(err).Fatal("HTTP server quit unexpectedly")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assetDelivery(w http.ResponseWriter, r *http.Request) {
|
func assetDelivery(w http.ResponseWriter, r *http.Request) {
|
||||||
assetName := r.URL.Path
|
assetName := strings.TrimLeft(r.URL.Path, "/")
|
||||||
|
|
||||||
dot := strings.LastIndex(assetName, ".")
|
dot := strings.LastIndex(assetName, ".")
|
||||||
if dot < 0 {
|
if dot < 0 {
|
||||||
|
@ -80,7 +135,7 @@ func assetDelivery(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext := assetName[dot:]
|
ext := assetName[dot:]
|
||||||
assetData, err := assets.ReadFile(path.Join("frontend", assetName))
|
assetData, err := assets.ReadFile(assetName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "404 not found", http.StatusNotFound)
|
http.Error(w, "404 not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
@ -91,28 +146,6 @@ func assetDelivery(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(assetData)
|
w.Write(assetData)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
|
||||||
cspHeader = strings.Join([]string{
|
|
||||||
"default-src 'none'",
|
|
||||||
"connect-src 'self'",
|
|
||||||
"font-src 'self'",
|
|
||||||
"img-src 'self'",
|
|
||||||
"script-src 'self' 'unsafe-inline'",
|
|
||||||
"style-src 'self' 'unsafe-inline'",
|
|
||||||
}, ";")
|
|
||||||
|
|
||||||
indexTpl *template.Template
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
source, err := assets.ReadFile("frontend/index.html")
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Fatal("frontend folder should contain index.html Go template")
|
|
||||||
}
|
|
||||||
indexTpl = template.Must(template.New("index.html").Funcs(tplFuncs).Parse(string(source)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||||
|
@ -122,9 +155,11 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
if err := indexTpl.Execute(w, struct {
|
if err := indexTpl.Execute(w, struct {
|
||||||
Version string
|
Customize customize
|
||||||
|
Version string
|
||||||
}{
|
}{
|
||||||
Version: version,
|
Customize: cust,
|
||||||
|
Version: version,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
http.Error(w, errors.Wrap(err, "executing template").Error(), http.StatusInternalServerError)
|
http.Error(w, errors.Wrap(err, "executing template").Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
23
src/app.vue
23
src/app.vue
|
@ -9,7 +9,16 @@
|
||||||
href="#"
|
href="#"
|
||||||
@click="newSecret"
|
@click="newSecret"
|
||||||
>
|
>
|
||||||
<i class="fas fa-user-secret" /> OTS - One Time Secrets
|
<i
|
||||||
|
v-if="!customize.appIcon"
|
||||||
|
class="fas fa-user-secret mr-1"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
class="mr-1"
|
||||||
|
:src="customize.appIcon"
|
||||||
|
>
|
||||||
|
<span v-if="!customize.disableAppTitle">{{ customize.appTitle || 'OTS - One Time Secrets' }}</span>
|
||||||
</b-navbar-brand>
|
</b-navbar-brand>
|
||||||
|
|
||||||
<b-navbar-toggle target="nav-collapse" />
|
<b-navbar-toggle target="nav-collapse" />
|
||||||
|
@ -25,7 +34,10 @@
|
||||||
<b-nav-item @click="newSecret">
|
<b-nav-item @click="newSecret">
|
||||||
<i class="fas fa-plus" /> {{ $t('btn-new-secret') }}
|
<i class="fas fa-plus" /> {{ $t('btn-new-secret') }}
|
||||||
</b-nav-item>
|
</b-nav-item>
|
||||||
<b-nav-form class="ml-2">
|
<b-nav-form
|
||||||
|
v-if="!customize.disableThemeSwitcher"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
<b-form-checkbox
|
<b-form-checkbox
|
||||||
v-model="darkTheme"
|
v-model="darkTheme"
|
||||||
switch
|
switch
|
||||||
|
@ -156,7 +168,10 @@
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
|
|
||||||
<b-row class="mt-5">
|
<b-row
|
||||||
|
v-if="!customize.disablePoweredBy"
|
||||||
|
class="mt-5"
|
||||||
|
>
|
||||||
<b-col class="footer">
|
<b-col class="footer">
|
||||||
{{ $t('text-powered-by') }} <a href="https://github.com/Luzifer/ots"><i class="fab fa-github" /> OTS</a> {{ $root.version }}
|
{{ $t('text-powered-by') }} <a href="https://github.com/Luzifer/ots"><i class="fab fa-github" /> OTS</a> {{ $root.version }}
|
||||||
</b-col>
|
</b-col>
|
||||||
|
@ -176,6 +191,7 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
customize: {},
|
||||||
error: '',
|
error: '',
|
||||||
explanationShown: false,
|
explanationShown: false,
|
||||||
mode: 'create',
|
mode: 'create',
|
||||||
|
@ -207,6 +223,7 @@ export default {
|
||||||
|
|
||||||
// Trigger initialization functions
|
// Trigger initialization functions
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.customize = window.OTSCustomize
|
||||||
this.darkTheme = window.getTheme() === 'dark'
|
this.darkTheme = window.getTheme() === 'dark'
|
||||||
window.onhashchange = this.hashLoad
|
window.onhashchange = this.hashLoad
|
||||||
this.hashLoad()
|
this.hashLoad()
|
||||||
|
|
|
@ -3,8 +3,6 @@ import VueI18n from 'vue-i18n'
|
||||||
|
|
||||||
import BootstrapVue from 'bootstrap-vue'
|
import BootstrapVue from 'bootstrap-vue'
|
||||||
|
|
||||||
import 'bootstrap/dist/css/bootstrap.css'
|
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
import app from './app.vue'
|
import app from './app.vue'
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// Force local fonts
|
// Force local fonts
|
||||||
$web-font-path: '';
|
$web-font-path: '';
|
||||||
|
|
||||||
|
@import "../node_modules/bootstrap/dist/css/bootstrap.css";
|
||||||
|
@import "../node_modules/bootstrap-vue/dist/bootstrap-vue.css";
|
||||||
|
|
||||||
@import "lato";
|
@import "lato";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"path"
|
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +20,7 @@ func assetSRIHash(assetName string) string {
|
||||||
return sri
|
return sri
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := assets.ReadFile(path.Join("frontend", assetName))
|
data, err := assets.ReadFile(assetName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue