feat: move sentry configuration from frontend to api
This commit is contained in:
parent
1899f16207
commit
a0e770438d
@ -40,8 +40,6 @@ service:
|
|||||||
enabletaskcomments: true
|
enabletaskcomments: true
|
||||||
# Whether totp is enabled. In most cases you want to leave that enabled.
|
# Whether totp is enabled. In most cases you want to leave that enabled.
|
||||||
enabletotp: true
|
enabletotp: true
|
||||||
# If not empty, enables logging of crashes and unhandled errors in sentry.
|
|
||||||
sentrydsn: ''
|
|
||||||
# If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database.
|
# If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database.
|
||||||
# Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm,
|
# Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm,
|
||||||
# each request made to this endpoint needs to provide an `Authorization: <token>` header with the token from below. <br/>
|
# each request made to this endpoint needs to provide an `Authorization: <token>` header with the token from below. <br/>
|
||||||
@ -61,6 +59,18 @@ service:
|
|||||||
# You probably don't need to set this value, it was created specifically for usage on [try](https://try.vikunja.io).
|
# You probably don't need to set this value, it was created specifically for usage on [try](https://try.vikunja.io).
|
||||||
demomode: false
|
demomode: false
|
||||||
|
|
||||||
|
sentry:
|
||||||
|
# If set to true, enables anonymous error tracking of api errors via Sentry. This allows us to gather more
|
||||||
|
# information about errors in order to debug and fix it.
|
||||||
|
enabled: false
|
||||||
|
# Configure the Sentry dsn used for api error tracking. Only used when Sentry is enabled for the api.
|
||||||
|
dsn: "https://440eedc957d545a795c17bbaf477497c@o1047380.ingest.sentry.io/4504254983634944"
|
||||||
|
# If set to true, enables anonymous error tracking of frontend errors via Sentry. This allows us to gather more
|
||||||
|
# information about errors in order to debug and fix it.
|
||||||
|
frontendenabled: false
|
||||||
|
# Configure the Sentry dsn used for frontend error tracking. Only used when Sentry is enabled for the frontend.
|
||||||
|
frontenddsn: "https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480"
|
||||||
|
|
||||||
database:
|
database:
|
||||||
# Database type to use. Supported types are mysql, postgres and sqlite.
|
# Database type to use. Supported types are mysql, postgres and sqlite.
|
||||||
type: "sqlite"
|
type: "sqlite"
|
||||||
|
@ -23,10 +23,6 @@
|
|||||||
// It has to be the full url, including the last /api/v1 part and port.
|
// It has to be the full url, including the last /api/v1 part and port.
|
||||||
// You can change this if your api is not reachable on the same port as the frontend.
|
// You can change this if your api is not reachable on the same port as the frontend.
|
||||||
window.API_URL = 'http://localhost:3456/api/v1'
|
window.API_URL = 'http://localhost:3456/api/v1'
|
||||||
// Enable error tracking with sentry. If this is set to true, will send anonymized data to
|
|
||||||
// our sentry instance to notify us of potential problems.
|
|
||||||
window.SENTRY_ENABLED = false
|
|
||||||
window.SENTRY_DSN = 'https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480'
|
|
||||||
// If enabled, allows the user to nest projects infinitely, instead of the default 2 levels.
|
// If enabled, allows the user to nest projects infinitely, instead of the default 2 levels.
|
||||||
// This setting might change in the future or be removed completely.
|
// This setting might change in the future or be removed completely.
|
||||||
window.PROJECT_INFINITE_NESTING_ENABLED = false
|
window.PROJECT_INFINITE_NESTING_ENABLED = false
|
||||||
|
@ -18,8 +18,8 @@ import {getBrowserLanguage, i18n, setLanguage} from './i18n'
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
API_URL: string;
|
API_URL: string;
|
||||||
SENTRY_ENABLED: boolean;
|
SENTRY_ENABLED?: boolean;
|
||||||
SENTRY_DSN: string;
|
SENTRY_DSN?: string;
|
||||||
PROJECT_INFINITE_NESTING_ENABLED: boolean;
|
PROJECT_INFINITE_NESTING_ENABLED: boolean;
|
||||||
ALLOW_ICON_CHANGES: boolean;
|
ALLOW_ICON_CHANGES: boolean;
|
||||||
CUSTOM_LOGO_URL?: string;
|
CUSTOM_LOGO_URL?: string;
|
||||||
|
@ -8,7 +8,7 @@ export default async function setupSentry(app: App, router: Router) {
|
|||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
app,
|
app,
|
||||||
dsn: window.SENTRY_DSN,
|
dsn: window.SENTRY_DSN ?? '',
|
||||||
release: import.meta.env.VITE_PLUGIN_SENTRY_CONFIG.release,
|
release: import.meta.env.VITE_PLUGIN_SENTRY_CONFIG.release,
|
||||||
dist: import.meta.env.VITE_PLUGIN_SENTRY_CONFIG.dist,
|
dist: import.meta.env.VITE_PLUGIN_SENTRY_CONFIG.dist,
|
||||||
integrations: [
|
integrations: [
|
||||||
|
@ -58,12 +58,16 @@ const (
|
|||||||
ServiceTimeZone Key = `service.timezone`
|
ServiceTimeZone Key = `service.timezone`
|
||||||
ServiceEnableTaskComments Key = `service.enabletaskcomments`
|
ServiceEnableTaskComments Key = `service.enabletaskcomments`
|
||||||
ServiceEnableTotp Key = `service.enabletotp`
|
ServiceEnableTotp Key = `service.enabletotp`
|
||||||
ServiceSentryDsn Key = `service.sentrydsn`
|
|
||||||
ServiceTestingtoken Key = `service.testingtoken`
|
ServiceTestingtoken Key = `service.testingtoken`
|
||||||
ServiceEnableEmailReminders Key = `service.enableemailreminders`
|
ServiceEnableEmailReminders Key = `service.enableemailreminders`
|
||||||
ServiceEnableUserDeletion Key = `service.enableuserdeletion`
|
ServiceEnableUserDeletion Key = `service.enableuserdeletion`
|
||||||
ServiceMaxAvatarSize Key = `service.maxavatarsize`
|
ServiceMaxAvatarSize Key = `service.maxavatarsize`
|
||||||
|
|
||||||
|
SentryEnabled Key = `sentry.enabled`
|
||||||
|
SentryDsn Key = `sentry.dsn`
|
||||||
|
SentryFrontendEnabled Key = `sentry.frontendenabled`
|
||||||
|
SentryFrontendDsn Key = `sentry.frontenddsn`
|
||||||
|
|
||||||
AuthLocalEnabled Key = `auth.local.enabled`
|
AuthLocalEnabled Key = `auth.local.enabled`
|
||||||
AuthOpenIDEnabled Key = `auth.openid.enabled`
|
AuthOpenIDEnabled Key = `auth.openid.enabled`
|
||||||
AuthOpenIDProviders Key = `auth.openid.providers`
|
AuthOpenIDProviders Key = `auth.openid.providers`
|
||||||
@ -306,6 +310,10 @@ func InitDefaultConfig() {
|
|||||||
ServiceMaxAvatarSize.setDefault(1024)
|
ServiceMaxAvatarSize.setDefault(1024)
|
||||||
ServiceDemoMode.setDefault(false)
|
ServiceDemoMode.setDefault(false)
|
||||||
|
|
||||||
|
// Sentry
|
||||||
|
SentryDsn.setDefault("https://440eedc957d545a795c17bbaf477497c@o1047380.ingest.sentry.io/4504254983634944")
|
||||||
|
SentryFrontendDsn.setDefault("https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480")
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
AuthLocalEnabled.setDefault(true)
|
AuthLocalEnabled.setDefault(true)
|
||||||
AuthOpenIDEnabled.setDefault(false)
|
AuthOpenIDEnabled.setDefault(false)
|
||||||
|
@ -114,9 +114,29 @@ func NewEcho() *echo.Echo {
|
|||||||
// panic recover
|
// panic recover
|
||||||
e.Use(middleware.Recover())
|
e.Use(middleware.Recover())
|
||||||
|
|
||||||
if config.ServiceSentryDsn.GetString() != "" {
|
setupSentry(e)
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
e.Validator = &CustomValidator{}
|
||||||
|
|
||||||
|
// Handler config
|
||||||
|
handler.SetAuthProvider(&web.Auths{
|
||||||
|
AuthObject: auth.GetAuthFromClaims,
|
||||||
|
})
|
||||||
|
handler.SetLoggingProvider(log.GetLogger())
|
||||||
|
handler.SetMaxItemsPerPage(config.ServiceMaxItemsPerPage.GetInt())
|
||||||
|
handler.SetSessionFactory(db.NewSession)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupSentry(e *echo.Echo) {
|
||||||
|
if !config.SentryEnabled.GetBool() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := sentry.Init(sentry.ClientOptions{
|
if err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: config.ServiceSentryDsn.GetString(),
|
Dsn: config.SentryDsn.GetString(),
|
||||||
AttachStacktrace: true,
|
AttachStacktrace: true,
|
||||||
Release: version.Version,
|
Release: version.Version,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -146,20 +166,6 @@ func NewEcho() *echo.Echo {
|
|||||||
}
|
}
|
||||||
e.DefaultHTTPErrorHandler(err, c)
|
e.DefaultHTTPErrorHandler(err, c)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
e.Validator = &CustomValidator{}
|
|
||||||
|
|
||||||
// Handler config
|
|
||||||
handler.SetAuthProvider(&web.Auths{
|
|
||||||
AuthObject: auth.GetAuthFromClaims,
|
|
||||||
})
|
|
||||||
handler.SetLoggingProvider(log.GetLogger())
|
|
||||||
handler.SetMaxItemsPerPage(config.ServiceMaxItemsPerPage.GetInt())
|
|
||||||
handler.SetSessionFactory(db.NewSession)
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes registers all routes for the application
|
// RegisterRoutes registers all routes for the application
|
||||||
|
@ -2,6 +2,7 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"code.vikunja.io/api/pkg/config"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"code.vikunja.io/api/frontend"
|
"code.vikunja.io/api/frontend"
|
||||||
|
|
||||||
@ -26,16 +28,84 @@ const (
|
|||||||
rootPath = `dist/`
|
rootPath = `dist/`
|
||||||
cacheControlMax = `max-age=315360000, public, max-age=31536000, s-maxage=31536000, immutable`
|
cacheControlMax = `max-age=315360000, public, max-age=31536000, s-maxage=31536000, immutable`
|
||||||
cacheControlNone = `public, max-age=0, s-maxage=0, must-revalidate`
|
cacheControlNone = `public, max-age=0, s-maxage=0, must-revalidate`
|
||||||
|
configScriptTagTemplate = `
|
||||||
|
<script>
|
||||||
|
window.SENTRY_ENABLED = {{ .SENTRY_ENABLED }}
|
||||||
|
window.SENTRY_DSN = '{{ .SENTRY_DSN }}'
|
||||||
|
window.ALLOW_ICON_CHANGES = {{ .ALLOW_ICON_CHANGES }}
|
||||||
|
window.CUSTOM_LOGO_URL = '{{ .CUSTOM_LOGO_URL }}'
|
||||||
|
</script>`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Because the files are embedded into the final binary, we can be absolutely sure the etag will never change
|
// Because the files are embedded into the final binary, we can be absolutely sure the etag will never change
|
||||||
// and we can cache its generation pretty heavily.
|
// and we can cache its generation pretty heavily.
|
||||||
var etagCache map[string]string
|
var etagCache map[string]string
|
||||||
var etagLock sync.Mutex
|
var etagLock sync.Mutex
|
||||||
|
var scriptConfigString string
|
||||||
|
var scriptConfigStringLock sync.Mutex
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
etagCache = make(map[string]string)
|
etagCache = make(map[string]string)
|
||||||
etagLock = sync.Mutex{}
|
etagLock = sync.Mutex{}
|
||||||
|
scriptConfigStringLock = sync.Mutex{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveIndexFile(c echo.Context, assetFs http.FileSystem) (err error) {
|
||||||
|
index, err := assetFs.Open(path.Join(rootPath, indexFile))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer index.Close()
|
||||||
|
|
||||||
|
if scriptConfigString == "" {
|
||||||
|
|
||||||
|
scriptConfigStringLock.Lock()
|
||||||
|
defer scriptConfigStringLock.Unlock()
|
||||||
|
|
||||||
|
// replace config variables
|
||||||
|
tmpl, err := template.New("config").Parse(configScriptTagTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var tplOutput bytes.Buffer
|
||||||
|
data := make(map[string]string)
|
||||||
|
|
||||||
|
data["SENTRY_ENABLED"] = "false"
|
||||||
|
if config.SentryFrontendEnabled.GetBool() {
|
||||||
|
data["SENTRY_ENABLED"] = "true"
|
||||||
|
}
|
||||||
|
data["SENTRY_DSN"] = config.SentryFrontendDsn.GetString()
|
||||||
|
data["ALLOW_ICON_CHANGES"] = "true" // TODO
|
||||||
|
data["CUSTOM_LOGO_URL"] = "" // TODO
|
||||||
|
|
||||||
|
err = tmpl.Execute(&tplOutput, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
scriptConfig := tplOutput.String()
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
_, err = buf.ReadFrom(index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptConfigString = strings.ReplaceAll(buf.String(), `<div id="app"></div>`, `<div id="app"></div>`+scriptConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := strings.NewReader(scriptConfigString)
|
||||||
|
|
||||||
|
info, err := index.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
etag, err := generateEtag(index, info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return serveFile(c, reader, info, etag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from echo's middleware.StaticWithConfig simplified and adjusted for caching
|
// Copied from echo's middleware.StaticWithConfig simplified and adjusted for caching
|
||||||
@ -71,10 +141,8 @@ func static() echo.MiddlewareFunc {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = assetFs.Open(path.Join(rootPath, indexFile))
|
// Handle all other requests with the index file
|
||||||
if err != nil {
|
return serveIndexFile(c, assetFs)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@ -85,24 +153,7 @@ func static() echo.MiddlewareFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
index, err := assetFs.Open(path.Join(name, indexFile))
|
return serveIndexFile(c, assetFs)
|
||||||
if err != nil {
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer index.Close()
|
|
||||||
|
|
||||||
info, err = index.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
etag, err := generateEtag(index, name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serveFile(c, index, info, etag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
etag, err := generateEtag(file, name)
|
etag, err := generateEtag(file, name)
|
||||||
@ -133,7 +184,7 @@ func generateEtag(file http.File, name string) (etag string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// copied from http.serveContent
|
// copied from http.serveContent
|
||||||
func getMimeType(name string, file http.File) (mineType string, err error) {
|
func getMimeType(name string, file io.ReadSeeker) (mineType string, err error) {
|
||||||
mineType = mime.TypeByExtension(filepath.Ext(name))
|
mineType = mime.TypeByExtension(filepath.Ext(name))
|
||||||
if mineType == "" {
|
if mineType == "" {
|
||||||
// read a chunk to decide between utf-8 text and binary
|
// read a chunk to decide between utf-8 text and binary
|
||||||
@ -149,7 +200,7 @@ func getMimeType(name string, file http.File) (mineType string, err error) {
|
|||||||
return mineType, nil
|
return mineType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCacheControlHeader(info os.FileInfo, file http.File) (header string, err error) {
|
func getCacheControlHeader(info os.FileInfo, file io.ReadSeeker) (header string, err error) {
|
||||||
// Don't cache service worker and related files
|
// Don't cache service worker and related files
|
||||||
if info.Name() == "robots.txt" ||
|
if info.Name() == "robots.txt" ||
|
||||||
info.Name() == "sw.js" ||
|
info.Name() == "sw.js" ||
|
||||||
@ -187,7 +238,7 @@ func getCacheControlHeader(info os.FileInfo, file http.File) (header string, err
|
|||||||
return cacheControlNone, nil
|
return cacheControlNone, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveFile(c echo.Context, file http.File, info os.FileInfo, etag string) error {
|
func serveFile(c echo.Context, file io.ReadSeeker, info os.FileInfo, etag string) error {
|
||||||
|
|
||||||
c.Response().Header().Set("Server", "Vikunja")
|
c.Response().Header().Set("Server", "Vikunja")
|
||||||
c.Response().Header().Set("Vary", "Accept-Encoding")
|
c.Response().Header().Set("Vary", "Accept-Encoding")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user