From fda8a992929b1466e75fb2813f2c4e293c12d244 Mon Sep 17 00:00:00 2001 From: Beckam White <47697544+BJDubb@users.noreply.github.com> Date: Sat, 10 Jan 2026 20:27:54 +1100 Subject: [PATCH] feat: add "redirect after copy/move" user setting (#5662) --- auth/hook.go | 37 ++++++++++--------- cmd/config.go | 1 + cmd/root.go | 9 ++--- cmd/users.go | 6 +++- cmd/users_update.go | 16 +++++---- frontend/src/components/prompts/Copy.vue | 3 +- frontend/src/components/prompts/Move.vue | 6 ++-- frontend/src/i18n/en.json | 1 + frontend/src/types/settings.d.ts | 1 + frontend/src/types/user.d.ts | 2 ++ frontend/src/views/settings/Profile.vue | 12 +++++++ http/auth.go | 46 ++++++++++++------------ settings/defaults.go | 22 ++++++------ users/users.go | 33 ++++++++--------- www/docs/cli/filebrowser-config-init.md | 1 + www/docs/cli/filebrowser-config-set.md | 1 + www/docs/cli/filebrowser-users-add.md | 1 + www/docs/cli/filebrowser-users-update.md | 1 + 18 files changed, 119 insertions(+), 80 deletions(-) diff --git a/auth/hook.go b/auth/hook.go index 9bb38969..0c5efac5 100644 --- a/auth/hook.go +++ b/auth/hook.go @@ -158,16 +158,17 @@ func (a *HookAuth) SaveUser() (*users.User, error) { // create user with the provided credentials d := &users.User{ - Username: a.Cred.Username, - Password: pass, - Scope: a.Settings.Defaults.Scope, - Locale: a.Settings.Defaults.Locale, - ViewMode: a.Settings.Defaults.ViewMode, - SingleClick: a.Settings.Defaults.SingleClick, - Sorting: a.Settings.Defaults.Sorting, - Perm: a.Settings.Defaults.Perm, - Commands: a.Settings.Defaults.Commands, - HideDotfiles: a.Settings.Defaults.HideDotfiles, + Username: a.Cred.Username, + Password: pass, + Scope: a.Settings.Defaults.Scope, + Locale: a.Settings.Defaults.Locale, + ViewMode: a.Settings.Defaults.ViewMode, + SingleClick: a.Settings.Defaults.SingleClick, + RedirectAfterCopyMove: a.Settings.Defaults.RedirectAfterCopyMove, + Sorting: a.Settings.Defaults.Sorting, + Perm: a.Settings.Defaults.Perm, + Commands: a.Settings.Defaults.Commands, + HideDotfiles: a.Settings.Defaults.HideDotfiles, } u = a.GetUser(d) @@ -219,13 +220,14 @@ func (a *HookAuth) GetUser(d *users.User) *users.User { Download: isAdmin || a.Fields.GetBoolean("user.perm.download", d.Perm.Download), } user := users.User{ - ID: d.ID, - Username: d.Username, - Password: d.Password, - Scope: a.Fields.GetString("user.scope", d.Scope), - Locale: a.Fields.GetString("user.locale", d.Locale), - ViewMode: users.ViewMode(a.Fields.GetString("user.viewMode", string(d.ViewMode))), - SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick), + ID: d.ID, + Username: d.Username, + Password: d.Password, + Scope: a.Fields.GetString("user.scope", d.Scope), + Locale: a.Fields.GetString("user.locale", d.Locale), + ViewMode: users.ViewMode(a.Fields.GetString("user.viewMode", string(d.ViewMode))), + SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick), + RedirectAfterCopyMove: a.Fields.GetBoolean("user.redirectAfterCopyMove", d.RedirectAfterCopyMove), Sorting: files.Sorting{ Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc), By: a.Fields.GetString("user.sorting.by", d.Sorting.By), @@ -251,6 +253,7 @@ var validHookFields = []string{ "user.locale", "user.viewMode", "user.singleClick", + "user.redirectAfterCopyMove", "user.sorting.by", "user.sorting.asc", "user.commands", diff --git a/cmd/config.go b/cmd/config.go index 3fb2e7e4..5b3314ed 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -240,6 +240,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale) fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode) fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick) + fmt.Fprintf(w, "\tRedirect after Copy/Move:\t%t\n", set.Defaults.RedirectAfterCopyMove) fmt.Fprintf(w, "\tFile Creation Mode:\t%O\n", set.FileMode) fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode) fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " ")) diff --git a/cmd/root.go b/cmd/root.go index 0f78535c..981eec4f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -393,10 +393,11 @@ func quickSetup(v *viper.Viper, s *storage.Storage) error { MinimumPasswordLength: settings.DefaultMinimumPasswordLength, UserHomeBasePath: settings.DefaultUsersHomeBasePath, Defaults: settings.UserDefaults{ - Scope: ".", - Locale: "en", - SingleClick: false, - AceEditorTheme: v.GetString("defaults.aceEditorTheme"), + Scope: ".", + Locale: "en", + SingleClick: false, + RedirectAfterCopyMove: true, + AceEditorTheme: v.GetString("defaults.aceEditorTheme"), Perm: users.Permissions{ Admin: false, Execute: true, diff --git a/cmd/users.go b/cmd/users.go index 86434a42..66487862 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -30,13 +30,14 @@ func printUsers(usrs []*users.User) { fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock") for _, u := range usrs { - fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n", + fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n", u.ID, u.Username, u.Scope, u.Locale, u.ViewMode, u.SingleClick, + u.RedirectAfterCopyMove, u.Perm.Admin, u.Perm.Execute, u.Perm.Create, @@ -77,6 +78,7 @@ func addUserFlags(flags *pflag.FlagSet) { flags.String("locale", "en", "locale for users") flags.String("viewMode", string(users.ListViewMode), "view mode for users") flags.Bool("singleClick", false, "use single clicks only") + flags.Bool("redirectAfterCopyMove", false, "redirect to destination after copy/move") flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)") flags.Bool("hideDotfiles", false, "hide dotfiles") flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users") @@ -110,6 +112,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all defaults.ViewMode, err = getAndParseViewMode(flags) case "singleClick": defaults.SingleClick, err = flags.GetBool(flag.Name) + case "redirectAfterCopyMove": + defaults.RedirectAfterCopyMove, err = flags.GetBool(flag.Name) case "aceEditorTheme": defaults.AceEditorTheme, err = flags.GetString(flag.Name) case "perm.admin": diff --git a/cmd/users_update.go b/cmd/users_update.go index 96f1e2d3..e9a484fc 100644 --- a/cmd/users_update.go +++ b/cmd/users_update.go @@ -52,13 +52,14 @@ options you want to change.`, } defaults := settings.UserDefaults{ - Scope: user.Scope, - Locale: user.Locale, - ViewMode: user.ViewMode, - SingleClick: user.SingleClick, - Perm: user.Perm, - Sorting: user.Sorting, - Commands: user.Commands, + Scope: user.Scope, + Locale: user.Locale, + ViewMode: user.ViewMode, + SingleClick: user.SingleClick, + RedirectAfterCopyMove: user.RedirectAfterCopyMove, + Perm: user.Perm, + Sorting: user.Sorting, + Commands: user.Commands, } err = getUserDefaults(flags, &defaults, false) @@ -70,6 +71,7 @@ options you want to change.`, user.Locale = defaults.Locale user.ViewMode = defaults.ViewMode user.SingleClick = defaults.SingleClick + user.RedirectAfterCopyMove = defaults.RedirectAfterCopyMove user.Perm = defaults.Perm user.Commands = defaults.Commands user.Sorting = defaults.Sorting diff --git a/frontend/src/components/prompts/Copy.vue b/frontend/src/components/prompts/Copy.vue index 9b5e4c63..09040e0a 100644 --- a/frontend/src/components/prompts/Copy.vue +++ b/frontend/src/components/prompts/Copy.vue @@ -109,7 +109,8 @@ export default { return; } - this.$router.push({ path: this.dest }); + if (this.user.redirectAfterCopyMove) + this.$router.push({ path: this.dest }); }) .catch((e) => { buttons.done("copy"); diff --git a/frontend/src/components/prompts/Move.vue b/frontend/src/components/prompts/Move.vue index d92e4b6e..0fec8679 100644 --- a/frontend/src/components/prompts/Move.vue +++ b/frontend/src/components/prompts/Move.vue @@ -79,7 +79,7 @@ export default { computed: { ...mapState(useFileStore, ["req", "selected"]), ...mapState(useAuthStore, ["user"]), - ...mapWritableState(useFileStore, ["preselect"]), + ...mapWritableState(useFileStore, ["reload", "preselect"]), excludedFolders() { return this.selected .filter((idx) => this.req.items[idx].isDir) @@ -108,7 +108,9 @@ export default { .then(() => { buttons.success("move"); this.preselect = removePrefix(items[0].to); - this.$router.push({ path: this.dest }); + if (this.user.redirectAfterCopyMove) + this.$router.push({ path: this.dest }); + else this.reload = true; }) .catch((e) => { buttons.done("move"); diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 60fe527f..3fcdf635 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -232,6 +232,7 @@ "permissions": "Permissions", "permissionsHelp": "You can set the user to be an administrator or choose the permissions individually. If you select \"Administrator\", all of the other options will be automatically checked. The management of users remains a privilege of an administrator.\n", "profileSettings": "Profile Settings", + "redirectAfterCopyMove": "Redirect to destination after copy/move", "ruleExample1": "prevents the access to any dotfile (such as .git, .gitignore) in every folder.\n", "ruleExample2": "blocks the access to the file named Caddyfile on the root of the scope.", "rules": "Rules", diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts index c213e016..18672bdf 100644 --- a/frontend/src/types/settings.d.ts +++ b/frontend/src/types/settings.d.ts @@ -18,6 +18,7 @@ interface SettingsDefaults { locale: string; viewMode: ViewModeType; singleClick: boolean; + redirectAfterCopyMove: boolean; sorting: Sorting; perm: Permissions; commands: any[]; diff --git a/frontend/src/types/user.d.ts b/frontend/src/types/user.d.ts index 40c453c5..317f1e43 100644 --- a/frontend/src/types/user.d.ts +++ b/frontend/src/types/user.d.ts @@ -10,6 +10,7 @@ interface IUser { lockPassword: boolean; hideDotfiles: boolean; singleClick: boolean; + redirectAfterCopyMove: boolean; dateFormat: boolean; viewMode: ViewModeType; sorting?: Sorting; @@ -30,6 +31,7 @@ interface IUserForm { lockPassword?: boolean; hideDotfiles?: boolean; singleClick?: boolean; + redirectAfterCopyMove?: boolean; dateFormat?: boolean; } diff --git a/frontend/src/views/settings/Profile.vue b/frontend/src/views/settings/Profile.vue index 8aafccb6..ffb20f0d 100644 --- a/frontend/src/views/settings/Profile.vue +++ b/frontend/src/views/settings/Profile.vue @@ -15,6 +15,14 @@ {{ t("settings.singleClick") }}

+

+ + {{ t("settings.redirectAfterCopyMove") }} +

{{ t("settings.setDateFormat") }} @@ -116,6 +124,7 @@ const currentPassword = ref(""); const isCurrentPasswordRequired = ref(false); const hideDotfiles = ref(false); const singleClick = ref(false); +const redirectAfterCopyMove = ref(false); const dateFormat = ref(false); const locale = ref(""); const aceEditorTheme = ref(""); @@ -140,6 +149,7 @@ onMounted(async () => { locale.value = authStore.user.locale; hideDotfiles.value = authStore.user.hideDotfiles; singleClick.value = authStore.user.singleClick; + redirectAfterCopyMove.value = authStore.user.redirectAfterCopyMove; dateFormat.value = authStore.user.dateFormat; aceEditorTheme.value = authStore.user.aceEditorTheme; layoutStore.loading = false; @@ -187,6 +197,7 @@ const updateSettings = async (event: Event) => { locale: locale.value, hideDotfiles: hideDotfiles.value, singleClick: singleClick.value, + redirectAfterCopyMove: redirectAfterCopyMove.value, dateFormat: dateFormat.value, aceEditorTheme: aceEditorTheme.value, }; @@ -195,6 +206,7 @@ const updateSettings = async (event: Event) => { "locale", "hideDotfiles", "singleClick", + "redirectAfterCopyMove", "dateFormat", "aceEditorTheme", ]); diff --git a/http/auth.go b/http/auth.go index 5a483a8d..4eceeafe 100644 --- a/http/auth.go +++ b/http/auth.go @@ -23,17 +23,18 @@ const ( ) type userInfo struct { - ID uint `json:"id"` - Locale string `json:"locale"` - ViewMode users.ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Perm users.Permissions `json:"perm"` - Commands []string `json:"commands"` - LockPassword bool `json:"lockPassword"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` - Username string `json:"username"` - AceEditorTheme string `json:"aceEditorTheme"` + ID uint `json:"id"` + Locale string `json:"locale"` + ViewMode users.ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + RedirectAfterCopyMove bool `json:"redirectAfterCopyMove"` + Perm users.Permissions `json:"perm"` + Commands []string `json:"commands"` + LockPassword bool `json:"lockPassword"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + Username string `json:"username"` + AceEditorTheme string `json:"aceEditorTheme"` } type authToken struct { @@ -204,17 +205,18 @@ func renewHandler(tokenExpireTime time.Duration) handleFunc { func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User, tokenExpirationTime time.Duration) (int, error) { claims := &authToken{ User: userInfo{ - ID: user.ID, - Locale: user.Locale, - ViewMode: user.ViewMode, - SingleClick: user.SingleClick, - Perm: user.Perm, - LockPassword: user.LockPassword, - Commands: user.Commands, - HideDotfiles: user.HideDotfiles, - DateFormat: user.DateFormat, - Username: user.Username, - AceEditorTheme: user.AceEditorTheme, + ID: user.ID, + Locale: user.Locale, + ViewMode: user.ViewMode, + SingleClick: user.SingleClick, + RedirectAfterCopyMove: user.RedirectAfterCopyMove, + Perm: user.Perm, + LockPassword: user.LockPassword, + Commands: user.Commands, + HideDotfiles: user.HideDotfiles, + DateFormat: user.DateFormat, + Username: user.Username, + AceEditorTheme: user.AceEditorTheme, }, RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(time.Now()), diff --git a/settings/defaults.go b/settings/defaults.go index 5b6c3f2a..46024179 100644 --- a/settings/defaults.go +++ b/settings/defaults.go @@ -8,16 +8,17 @@ import ( // UserDefaults is a type that holds the default values // for some fields on User. type UserDefaults struct { - Scope string `json:"scope"` - Locale string `json:"locale"` - ViewMode users.ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Sorting files.Sorting `json:"sorting"` - Perm users.Permissions `json:"perm"` - Commands []string `json:"commands"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` - AceEditorTheme string `json:"aceEditorTheme"` + Scope string `json:"scope"` + Locale string `json:"locale"` + ViewMode users.ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + RedirectAfterCopyMove bool `json:"redirectAfterCopyMove"` + Sorting files.Sorting `json:"sorting"` + Perm users.Permissions `json:"perm"` + Commands []string `json:"commands"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + AceEditorTheme string `json:"aceEditorTheme"` } // Apply applies the default options to a user. @@ -26,6 +27,7 @@ func (d *UserDefaults) Apply(u *users.User) { u.Locale = d.Locale u.ViewMode = d.ViewMode u.SingleClick = d.SingleClick + u.RedirectAfterCopyMove = d.RedirectAfterCopyMove u.Perm = d.Perm u.Sorting = d.Sorting u.Commands = d.Commands diff --git a/users/users.go b/users/users.go index 0fcc26d8..7181b299 100644 --- a/users/users.go +++ b/users/users.go @@ -20,22 +20,23 @@ const ( // User describes a user. type User struct { - ID uint `storm:"id,increment" json:"id"` - Username string `storm:"unique" json:"username"` - Password string `json:"password"` - Scope string `json:"scope"` - Locale string `json:"locale"` - LockPassword bool `json:"lockPassword"` - ViewMode ViewMode `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Perm Permissions `json:"perm"` - Commands []string `json:"commands"` - Sorting files.Sorting `json:"sorting"` - Fs afero.Fs `json:"-" yaml:"-"` - Rules []rules.Rule `json:"rules"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` - AceEditorTheme string `json:"aceEditorTheme"` + ID uint `storm:"id,increment" json:"id"` + Username string `storm:"unique" json:"username"` + Password string `json:"password"` + Scope string `json:"scope"` + Locale string `json:"locale"` + LockPassword bool `json:"lockPassword"` + ViewMode ViewMode `json:"viewMode"` + SingleClick bool `json:"singleClick"` + RedirectAfterCopyMove bool `json:"redirectAfterCopyMove"` + Perm Permissions `json:"perm"` + Commands []string `json:"commands"` + Sorting files.Sorting `json:"sorting"` + Fs afero.Fs `json:"-" yaml:"-"` + Rules []rules.Rule `json:"rules"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + AceEditorTheme string `json:"aceEditorTheme"` } // GetRules implements rules.Provider. diff --git a/www/docs/cli/filebrowser-config-init.md b/www/docs/cli/filebrowser-config-init.md index f4ff829f..0f16efa7 100644 --- a/www/docs/cli/filebrowser-config-init.md +++ b/www/docs/cli/filebrowser-config-init.md @@ -61,6 +61,7 @@ filebrowser config init [flags] --recaptcha.host string use another host for ReCAPTCHA. recaptcha.net might be useful in China (default "https://www.google.com") --recaptcha.key string ReCaptcha site key --recaptcha.secret string ReCaptcha secret + --redirectAfterCopyMove redirect to destination after copy/move -r, --root string root to prepend to relative paths (default ".") --scope string scope for users (default ".") --shell string shell command to which other commands should be appended diff --git a/www/docs/cli/filebrowser-config-set.md b/www/docs/cli/filebrowser-config-set.md index 5b166d7d..603bb506 100644 --- a/www/docs/cli/filebrowser-config-set.md +++ b/www/docs/cli/filebrowser-config-set.md @@ -58,6 +58,7 @@ filebrowser config set [flags] --recaptcha.host string use another host for ReCAPTCHA. recaptcha.net might be useful in China (default "https://www.google.com") --recaptcha.key string ReCaptcha site key --recaptcha.secret string ReCaptcha secret + --redirectAfterCopyMove redirect to destination after copy/move -r, --root string root to prepend to relative paths (default ".") --scope string scope for users (default ".") --shell string shell command to which other commands should be appended diff --git a/www/docs/cli/filebrowser-users-add.md b/www/docs/cli/filebrowser-users-add.md index c6c5b53d..1de95e4d 100644 --- a/www/docs/cli/filebrowser-users-add.md +++ b/www/docs/cli/filebrowser-users-add.md @@ -28,6 +28,7 @@ filebrowser users add [flags] --perm.modify modify perm for users (default true) --perm.rename rename perm for users (default true) --perm.share share perm for users (default true) + --redirectAfterCopyMove redirect to destination after copy/move --scope string scope for users (default ".") --singleClick use single clicks only --sorting.asc sorting by ascending order diff --git a/www/docs/cli/filebrowser-users-update.md b/www/docs/cli/filebrowser-users-update.md index 996bdac7..4ba7d608 100644 --- a/www/docs/cli/filebrowser-users-update.md +++ b/www/docs/cli/filebrowser-users-update.md @@ -30,6 +30,7 @@ filebrowser users update [flags] --perm.modify modify perm for users (default true) --perm.rename rename perm for users (default true) --perm.share share perm for users (default true) + --redirectAfterCopyMove redirect to destination after copy/move --scope string scope for users (default ".") --singleClick use single clicks only --sorting.asc sorting by ascending order