Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a3160399b | ||
|
|
71b718daff | ||
|
|
e776260680 | ||
|
|
e033af1305 | ||
|
|
ddd4ce2b75 | ||
|
|
9c618c2d4c | ||
|
|
4a461e5199 | ||
|
|
2db27856e0 | ||
|
|
ec2654a774 | ||
|
|
4e441168d7 |
No files matched your search
@@ -2,6 +2,32 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||
|
||||
## v0.7.10-beta
|
||||
|
||||
**OIDC change**: if you specify `oidc.userIdentifier: "username"`, originally this would map to `preferred_username` but now it maps to `username` explicitly. To maintain the same behavior update your config to `userIdentifier: "preferred_username"`. This was updated to allow for `username` to work as [some might need](https://github.com/gtsteffaniak/filebrowser/pull/789).
|
||||
|
||||
**New Features**:
|
||||
- Added settings option to stop sidebar from automatically hiding on editor and previews. https://github.com/gtsteffaniak/filebrowser/issues/744
|
||||
- Added more secrets loadable from environment variables. https://github.com/gtsteffaniak/filebrowser/issues/790
|
||||
- Include/exclude files are checked for existence to assist with configuration, will show as warning if something is configured but doesn't exist.
|
||||
- Added open in new tab link for preview items to view the raw picture, pdf, etc. Especially helpful for safari viewing PDF documents. https://github.com/gtsteffaniak/filebrowser/issues/734
|
||||
- Added autoplay media toggle in user profile, to automatically play videos and audio.
|
||||
|
||||
**Notes**:
|
||||
- Allowed to delete default admin user https://github.com/gtsteffaniak/filebrowser/issues/811 https://github.com/gtsteffaniak/filebrowser/issues/762
|
||||
- Better try/catch error handling for user feedback for shares https://github.com/gtsteffaniak/filebrowser/issues/732
|
||||
|
||||
**BugFixes**:
|
||||
- Fix share scope creation issue https://github.com/gtsteffaniak/filebrowser/issues/809
|
||||
- Fix oidc token logout issue https://github.com/gtsteffaniak/filebrowser/issues/791
|
||||
- Non-admin users OTP issue https://github.com/gtsteffaniak/filebrowser/issues/815
|
||||
- Linewrap issue for a few cases https://github.com/gtsteffaniak/filebrowser/issues/810
|
||||
- BaseUrl redirect issue with proxies https://github.com/gtsteffaniak/filebrowser/issues/796
|
||||
- Fix exclude still shows up in ui issue https://github.com/gtsteffaniak/filebrowser/issues/797
|
||||
- Copy/move functions are async https://github.com/gtsteffaniak/filebrowser/issues/812
|
||||
- fix subtitle fetch issue https://github.com/gtsteffaniak/filebrowser/issues/766
|
||||
- fix location memory issue for url encoded file names
|
||||
|
||||
## v0.7.9-beta
|
||||
|
||||
**New Features**:
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ Thank you for your interest in contributing to FileBrowser Quantum! This guide w
|
||||
- Ubuntu/Debian: `sudo apt-get install ffmpeg`
|
||||
- macOS: `brew install ffmpeg`
|
||||
- Windows: Download from [ffmpeg.org](https://ffmpeg.org/download.html)
|
||||
|
||||
|
||||
- **mupdf-tools**: Required for PDF preview generation
|
||||
- Ubuntu/Debian: `sudo apt-get install mupdf-tools`
|
||||
- macOS: `brew install mupdf-tools`
|
||||
|
||||
@@ -5,6 +5,11 @@ server:
|
||||
- levels: "info|error|debug"
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
config:
|
||||
exclude:
|
||||
folders:
|
||||
- "excluded"
|
||||
|
||||
auth:
|
||||
methods:
|
||||
noauth: true
|
||||
|
||||
@@ -76,7 +76,7 @@ func GenerateOtpForUser(user *users.User, userStore *users.Storage) (string, err
|
||||
}
|
||||
// set cache so verify can attempt to use it but not require it for user yet.
|
||||
TotpCache.Set(user.Username, secretText+"||"+nonce)
|
||||
url := "otpauth://totp/FileBrowser%20Quantum?secret=" + secretText
|
||||
url := fmt.Sprintf("otpauth://totp/%v?secret=%v", "FileBrowser Quantum: "+user.Username, secretText)
|
||||
return url, nil
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ func VerifyTotpCode(user *users.User, code string, userStore *users.Storage) err
|
||||
user.TOTPNonce = totpNonce // The nonce if encrypted, or empty if plaintext
|
||||
user.OtpEnabled = true // Enable OTP for the user
|
||||
// save user
|
||||
if err := userStore.Update(user, user.Permissions.Admin, "TOTPSecret", "TOTPNonce"); err != nil {
|
||||
if err := userStore.Update(user, true, "TOTPSecret", "TOTPNonce"); err != nil {
|
||||
logger.Error("error updating user with OTP token:", err)
|
||||
return fmt.Errorf("error updating user with OTP token: %w", err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ func runCLI() bool {
|
||||
var asAdmin bool
|
||||
|
||||
setCmd.StringVar(&user, "u", "", "Comma-separated username and password: \"set -u <username>,<password>\"")
|
||||
setCmd.BoolVar(&asAdmin, "a", false, "Create user as admin user, used in combination with -u")
|
||||
setCmd.BoolVar(&asAdmin, "a", false, "Create a user as admin user, used in combination with -u")
|
||||
setCmd.StringVar(&scope, "s", "", "Specify a user scope, otherwise default user config scope is used")
|
||||
setCmd.StringVar(&dbConfig, "c", "config.yaml", "Path to the config file, default: config.yaml")
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ var (
|
||||
ErrPermissionDenied = errors.New("permission denied")
|
||||
ErrInvalidRequestParams = errors.New("invalid request params")
|
||||
ErrSourceIsParent = errors.New("source is parent")
|
||||
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
|
||||
ErrNoTotpProvided = errors.New("OTP code is required for user")
|
||||
ErrNoTotpConfigured = errors.New("OTP is enforced, but user is not yet configured")
|
||||
ErrUnauthorized = errors.New("user unauthorized")
|
||||
|
||||
@@ -57,7 +57,7 @@ type OidcConfig struct {
|
||||
ClientSecret string `json:"clientSecret"` // client secret of the OIDC application
|
||||
IssuerUrl string `json:"issuerUrl"` // authorization URL of the OIDC provider
|
||||
Scopes string `json:"scopes"` // scopes to request from the OIDC provider
|
||||
UserIdentifier string `json:"userIdentifier"` // the user identifier to use for authentication. Default is "username", can be "email" or "username", or "phone"
|
||||
UserIdentifier string `json:"userIdentifier"` // the field value to use as the username. Default is "preferred_username", can also be "email" or "username", or "phone"
|
||||
DisableVerifyTLS bool `json:"disableVerifyTLS"` // disable TLS verification for the OIDC provider. This is insecure and should only be used for testing.
|
||||
LogoutRedirectUrl string `json:"logoutRedirectUrl"` // if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected.
|
||||
CreateUser bool `json:"createUser"` // create user if not exists
|
||||
@@ -73,7 +73,7 @@ func validateOidcAuth() error {
|
||||
return errors.New("OIDC is not enabled")
|
||||
}
|
||||
if oidcCfg.UserIdentifier == "" {
|
||||
oidcCfg.UserIdentifier = "username"
|
||||
oidcCfg.UserIdentifier = "preferred_username"
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -97,6 +97,8 @@ func setupSources(generate bool) {
|
||||
Config.Server.DefaultSource = source
|
||||
}
|
||||
}
|
||||
modifyExcludeInclude(&source)
|
||||
|
||||
if source.Config.DefaultUserScope == "" {
|
||||
source.Config.DefaultUserScope = "/"
|
||||
}
|
||||
@@ -226,7 +228,9 @@ func loadConfigWithDefaults(configFile string) error {
|
||||
// Open and read the YAML file
|
||||
yamlFile, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
logger.Errorf("could not open config file '%v', using default settings.", configFile)
|
||||
if configFile != "" {
|
||||
logger.Errorf("could not open config file '%v', using default settings.", configFile)
|
||||
}
|
||||
Config.Server.Sources = []Source{
|
||||
{
|
||||
Path: ".",
|
||||
@@ -267,7 +271,6 @@ func ValidateConfig(config Settings) error {
|
||||
}
|
||||
|
||||
func loadEnvConfig() {
|
||||
|
||||
adminPassword, ok := os.LookupEnv("FILEBROWSER_ADMIN_PASSWORD")
|
||||
if ok {
|
||||
logger.Info("Using admin password from FILEBROWSER_ADMIN_PASSWORD environment variable")
|
||||
@@ -284,6 +287,36 @@ func loadEnvConfig() {
|
||||
Config.Integrations.Media.FfmpegPath = ffmpegPath
|
||||
}
|
||||
|
||||
oidcClientId := os.Getenv("FILEBROWSER_OIDC_CLIENT_ID")
|
||||
if oidcClientId != "" {
|
||||
Config.Auth.Methods.OidcAuth.ClientID = oidcClientId
|
||||
logger.Info("Using OIDC Client ID from FILEBROWSER_OIDC_CLIENT_ID environment variable")
|
||||
}
|
||||
|
||||
oidcClientSecret := os.Getenv("FILEBROWSER_OIDC_CLIENT_SECRET")
|
||||
if oidcClientSecret != "" {
|
||||
Config.Auth.Methods.OidcAuth.ClientSecret = oidcClientSecret
|
||||
logger.Info("Using OIDC Client Secret from FILEBROWSER_OIDC_CLIENT_SECRET environment variable")
|
||||
}
|
||||
|
||||
jwtTokenSecret := os.Getenv("FILEBROWSER_JWT_TOKEN_SECRET")
|
||||
if jwtTokenSecret != "" {
|
||||
Config.Auth.Key = jwtTokenSecret
|
||||
logger.Info("Using JWT Token Secret from FILEBROWSER_JWT_TOKEN_SECRET environment variable")
|
||||
}
|
||||
|
||||
totpSecret := os.Getenv("FILEBROWSER_TOTP_SECRET")
|
||||
if totpSecret != "" {
|
||||
Config.Auth.TotpSecret = totpSecret
|
||||
logger.Info("Using TOTP Secret from FILEBROWSER_TOTP_SECRET environment variable")
|
||||
}
|
||||
|
||||
recaptchaSecret := os.Getenv("FILEBROWSER_RECAPTCHA_SECRET")
|
||||
if recaptchaSecret != "" {
|
||||
Config.Auth.Methods.PasswordAuth.Recaptcha.Secret = recaptchaSecret
|
||||
logger.Info("Using ReCaptcha Secret from FILEBROWSER_RECAPTCHA_SECRET environment variable")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setDefaults() Settings {
|
||||
@@ -450,3 +483,28 @@ func GetSources(u *users.User) []string {
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
func modifyExcludeInclude(config *Source) {
|
||||
normalize := func(s []string, checkExists bool) {
|
||||
for i, v := range s {
|
||||
s[i] = "/" + strings.Trim(v, "/")
|
||||
// check if file/folder exists
|
||||
if checkExists {
|
||||
realPath, err := filepath.Abs(config.Path + s[i])
|
||||
if err != nil {
|
||||
logger.Warningf("could not get absolute path for %v: %v", s[i], err)
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(realPath); os.IsNotExist(err) {
|
||||
logger.Warningf("configured exclude/include path %v does not exist", realPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normalize(config.Config.Exclude.Files, true)
|
||||
normalize(config.Config.Exclude.Folders, true)
|
||||
normalize(config.Config.Include.Files, true)
|
||||
normalize(config.Config.Include.Folders, true)
|
||||
|
||||
}
|
||||
@@ -92,9 +92,9 @@ type SourceConfig struct {
|
||||
}
|
||||
|
||||
type IndexFilter struct {
|
||||
Files []string `json:"files"` // array of file names to include/exclude
|
||||
Folders []string `json:"folders"` // array of folder names to include/exclude
|
||||
FileEndsWith []string `json:"fileEndsWith"` // array of file names to include/exclude (eg "a.jpg")
|
||||
Files []string `json:"files"` // list of file names to include/exclude Eg. "folder1" or "file1.txt" or "folder1/file1.txt" (do not include source path, just the subpaths from the source path)
|
||||
Folders []string `json:"folders"` // list of folder names to include/exclude. Eg. "folder1" or "folder1/subfolder" (do not include source path, just the subpaths from the source path)
|
||||
FileEndsWith []string `json:"fileEndsWith"` // list of file names to include/exclude. Eg. "a.jpg"
|
||||
}
|
||||
|
||||
type Frontend struct {
|
||||
|
||||
@@ -124,18 +124,8 @@ func (s *Storage) Save(user *User, changePass, disableScopeChange bool) error {
|
||||
func (s *Storage) Delete(id interface{}) error {
|
||||
switch id := id.(type) {
|
||||
case string:
|
||||
user, err := s.back.GetBy(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.ID == 1 {
|
||||
return errors.ErrRootUserDeletion
|
||||
}
|
||||
return s.back.DeleteByUsername(id)
|
||||
case uint:
|
||||
if id == 1 {
|
||||
return errors.ErrRootUserDeletion
|
||||
}
|
||||
return s.back.DeleteByID(id)
|
||||
default:
|
||||
return errors.ErrInvalidDataType
|
||||
|
||||
@@ -41,12 +41,14 @@ type Sorting struct {
|
||||
}
|
||||
|
||||
type Preview struct {
|
||||
DisableHideSidebar bool `json:"disableHideSidebar"` // disable the hide sidebar preview for previews and editors
|
||||
HighQuality bool `json:"highQuality"` // generate high quality preview images
|
||||
Image bool `json:"image"` // show real image as icon instead of generic photo icon
|
||||
Video bool `json:"video"` // show preview image for video files
|
||||
MotionVideoPreview bool `json:"motionVideoPreview"` // show multiple frames for videos in preview when hovering
|
||||
Office bool `json:"office"` // show preview image for office files
|
||||
PopUp bool `json:"popup"` // show larger popup preview when hovering
|
||||
AutoplayMedia bool `json:"autoplayMedia"` // autoplay media files in preview
|
||||
}
|
||||
|
||||
// User describes a user.
|
||||
@@ -91,6 +93,7 @@ type NonAdminEditable struct {
|
||||
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"` // comma separated list of file extensions to disable onlyoffice preview for
|
||||
DisableOfficePreviewExt string `json:"disableOfficePreviewExt"` // comma separated list of file extensions to disable office preview for
|
||||
DisableUpdateNotifications bool `json:"disableUpdateNotifications"` // disable update notifications
|
||||
|
||||
}
|
||||
|
||||
var PublicUser = User{
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ require (
|
||||
github.com/asdine/storm/v3 v3.2.1
|
||||
github.com/coreos/go-oidc/v3 v3.14.1
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
github.com/gen2brain/go-fitz v1.24.14
|
||||
github.com/gen2brain/go-fitz v1.24.15
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
@@ -22,6 +22,7 @@ require (
|
||||
github.com/swaggo/swag v1.16.4
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/image v0.28.0
|
||||
golang.org/x/mod v0.25.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sys v0.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -238,7 +239,6 @@ require (
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
|
||||
+2
-3
@@ -204,8 +204,8 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gen2brain/go-fitz v1.24.14 h1:09weRkjVtLYNGo7l0J7DyOwBExbwi8SJ9h8YPhw9WEo=
|
||||
github.com/gen2brain/go-fitz v1.24.14/go.mod h1:0KaZeQgASc20Yp5R/pFzyy7SmP01XcoHKNF842U2/S4=
|
||||
github.com/gen2brain/go-fitz v1.24.15 h1:sJNB1MOWkqnzzENPHggFpgxTwW0+S5WF/rM5wUBpJWo=
|
||||
github.com/gen2brain/go-fitz v1.24.15/go.mod h1:SftkiVbTHqF141DuiLwBBM65zP7ig6AVDQpf2WlHamo=
|
||||
github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY=
|
||||
github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA=
|
||||
github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY=
|
||||
@@ -327,7 +327,6 @@ github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUP
|
||||
github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s=
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
|
||||
github.com/golangci/golangci-lint v1.64.8 h1:y5TdeVidMtBGG32zgSC7ZXTFNHrsJkDnpO4ItB3Am+I=
|
||||
github.com/golangci/golangci-lint/v2 v2.1.6 h1:LXqShFfAGM5BDzEOWD2SL1IzJAgUOqES/HRBsfKjI+w=
|
||||
github.com/golangci/golangci-lint/v2 v2.1.6/go.mod h1:EPj+fgv4TeeBq3TcqaKZb3vkiV5dP4hHHKhXhEhzci8=
|
||||
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8=
|
||||
|
||||
+6
-20
@@ -50,7 +50,6 @@ func extractToken(r *http.Request) (string, error) {
|
||||
// Check for Authorization header
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader != "" {
|
||||
|
||||
hasToken = true
|
||||
// Split the header to get "Bearer {token}"
|
||||
parts := strings.Split(authHeader, " ")
|
||||
@@ -129,33 +128,20 @@ func loginHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (in
|
||||
// @Description logs a user out of the application.
|
||||
// @Tags Auth
|
||||
// @Success 302 {string} string "Redirect to redirect URL if configured in oidc config."
|
||||
// @Router /api/auth/logout [get]
|
||||
// @Router /api/auth/logout [post]
|
||||
func logoutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
origin = fmt.Sprintf("%s://%s", getScheme(r), r.Host)
|
||||
}
|
||||
logoutUrl := fmt.Sprintf("%s/login", origin)
|
||||
proxyRedirectUrl := config.Auth.Methods.ProxyAuth.LogoutRedirectUrl
|
||||
oidcRedirectUrl := settings.Config.Auth.Methods.OidcAuth.LogoutRedirectUrl
|
||||
proxyUser := r.Header.Get(config.Auth.Methods.ProxyAuth.Header)
|
||||
|
||||
if config.Auth.Methods.ProxyAuth.Enabled && proxyUser != "" {
|
||||
logoutUrl := fmt.Sprintf("%vlogin", config.Server.BaseURL)
|
||||
if d.user.LoginMethod == "proxy" {
|
||||
proxyRedirectUrl := config.Auth.Methods.ProxyAuth.LogoutRedirectUrl
|
||||
if proxyRedirectUrl != "" {
|
||||
logoutUrl = proxyRedirectUrl
|
||||
}
|
||||
} else {
|
||||
} else if d.user.LoginMethod == "oidc" {
|
||||
oidcRedirectUrl := config.Auth.Methods.OidcAuth.LogoutRedirectUrl
|
||||
if oidcRedirectUrl != "" {
|
||||
logoutUrl = oidcRedirectUrl
|
||||
}
|
||||
}
|
||||
// clear cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "auth",
|
||||
Value: "",
|
||||
MaxAge: -1,
|
||||
Path: "/",
|
||||
})
|
||||
http.Redirect(w, r, logoutUrl, http.StatusFound)
|
||||
return 302, nil
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func StartHttp(ctx context.Context, storage *storage.Storage, shutdownComplete c
|
||||
|
||||
// Auth routes
|
||||
api.HandleFunc("POST /auth/login", userWithoutOTP(loginHandler))
|
||||
api.HandleFunc("GET /auth/logout", withoutUser(logoutHandler))
|
||||
api.HandleFunc("POST /auth/logout", withUser(logoutHandler))
|
||||
api.HandleFunc("POST /auth/signup", withoutUser(signupHandler))
|
||||
api.HandleFunc("POST /auth/otp/generate", userWithoutOTP(generateOTPHandler))
|
||||
api.HandleFunc("POST /auth/otp/verify", userWithoutOTP(verifyOTPHandler))
|
||||
|
||||
@@ -53,7 +53,6 @@ func oidcLoginHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
nonce := utils.InsecureRandomIdentifier(16)
|
||||
fbRedirect := r.URL.Query().Get("redirect")
|
||||
state := fmt.Sprintf("%s:%s", nonce, fbRedirect)
|
||||
|
||||
authURL := oauth2Config.AuthCodeURL(state)
|
||||
http.Redirect(w, r, authURL, http.StatusFound)
|
||||
return 0, nil
|
||||
@@ -195,7 +194,7 @@ func oidcCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||
}
|
||||
}
|
||||
if loginUsername == "" {
|
||||
logger.Error("No valid username found in ID token or UserInfo response.")
|
||||
logger.Errorf("No valid username found for identifier '%v' in ID token or UserInfo response.", oidcCfg.UserIdentifier)
|
||||
return http.StatusInternalServerError, fmt.Errorf("no valid username found in ID token or UserInfo response from claims")
|
||||
}
|
||||
isAdmin := false // Default to non-admin user
|
||||
|
||||
@@ -425,9 +425,6 @@ func inspectIndex(w http.ResponseWriter, r *http.Request) {
|
||||
source := r.URL.Query().Get("source")
|
||||
if source == "" {
|
||||
source = config.Server.DefaultSource.Name
|
||||
} else {
|
||||
// decode url encoded source name
|
||||
source, _ = url.QueryUnescape(source)
|
||||
}
|
||||
// Decode the URL-encoded path
|
||||
path, _ := url.QueryUnescape(encodedPath)
|
||||
|
||||
+13
-4
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/database/share"
|
||||
)
|
||||
|
||||
@@ -75,12 +76,16 @@ func shareGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
|
||||
sourcePath, ok := config.Server.NameToSource[source]
|
||||
if !ok {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid source name: %s", source)
|
||||
}
|
||||
s, err := store.Share.Gets(path, sourcePath.Path, d.user.ID)
|
||||
userscope, err := settings.GetScopeFromSourceName(d.user.Scopes, source)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
scopePath := utils.JoinPathAsUnix(userscope, path)
|
||||
s, err := store.Share.Gets(scopePath, sourcePath.Path, d.user.ID)
|
||||
if err == errors.ErrNotExist {
|
||||
return renderJSON(w, r, []*share.Link{})
|
||||
}
|
||||
@@ -194,9 +199,13 @@ func sharePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
sourceName = config.Server.DefaultSource.Name
|
||||
}
|
||||
source := config.Server.NameToSource[sourceName]
|
||||
|
||||
userscope, err := settings.GetScopeFromSourceName(d.user.Scopes, source.Name)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
scopePath := utils.JoinPathAsUnix(userscope, path)
|
||||
s = &share.Link{
|
||||
Path: path,
|
||||
Path: scopePath,
|
||||
Hash: secure_hash,
|
||||
Source: source.Path, // path instead to persist accoss name change
|
||||
Expire: expire,
|
||||
|
||||
@@ -118,10 +118,6 @@ func userDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContext
|
||||
return http.StatusForbidden, fmt.Errorf("cannot delete users without admin permissions")
|
||||
}
|
||||
|
||||
if givenUserId == 1 {
|
||||
return http.StatusForbidden, fmt.Errorf("cannot delete the default admin user")
|
||||
}
|
||||
|
||||
// Delete the user
|
||||
err := store.Users.Delete(givenUserId)
|
||||
if err != nil {
|
||||
|
||||
@@ -22,8 +22,6 @@ func errToStatus(err error) int {
|
||||
return http.StatusForbidden
|
||||
case errors.Is(err, libErrors.ErrInvalidRequestParams):
|
||||
return http.StatusBadRequest
|
||||
case errors.Is(err, libErrors.ErrRootUserDeletion):
|
||||
return http.StatusForbidden
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
@@ -110,7 +110,6 @@ func (idx *Index) indexDirectory(adjustedPath string, quick, recursive bool) err
|
||||
// check if excluded from indexing
|
||||
hidden := isHidden(dirInfo, idx.Path+adjustedPath)
|
||||
if idx.shouldSkip(dirInfo.IsDir(), hidden, adjustedPath) {
|
||||
logger.Debugf("skipping directory %s due to exclusion rules", adjustedPath)
|
||||
return errors.ErrNotIndexed
|
||||
}
|
||||
|
||||
@@ -153,7 +152,7 @@ func (idx *Index) indexDirectory(adjustedPath string, quick, recursive bool) err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
dirFileInfo, err2 := idx.GetDirInfo(dir, dirInfo, realPath, adjustedPath, combinedPath, quick, recursive)
|
||||
dirFileInfo, err2 := idx.GetDirInfo(dir, dirInfo, realPath, adjustedPath, combinedPath, quick, recursive,true)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
@@ -187,7 +186,7 @@ func (idx *Index) GetFsDirInfo(adjustedPath string) (*iteminfo.FileInfo, error)
|
||||
combinedPath = "/"
|
||||
}
|
||||
var response *iteminfo.FileInfo
|
||||
response, err = idx.GetDirInfo(dir, dirInfo, realPath, adjustedPath, combinedPath, false, false)
|
||||
response, err = idx.GetDirInfo(dir, dirInfo, realPath, adjustedPath, combinedPath, false, false,false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -214,7 +213,7 @@ func (idx *Index) GetFsDirInfo(adjustedPath string) (*iteminfo.FileInfo, error)
|
||||
|
||||
}
|
||||
|
||||
func (idx *Index) GetDirInfo(dirInfo *os.File, stat os.FileInfo, realPath, adjustedPath, combinedPath string, quick, recursive bool) (*iteminfo.FileInfo, error) {
|
||||
func (idx *Index) GetDirInfo(dirInfo *os.File, stat os.FileInfo, realPath, adjustedPath, combinedPath string, quick, recursive, checkSkip bool) (*iteminfo.FileInfo, error) {
|
||||
// Read directory contents
|
||||
files, err := dirInfo.Readdir(-1)
|
||||
if err != nil {
|
||||
@@ -229,7 +228,7 @@ func (idx *Index) GetDirInfo(dirInfo *os.File, stat os.FileInfo, realPath, adjus
|
||||
hidden := isHidden(file, idx.Path+combinedPath)
|
||||
isDir := iteminfo.IsDirectory(file)
|
||||
fullCombined := combinedPath + file.Name()
|
||||
if idx.shouldSkip(isDir, hidden, fullCombined) && recursive {
|
||||
if checkSkip && idx.shouldSkip(isDir, hidden, fullCombined) {
|
||||
continue
|
||||
}
|
||||
itemInfo := &iteminfo.ItemInfo{
|
||||
@@ -423,7 +422,6 @@ func (idx *Index) shouldSkip(isDir bool, isHidden bool, fullCombined string) boo
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// check exclusions
|
||||
if isDir && slices.Contains(idx.Config.Exclude.Folders, fullCombined) {
|
||||
return true
|
||||
@@ -443,7 +441,9 @@ func (idx *Index) shouldSkip(isDir bool, isHidden bool, fullCombined string) boo
|
||||
break
|
||||
}
|
||||
}
|
||||
return shouldSkip
|
||||
if shouldSkip {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -41,7 +41,6 @@ var MuPdfConvertable = []string{
|
||||
".svg", // SVG
|
||||
".txt", // TXT
|
||||
".docx", // DOCX
|
||||
".ppt", // PPT
|
||||
".pptx", // PPTX
|
||||
".xlsx", // XLSX
|
||||
".hwp", // HWP
|
||||
|
||||
@@ -25,7 +25,7 @@ func (i *ExtendedFileInfo) DetectSubtitles(parentInfo *FileInfo) {
|
||||
|
||||
for _, subtitleExt := range SubtitleExts {
|
||||
if strings.HasSuffix(f.Name, subtitleExt) {
|
||||
i.Subtitles = append(i.Subtitles, parentInfo.Path+f.Name)
|
||||
i.Subtitles = append(i.Subtitles, f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const docTemplate = `{
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/api/auth/logout": {
|
||||
"get": {
|
||||
"post": {
|
||||
"description": "logs a user out of the application.",
|
||||
"tags": [
|
||||
"Auth"
|
||||
@@ -1818,21 +1818,21 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fileEndsWith": {
|
||||
"description": "array of file names to include/exclude (eg \"a.jpg\")",
|
||||
"description": "list of file names to include/exclude. Eg. \"a.jpg\"",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"description": "array of file names to include/exclude",
|
||||
"description": "list of file names to include/exclude Eg. \"folder1\" or \"file1.txt\" or \"folder1/file1.txt\" (do not include source path, just the subpaths from the source path)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"description": "array of folder names to include/exclude",
|
||||
"description": "list of folder names to include/exclude. Eg. \"folder1\" or \"folder1/subfolder\" (do not include source path, just the subpaths from the source path)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -1947,7 +1947,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"userIdentifier": {
|
||||
"description": "the user identifier to use for authentication. Default is \"username\", can be \"email\" or \"username\", or \"phone\"",
|
||||
"description": "the field value to use as the username. Default is \"preferred_username\", can also be \"email\" or \"username\", or \"phone\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -2394,6 +2394,14 @@ const docTemplate = `{
|
||||
"users.Preview": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"autoplayMedia": {
|
||||
"description": "autoplay media files in preview",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableHideSidebar": {
|
||||
"description": "disable the hide sidebar preview for previews and editors",
|
||||
"type": "boolean"
|
||||
},
|
||||
"highQuality": {
|
||||
"description": "generate high quality preview images",
|
||||
"type": "boolean"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"paths": {
|
||||
"/api/auth/logout": {
|
||||
"get": {
|
||||
"post": {
|
||||
"description": "logs a user out of the application.",
|
||||
"tags": [
|
||||
"Auth"
|
||||
@@ -1807,21 +1807,21 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fileEndsWith": {
|
||||
"description": "array of file names to include/exclude (eg \"a.jpg\")",
|
||||
"description": "list of file names to include/exclude. Eg. \"a.jpg\"",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"description": "array of file names to include/exclude",
|
||||
"description": "list of file names to include/exclude Eg. \"folder1\" or \"file1.txt\" or \"folder1/file1.txt\" (do not include source path, just the subpaths from the source path)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"description": "array of folder names to include/exclude",
|
||||
"description": "list of folder names to include/exclude. Eg. \"folder1\" or \"folder1/subfolder\" (do not include source path, just the subpaths from the source path)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -1936,7 +1936,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"userIdentifier": {
|
||||
"description": "the user identifier to use for authentication. Default is \"username\", can be \"email\" or \"username\", or \"phone\"",
|
||||
"description": "the field value to use as the username. Default is \"preferred_username\", can also be \"email\" or \"username\", or \"phone\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -2383,6 +2383,14 @@
|
||||
"users.Preview": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"autoplayMedia": {
|
||||
"description": "autoplay media files in preview",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableHideSidebar": {
|
||||
"description": "disable the hide sidebar preview for previews and editors",
|
||||
"type": "boolean"
|
||||
},
|
||||
"highQuality": {
|
||||
"description": "generate high quality preview images",
|
||||
"type": "boolean"
|
||||
|
||||
@@ -145,17 +145,20 @@ definitions:
|
||||
settings.IndexFilter:
|
||||
properties:
|
||||
fileEndsWith:
|
||||
description: array of file names to include/exclude (eg "a.jpg")
|
||||
description: list of file names to include/exclude. Eg. "a.jpg"
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
files:
|
||||
description: array of file names to include/exclude
|
||||
description: list of file names to include/exclude Eg. "folder1" or "file1.txt"
|
||||
or "folder1/file1.txt" (do not include source path, just the subpaths from
|
||||
the source path)
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
folders:
|
||||
description: array of folder names to include/exclude
|
||||
description: list of folder names to include/exclude. Eg. "folder1" or "folder1/subfolder"
|
||||
(do not include source path, just the subpaths from the source path)
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -239,8 +242,8 @@ definitions:
|
||||
description: scopes to request from the OIDC provider
|
||||
type: string
|
||||
userIdentifier:
|
||||
description: the user identifier to use for authentication. Default is "username",
|
||||
can be "email" or "username", or "phone"
|
||||
description: the field value to use as the username. Default is "preferred_username",
|
||||
can also be "email" or "username", or "phone"
|
||||
type: string
|
||||
type: object
|
||||
settings.OnlyOffice:
|
||||
@@ -571,6 +574,12 @@ definitions:
|
||||
type: object
|
||||
users.Preview:
|
||||
properties:
|
||||
autoplayMedia:
|
||||
description: autoplay media files in preview
|
||||
type: boolean
|
||||
disableHideSidebar:
|
||||
description: disable the hide sidebar preview for previews and editors
|
||||
type: boolean
|
||||
highQuality:
|
||||
description: generate high quality preview images
|
||||
type: boolean
|
||||
@@ -695,7 +704,7 @@ info:
|
||||
contact: {}
|
||||
paths:
|
||||
/api/auth/logout:
|
||||
get:
|
||||
post:
|
||||
description: logs a user out of the application.
|
||||
responses:
|
||||
"302":
|
||||
|
||||
@@ -66,7 +66,7 @@ auth:
|
||||
clientSecret: "" # client secret of the OIDC application
|
||||
issuerUrl: "" # authorization URL of the OIDC provider
|
||||
scopes: "" # scopes to request from the OIDC provider
|
||||
userIdentifier: "" # the user identifier to use for authentication. Default is "username", can be "email" or "username", or "phone"
|
||||
userIdentifier: "" # the field value to use as the username. Default is "preferred_username", can also be "email" or "username", or "phone"
|
||||
disableVerifyTLS: false # disable TLS verification for the OIDC provider. This is insecure and should only be used for testing.
|
||||
logoutRedirectUrl: "" # if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected.
|
||||
createUser: false # create user if not exists
|
||||
@@ -103,12 +103,14 @@ userDefaults:
|
||||
lockPassword: false # disable the user from changing their password
|
||||
disableSettings: false # disable the user from viewing the settings page
|
||||
preview:
|
||||
disableHideSidebar: false
|
||||
highQuality: false
|
||||
image: false
|
||||
video: false
|
||||
motionVideoPreview: false
|
||||
office: false
|
||||
popup: false
|
||||
autoplayMedia: false
|
||||
permissions:
|
||||
api: false
|
||||
admin: false
|
||||
|
||||
@@ -149,4 +149,4 @@ async function syncAllTranslations() {
|
||||
syncAllTranslations().catch(error => {
|
||||
console.error("\n❌ An error occurred during translation synchronization:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
@@ -182,6 +182,9 @@ export async function moveCopy(
|
||||
|
||||
// Await all promises and ensure errors propagate
|
||||
await Promise.all(promises)
|
||||
notify.showSuccess(
|
||||
action === 'copy' ? 'Resources copied successfully' : 'Resources moved successfully'
|
||||
)
|
||||
} catch (err) {
|
||||
notify.showError(err.message || 'Error moving/copying resources')
|
||||
throw err // Re-throw the error to propagate it back to the caller
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
class="button no-select"
|
||||
:class="{ 'dark-mode': isDarkMode }"
|
||||
>
|
||||
<action v-if="showGoToRaw" icon="open_in_new" :label="$t('buttons.openFile')" @action="goToRaw()" />
|
||||
<action v-if="isPreview" icon="file_download" :label="$t('buttons.download')" @action="startDownload" />
|
||||
<action v-if="showEdit" icon="edit" :label="$t('buttons.edit')" @action="edit()" />
|
||||
<action v-if="showSave" icon="save" :label="$t('buttons.save')" @action="save()" />
|
||||
@@ -118,7 +119,7 @@ import { onlyOfficeUrl } from "@/utils/constants.js";
|
||||
import buttons from "@/utils/buttons";
|
||||
import { notify } from "@/notify";
|
||||
import { eventBus } from "@/store/eventBus";
|
||||
|
||||
import { filesApi } from "@/api";
|
||||
export default {
|
||||
name: "ContextMenu",
|
||||
components: {
|
||||
@@ -135,6 +136,10 @@ export default {
|
||||
noItems() {
|
||||
return !this.showEdit && !this.showSave && !this.showDelete;
|
||||
},
|
||||
showGoToRaw() {
|
||||
return getters.currentView() == "preview" ||
|
||||
getters.currentView() == "markdownViewer"
|
||||
},
|
||||
showEdit() {
|
||||
return getters.currentView() == "markdownViewer" && state.user.permissions.modify;
|
||||
},
|
||||
@@ -257,6 +262,16 @@ export default {
|
||||
startDownload() {
|
||||
downloadFiles();
|
||||
},
|
||||
goToRaw() {
|
||||
const downloadUrl = filesApi.getDownloadURL(
|
||||
state.req.source,
|
||||
state.req.path,
|
||||
true,
|
||||
false
|
||||
);
|
||||
window.open(downloadUrl, "_blank");
|
||||
mutations.closeHovers();
|
||||
},
|
||||
async edit() {
|
||||
window.location.hash = "#edit";
|
||||
},
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
<a :href="getRelative(s.path)" @contextmenu="addSelected(event, s)">
|
||||
<Icon :mimetype="s.type" :filename="s.name" />
|
||||
<span class="text-container">
|
||||
{{ basePath(s.path, s.type == "directory") }}/{{ baseName(s.path) }}/<!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
{{ basePath(s.path, s.type == "directory") }}{{ baseName(s.path) }} <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
</span>
|
||||
<div class="filesize">{{ humanSize(s.size) }}</div>
|
||||
</a>
|
||||
@@ -420,7 +420,7 @@ export default {
|
||||
if (!isDir) {
|
||||
result = url.removeLeadingSlash(result); // fix weird rtl thing
|
||||
}
|
||||
return result;
|
||||
return result + "/";
|
||||
},
|
||||
baseName(str) {
|
||||
let parts = url.removeTrailingSlash(str).split("/");
|
||||
|
||||
@@ -85,7 +85,6 @@ export default {
|
||||
".svg": true,
|
||||
".txt": true,
|
||||
".docx": true,
|
||||
".ppt": true,
|
||||
".pptx": true,
|
||||
".xlsx": true,
|
||||
".hwp": true,
|
||||
|
||||
@@ -107,7 +107,7 @@ export default {
|
||||
return !this.isSelected && this.reducedOpacity;
|
||||
},
|
||||
getID() {
|
||||
return url.base64Encode(this.name);
|
||||
return url.base64Encode(encodeURIComponent(this.name));
|
||||
},
|
||||
quickNav() {
|
||||
return state.user.singleClick && !state.multiple;
|
||||
|
||||
@@ -137,14 +137,11 @@ export default {
|
||||
});
|
||||
} else {
|
||||
// Await the action call for non-conflicting cases
|
||||
await action(overwrite, rename);
|
||||
action(overwrite, rename);
|
||||
}
|
||||
mutations.closeHovers();
|
||||
mutations.setSearch(false);
|
||||
notify.showSuccess("Successfully copied file/folder, redirecting...");
|
||||
setTimeout(() => {
|
||||
this.$router.push(this.dest);
|
||||
}, 1000);
|
||||
notify.showSuccess("Server is copying files...");
|
||||
} catch (error) {
|
||||
notify.showError(error);
|
||||
}
|
||||
|
||||
@@ -136,14 +136,11 @@ export default {
|
||||
});
|
||||
} else {
|
||||
// Await the action call for non-conflicting cases
|
||||
await action(overwrite, rename);
|
||||
action(overwrite, rename);
|
||||
}
|
||||
mutations.closeHovers();
|
||||
mutations.setSearch(false);
|
||||
notify.showSuccess("Successfully moved file/folder, redirecting...");
|
||||
setTimeout(() => {
|
||||
this.$router.push(this.dest);
|
||||
}, 1000);
|
||||
notify.showSuccess("Server is moving files...");
|
||||
} catch (error) {
|
||||
notify.showError(error);
|
||||
}
|
||||
|
||||
@@ -212,35 +212,43 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
let isPermanent = !this.time || this.time === 0;
|
||||
let res = null;
|
||||
if (isPermanent) {
|
||||
res = await shareApi.create(this.subpath, this.source, this.password);
|
||||
} else {
|
||||
res = await shareApi.create(
|
||||
this.subpath,
|
||||
this.source,
|
||||
this.password,
|
||||
this.time.toString(),
|
||||
this.unit
|
||||
);
|
||||
try {
|
||||
let isPermanent = !this.time || this.time == 0;
|
||||
let res = null;
|
||||
if (isPermanent) {
|
||||
res = await shareApi.create(this.subpath, this.source, this.password);
|
||||
} else {
|
||||
res = await shareApi.create(
|
||||
this.subpath,
|
||||
this.source,
|
||||
this.password,
|
||||
this.time.toString(),
|
||||
this.unit
|
||||
);
|
||||
}
|
||||
|
||||
this.links.push(res);
|
||||
this.sort();
|
||||
|
||||
this.time = "";
|
||||
this.unit = "hours";
|
||||
this.password = "";
|
||||
|
||||
this.listing = true;
|
||||
} catch (err) {
|
||||
notify.showError(err);
|
||||
}
|
||||
|
||||
this.links.push(res);
|
||||
this.sort();
|
||||
|
||||
this.time = "";
|
||||
this.unit = "hours";
|
||||
this.password = "";
|
||||
|
||||
this.listing = true;
|
||||
},
|
||||
async deleteLink(event, link) {
|
||||
event.preventDefault();
|
||||
await shareApi.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
if (this.links.length === 0) {
|
||||
this.listing = false;
|
||||
try {
|
||||
await shareApi.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
if (this.links.length === 0) {
|
||||
this.listing = false;
|
||||
}
|
||||
} catch (err) {
|
||||
notify.showError(err);
|
||||
}
|
||||
},
|
||||
humanTime(time) {
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; flex-direction: column"
|
||||
v-if="stateUser.username == user.username"
|
||||
>
|
||||
<div class="settings-items">
|
||||
<ToggleSwitch class="item" v-model="user.otpEnabled" :name="$t('otp.name')" />
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
@media (max-width: 800px) {
|
||||
#listingView .header .name,
|
||||
#listingView.list .item .name,
|
||||
#listingView.compact .item .name {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
#listingView.list .item div:last-of-type{
|
||||
display:block;
|
||||
width:100%;
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "الإعدادات",
|
||||
"signup": "Signup",
|
||||
"siteSettings": "إعدادات الموقع",
|
||||
"sources": "المصادر"
|
||||
"sources": "المصادر",
|
||||
"updateIsAvailable": "يتوفر تحديث!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "تم نسخ الملف"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "معاينة الملفات المكتبية",
|
||||
"popupPreview": "تمكين المعاينة المنبثقة",
|
||||
"filePreviewOptions": "خيارات معاينة الملف",
|
||||
"editOffice": "تحرير/عرض ملفات المكتب"
|
||||
"editOffice": "تحرير/عرض ملفات المكتب",
|
||||
"disableUpdateNotifications": "تعطيل إظهار شعار تحديث الإشعارات في الشريط الجانبي.",
|
||||
"disableHideSidebar": "تعطيل إخفاء الشريط الجانبي تلقائيًا عند معاينة الملفات أو تحريرها.",
|
||||
"autoplayMedia": "التشغيل التلقائي لملفات الوسائط في المعاينة"
|
||||
},
|
||||
"otp": {
|
||||
"name": "المصادقة الثنائية",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Einstellungen",
|
||||
"signup": "Registrieren",
|
||||
"siteSettings": "Seiteneinstellungen",
|
||||
"sources": "Quellen"
|
||||
"sources": "Quellen",
|
||||
"updateIsAvailable": "Ein Update ist verfügbar!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link wurde kopiert!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Vorschau von Office-Dateien",
|
||||
"popupPreview": "Pop-up-Vorschau einschalten",
|
||||
"filePreviewOptions": "Optionen für die Dateivorschau",
|
||||
"editOffice": "Office-Dateien bearbeiten/anzeigen"
|
||||
"editOffice": "Office-Dateien bearbeiten/anzeigen",
|
||||
"disableUpdateNotifications": "Deaktivieren Sie die Anzeige des Banners für Benachrichtigungsaktualisierungen in der Seitenleiste.",
|
||||
"disableHideSidebar": "Deaktivieren Sie das automatische Ausblenden der Seitenleiste bei der Vorschau oder Bearbeitung von Dateien.",
|
||||
"autoplayMedia": "Autoplay von Mediendateien in der Vorschau"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Zwei-Faktoren-Authentifizierung",
|
||||
|
||||
@@ -305,7 +305,8 @@
|
||||
"settings": "Ρυθμίσεις",
|
||||
"signup": "Εγγραφή",
|
||||
"siteSettings": "Ρυθμίσεις ιστότοπου",
|
||||
"sources": "Πηγές"
|
||||
"sources": "Πηγές",
|
||||
"updateIsAvailable": "Μια ενημέρωση είναι διαθέσιμη!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Ο σύνδεσμος αντιγράφηκε!"
|
||||
@@ -371,7 +372,10 @@
|
||||
"previewOffice": "Προεπισκόπηση αρχείων γραφείου",
|
||||
"popupPreview": "Ενεργοποίηση αναδυόμενης προεπισκόπησης",
|
||||
"filePreviewOptions": "Επιλογές προεπισκόπησης αρχείων",
|
||||
"editOffice": "Επεξεργασία/προβολή αρχείων γραφείου"
|
||||
"editOffice": "Επεξεργασία/προβολή αρχείων γραφείου",
|
||||
"disableUpdateNotifications": "Απενεργοποιήστε την εμφάνιση του banner ενημέρωσης ειδοποιήσεων στην πλαϊνή γραμμή.",
|
||||
"disableHideSidebar": "Απενεργοποίηση της αυτόματης απόκρυψης της πλευρικής γραμμής κατά την προεπισκόπηση ή την επεξεργασία αρχείων.",
|
||||
"autoplayMedia": "Αυτόματη αναπαραγωγή αρχείων πολυμέσων στην προεπισκόπηση"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Αυθεντικοποίηση δύο παραγόντων",
|
||||
|
||||
@@ -198,7 +198,9 @@
|
||||
"editOffice": "Edit/view office files",
|
||||
"popupPreview": "Enable pop-up preview",
|
||||
"filePreviewOptions": "File preview options",
|
||||
"disableUpdateNotifications": "Disable showing notification update banner in sidebar."
|
||||
"disableUpdateNotifications": "Disable showing notification update banner in sidebar.",
|
||||
"disableHideSidebar": "Disable automatically hiding sidebar when previewing or editing files.",
|
||||
"autoplayMedia": "Autoplay media files in preview"
|
||||
},
|
||||
"settings": {
|
||||
"enterPassword": "Enter password",
|
||||
@@ -298,7 +300,8 @@
|
||||
"userManagement": "User Management",
|
||||
"userUpdated": "User updated!",
|
||||
"username": "Username",
|
||||
"users": "Users"
|
||||
"users": "Users",
|
||||
"adminOptions": "Admin user options"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Help",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Ajustes",
|
||||
"signup": "Registrate",
|
||||
"siteSettings": "Ajustes del sitio",
|
||||
"sources": "Fuentes"
|
||||
"sources": "Fuentes",
|
||||
"updateIsAvailable": "Ya está disponible la actualización."
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "¡Link copiado!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Vista previa de archivos ofimáticos",
|
||||
"popupPreview": "Activar la vista previa emergente",
|
||||
"filePreviewOptions": "Opciones de previsualización de archivos",
|
||||
"editOffice": "Editar/ver archivos ofimáticos"
|
||||
"editOffice": "Editar/ver archivos ofimáticos",
|
||||
"disableUpdateNotifications": "Desactivar la visualización del banner de actualización de notificaciones en la barra lateral.",
|
||||
"disableHideSidebar": "Desactivar la ocultación automática de la barra lateral al previsualizar o editar archivos.",
|
||||
"autoplayMedia": "Reproducción automática de archivos multimedia en vista previa"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Autenticación de dos factores",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Paramètres",
|
||||
"signup": "S'inscrire",
|
||||
"siteSettings": "Paramètres du site",
|
||||
"sources": "Sources d'information"
|
||||
"sources": "Sources d'information",
|
||||
"updateIsAvailable": "Une mise à jour est disponible !"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Lien copié !"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Prévisualisation des fichiers bureautique",
|
||||
"popupPreview": "Activer la prévisualisation dans les fenêtres contextuelles",
|
||||
"editOffice": "Editer/visualiser des fichiers bureautiques",
|
||||
"filePreviewOptions": "Options de prévisualisation des fichiers"
|
||||
"filePreviewOptions": "Options de prévisualisation des fichiers",
|
||||
"disableUpdateNotifications": "Désactiver l'affichage de la bannière de mise à jour des notifications dans la barre latérale.",
|
||||
"disableHideSidebar": "Désactiver le masquage automatique de la barre latérale lors de la prévisualisation ou de l'édition de fichiers.",
|
||||
"autoplayMedia": "Lecture automatique des fichiers multimédias dans l'aperçu"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Authentification à deux facteurs",
|
||||
|
||||
@@ -298,7 +298,8 @@
|
||||
"settings": "הגדרות",
|
||||
"signup": "הרשמה",
|
||||
"siteSettings": "הגדרות",
|
||||
"sources": "מקורות"
|
||||
"sources": "מקורות",
|
||||
"updateIsAvailable": "עדכון זמין!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "הקישור הועתק!"
|
||||
@@ -366,7 +367,10 @@
|
||||
"highQualityPreview": "תצוגה מקדימה באיכות גבוהה",
|
||||
"editOffice": "ערוך/הצג קבצי Office",
|
||||
"popupPreview": "הפעל תצוגה מקדימה מוקפצת",
|
||||
"filePreviewOptions": "אפשרויות תצוגה מקדימה של קבצים"
|
||||
"filePreviewOptions": "אפשרויות תצוגה מקדימה של קבצים",
|
||||
"disableUpdateNotifications": "השבת הצגת כרטיסיית עדכון הודעות בסרגל הצד.",
|
||||
"disableHideSidebar": "השבת הסתרה אוטומטית של סרגל הצד בעת תצוגה מקדימה או עריכת קבצים.",
|
||||
"autoplayMedia": "הפעל קבצי מדיה באופן אוטומטי בתצוגה מקדימה"
|
||||
},
|
||||
"otp": {
|
||||
"name": "אימות דו-גורמי",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Beállítások",
|
||||
"signup": "Regisztráció",
|
||||
"siteSettings": "Oldalbeállítások",
|
||||
"sources": "Források"
|
||||
"sources": "Források",
|
||||
"updateIsAvailable": "Elérhető a frissítés!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link másolva!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Office fájlok előnézete",
|
||||
"popupPreview": "Pop-up előnézet engedélyezése",
|
||||
"filePreviewOptions": "Fájl előnézeti beállítások",
|
||||
"editOffice": "Office fájlok szerkesztése/megjelenítése"
|
||||
"editOffice": "Office fájlok szerkesztése/megjelenítése",
|
||||
"disableUpdateNotifications": "Értesítési frissítés banner megjelenítésének kikapcsolása az oldalsávban.",
|
||||
"disableHideSidebar": "Az oldalsáv automatikus elrejtésének kikapcsolása fájlok előnézeténél vagy szerkesztésénél.",
|
||||
"autoplayMedia": "Médiafájlok automatikus lejátszása előnézetben"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Kétfaktoros hitelesítés",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Impostazioni",
|
||||
"signup": "Registrati",
|
||||
"siteSettings": "Impostazioni del sito",
|
||||
"sources": "Fonti"
|
||||
"sources": "Fonti",
|
||||
"updateIsAvailable": "È disponibile un aggiornamento!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link copiato!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Anteprima dei file office",
|
||||
"popupPreview": "Abilita l'anteprima a comparsa",
|
||||
"filePreviewOptions": "Opzioni di anteprima dei file",
|
||||
"editOffice": "Modificare/visualizzare i file dell'ufficio"
|
||||
"editOffice": "Modificare/visualizzare i file dell'ufficio",
|
||||
"disableUpdateNotifications": "Disattivare la visualizzazione del banner di aggiornamento delle notifiche nella barra laterale.",
|
||||
"disableHideSidebar": "Disabilita la possibilità di nascondere automaticamente la barra laterale durante l'anteprima o la modifica dei file.",
|
||||
"autoplayMedia": "Riproduzione automatica dei file multimediali nell'anteprima"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Autenticazione a due fattori",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "設定",
|
||||
"signup": "Signup",
|
||||
"siteSettings": "サイト設定",
|
||||
"sources": "情報源"
|
||||
"sources": "情報源",
|
||||
"updateIsAvailable": "アップデートがある!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "リンクがコピーされました!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "オフィスファイルのプレビュー",
|
||||
"popupPreview": "ポップアップ・プレビューを有効にする",
|
||||
"filePreviewOptions": "ファイルプレビューオプション",
|
||||
"editOffice": "オフィスファイルの編集/閲覧"
|
||||
"editOffice": "オフィスファイルの編集/閲覧",
|
||||
"disableUpdateNotifications": "サイドバーに通知更新バナーを表示しないようにしました。",
|
||||
"disableHideSidebar": "ファイルのプレビューや編集時にサイドバーを自動的に非表示にします。",
|
||||
"autoplayMedia": "プレビューでメディアファイルを自動再生"
|
||||
},
|
||||
"otp": {
|
||||
"name": "二要素認証",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "설정",
|
||||
"signup": "가입하기",
|
||||
"siteSettings": "사이트 설정",
|
||||
"sources": "출처"
|
||||
"sources": "출처",
|
||||
"updateIsAvailable": "업데이트를 사용할 수 있습니다!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "링크가 복사되었습니다!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "오피스 파일 미리 보기",
|
||||
"popupPreview": "팝업 미리 보기 사용",
|
||||
"filePreviewOptions": "파일 미리 보기 옵션",
|
||||
"editOffice": "오피스 파일 편집/보기"
|
||||
"editOffice": "오피스 파일 편집/보기",
|
||||
"disableUpdateNotifications": "사이드바에 알림 업데이트 배너 표시를 비활성화합니다.",
|
||||
"disableHideSidebar": "파일을 미리 보거나 편집할 때 사이드바 자동 숨기기를 비활성화합니다.",
|
||||
"autoplayMedia": "미리보기에서 미디어 파일 자동 재생"
|
||||
},
|
||||
"otp": {
|
||||
"name": "2단계 인증",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Ustawienia",
|
||||
"signup": "Rejestracja",
|
||||
"siteSettings": "Ustawienia Strony",
|
||||
"sources": "Źródła"
|
||||
"sources": "Źródła",
|
||||
"updateIsAvailable": "Aktualizacja jest już dostępna!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link Skopiowany!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Podgląd plików biurowych",
|
||||
"popupPreview": "Włącz wyskakujący podgląd",
|
||||
"filePreviewOptions": "Opcje podglądu plików",
|
||||
"editOffice": "Edycja/przeglądanie plików biurowych"
|
||||
"editOffice": "Edycja/przeglądanie plików biurowych",
|
||||
"disableUpdateNotifications": "Wyłącz wyświetlanie banera aktualizacji powiadomień na pasku bocznym.",
|
||||
"disableHideSidebar": "Wyłącz automatyczne ukrywanie paska bocznego podczas podglądu lub edycji plików.",
|
||||
"autoplayMedia": "Automatyczne odtwarzanie plików multimedialnych w podglądzie"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Uwierzytelnianie dwuskładnikowe",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Configurações",
|
||||
"signup": "Cadastrar",
|
||||
"siteSettings": "Configurações do site",
|
||||
"sources": "Fontes"
|
||||
"sources": "Fontes",
|
||||
"updateIsAvailable": "Uma atualização está disponível!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link copiado!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Visualizar arquivos do Office",
|
||||
"popupPreview": "Ativar visualização pop-up",
|
||||
"filePreviewOptions": "Opções de visualização de arquivos",
|
||||
"editOffice": "Editar/visualizar arquivos de escritório"
|
||||
"editOffice": "Editar/visualizar arquivos de escritório",
|
||||
"disableUpdateNotifications": "Desativar a exibição do banner de atualização de notificação na barra lateral.",
|
||||
"disableHideSidebar": "Desative a ocultação automática da barra lateral ao visualizar ou editar arquivos.",
|
||||
"autoplayMedia": "Reprodução automática de arquivos de mídia na visualização"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Autenticação de dois fatores",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Setări",
|
||||
"signup": "Înregistrare",
|
||||
"siteSettings": "Setări site",
|
||||
"sources": "Surse"
|
||||
"sources": "Surse",
|
||||
"updateIsAvailable": "O actualizare este disponibilă!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Legătură copiată!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Previzualizare fișiere office",
|
||||
"popupPreview": "Activați previzualizarea pop-up",
|
||||
"filePreviewOptions": "Opțiuni de previzualizare a fișierelor",
|
||||
"editOffice": "Editarea/vizualizarea fișierelor office"
|
||||
"editOffice": "Editarea/vizualizarea fișierelor office",
|
||||
"disableUpdateNotifications": "Dezactivați afișarea bannerului de actualizare a notificărilor în bara laterală.",
|
||||
"disableHideSidebar": "Dezactivați ascunderea automată a barei laterale la previzualizarea sau editarea fișierelor.",
|
||||
"autoplayMedia": "Redarea automată a fișierelor media în previzualizare"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Autentificare cu doi factori",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Настройки",
|
||||
"signup": "Зарегистрироваться",
|
||||
"siteSettings": "Настройки сайта",
|
||||
"sources": "Источники"
|
||||
"sources": "Источники",
|
||||
"updateIsAvailable": "Обновление доступно!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Ссылка скопирована!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Предварительный просмотр офисных файлов",
|
||||
"popupPreview": "Включить предварительный просмотр всплывающих окон",
|
||||
"filePreviewOptions": "Параметры предварительного просмотра файлов",
|
||||
"editOffice": "Редактирование/просмотр офисных файлов"
|
||||
"editOffice": "Редактирование/просмотр офисных файлов",
|
||||
"disableUpdateNotifications": "Отключите показ баннера обновлений в боковой панели.",
|
||||
"disableHideSidebar": "Отключите автоматическое скрытие боковой панели при предварительном просмотре или редактировании файлов.",
|
||||
"autoplayMedia": "Автовоспроизведение медиафайлов в режиме предварительного просмотра"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Двухфакторная аутентификация",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Nastavenia",
|
||||
"signup": "Registrovať",
|
||||
"siteSettings": "Nastavenia stránky",
|
||||
"sources": "Zdroje"
|
||||
"sources": "Zdroje",
|
||||
"updateIsAvailable": "K dispozícii je aktualizácia!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Odkaz skopírovaný!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Náhľad kancelárskych súborov",
|
||||
"popupPreview": "Povolenie pop-up náhľadu",
|
||||
"filePreviewOptions": "Možnosti náhľadu súborov",
|
||||
"editOffice": "Úprava/zobrazenie kancelárskych súborov"
|
||||
"editOffice": "Úprava/zobrazenie kancelárskych súborov",
|
||||
"disableUpdateNotifications": "Zakázanie zobrazovania bannera s oznámením o aktualizácii v bočnom paneli.",
|
||||
"disableHideSidebar": "Zakázanie automatického skrývania bočného panela pri náhľade alebo úprave súborov.",
|
||||
"autoplayMedia": "Automatické prehrávanie mediálnych súborov v náhľade"
|
||||
},
|
||||
"otp": {
|
||||
"name": "Dvojfaktorové overovanie",
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"settings": "Ayarlar",
|
||||
"signup": "Kayıt",
|
||||
"siteSettings": "Site ayarları!",
|
||||
"sources": "Kaynaklar"
|
||||
"sources": "Kaynaklar",
|
||||
"updateIsAvailable": "Bir güncelleme mevcut!"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link kopyalandı!"
|
||||
@@ -370,7 +371,10 @@
|
||||
"previewOffice": "Ofis dosyalarını önizleme",
|
||||
"popupPreview": "Açılır pencere önizlemesini etkinleştir",
|
||||
"filePreviewOptions": "Dosya önizleme seçenekleri",
|
||||
"editOffice": "Ofis dosyalarını düzenleme/görüntüleme"
|
||||
"editOffice": "Ofis dosyalarını düzenleme/görüntüleme",
|
||||
"disableUpdateNotifications": "Kenar çubuğunda bildirim güncelleme banner'ını göstermeyi devre dışı bırakın.",
|
||||
"disableHideSidebar": "Dosyaları önizlerken veya düzenlerken kenar çubuğunu otomatik olarak gizlemeyi devre dışı bırakın.",
|
||||
"autoplayMedia": "Medya dosyalarını önizlemede otomatik oynatma"
|
||||
},
|
||||
"otp": {
|
||||
"name": "İki Faktörlü Kimlik Doğrulama",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { baseURL, name, oidcAvailable, passwordAvailable } from "@/utils/constan
|
||||
import { getters, state } from "@/store";
|
||||
import { mutations } from "@/store";
|
||||
import { validateLogin } from "@/utils/auth";
|
||||
import { removeLeadingSlash } from "@/utils/url";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
const titles = {
|
||||
@@ -150,7 +151,8 @@ router.beforeResolve(async (to, from, next) => {
|
||||
}
|
||||
|
||||
if (oidcAvailable) {
|
||||
window.location.href = `/api/auth/oidc/login?redirect=${encodeURIComponent(to.fullPath)}`;
|
||||
const modifiedPath = encodeURIComponent(baseURL+removeLeadingSlash(to.fullPath))
|
||||
window.location.href = baseURL+`api/auth/oidc/login?redirect=${modifiedPath}`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,14 @@ export const getters = {
|
||||
},
|
||||
isSidebarVisible: () => {
|
||||
const currentView = getters.currentView()
|
||||
const previewViews = [
|
||||
'preview',
|
||||
'markdownViewer',
|
||||
'epubViewer',
|
||||
'docViewer',
|
||||
'onlyOfficeEditor',
|
||||
'editor'
|
||||
]
|
||||
let visible =
|
||||
(state.showSidebar || getters.isStickySidebar()) &&
|
||||
state.user.username != 'publicUser'
|
||||
@@ -152,10 +160,7 @@ export const getters = {
|
||||
) {
|
||||
visible = false
|
||||
}
|
||||
if (currentView == 'preview' || currentView == 'markdownViewer') {
|
||||
visible = false
|
||||
}
|
||||
if (currentView == 'editor' || currentView == 'onlyOfficeEditor') {
|
||||
if (previewViews.includes(currentView) && !state.user.preview?.disableHideSidebar) {
|
||||
visible = false
|
||||
}
|
||||
return visible
|
||||
|
||||
@@ -217,7 +217,6 @@ export const mutations = {
|
||||
if (value.username != "publicUser") {
|
||||
mutations.setSources(value);
|
||||
}
|
||||
console.log("Setting current user:", value);
|
||||
// Ensure locale exists and is valid
|
||||
if (!value.locale) {
|
||||
value.locale = i18n.detectLocale(); // Default to detected locale if missing
|
||||
|
||||
@@ -56,11 +56,26 @@ export function generateRandomCode(length) {
|
||||
return code;
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
if (state.user.loginMethod == "oidc" || state.user.loginMethod == "proxy") {
|
||||
let apiPath = getApiPath("api/auth/logout")
|
||||
window.location.href = apiPath;
|
||||
return
|
||||
export async function logout() {
|
||||
|
||||
if (state.user.loginMethod === "oidc" || state.user.loginMethod === "proxy") {
|
||||
try {
|
||||
const res = await fetch(getApiPath("api/auth/logout"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${state.jwt}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.redirected) {
|
||||
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
||||
mutations.setCurrentUser(null);
|
||||
window.location.href = res.url;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
||||
mutations.setCurrentUser(null);
|
||||
|
||||
@@ -112,7 +112,6 @@ export default {
|
||||
redirect = baseURL + redirect;
|
||||
}
|
||||
this.loginURL += `?redirect=${encodeURIComponent(redirect)}`;
|
||||
|
||||
if (!recaptcha) return;
|
||||
window.grecaptcha.ready(function () {
|
||||
window.grecaptcha.render("recaptcha", {
|
||||
|
||||
@@ -29,7 +29,20 @@
|
||||
/>
|
||||
</video>
|
||||
|
||||
<object v-else-if="previewType == 'pdf'" class="pdf" :data="raw"></object>
|
||||
<div v-else-if="previewType == 'pdf'" class="pdf-wrapper">
|
||||
<iframe class="pdf" :src="raw"></iframe>
|
||||
<a
|
||||
v-if="isMobileSafari"
|
||||
:href="raw"
|
||||
target="_blank"
|
||||
class="button button--flat floating-btn"
|
||||
>
|
||||
<div>
|
||||
<i class="material-icons">open_in_new</i>{{ $t("buttons.openFile") }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div v-else class="info">
|
||||
<div class="title">
|
||||
<i class="material-icons">feedback</i>
|
||||
@@ -104,7 +117,6 @@ export default {
|
||||
showNav: true,
|
||||
navTimeout: null,
|
||||
hoverNav: false,
|
||||
autoPlay: false,
|
||||
previousRaw: "",
|
||||
nextRaw: "",
|
||||
currentPrompt: null, // Replaces Vuex getter `currentPrompt`
|
||||
@@ -112,6 +124,15 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
autoPlay() {
|
||||
return state.user.preview.autoplayMedia;
|
||||
},
|
||||
isMobileSafari() {
|
||||
const userAgent = window.navigator.userAgent;
|
||||
const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
|
||||
return isIOS && isSafari;
|
||||
},
|
||||
pdfConvertable() {
|
||||
const ext = "." + state.req.name.split(".").pop().toLowerCase(); // Ensure lowercase and dot
|
||||
const pdfConvertCompatibleFileExtensions = {
|
||||
@@ -121,7 +142,6 @@ export default {
|
||||
".cbz": true,
|
||||
".svg": true,
|
||||
".docx": true,
|
||||
".ppt": true,
|
||||
".pptx": true,
|
||||
".xlsx": true,
|
||||
".hwp": true,
|
||||
@@ -201,14 +221,10 @@ export default {
|
||||
return [];
|
||||
}
|
||||
let subs = [];
|
||||
for (let subtitleFile of state.req.subtitles) {
|
||||
if (state.serverHasMultipleSources) {
|
||||
subtitleFile = "/files/" + state.req.source + subtitleFile;
|
||||
} else {
|
||||
subtitleFile = "/files" + subtitleFile;
|
||||
}
|
||||
for (const subtitleFile of state.req.subtitles) {
|
||||
const ext = getFileExtension(subtitleFile);
|
||||
const resp = await filesApi.fetchFiles(subtitleFile, true); // Fetch .srt file
|
||||
const subUrl = url.removeLastDir(state.req.url) + "/" + subtitleFile;
|
||||
const resp = await filesApi.fetchFiles(subUrl, true); // Fetch .srt file
|
||||
let vttContent = resp.content;
|
||||
// Convert SRT to VTT (assuming srt2vtt() does this)
|
||||
vttContent = convertToVTT(ext, resp.content);
|
||||
@@ -245,7 +261,7 @@ export default {
|
||||
this.hoverNav = false;
|
||||
this.$router.replace({ path: this.nextLink });
|
||||
},
|
||||
key(event) {
|
||||
async key(event) {
|
||||
if (getters.currentPromptName() != null) {
|
||||
return;
|
||||
}
|
||||
@@ -269,9 +285,23 @@ export default {
|
||||
}
|
||||
},
|
||||
async updatePreview() {
|
||||
if (this.$refs.player && this.$refs.player.paused && !this.$refs.player.ended) {
|
||||
this.autoPlay = false;
|
||||
// Try to autoplay media, handle browser restrictions
|
||||
if (this.autoPlay && (this.previewType === "video" || this.previewType === "audio")) {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.player) {
|
||||
const playPromise = this.$refs.player.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.catch((error) => {
|
||||
if (this.$refs.player) {
|
||||
this.$refs.player.muted = true;
|
||||
this.$refs.player.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.listing) {
|
||||
const path = url.removeLastDir(getters.routePath());
|
||||
const res = await filesApi.fetchFiles(path);
|
||||
@@ -348,3 +378,25 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pdf-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pdf-wrapper .pdf {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pdf-wrapper .floating-btn {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
}
|
||||
.pdf-wrapper .floating-btn:hover {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
</style>
|
||||
@@ -23,10 +23,7 @@
|
||||
v-model="localuser.quickDownload"
|
||||
:name="$t('profileSettings.showQuickDownload')"
|
||||
/>
|
||||
</div>
|
||||
<h3>{{ $t("profileSettings.filePreviewOptions") }}</h3>
|
||||
<div class="settings-items">
|
||||
<ToggleSwitch
|
||||
<ToggleSwitch
|
||||
class="item"
|
||||
v-model="localuser.preview.image"
|
||||
:name="$t('profileSettings.previewImages')"
|
||||
@@ -60,6 +57,19 @@
|
||||
:name="$t('profileSettings.popupPreview')"
|
||||
/>
|
||||
</div>
|
||||
<h3>{{ $t("profileSettings.filePreviewOptions") }}</h3>
|
||||
<div class="settings-items">
|
||||
<ToggleSwitch
|
||||
class="item"
|
||||
v-model="localuser.preview.autoplayMedia"
|
||||
:name="$t('profileSettings.autoplayMedia')"
|
||||
/>
|
||||
<ToggleSwitch
|
||||
class="item"
|
||||
v-model="localuser.preview.disableHideSidebar"
|
||||
:name="$t('profileSettings.disableHideSidebar')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="hasOnlyOfficeEnabled">
|
||||
<h3>{{ $t("settings.disableOfficePreview") }}</h3>
|
||||
<p>
|
||||
@@ -107,12 +117,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
v-if="localuser.permissions?.admin"
|
||||
class="item"
|
||||
v-model="localuser.disableUpdateNotifications"
|
||||
:name="$t('profileSettings.disableUpdateNotifications')"
|
||||
/>
|
||||
|
||||
<h3>{{ $t("settings.themeColor") }}</h3>
|
||||
<ButtonGroup
|
||||
:buttons="colorChoices"
|
||||
@@ -125,6 +130,15 @@
|
||||
:locale="localuser.locale"
|
||||
@update:locale="updateLocale"
|
||||
></Languages>
|
||||
<h3 v-if="user.permissions.admin">{{ $t("settings.adminOptions") }}</h3>
|
||||
<div v-if="user.permissions.admin" class="settings-items">
|
||||
<ToggleSwitch
|
||||
v-if="localuser.permissions?.admin"
|
||||
class="item"
|
||||
v-model="localuser.disableUpdateNotifications"
|
||||
:name="$t('profileSettings.disableUpdateNotifications')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -175,6 +189,9 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
muPdfAvailable() {
|
||||
return muPdfAvailable;
|
||||
},
|
||||
@@ -267,4 +284,7 @@ export default {
|
||||
.card-content h3 {
|
||||
text-align: center;
|
||||
}
|
||||
#officePreviewExt {
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
||||
@@ -76,10 +76,8 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
|
||||
await page.locator('li[aria-label="myfolder"]').click();
|
||||
await expect(page.locator('li[aria-selected="true"]')).toHaveCount(1);
|
||||
await page.locator('button[aria-label="Copy"]').click();
|
||||
const popup = page.locator('#popup-notification-content');
|
||||
await popup.waitFor({ state: 'visible' });
|
||||
await expect(popup).toHaveText("Successfully copied file/folder, redirecting...");
|
||||
await page.waitForURL('**/myfolder/');
|
||||
await expect(page.locator('#popup-notification-content')).toHaveText("Resources copied successfully");
|
||||
await page.goto("/files/playwright-files/myfolder/");
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - myfolder");
|
||||
// verify exists and copy again
|
||||
await page.locator('a[aria-label="copyme.txt"]').waitFor({ state: 'visible' });
|
||||
@@ -93,9 +91,9 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
|
||||
await page.locator('input[aria-label="New Folder Name"]').fill('newfolder');
|
||||
await page.locator('button[aria-label="Create"]').click();
|
||||
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - newfolder");
|
||||
await expect(page).toHaveTitle(/.* - newfolder/);
|
||||
await page.goBack();
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - myfolder");
|
||||
await expect(page).toHaveTitle(/.* - myfolder/);
|
||||
|
||||
await page.locator('a[aria-label="copyme.txt"]').click( { button: "right" });
|
||||
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
||||
@@ -104,11 +102,9 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
|
||||
await expect(page.locator('div[aria-label="filelist-path"]')).toHaveText('Path: /myfolder/');
|
||||
await page.locator('li[aria-label="newfolder"]').click();
|
||||
await page.locator('button[aria-label="Copy"]').click();
|
||||
const popup2 = page.locator('#popup-notification-content');
|
||||
await popup2.waitFor({ state: 'visible' });
|
||||
await expect(popup2).toHaveText("Successfully copied file/folder, redirecting...");
|
||||
//await page.waitForURL('**/newfolder/');
|
||||
//await expect(page).toHaveTitle("Graham's Filebrowser - Files - newfolder");
|
||||
await expect(page.locator('#popup-notification-content')).toHaveText("Resources copied successfully");
|
||||
await page.goto("/files/playwright-files/myfolder/newfolder/");
|
||||
await expect(page).toHaveTitle(/.* - newfolder/);
|
||||
checkForErrors();
|
||||
})
|
||||
|
||||
@@ -127,7 +123,6 @@ test("delete file", async({ page, checkForErrors, context }) => {
|
||||
await popup.waitFor({ state: 'visible' });
|
||||
await expect(popup).toHaveText("Deleted item successfully! reloading...");
|
||||
checkForErrors();
|
||||
|
||||
})
|
||||
|
||||
test("delete nested file prompt", async({ page, checkForErrors, context }) => {
|
||||
|
||||
@@ -52,10 +52,8 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
|
||||
await page.locator('li[aria-label="myfolder"]').click();
|
||||
await expect(page.locator('li[aria-selected="true"]')).toHaveCount(1);
|
||||
await page.locator('button[aria-label="Copy"]').click();
|
||||
const popup = page.locator('#popup-notification-content');
|
||||
await popup.waitFor({ state: 'visible' });
|
||||
await expect(popup).toHaveText("Successfully copied file/folder, redirecting...");
|
||||
await page.waitForURL('**/myfolder/');
|
||||
await expect(page.locator('#popup-notification-content')).toHaveText("Resources copied successfully");
|
||||
await page.goto("/files/myfolder/");
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - myfolder");
|
||||
// verify exists and copy again
|
||||
await page.locator('a[aria-label="copyme.txt"]').waitFor({ state: 'visible' });
|
||||
@@ -69,9 +67,9 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
|
||||
await page.locator('input[aria-label="New Folder Name"]').fill('newfolder');
|
||||
await page.locator('button[aria-label="Create"]').click();
|
||||
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - newfolder");
|
||||
await expect(page).toHaveTitle(/.* - newfolder/);
|
||||
await page.goBack();
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - myfolder");
|
||||
await expect(page).toHaveTitle(/.* - myfolder/);
|
||||
|
||||
await page.locator('a[aria-label="copyme.txt"]').click( { button: "right" });
|
||||
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
||||
@@ -80,11 +78,9 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
|
||||
await expect(page.locator('div[aria-label="filelist-path"]')).toHaveText('Path: /myfolder/');
|
||||
await page.locator('li[aria-label="newfolder"]').click();
|
||||
await page.locator('button[aria-label="Copy"]').click();
|
||||
const popup2 = page.locator('#popup-notification-content');
|
||||
await popup2.waitFor({ state: 'visible' });
|
||||
await expect(popup2).toHaveText("Successfully copied file/folder, redirecting...");
|
||||
await page.waitForURL('**/newfolder/');
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - newfolder");
|
||||
await expect(page.locator('#popup-notification-content')).toHaveText("Resources copied successfully");
|
||||
await page.goto("/files/myfolder/newfolder/");
|
||||
await expect(page).toHaveTitle(/.* - newfolder/);
|
||||
checkForErrors();
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { test, expect } from "../test-setup";
|
||||
|
||||
test("navigate folders", async ({ page, checkForErrors, context }) => {
|
||||
await page.goto("/files/");
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
||||
// excluded folder should not be visible in the file list
|
||||
await expect(page.locator('a[aria-label="excluded"]')).toHaveCount(0);
|
||||
await page.goto("/files/excluded");
|
||||
const msg = "Error: {\"status\":500,\"message\":\"could not refresh file info: directory or item excluded from indexing\"}"
|
||||
await expect(page.locator('#popup-notification-content')).toHaveText(msg);
|
||||
checkForErrors(2,1);
|
||||
});
|
||||
Whitespace-only changes.
@@ -28,7 +28,7 @@ run: build-frontend
|
||||
else \
|
||||
sed -i '/func init/,+3d' ./swagger/docs/docs.go; \
|
||||
fi && \
|
||||
FILEBROWSER_NO_EMBEDED=true CGO_ENABLED=1 go run -tags mupdf \
|
||||
FILEBROWSER_NO_EMBEDED=true CGO_ENABLED=1 go run \
|
||||
--ldflags="-w -s -X 'github.com/gtsteffaniak/filebrowser/backend/version.CommitSHA=testingCommit' -X 'github.com/gtsteffaniak/filebrowser/backend/version.Version=testing'" . -c test_config.yaml
|
||||
|
||||
build-frontend:
|
||||
|
||||
Reference in new issue
Block a user