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.