mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Config: Add an option to disable the web user interface #5111
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
88126e3e48
commit
f7a6b0fa6c
15 changed files with 82 additions and 23 deletions
|
|
@ -21,7 +21,11 @@ var brokenVideo []byte
|
|||
func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) {
|
||||
resp := i18n.NewResponse(code, id, params...)
|
||||
|
||||
log.Debugf("api-v1: abort %s with code %d (%s)", clean.Log(c.FullPath()), code, strings.ToLower(resp.String()))
|
||||
if code >= 400 {
|
||||
log.Debugf("api: aborting request with error code %d (%s)", code, strings.ToLower(resp.String()))
|
||||
} else {
|
||||
log.Debugf("api: aborting request with response code %d (%s)", code, strings.ToLower(resp.String()))
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(code, resp)
|
||||
}
|
||||
|
|
@ -31,7 +35,12 @@ func Error(c *gin.Context, code int, err error, id i18n.Message, params ...inter
|
|||
|
||||
if err != nil {
|
||||
resp.Details = err.Error()
|
||||
log.Errorf("api-v1: error %s with code %d in %s (%s)", clean.Error(err), code, clean.Log(c.FullPath()), strings.ToLower(resp.String()))
|
||||
|
||||
if reqPath := c.FullPath(); reqPath == "" {
|
||||
log.Errorf("api: error %d %s (%s)", code, clean.Error(err), strings.ToLower(resp.String()))
|
||||
} else {
|
||||
log.Errorf("api: error %d %s in %s (%s)", code, clean.Error(err), clean.Log(reqPath), strings.ToLower(resp.String()))
|
||||
}
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(code, resp)
|
||||
|
|
@ -107,7 +116,7 @@ func AbortBadRequest(c *gin.Context, errs ...error) {
|
|||
for _, err := range errs {
|
||||
if err != nil {
|
||||
// Add error message to the debug logs.
|
||||
log.Debugf("api-v1: %s", err)
|
||||
log.Debugf("api: %s", err)
|
||||
|
||||
// Attach error to the current context
|
||||
_ = c.Error(err)
|
||||
|
|
|
|||
|
|
@ -22,10 +22,18 @@ func GetClientConfig(router *gin.RouterGroup) {
|
|||
sess := Session(ClientIP(c), AuthToken(c))
|
||||
conf := get.Config()
|
||||
|
||||
if sess == nil {
|
||||
c.JSON(http.StatusOK, conf.ClientPublic())
|
||||
} else {
|
||||
// Check authentication.
|
||||
if sess != nil {
|
||||
// Return custom client config for authenticated user.
|
||||
c.JSON(http.StatusOK, conf.ClientSession(sess))
|
||||
return
|
||||
} else if conf.DisableFrontend() {
|
||||
// Abort if not authenticated, and the web frontend is disabled.
|
||||
AbortUnauthorized(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Return public client config for loading the web frontend
|
||||
c.JSON(http.StatusOK, conf.ClientPublic())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func AlbumCover(router *gin.RouterGroup) {
|
|||
cacheKey := CacheKey(albumCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
log.Tracef("api-v1: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
log.Tracef("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
cached := cacheData.(ThumbCache)
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ func LabelCover(router *gin.RouterGroup) {
|
|||
cacheKey := CacheKey(labelCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
log.Tracef("api-v1: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
log.Tracef("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
cached := cacheData.(ThumbCache)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func FolderCover(router *gin.RouterGroup) {
|
|||
cacheKey := CacheKey(folderCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
log.Tracef("api-v1: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
log.Tracef("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
cached := cacheData.(ThumbCache)
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
|
|||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
cached := cacheData.(FoldersResponse)
|
||||
|
||||
log.Tracef("api-v1: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
log.Tracef("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.JSON(http.StatusOK, cached)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func GetServiceFolders(router *gin.RouterGroup) {
|
|||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
cached := cacheData.(fs.FileInfos)
|
||||
|
||||
log.Tracef("api-v1: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
log.Tracef("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.JSON(http.StatusOK, cached)
|
||||
return
|
||||
|
|
@ -278,7 +278,7 @@ func DeleteService(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := m.Delete(); err != nil {
|
||||
if err = m.Delete(); err != nil {
|
||||
Error(c, http.StatusInternalServerError, err, i18n.ErrDeleteFailed)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ func GetThumb(router *gin.RouterGroup) {
|
|||
cacheKey := CacheKey("thumbs", fileHash, string(sizeName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
log.Tracef("api-v1: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
log.Tracef("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
cached := cacheData.(ThumbCache)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ import (
|
|||
var Sponsor = Env(EnvDemo, EnvSponsor, EnvTest)
|
||||
var Features = Community
|
||||
|
||||
// DisableFrontend checks if the web user interface routes should be disabled.
|
||||
func (c *Config) DisableFrontend() bool {
|
||||
return c.options.DisableFrontend
|
||||
}
|
||||
|
||||
// DisableSettings checks if users should not be allowed to change settings.
|
||||
func (c *Config) DisableSettings() bool {
|
||||
return c.options.DisableSettings
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConfig_DisableFrontend(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.False(t, c.DisableFrontend())
|
||||
}
|
||||
|
||||
func TestConfig_DisableSettings(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.False(t, c.DisableSettings())
|
||||
}
|
||||
|
||||
func TestConfig_DisableWebDAV(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
|
|
|
|||
|
|
@ -392,9 +392,14 @@ var Flags = CliFlags{
|
|||
Usage: "enable new features that may be incomplete or unstable",
|
||||
EnvVars: EnvVars("EXPERIMENTAL"),
|
||||
}}, {
|
||||
Flag: &cli.BoolFlag{
|
||||
Name: "disable-frontend",
|
||||
Usage: "disable the web user interface so that only the service API endpoints are accessible",
|
||||
EnvVars: EnvVars("DISABLE_FRONTEND"),
|
||||
}}, {
|
||||
Flag: &cli.BoolFlag{
|
||||
Name: "disable-settings",
|
||||
Usage: "disable the settings user interface and server API, e.g. in combination with public mode",
|
||||
Usage: "disable the settings frontend and related API endpoints, e.g. in combination with public mode",
|
||||
EnvVars: EnvVars("DISABLE_SETTINGS"),
|
||||
}}, {
|
||||
Flag: &cli.BoolFlag{
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ type Options struct {
|
|||
AutoImport int `yaml:"AutoImport" json:"AutoImport" flag:"auto-import"`
|
||||
ReadOnly bool `yaml:"ReadOnly" json:"ReadOnly" flag:"read-only"`
|
||||
Experimental bool `yaml:"Experimental" json:"Experimental" flag:"experimental"`
|
||||
DisableFrontend bool `yaml:"DisableFrontend" json:"-" flag:"disable-frontend"`
|
||||
DisableSettings bool `yaml:"DisableSettings" json:"-" flag:"disable-settings"`
|
||||
DisableBackups bool `yaml:"DisableBackups" json:"DisableBackups" flag:"disable-backups"`
|
||||
DisableRestart bool `yaml:"DisableRestart" json:"-" flag:"disable-restart"`
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||
{"read-only", fmt.Sprintf("%t", c.ReadOnly())},
|
||||
{"develop", fmt.Sprintf("%t", c.Develop())},
|
||||
{"experimental", fmt.Sprintf("%t", c.Experimental())},
|
||||
{"disable-frontend", fmt.Sprintf("%t", c.DisableFrontend())},
|
||||
{"disable-settings", fmt.Sprintf("%t", c.DisableSettings())},
|
||||
{"disable-backups", fmt.Sprintf("%t", c.DisableBackups())},
|
||||
{"disable-restart", fmt.Sprintf("%t", c.DisableRestart())},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ import (
|
|||
|
||||
// registerSharingRoutes adds routes for link sharing.
|
||||
func registerSharingRoutes(router *gin.Engine, conf *config.Config) {
|
||||
// Return if the web user interface is disabled.
|
||||
if conf.DisableFrontend() {
|
||||
return
|
||||
}
|
||||
|
||||
s := router.Group(conf.BaseUri("/s"))
|
||||
{
|
||||
api.Shares(s)
|
||||
|
|
|
|||
|
|
@ -7,20 +7,12 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/api"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/media/http/header"
|
||||
)
|
||||
|
||||
// registerStaticRoutes adds routes for serving static content and templates.
|
||||
func registerStaticRoutes(router *gin.Engine, conf *config.Config) {
|
||||
// Redirects to the login page.
|
||||
login := func(c *gin.Context) {
|
||||
if conf.OIDCEnabled() && conf.OIDCRedirect() {
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.OIDCLoginUri())
|
||||
} else {
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.LoginUri())
|
||||
}
|
||||
}
|
||||
|
||||
// Control how crawlers index the site by serving a "robots.txt" file in addition
|
||||
// to the "X-Robots-Tag" response header set in the Security middleware:
|
||||
// https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt
|
||||
|
|
@ -35,6 +27,24 @@ func registerStaticRoutes(router *gin.Engine, conf *config.Config) {
|
|||
}
|
||||
})
|
||||
|
||||
// Return if the web user interface is disabled.
|
||||
if conf.DisableFrontend() {
|
||||
log.Info("frontend: disabled")
|
||||
router.NoRoute(func(c *gin.Context) {
|
||||
api.Abort(c, http.StatusNotFound, i18n.ErrNotFound)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Redirects to the login page.
|
||||
login := func(c *gin.Context) {
|
||||
if conf.OIDCEnabled() && conf.OIDCRedirect() {
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.OIDCLoginUri())
|
||||
} else {
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.LoginUri())
|
||||
}
|
||||
}
|
||||
|
||||
router.Any(conf.BaseUri("/"), login)
|
||||
|
||||
// Shows "Page Not found" error if no other handler is registered.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ import (
|
|||
|
||||
// registerWebAppRoutes adds routes for the web user interface.
|
||||
func registerWebAppRoutes(router *gin.Engine, conf *config.Config) {
|
||||
// Return if the web user interface is disabled.
|
||||
if conf.DisableFrontend() {
|
||||
return
|
||||
}
|
||||
|
||||
// Serve user interface bootstrap template on all routes starting with "/library".
|
||||
ui := func(c *gin.Context) {
|
||||
// Prevent CDNs from caching this endpoint.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue