diff --git a/docker/demo/Dockerfile b/docker/demo/Dockerfile index 38e538749..a4ade9b8a 100644 --- a/docker/demo/Dockerfile +++ b/docker/demo/Dockerfile @@ -14,6 +14,7 @@ ENV PHOTOPRISM_STORAGE_PATH /photoprism/storage ENV PHOTOPRISM_DEBUG false ENV PHOTOPRISM_READONLY false ENV PHOTOPRISM_PUBLIC true +ENV PHOTOPRISM_DEMO true ENV PHOTOPRISM_EXPERIMENTAL true ENV PHOTOPRISM_UPLOAD_NSFW false ENV PHOTOPRISM_DETECT_NSFW false diff --git a/frontend/src/app.js b/frontend/src/app.js index 0e0092dd8..fa2903300 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -107,7 +107,9 @@ const router = new Router({ }); router.beforeEach((to, from, next) => { - if (to.matched.some(record => record.meta.admin)) { + if (to.matched.some(record => record.meta.settings) && config.values.disable.settings) { + next({name: "home"}); + } else if (to.matched.some(record => record.meta.admin)) { if (isPublic || session.isAdmin()) { next(); } else { diff --git a/frontend/src/common/config.js b/frontend/src/common/config.js index 4d5fdadaf..51df0da33 100644 --- a/frontend/src/common/config.js +++ b/frontend/src/common/config.js @@ -49,6 +49,7 @@ export default class Config { if (!values || !values.siteTitle) { console.warn("config: values are empty"); this.debug = true; + this.demo = false; this.values = {}; this.page = { title: "PhotoPrism", @@ -64,6 +65,7 @@ export default class Config { this.values = values; this.debug = !!values.debug; + this.demo = !!values.demo; Event.subscribe("config.updated", (ev, data) => this.setValues(data.config)); Event.subscribe("count", (ev, data) => this.onCount(ev, data)); diff --git a/frontend/src/pages/settings.vue b/frontend/src/pages/settings.vue index 8757010b8..0d577c3ca 100644 --- a/frontend/src/pages/settings.vue +++ b/frontend/src/pages/settings.vue @@ -9,27 +9,14 @@ slider-color="secondary-dark" :height="$vuetify.breakpoint.smAndDown ? 48 : 64" > - - General - - - - Backup - - - - Account + + {{ tab.icon }} - - - - - - - - + + @@ -38,24 +25,98 @@ diff --git a/frontend/src/pages/settings/sync.vue b/frontend/src/pages/settings/sync.vue index fbeede2f6..39b2ad441 100644 --- a/frontend/src/pages/settings/sync.vue +++ b/frontend/src/pages/settings/sync.vue @@ -59,12 +59,13 @@ @submit.prevent="add"> + class="action-webdav-dialog ml-0" :disabled="demo"> Connect via WebDAV Add Server @@ -91,7 +92,10 @@ import {DateTime} from "luxon"; export default { name: 'p-settings-sync', data() { + const isDemo = this.$config.get("demo"); + return { + demo: isDemo, config: this.$config.values, readonly: this.$config.get("readonly"), settings: new Settings(this.$config.values.settings), diff --git a/frontend/src/routes.js b/frontend/src/routes.js index 6549ee091..f6011612b 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -273,43 +273,29 @@ export default [ name: "settings", path: "/settings", component: Settings, - meta: {title: $gettext("Settings"), auth: true, background: "application-light"}, - props: {tab: 0}, - beforeEnter: (to, from, next) => { - if (c.disable.settings) { - next({name: "home"}); - } else { - next(); - } - }, + meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"}, + props: {tab: "settings-general"}, + }, + { + name: "settings_library", + path: "/settings/library", + component: Settings, + meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"}, + props: {tab: "settings-library"}, }, { name: "settings_sync", path: "/settings/sync", component: Settings, - meta: {title: $gettext("Settings"), auth: true, background: "application-light"}, - props: {tab: 1}, - beforeEnter: (to, from, next) => { - if (c.disable.settings) { - next({name: "home"}); - } else { - next(); - } - }, + meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"}, + props: {tab: "settings-sync"}, }, { name: "settings_account", path: "/settings/account", component: Settings, - meta: {title: $gettext("Settings"), auth: true, background: "application-light"}, - props: {tab: 2}, - beforeEnter: (to, from, next) => { - if (c.disable.settings) { - next({name: "home"}); - } else { - next(); - } - }, + meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"}, + props: {tab: "settings-account"}, }, { name: "discover", diff --git a/frontend/src/share.js b/frontend/src/share.js index d2b5b35d5..19501d6e8 100644 --- a/frontend/src/share.js +++ b/frontend/src/share.js @@ -107,7 +107,9 @@ const router = new Router({ }); router.beforeEach((to, from, next) => { - if (to.matched.some(record => record.meta.admin)) { + if (to.matched.some(record => record.meta.settings) && config.values.disable.settings) { + next({name: "home"}); + } else if (to.matched.some(record => record.meta.admin)) { if (isPublic || session.isAdmin()) { next(); } else { diff --git a/internal/config/auth.go b/internal/config/auth.go index 99929fb3a..e00198fc9 100644 --- a/internal/config/auth.go +++ b/internal/config/auth.go @@ -27,7 +27,7 @@ func (c *Config) CheckPassword(p string) bool { return ap == p } -// InvalidDownloadToken returns true if the token is invalid. +// InvalidDownloadToken tests if the token is invalid. func (c *Config) InvalidDownloadToken(t string) bool { return c.DownloadToken() != t } @@ -41,7 +41,7 @@ func (c *Config) DownloadToken() string { return c.params.DownloadToken } -// InvalidPreviewToken returns true if the preview token is invalid. +// InvalidPreviewToken tests if the preview token is invalid. func (c *Config) InvalidPreviewToken(t string) bool { return c.PreviewToken() != t && c.DownloadToken() != t } diff --git a/internal/config/client.go b/internal/config/client.go index b0e2afeeb..04797aa9d 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -23,6 +23,7 @@ type ClientConfig struct { SiteDescription string `json:"siteDescription"` SiteAuthor string `json:"siteAuthor"` Debug bool `json:"debug"` + Demo bool `json:"demo"` ReadOnly bool `json:"readonly"` UploadNSFW bool `json:"uploadNSFW"` Public bool `json:"public"` @@ -139,8 +140,8 @@ func (c *Config) PublicConfig() ClientConfig { Share: settings.Share, }, Disable: ClientDisable{ - Backups: true, - Settings: true, + Backups: c.DisableBackups(), + Settings: c.DisableSettings(), Places: c.DisablePlaces(), }, Flags: strings.Join(c.Flags(), " "), @@ -154,6 +155,7 @@ func (c *Config) PublicConfig() ClientConfig { Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), + Demo: c.Demo(), ReadOnly: c.ReadOnly(), Public: c.Public(), Experimental: c.Experimental(), @@ -183,8 +185,8 @@ func (c *Config) GuestConfig() ClientConfig { Share: settings.Share, }, Disable: ClientDisable{ - Backups: true, - Settings: true, + Backups: c.DisableBackups(), + Settings: c.DisableSettings(), Places: c.DisablePlaces(), }, Flags: "readonly public shared", @@ -198,6 +200,7 @@ func (c *Config) GuestConfig() ClientConfig { Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), + Demo: c.Demo(), ReadOnly: true, UploadNSFW: c.UploadNSFW(), Public: true, @@ -236,6 +239,7 @@ func (c *Config) UserConfig() ClientConfig { Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), + Demo: c.Demo(), ReadOnly: c.ReadOnly(), UploadNSFW: c.UploadNSFW(), Public: c.Public(), diff --git a/internal/config/config.go b/internal/config/config.go index 1fd98d87a..6665b661a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -199,32 +199,41 @@ func (c *Config) SiteAuthor() string { return c.params.SiteAuthor } -// Debug returns true if Debug mode is on. +// Debug tests if debug mode is enabled. func (c *Config) Debug() bool { return c.params.Debug } -// Public returns true if app requires no authentication. +// Demo tests if demo mode is enabled. +func (c *Config) Demo() bool { + return c.params.Demo +} + +// Public tests if app runs in public mode and requires no authentication. func (c *Config) Public() bool { + if c.Demo() { + return true + } + return c.params.Public } -// Experimental returns true if experimental features should be enabled. +// Experimental tests if experimental features should be enabled. func (c *Config) Experimental() bool { return c.params.Experimental } -// ReadOnly returns true if photo directories are write protected. +// ReadOnly tests if photo directories are write protected. func (c *Config) ReadOnly() bool { return c.params.ReadOnly } -// DetectNSFW returns true if NSFW photos should be detected and flagged. +// DetectNSFW tests if NSFW photos should be detected and flagged. func (c *Config) DetectNSFW() bool { return c.params.DetectNSFW } -// UploadNSFW returns true if NSFW photos can be uploaded. +// UploadNSFW tests if NSFW photos can be uploaded. func (c *Config) UploadNSFW() bool { return c.params.UploadNSFW } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 378ffa83e..98b87205f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -249,6 +249,7 @@ func TestConfig_ClientConfig(t *testing.T) { assert.NotEmpty(t, cc.JSHash) assert.NotEmpty(t, cc.CSSHash) assert.Equal(t, true, cc.Debug) + assert.Equal(t, false, cc.Demo) assert.Equal(t, false, cc.ReadOnly) } diff --git a/internal/config/disable.go b/internal/config/disable.go index 0b2e70469..95e877de1 100644 --- a/internal/config/disable.go +++ b/internal/config/disable.go @@ -9,6 +9,15 @@ func (c *Config) DisableBackups() bool { return c.params.DisableBackups } +// DisableWebDAV tests if the built-in WebDAV server should be disabled. +func (c *Config) DisableWebDAV() bool { + if c.ReadOnly() || c.Demo() { + return true + } + + return c.params.DisableWebDAV +} + // DisableSettings tests if users should not be allowed to change settings. func (c *Config) DisableSettings() bool { return c.params.DisableSettings diff --git a/internal/config/flags.go b/internal/config/flags.go index c39630555..8c51f8d49 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -11,6 +11,12 @@ var GlobalFlags = []cli.Flag{ Usage: "run in debug mode (shows additional log messages)", EnvVar: "PHOTOPRISM_DEBUG", }, + cli.BoolFlag{ + Name: "demo", + Hidden: true, + Usage: "run in demo mode", + EnvVar: "PHOTOPRISM_DEMO", + }, cli.BoolFlag{ Name: "public, p", Usage: "no authentication required (disables password protection)", @@ -97,6 +103,11 @@ var GlobalFlags = []cli.Flag{ Usage: "don't backup photo and album metadata to YAML files", EnvVar: "PHOTOPRISM_DISABLE_BACKUPS", }, + cli.BoolFlag{ + Name: "disable-webdav", + Usage: "disable built-in WebDAV server", + EnvVar: "PHOTOPRISM_DISABLE_WEBDAV", + }, cli.BoolFlag{ Name: "disable-settings", Usage: "users can not view or change settings", diff --git a/internal/config/fs.go b/internal/config/fs.go index 29f0c178b..263703a48 100644 --- a/internal/config/fs.go +++ b/internal/config/fs.go @@ -234,12 +234,12 @@ func (c *Config) SidecarPath() string { return c.params.SidecarPath } -// SidecarPathIsAbs returns true if sidecar path is absolute. +// SidecarPathIsAbs tests if sidecar path is absolute. func (c *Config) SidecarPathIsAbs() bool { return filepath.IsAbs(c.SidecarPath()) } -// SidecarWritable returns true if sidecar files can be created. +// SidecarWritable tests if sidecar files can be created. func (c *Config) SidecarWritable() bool { return !c.ReadOnly() || c.SidecarPathIsAbs() } diff --git a/internal/config/params.go b/internal/config/params.go index 770c9bfd0..7e8bdefd5 100644 --- a/internal/config/params.go +++ b/internal/config/params.go @@ -34,6 +34,7 @@ type Params struct { Version string Copyright string Debug bool `yaml:"Debug" flag:"debug"` + Demo bool `yaml:"Demo" flag:"demo"` Public bool `yaml:"Public" flag:"public"` ReadOnly bool `yaml:"ReadOnly" flag:"read-only"` Experimental bool `yaml:"Experimental" flag:"experimental"` @@ -52,6 +53,7 @@ type Params struct { Workers int `yaml:"Workers" flag:"workers"` WakeupInterval int `yaml:"WakeupInterval" flag:"wakeup-interval"` DisableBackups bool `yaml:"DisableBackups" flag:"disable-backups"` + DisableWebDAV bool `yaml:"DisableWebDAV" flag:"disable-webdav"` DisableSettings bool `yaml:"DisableSettings" flag:"disable-settings"` DisablePlaces bool `yaml:"DisablePlaces" flag:"disable-places"` DisableExifTool bool `yaml:"DisableExifTool" flag:"disable-exiftool"` diff --git a/internal/config/server.go b/internal/config/server.go index 7255001fb..636bc38cb 100644 --- a/internal/config/server.go +++ b/internal/config/server.go @@ -6,7 +6,7 @@ import ( "github.com/photoprism/photoprism/pkg/fs" ) -// DetachServer returns true if server should detach from console (daemon mode). +// DetachServer tests if server should detach from console (daemon mode). func (c *Config) DetachServer() bool { return c.params.DetachServer } @@ -47,7 +47,7 @@ func (c *Config) TemplatesPath() string { return filepath.Join(c.AssetsPath(), "templates") } -// TemplateExists returns true if a template with the given name exists (e.g. index.tmpl). +// TemplateExists tests if a template with the given name exists (e.g. index.tmpl). func (c *Config) TemplateExists(name string) bool { return fs.FileExists(filepath.Join(c.TemplatesPath(), name)) } diff --git a/internal/server/routes.go b/internal/server/routes.go index 194bda9b7..e7006732a 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -127,14 +127,16 @@ func registerRoutes(router *gin.Engine, conf *config.Config) { } // WebDAV server for file management, sync and sharing. - WebDAV(conf.OriginalsPath(), router.Group("/originals", BasicAuth()), conf) - log.Info("webdav: /originals/ waiting for connection") - - if conf.ReadOnly() { - log.Info("webdav: /import/ not available in read-only mode") + if conf.DisableWebDAV() { + log.Info("webdav: server disabled") } else { - WebDAV(conf.ImportPath(), router.Group("/import", BasicAuth()), conf) - log.Info("webdav: /import/ waiting for connection") + WebDAV(conf.OriginalsPath(), router.Group("/originals", BasicAuth()), conf) + log.Info("webdav: /originals/ enabled, waiting for requests") + + if conf.ImportPath() != "" { + WebDAV(conf.ImportPath(), router.Group("/import", BasicAuth()), conf) + log.Info("webdav: /import/ enabled, waiting for requests") + } } // Default HTML page for client-side rendering and routing via VueJS.