V0.2.9 release (#205)
This commit is contained in:
parent
0bad14b51e
commit
62d1cd88a1
|
@ -12,6 +12,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: Find latest tag
|
||||||
|
run: |
|
||||||
|
echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV
|
||||||
|
echo "latest tag is $LATEST_TAG"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
@ -31,7 +35,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
VERSION=${{ env.LATEST_TAG }}
|
||||||
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|
|
@ -52,5 +52,5 @@ jobs:
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
build-args: |
|
build-args: |
|
||||||
version=${{ steps.meta.outputs.version }}
|
VERSION=${{ steps.meta.outputs.version }}
|
||||||
commitSHA=${{ steps.meta.outputs.revision }}
|
REVISION=${{ steps.meta.outputs.revision }}
|
|
@ -35,7 +35,7 @@ jobs:
|
||||||
JSON="${{ steps.meta.outputs.tags }}"
|
JSON="${{ steps.meta.outputs.tags }}"
|
||||||
# Use jq to remove 'v' from the version field
|
# Use jq to remove 'v' from the version field
|
||||||
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
||||||
echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT
|
echo "CLEANED_TAG=$JSON" >> $GITHUB_ENV
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
|
@ -46,5 +46,5 @@ jobs:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
|
tags: ${{ env.CLEANED_TAG }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -2,6 +2,25 @@
|
||||||
|
|
||||||
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).
|
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.2.9
|
||||||
|
|
||||||
|
This release focused on UI navigation experience. Improving keyboard navigation and adds right click context menu.
|
||||||
|
|
||||||
|
**New Features**:
|
||||||
|
- listing view items are middle-clickable on selected listing or when in single-click mode.
|
||||||
|
- listing view items can be navigated via arrow keys.
|
||||||
|
- listing view can jump to items using letters and number keys to cycle through files that start with that character.
|
||||||
|
- You can use the enter key and backspace key to navigate backwards and forwards on selected items.
|
||||||
|
- ctr-space will open/close the search (leaving ctr-f to browser default find prompt)
|
||||||
|
- Added right-click context menu to replace the file selection prompt.
|
||||||
|
|
||||||
|
**Bugfixes**:
|
||||||
|
- Fixed drag to upload not working.
|
||||||
|
- Fixed shared video link issues.
|
||||||
|
- Fixed user edit bug related to other user.
|
||||||
|
- Fixed password reset bug.
|
||||||
|
- Fixed loading state getting stuck.
|
||||||
|
|
||||||
## v0.2.8
|
## v0.2.8
|
||||||
|
|
||||||
- **Feature**: New gallary view scaling options (closes [#141](https://github.com/gtsteffaniak/filebrowser/issues/141))
|
- **Feature**: New gallary view scaling options (closes [#141](https://github.com/gtsteffaniak/filebrowser/issues/141))
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="frontend/public/img/icons/favicon-256x256.png" width="100" title="Login With Custom URL">
|
<img src="frontend/public/img/icons/favicon-256x256.png" width="100" title="Login With Custom URL">
|
||||||
</p>
|
</p>
|
||||||
<h3 align="center">Filebrowser Quantum - A modern web-based file manager</h3>
|
<h3 align="center">FileBrowser Quantum - A modern web-based file manager</h3>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="800" src="https://github.com/user-attachments/assets/8ba93582-aba2-4996-8ac3-25f763a2e596" title="Main Screenshot">
|
<img width="800" src="https://github.com/user-attachments/assets/8ba93582-aba2-4996-8ac3-25f763a2e596" title="Main Screenshot">
|
||||||
</p>
|
</p>
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
> Starting with v0.2.4 *ALL* share links need to be re-created (due to
|
> Starting with v0.2.4 *ALL* share links need to be re-created (due to
|
||||||
> security fix).
|
> security fix).
|
||||||
|
|
||||||
Filebrowser Quantum is a fork of the filebrowser opensource project with the
|
FileBrowser Quantum is a fork of the filebrowser opensource project with the
|
||||||
following changes:
|
following changes:
|
||||||
|
|
||||||
1. [x] Enhanced lightning fast indexed search
|
1. [x] Enhanced lightning fast indexed search
|
||||||
|
@ -33,7 +33,7 @@ following changes:
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
Filebrowser Quantum provides a file managing interface within a specified directory
|
FileBrowser Quantum provides a file managing interface within a specified directory
|
||||||
and can be used to upload, delete, preview, rename, and edit your files.
|
and can be used to upload, delete, preview, rename, and edit your files.
|
||||||
It allows the creation of multiple users and each user can have its
|
It allows the creation of multiple users and each user can have its
|
||||||
directory.
|
directory.
|
||||||
|
@ -44,7 +44,7 @@ aesthetics and performance. Improved search, simplified ui
|
||||||
(without removing features) and more secure and up-to-date
|
(without removing features) and more secure and up-to-date
|
||||||
build are just a few examples.
|
build are just a few examples.
|
||||||
|
|
||||||
Filebrowser Quantum differs significantly to the original.
|
FileBrowser Quantum differs significantly to the original.
|
||||||
There are hundreds of thousands of lines changed and they are generally
|
There are hundreds of thousands of lines changed and they are generally
|
||||||
no longer compatible with each other. This has been intentional -- the
|
no longer compatible with each other. This has been intentional -- the
|
||||||
focus of this fork is on a few key principles:
|
focus of this fork is on a few key principles:
|
||||||
|
|
|
@ -187,7 +187,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
|
||||||
func (a *HookAuth) GetUser(d *users.User) *users.User {
|
func (a *HookAuth) GetUser(d *users.User) *users.User {
|
||||||
// adds all permissions when user is admin
|
// adds all permissions when user is admin
|
||||||
isAdmin := d.Perm.Admin
|
isAdmin := d.Perm.Admin
|
||||||
perms := users.Permissions{
|
perms := settings.Permissions{
|
||||||
Admin: isAdmin,
|
Admin: isAdmin,
|
||||||
Execute: isAdmin || d.Perm.Execute,
|
Execute: isAdmin || d.Perm.Execute,
|
||||||
Create: isAdmin || d.Perm.Create,
|
Create: isAdmin || d.Perm.Create,
|
||||||
|
|
|
@ -5,49 +5,37 @@
|
||||||
? github.com/gtsteffaniak/filebrowser/auth [no test files]
|
? github.com/gtsteffaniak/filebrowser/auth [no test files]
|
||||||
? github.com/gtsteffaniak/filebrowser/cmd [no test files]
|
? github.com/gtsteffaniak/filebrowser/cmd [no test files]
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/diskcache 0.003s
|
ok github.com/gtsteffaniak/filebrowser/diskcache 0.004s
|
||||||
? github.com/gtsteffaniak/filebrowser/errors [no test files]
|
? github.com/gtsteffaniak/filebrowser/errors [no test files]
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
pkg: github.com/gtsteffaniak/filebrowser/files
|
pkg: github.com/gtsteffaniak/filebrowser/files
|
||||||
cpu: 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz
|
cpu: 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz
|
||||||
BenchmarkFillIndex-8 10 3587120 ns/op 273640 B/op 2013 allocs/op
|
BenchmarkFillIndex-8 10 3559830 ns/op 274639 B/op 2026 allocs/op
|
||||||
BenchmarkSearchAllIndexes-8 10 31291180 ns/op 19500700 B/op 298636 allocs/op
|
BenchmarkSearchAllIndexes-8 10 31912612 ns/op 20545741 B/op 312477 allocs/op
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/files 0.408s
|
ok github.com/gtsteffaniak/filebrowser/files 0.417s
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/fileutils 0.003s
|
ok github.com/gtsteffaniak/filebrowser/fileutils 0.002s
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
2024/08/27 16:16:13 h: 401 <nil>
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
2024/08/27 16:16:13 h: 401 <nil>
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
2024/08/27 16:16:13 h: 401 <nil>
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
2024/08/27 16:16:13 h: 401 <nil>
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
2024/08/27 16:16:13 h: 401 <nil>
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
2024/08/27 16:16:13 h: 401 <nil>
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
|
||||||
2024/02/07 07:16:43 h: 401 <nil>
|
|
||||||
2024/02/07 07:16:43 h: 401 <nil>
|
|
||||||
2024/02/07 07:16:43 h: 401 <nil>
|
|
||||||
2024/02/07 07:16:43 h: 401 <nil>
|
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
|
||||||
2024/02/07 07:16:43 Saving new user: publicUser
|
|
||||||
2024/02/07 07:16:43 h: 401 <nil>
|
|
||||||
2024/02/07 07:16:43 h: 401 <nil>
|
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/http 0.202s
|
ok github.com/gtsteffaniak/filebrowser/http 0.100s
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/img 0.125s
|
ok github.com/gtsteffaniak/filebrowser/img 0.124s
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/rules 0.002s
|
ok github.com/gtsteffaniak/filebrowser/rules 0.002s
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/runner 0.003s
|
ok github.com/gtsteffaniak/filebrowser/runner 0.003s
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/settings 0.005s
|
ok github.com/gtsteffaniak/filebrowser/settings 0.004s
|
||||||
? github.com/gtsteffaniak/filebrowser/share [no test files]
|
? github.com/gtsteffaniak/filebrowser/share [no test files]
|
||||||
? github.com/gtsteffaniak/filebrowser/storage [no test files]
|
? github.com/gtsteffaniak/filebrowser/storage [no test files]
|
||||||
? github.com/gtsteffaniak/filebrowser/storage/bolt [no test files]
|
? github.com/gtsteffaniak/filebrowser/storage/bolt [no test files]
|
||||||
PASS
|
PASS
|
||||||
ok github.com/gtsteffaniak/filebrowser/users 0.003s
|
ok github.com/gtsteffaniak/filebrowser/users 0.002s
|
||||||
? github.com/gtsteffaniak/filebrowser/version [no test files]
|
? github.com/gtsteffaniak/filebrowser/version [no test files]
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/gtsteffaniak/filebrowser/img"
|
"github.com/gtsteffaniak/filebrowser/img"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/users"
|
||||||
|
"github.com/gtsteffaniak/filebrowser/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed dist/*
|
//go:embed dist/*
|
||||||
|
@ -47,7 +48,7 @@ func init() {
|
||||||
// Bind the flags to the pflag command line parser
|
// Bind the flags to the pflag command line parser
|
||||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
log.Println("Initializing with config file:", *configFlag)
|
log.Printf("Initializing FileBrowser Quantum (%v) with config file: %v \n", version.Version, *configFlag)
|
||||||
log.Println("Embeded Frontend:", !nonEmbededFS)
|
log.Println("Embeded Frontend:", !nonEmbededFS)
|
||||||
settings.Initialize(*configFlag)
|
settings.Initialize(*configFlag)
|
||||||
}
|
}
|
||||||
|
@ -162,8 +163,7 @@ func quickSetup(d pythonData) {
|
||||||
checkErr("d.store.Settings.Save", err)
|
checkErr("d.store.Settings.Save", err)
|
||||||
err = d.store.Settings.SaveServer(&settings.Config.Server)
|
err = d.store.Settings.SaveServer(&settings.Config.Server)
|
||||||
checkErr("d.store.Settings.SaveServer", err)
|
checkErr("d.store.Settings.SaveServer", err)
|
||||||
user := &users.User{}
|
user := users.ApplyDefaults(users.User{})
|
||||||
settings.Config.UserDefaults.Apply(user)
|
|
||||||
user.Username = settings.Config.Auth.AdminUsername
|
user.Username = settings.Config.Auth.AdminUsername
|
||||||
user.Password = settings.Config.Auth.AdminPassword
|
user.Password = settings.Config.Auth.AdminPassword
|
||||||
user.Perm.Admin = true
|
user.Perm.Admin = true
|
||||||
|
@ -171,7 +171,7 @@ func quickSetup(d pythonData) {
|
||||||
user.DarkMode = true
|
user.DarkMode = true
|
||||||
user.ViewMode = "normal"
|
user.ViewMode = "normal"
|
||||||
user.LockPassword = false
|
user.LockPassword = false
|
||||||
user.Perm = users.Permissions{
|
user.Perm = settings.Permissions{
|
||||||
Create: true,
|
Create: true,
|
||||||
Rename: true,
|
Rename: true,
|
||||||
Modify: true,
|
Modify: true,
|
||||||
|
@ -180,6 +180,6 @@ func quickSetup(d pythonData) {
|
||||||
Download: true,
|
Download: true,
|
||||||
Admin: true,
|
Admin: true,
|
||||||
}
|
}
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(&user)
|
||||||
checkErr("d.store.Users.Save", err)
|
checkErr("d.store.Users.Save", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,11 +67,6 @@ func getUserIdentifier(flags *pflag.FlagSet) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func printRules(rulez []rules.Rule, id interface{}) {
|
func printRules(rulez []rules.Rule, id interface{}) {
|
||||||
if id == nil {
|
|
||||||
fmt.Printf("Global Rules:\n\n")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Rules for user %v:\n\n", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, rule := range rulez {
|
for id, rule := range rulez {
|
||||||
fmt.Printf("(%d) ", id)
|
fmt.Printf("(%d) ", id)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -26,6 +26,6 @@ var usersRmCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
checkErr("usersRmCmd", err)
|
checkErr("usersRmCmd", err)
|
||||||
fmt.Println("user deleted successfully")
|
log.Println("user deleted successfully")
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,6 @@ var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print the version number",
|
Short: "Print the version number",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Println("File Browser v" + version.Version + "/" + version.CommitSHA)
|
fmt.Println("File Browser " + version.Version + "/" + version.CommitSHA)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5" //nolint:gosec
|
"crypto/md5"
|
||||||
"crypto/sha1" //nolint:gosec
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -52,6 +52,7 @@ type FileInfo struct {
|
||||||
// FileOptions are the options when getting a file info.
|
// FileOptions are the options when getting a file info.
|
||||||
type FileOptions struct {
|
type FileOptions struct {
|
||||||
Path string // realpath
|
Path string // realpath
|
||||||
|
IsDir bool
|
||||||
Modify bool
|
Modify bool
|
||||||
Expand bool
|
Expand bool
|
||||||
ReadHeader bool
|
ReadHeader bool
|
||||||
|
@ -83,7 +84,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||||
if !opts.Checker.Check(opts.Path) {
|
if !opts.Checker.Check(opts.Path) {
|
||||||
return nil, os.ErrPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
file, err := stat(opts.Path, opts) // Pass opts.Path here
|
file, err := stat(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -101,7 +102,6 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||||
}
|
}
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
||||||
// Lock access for the specific path
|
// Lock access for the specific path
|
||||||
pathMutex := getMutex(opts.Path)
|
pathMutex := getMutex(opts.Path)
|
||||||
|
@ -111,71 +111,65 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
||||||
return nil, os.ErrPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
index := GetIndex(rootPath)
|
index := GetIndex(rootPath)
|
||||||
trimmed := strings.TrimPrefix(opts.Path, "/")
|
adjustedPath := index.makeIndexPath(opts.Path, opts.IsDir)
|
||||||
if trimmed == "" {
|
if opts.IsDir {
|
||||||
trimmed = "/"
|
|
||||||
}
|
|
||||||
adjustedPath := makeIndexPath(trimmed, index.Root)
|
|
||||||
var info FileInfo
|
|
||||||
info, exists := index.GetMetadataInfo(adjustedPath)
|
info, exists := index.GetMetadataInfo(adjustedPath)
|
||||||
if exists && !opts.Content {
|
if exists && !opts.Content {
|
||||||
// Check if the cache time is less than 1 second
|
// Let's not refresh if less than a second has passed
|
||||||
if time.Since(info.CacheTime) > time.Second {
|
if time.Since(info.CacheTime) > time.Second {
|
||||||
go RefreshFileInfo(opts)
|
go RefreshFileInfo(opts) //nolint:errcheck
|
||||||
}
|
}
|
||||||
// refresh cache after
|
// refresh cache after
|
||||||
return &info, nil
|
return &info, nil
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
// don't bother caching content
|
// don't bother caching content
|
||||||
if opts.Content {
|
if opts.Content {
|
||||||
file, err := NewFileInfo(opts)
|
file, err := NewFileInfo(opts)
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
updated := RefreshFileInfo(opts)
|
err := RefreshFileInfo(opts)
|
||||||
if !updated {
|
if err != nil {
|
||||||
file, err := NewFileInfo(opts)
|
file, err := NewFileInfo(opts)
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
info, exists = index.GetMetadataInfo(adjustedPath)
|
info, exists := index.GetMetadataInfo(adjustedPath)
|
||||||
if !exists || info.Name == "" {
|
if !exists || info.Name == "" {
|
||||||
return &FileInfo{}, errors.ErrEmptyKey
|
return &FileInfo{}, errors.ErrEmptyKey
|
||||||
}
|
}
|
||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefreshFileInfo(opts FileOptions) bool {
|
func RefreshFileInfo(opts FileOptions) error {
|
||||||
if !opts.Checker.Check(opts.Path) {
|
if !opts.Checker.Check(opts.Path) {
|
||||||
return false
|
return fmt.Errorf("permission denied: %s", opts.Path)
|
||||||
}
|
}
|
||||||
index := GetIndex(rootPath)
|
index := GetIndex(rootPath)
|
||||||
trimmed := strings.TrimPrefix(opts.Path, "/")
|
adjustedPath := index.makeIndexPath(opts.Path, opts.IsDir)
|
||||||
if trimmed == "" {
|
file, err := stat(opts)
|
||||||
trimmed = "/"
|
|
||||||
}
|
|
||||||
adjustedPath := makeIndexPath(trimmed, index.Root)
|
|
||||||
file, err := stat(opts.Path, opts) // Pass opts.Path here
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return fmt.Errorf("File/folder does not exist to refresh data: %s", opts.Path)
|
||||||
}
|
}
|
||||||
_ = file.detectType(adjustedPath, true, opts.Content, opts.ReadHeader)
|
_ = file.detectType(opts.Path, true, opts.Content, opts.ReadHeader)
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
err := file.readListing(opts.Path, opts.Checker, opts.ReadHeader)
|
err := file.readListing(opts.Path, opts.Checker, opts.ReadHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return fmt.Errorf("Dir info could not be read: %s", opts.Path)
|
||||||
}
|
}
|
||||||
return index.UpdateFileMetadata(adjustedPath, *file)
|
|
||||||
} else {
|
|
||||||
return index.UpdateFileMetadata(adjustedPath, *file)
|
|
||||||
}
|
}
|
||||||
|
result := index.UpdateFileMetadata(adjustedPath, *file)
|
||||||
|
if !result {
|
||||||
|
return fmt.Errorf("File/folder does not exist in metadata: %s", adjustedPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stat(path string, opts FileOptions) (*FileInfo, error) {
|
func stat(opts FileOptions) (*FileInfo, error) {
|
||||||
info, err := os.Lstat(path)
|
info, err := os.Lstat(opts.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
file := &FileInfo{
|
file := &FileInfo{
|
||||||
Path: opts.Path,
|
Path: opts.Path,
|
||||||
Name: info.Name(),
|
Name: info.Name(),
|
||||||
|
@ -185,13 +179,12 @@ func stat(path string, opts FileOptions) (*FileInfo, error) {
|
||||||
Extension: filepath.Ext(info.Name()),
|
Extension: filepath.Ext(info.Name()),
|
||||||
Token: opts.Token,
|
Token: opts.Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
file.IsDir = true
|
file.IsDir = true
|
||||||
}
|
}
|
||||||
if info.Mode()&os.ModeSymlink != 0 {
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
file.IsSymlink = true
|
file.IsSymlink = true
|
||||||
targetInfo, err := os.Stat(path)
|
targetInfo, err := os.Stat(opts.Path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
file.Size = targetInfo.Size()
|
file.Size = targetInfo.Size()
|
||||||
file.IsDir = targetInfo.IsDir()
|
file.IsDir = targetInfo.IsDir()
|
||||||
|
@ -248,20 +241,19 @@ func (i *FileInfo) RealPath() string {
|
||||||
return i.Path
|
return i.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRealPath(relativePath ...string) (string, error) {
|
func GetRealPath(relativePath ...string) (string, bool, error) {
|
||||||
combined := []string{settings.Config.Server.Root}
|
combined := []string{settings.Config.Server.Root}
|
||||||
for _, path := range relativePath {
|
for _, path := range relativePath {
|
||||||
combined = append(combined, strings.TrimPrefix(path, settings.Config.Server.Root))
|
combined = append(combined, strings.TrimPrefix(path, settings.Config.Server.Root))
|
||||||
}
|
}
|
||||||
joinedPath := filepath.Join(combined...)
|
joinedPath := filepath.Join(combined...)
|
||||||
|
|
||||||
// Convert relative path to absolute path
|
// Convert relative path to absolute path
|
||||||
absolutePath, err := filepath.Abs(joinedPath)
|
absolutePath, err := filepath.Abs(joinedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", false, err
|
||||||
}
|
}
|
||||||
if !Exists(absolutePath) {
|
if !Exists(absolutePath) {
|
||||||
return absolutePath, nil // return without error
|
return absolutePath, false, nil // return without error
|
||||||
}
|
}
|
||||||
// Resolve symlinks and get the real path
|
// Resolve symlinks and get the real path
|
||||||
return resolveSymlinks(absolutePath)
|
return resolveSymlinks(absolutePath)
|
||||||
|
@ -272,10 +264,9 @@ func DeleteFiles(absPath string, opts FileOptions) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
parentDir := filepath.Dir(absPath)
|
opts.Path = filepath.Dir(absPath)
|
||||||
opts.Path = parentDir
|
err = RefreshFileInfo(opts)
|
||||||
updated := RefreshFileInfo(opts)
|
if err != nil {
|
||||||
if !updated {
|
|
||||||
return errors.ErrEmptyKey
|
return errors.ErrEmptyKey
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -288,16 +279,14 @@ func WriteDirectory(opts FileOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.Path = filepath.Dir(opts.Path)
|
opts.Path = filepath.Dir(opts.Path)
|
||||||
updated := RefreshFileInfo(opts)
|
err = RefreshFileInfo(opts)
|
||||||
if !updated {
|
if err != nil {
|
||||||
return errors.ErrEmptyKey
|
return errors.ErrEmptyKey
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteFile(opts FileOptions, in io.Reader) error {
|
func WriteFile(opts FileOptions, in io.Reader) error {
|
||||||
fmt.Println("writing file", opts.Path)
|
|
||||||
dst := opts.Path
|
dst := opts.Path
|
||||||
parentDir := filepath.Dir(dst)
|
parentDir := filepath.Dir(dst)
|
||||||
// Split the directory from the destination path
|
// Split the directory from the destination path
|
||||||
|
@ -321,23 +310,21 @@ func WriteFile(opts FileOptions, in io.Reader) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("refreshing info for ", parentDir)
|
|
||||||
opts.Path = parentDir
|
opts.Path = parentDir
|
||||||
updated := RefreshFileInfo(opts)
|
err = RefreshFileInfo(opts)
|
||||||
if !updated {
|
if err != nil {
|
||||||
return errors.ErrEmptyKey
|
return errors.ErrEmptyKey
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveSymlinks resolves symlinks in the given path
|
// resolveSymlinks resolves symlinks in the given path
|
||||||
func resolveSymlinks(path string) (string, error) {
|
func resolveSymlinks(path string) (string, bool, error) {
|
||||||
for {
|
for {
|
||||||
// Get the file info
|
// Get the file info
|
||||||
info, err := os.Lstat(path)
|
info, err := os.Lstat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a symlink
|
// Check if it's a symlink
|
||||||
|
@ -345,14 +332,14 @@ func resolveSymlinks(path string) (string, error) {
|
||||||
// Read the symlink target
|
// Read the symlink target
|
||||||
target, err := os.Readlink(path)
|
target, err := os.Readlink(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the target relative to the symlink's directory
|
// Resolve the target relative to the symlink's directory
|
||||||
path = filepath.Join(filepath.Dir(path), target)
|
path = filepath.Join(filepath.Dir(path), target)
|
||||||
} else {
|
} else {
|
||||||
// Not a symlink, so we are done
|
// Not a symlink, so return the resolved path and check if it's a directory
|
||||||
return path, nil
|
return path, info.IsDir(), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_GetRealPath(t *testing.T) {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trimPrefix := filepath.Dir(filepath.Dir(cwd)) + "/"
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
paths []string
|
||||||
|
want struct {
|
||||||
|
path string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "current directory",
|
||||||
|
paths: []string{
|
||||||
|
"./",
|
||||||
|
},
|
||||||
|
want: struct {
|
||||||
|
path string
|
||||||
|
isDir bool
|
||||||
|
}{
|
||||||
|
path: "backend/files",
|
||||||
|
isDir: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "current directory",
|
||||||
|
paths: []string{
|
||||||
|
"./file.go",
|
||||||
|
},
|
||||||
|
want: struct {
|
||||||
|
path string
|
||||||
|
isDir bool
|
||||||
|
}{
|
||||||
|
path: "backend/files/file.go",
|
||||||
|
isDir: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other test case",
|
||||||
|
paths: []string{
|
||||||
|
"/mnt/doesnt/exist",
|
||||||
|
},
|
||||||
|
want: struct {
|
||||||
|
path string
|
||||||
|
isDir bool
|
||||||
|
}{
|
||||||
|
path: "/mnt/doesnt/exist",
|
||||||
|
isDir: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
realPath, isDir, err := GetRealPath(tt.paths...)
|
||||||
|
adjustedRealPath := strings.TrimPrefix(realPath, trimPrefix)
|
||||||
|
if tt.want.path != adjustedRealPath || tt.want.isDir != isDir {
|
||||||
|
t.Errorf("expected %v:%v but got: %v:%v", tt.want.path, tt.want.isDir, adjustedRealPath, isDir)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Error("got error", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,6 +16,7 @@ type Directory struct {
|
||||||
Metadata map[string]FileInfo
|
Metadata map[string]FileInfo
|
||||||
Files string
|
Files string
|
||||||
}
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
Name string
|
||||||
IsDir bool
|
IsDir bool
|
||||||
|
@ -80,8 +82,7 @@ func indexingScheduler(intervalMinutes uint32) {
|
||||||
// Define a function to recursively index files and directories
|
// Define a function to recursively index files and directories
|
||||||
func (si *Index) indexFiles(path string) error {
|
func (si *Index) indexFiles(path string) error {
|
||||||
// Check if the current directory has been modified since the last indexing
|
// Check if the current directory has been modified since the last indexing
|
||||||
path = strings.TrimSuffix(path, "/")
|
adjustedPath := si.makeIndexPath(path, true)
|
||||||
adjustedPath := makeIndexPath(path, si.Root)
|
|
||||||
dir, err := os.Open(path)
|
dir, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Directory must have been deleted, remove it from the index
|
// Directory must have been deleted, remove it from the index
|
||||||
|
@ -114,7 +115,7 @@ func (si *Index) indexFiles(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (si *Index) InsertFiles(path string) {
|
func (si *Index) InsertFiles(path string) {
|
||||||
adjustedPath := makeIndexPath(path, si.Root)
|
adjustedPath := si.makeIndexPath(path, false)
|
||||||
subDirectory := Directory{}
|
subDirectory := Directory{}
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
|
|
||||||
|
@ -130,9 +131,9 @@ func (si *Index) InsertFiles(path string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (si *Index) InsertDirs(path string) {
|
func (si *Index) InsertDirs(path string) {
|
||||||
adjustedPath := makeIndexPath(path, si.Root)
|
|
||||||
for _, f := range si.GetQuickList() {
|
for _, f := range si.GetQuickList() {
|
||||||
if f.IsDir {
|
if f.IsDir {
|
||||||
|
adjustedPath := si.makeIndexPath(path, true)
|
||||||
if _, exists := si.Directories[adjustedPath]; exists {
|
if _, exists := si.Directories[adjustedPath]; exists {
|
||||||
si.UpdateCount("dirs")
|
si.UpdateCount("dirs")
|
||||||
// Add or update the directory in the map
|
// Add or update the directory in the map
|
||||||
|
@ -154,14 +155,21 @@ func (si *Index) InsertDirs(path string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeIndexPath(path string, root string) string {
|
func (si *Index) makeIndexPath(subPath string, isDir bool) string {
|
||||||
if path == root {
|
if si.Root == subPath {
|
||||||
return "/"
|
return "/"
|
||||||
}
|
}
|
||||||
adjustedPath := strings.TrimPrefix(path, root+"/")
|
// clean path
|
||||||
|
subPath = strings.TrimSuffix(subPath, "/")
|
||||||
|
// remove index prefix
|
||||||
|
adjustedPath := strings.TrimPrefix(subPath, si.Root)
|
||||||
|
// remove trailing slash
|
||||||
adjustedPath = strings.TrimSuffix(adjustedPath, "/")
|
adjustedPath = strings.TrimSuffix(adjustedPath, "/")
|
||||||
|
// add leading slash for root of index
|
||||||
if adjustedPath == "" {
|
if adjustedPath == "" {
|
||||||
adjustedPath = "/"
|
adjustedPath = "/"
|
||||||
|
} else if !isDir {
|
||||||
|
adjustedPath = filepath.Dir(adjustedPath)
|
||||||
}
|
}
|
||||||
return adjustedPath
|
return adjustedPath
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,24 +8,6 @@ import (
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetFileMetadata retrieves the FileInfo from the specified directory in the index.
|
|
||||||
func (si *Index) GetFileMetadata(adjustedPath string) (FileInfo, bool) {
|
|
||||||
si.mu.RLock()
|
|
||||||
dir, exists := si.Directories[adjustedPath]
|
|
||||||
si.mu.RUnlock()
|
|
||||||
if exists {
|
|
||||||
// Initialize the Metadata map if it is nil
|
|
||||||
if dir.Metadata == nil {
|
|
||||||
dir.Metadata = make(map[string]FileInfo)
|
|
||||||
si.SetDirectoryInfo(adjustedPath, dir)
|
|
||||||
return FileInfo{}, false
|
|
||||||
} else {
|
|
||||||
return dir.Metadata[adjustedPath], true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FileInfo{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFileMetadata updates the FileInfo for the specified directory in the index.
|
// UpdateFileMetadata updates the FileInfo for the specified directory in the index.
|
||||||
func (si *Index) UpdateFileMetadata(adjustedPath string, info FileInfo) bool {
|
func (si *Index) UpdateFileMetadata(adjustedPath string, info FileInfo) bool {
|
||||||
si.mu.Lock()
|
si.mu.Lock()
|
||||||
|
@ -45,7 +27,6 @@ func (si *Index) UpdateFileMetadata(adjustedPath string, info FileInfo) bool {
|
||||||
// SetFileMetadata sets the FileInfo for the specified directory in the index.
|
// SetFileMetadata sets the FileInfo for the specified directory in the index.
|
||||||
// internal use only
|
// internal use only
|
||||||
func (si *Index) SetFileMetadata(adjustedPath string, info FileInfo) bool {
|
func (si *Index) SetFileMetadata(adjustedPath string, info FileInfo) bool {
|
||||||
|
|
||||||
_, exists := si.Directories[adjustedPath]
|
_, exists := si.Directories[adjustedPath]
|
||||||
if !exists {
|
if !exists {
|
||||||
return false
|
return false
|
||||||
|
@ -57,6 +38,7 @@ func (si *Index) SetFileMetadata(adjustedPath string, info FileInfo) bool {
|
||||||
|
|
||||||
// GetMetadataInfo retrieves the FileInfo from the specified directory in the index.
|
// GetMetadataInfo retrieves the FileInfo from the specified directory in the index.
|
||||||
func (si *Index) GetMetadataInfo(adjustedPath string) (FileInfo, bool) {
|
func (si *Index) GetMetadataInfo(adjustedPath string) (FileInfo, bool) {
|
||||||
|
fi := FileInfo{}
|
||||||
si.mu.RLock()
|
si.mu.RLock()
|
||||||
dir, exists := si.Directories[adjustedPath]
|
dir, exists := si.Directories[adjustedPath]
|
||||||
si.mu.RUnlock()
|
si.mu.RUnlock()
|
||||||
|
@ -65,11 +47,11 @@ func (si *Index) GetMetadataInfo(adjustedPath string) (FileInfo, bool) {
|
||||||
if dir.Metadata == nil {
|
if dir.Metadata == nil {
|
||||||
dir.Metadata = make(map[string]FileInfo)
|
dir.Metadata = make(map[string]FileInfo)
|
||||||
si.SetDirectoryInfo(adjustedPath, dir)
|
si.SetDirectoryInfo(adjustedPath, dir)
|
||||||
|
} else {
|
||||||
|
fi = dir.Metadata[adjustedPath]
|
||||||
}
|
}
|
||||||
info, metadataExists := dir.Metadata[adjustedPath]
|
|
||||||
return info, metadataExists
|
|
||||||
}
|
}
|
||||||
return FileInfo{}, false
|
return fi, exists
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDirectoryInfo sets the directory information in the index.
|
// SetDirectoryInfo sets the directory information in the index.
|
||||||
|
@ -84,10 +66,7 @@ func (si *Index) GetDirectoryInfo(adjustedPath string) (Directory, bool) {
|
||||||
si.mu.RLock()
|
si.mu.RLock()
|
||||||
dir, exists := si.Directories[adjustedPath]
|
dir, exists := si.Directories[adjustedPath]
|
||||||
si.mu.RUnlock()
|
si.mu.RUnlock()
|
||||||
if exists {
|
return dir, exists
|
||||||
return dir, true
|
|
||||||
}
|
|
||||||
return Directory{}, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (si *Index) RemoveDirectory(path string) {
|
func (si *Index) RemoveDirectory(path string) {
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock for fs.FileInfo
|
||||||
|
type mockFileInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockFileInfo) Name() string { return m.name }
|
||||||
|
func (m mockFileInfo) Size() int64 { return 0 }
|
||||||
|
func (m mockFileInfo) Mode() os.FileMode { return 0 }
|
||||||
|
func (m mockFileInfo) ModTime() time.Time { return time.Now() }
|
||||||
|
func (m mockFileInfo) IsDir() bool { return m.isDir }
|
||||||
|
func (m mockFileInfo) Sys() interface{} { return nil }
|
||||||
|
|
||||||
|
var testIndex Index
|
||||||
|
|
||||||
|
// Test for GetFileMetadata
|
||||||
|
//func TestGetFileMetadata(t *testing.T) {
|
||||||
|
// t.Parallel()
|
||||||
|
// tests := []struct {
|
||||||
|
// name string
|
||||||
|
// adjustedPath string
|
||||||
|
// fileName string
|
||||||
|
// expectedName string
|
||||||
|
// expectedExists bool
|
||||||
|
// }{
|
||||||
|
// {
|
||||||
|
// name: "testpath exists",
|
||||||
|
// adjustedPath: "/testpath",
|
||||||
|
// fileName: "testfile.txt",
|
||||||
|
// expectedName: "testfile.txt",
|
||||||
|
// expectedExists: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "testpath not exists",
|
||||||
|
// adjustedPath: "/testpath",
|
||||||
|
// fileName: "nonexistent.txt",
|
||||||
|
// expectedName: "",
|
||||||
|
// expectedExists: false,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "File exists in /anotherpath",
|
||||||
|
// adjustedPath: "/anotherpath",
|
||||||
|
// fileName: "afile.txt",
|
||||||
|
// expectedName: "afile.txt",
|
||||||
|
// expectedExists: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "File does not exist in /anotherpath",
|
||||||
|
// adjustedPath: "/anotherpath",
|
||||||
|
// fileName: "nonexistentfile.txt",
|
||||||
|
// expectedName: "",
|
||||||
|
// expectedExists: false,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Directory does not exist",
|
||||||
|
// adjustedPath: "/nonexistentpath",
|
||||||
|
// fileName: "testfile.txt",
|
||||||
|
// expectedName: "",
|
||||||
|
// expectedExists: false,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for _, tt := range tests {
|
||||||
|
// t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// fileInfo, exists := testIndex.GetFileMetadata(tt.adjustedPath)
|
||||||
|
// if exists != tt.expectedExists || fileInfo.Name != tt.expectedName {
|
||||||
|
// t.Errorf("expected %v:%v but got: %v:%v", tt.expectedName, tt.expectedExists, //fileInfo.Name, exists)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Test for UpdateFileMetadata
|
||||||
|
func TestUpdateFileMetadata(t *testing.T) {
|
||||||
|
index := &Index{
|
||||||
|
Directories: map[string]Directory{
|
||||||
|
"/testpath": {
|
||||||
|
Metadata: map[string]FileInfo{
|
||||||
|
"testfile.txt": {Name: "testfile.txt"},
|
||||||
|
"anotherfile.txt": {Name: "anotherfile.txt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
info := FileInfo{Name: "testfile.txt"}
|
||||||
|
|
||||||
|
success := index.UpdateFileMetadata("/testpath", info)
|
||||||
|
if !success {
|
||||||
|
t.Fatalf("expected UpdateFileMetadata to succeed")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, exists := index.Directories["/testpath"]
|
||||||
|
if !exists || dir.Metadata["testfile.txt"].Name != "testfile.txt" {
|
||||||
|
t.Fatalf("expected testfile.txt to be updated in the directory metadata")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for GetDirMetadata
|
||||||
|
func TestGetDirMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, exists := testIndex.GetMetadataInfo("/testpath")
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("expected GetDirMetadata to return initialized metadata map")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists = testIndex.GetMetadataInfo("/nonexistent")
|
||||||
|
if exists {
|
||||||
|
t.Fatalf("expected GetDirMetadata to return false for nonexistent directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for SetDirectoryInfo
|
||||||
|
func TestSetDirectoryInfo(t *testing.T) {
|
||||||
|
index := &Index{
|
||||||
|
Directories: map[string]Directory{
|
||||||
|
"/testpath": {
|
||||||
|
Metadata: map[string]FileInfo{
|
||||||
|
"testfile.txt": {Name: "testfile.txt"},
|
||||||
|
"anotherfile.txt": {Name: "anotherfile.txt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dir := Directory{Metadata: map[string]FileInfo{"testfile.txt": {Name: "testfile.txt"}}}
|
||||||
|
index.SetDirectoryInfo("/newPath", dir)
|
||||||
|
storedDir, exists := index.Directories["/newPath"]
|
||||||
|
if !exists || storedDir.Metadata["testfile.txt"].Name != "testfile.txt" {
|
||||||
|
t.Fatalf("expected SetDirectoryInfo to store directory info correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for GetDirectoryInfo
|
||||||
|
func TestGetDirectoryInfo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dir, exists := testIndex.GetDirectoryInfo("/testpath")
|
||||||
|
if !exists || dir.Metadata["testfile.txt"].Name != "testfile.txt" {
|
||||||
|
t.Fatalf("expected GetDirectoryInfo to return correct directory info")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists = testIndex.GetDirectoryInfo("/nonexistent")
|
||||||
|
if exists {
|
||||||
|
t.Fatalf("expected GetDirectoryInfo to return false for nonexistent directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for RemoveDirectory
|
||||||
|
func TestRemoveDirectory(t *testing.T) {
|
||||||
|
index := &Index{
|
||||||
|
Directories: map[string]Directory{
|
||||||
|
"/testpath": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
index.RemoveDirectory("/testpath")
|
||||||
|
_, exists := index.Directories["/testpath"]
|
||||||
|
if exists {
|
||||||
|
t.Fatalf("expected directory to be removed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for UpdateCount
|
||||||
|
func TestUpdateCount(t *testing.T) {
|
||||||
|
index := &Index{}
|
||||||
|
index.UpdateCount("files")
|
||||||
|
if index.NumFiles != 1 {
|
||||||
|
t.Fatalf("expected NumFiles to be 1 after UpdateCount('files')")
|
||||||
|
}
|
||||||
|
if index.NumFiles != 1 {
|
||||||
|
t.Fatalf("expected NumFiles to be 1 after UpdateCount('files')")
|
||||||
|
}
|
||||||
|
index.UpdateCount("dirs")
|
||||||
|
if index.NumDirs != 1 {
|
||||||
|
t.Fatalf("expected NumDirs to be 1 after UpdateCount('dirs')")
|
||||||
|
}
|
||||||
|
index.UpdateCount("unknown")
|
||||||
|
// Just ensure it does not panic or update any counters
|
||||||
|
if index.NumFiles != 1 || index.NumDirs != 1 {
|
||||||
|
t.Fatalf("expected counts to remain unchanged for unknown type")
|
||||||
|
}
|
||||||
|
index.resetCount()
|
||||||
|
if index.NumFiles != 0 || index.NumDirs != 0 || !index.inProgress {
|
||||||
|
t.Fatalf("expected resetCount to reset counts and set inProgress to true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testIndex = Index{
|
||||||
|
NumFiles: 10,
|
||||||
|
NumDirs: 5,
|
||||||
|
inProgress: false,
|
||||||
|
Directories: map[string]Directory{
|
||||||
|
"/testpath": {
|
||||||
|
Metadata: map[string]FileInfo{
|
||||||
|
"testfile.txt": {Name: "testfile.txt"},
|
||||||
|
"anotherfile.txt": {Name: "anotherfile.txt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/anotherpath": {
|
||||||
|
Metadata: map[string]FileInfo{
|
||||||
|
"afile.txt": {Name: "afile.txt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files := []fs.FileInfo{
|
||||||
|
mockFileInfo{name: "file1.txt", isDir: false},
|
||||||
|
mockFileInfo{name: "dir1", isDir: true},
|
||||||
|
}
|
||||||
|
testIndex.UpdateQuickList(files)
|
||||||
|
}
|
|
@ -47,7 +47,7 @@ require (
|
||||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.etcd.io/bbolt v1.3.10 // indirect
|
go.etcd.io/bbolt v1.3.11 // indirect
|
||||||
golang.org/x/net v0.28.0 // indirect
|
golang.org/x/net v0.28.0 // indirect
|
||||||
golang.org/x/sys v0.24.0 // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||||
|
|
|
@ -2,7 +2,6 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
||||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
|
||||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
@ -33,8 +32,6 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV
|
||||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
|
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
|
||||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
||||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||||
|
@ -42,11 +39,9 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
|
||||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
@ -62,7 +57,6 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
|
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
|
||||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
|
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
|
||||||
|
@ -71,7 +65,6 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
|
|
||||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
@ -85,12 +78,10 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
|
|
||||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
@ -103,29 +94,21 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
|
github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
|
||||||
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
|
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
|
||||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
|
||||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
|
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
|
||||||
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
@ -143,7 +126,6 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
|
|
||||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
@ -153,18 +135,13 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
|
||||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
|
||||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
|
||||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
|
||||||
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
|
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
|
||||||
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
@ -174,15 +151,13 @@ golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
|
||||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -192,22 +167,16 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
|
||||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
|
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
|
||||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
|
|
@ -2,7 +2,6 @@ package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -127,11 +126,10 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int,
|
||||||
return http.StatusBadRequest, nil
|
return http.StatusBadRequest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &users.User{
|
user := users.ApplyDefaults(users.User{})
|
||||||
Username: info.Username,
|
user.Username = info.Username
|
||||||
Password: info.Password,
|
user.Password = info.Password
|
||||||
}
|
|
||||||
settings.Config.UserDefaults.Apply(user)
|
|
||||||
userHome, err := d.settings.MakeUserDir(user.Username, user.Scope, d.server.Root)
|
userHome, err := d.settings.MakeUserDir(user.Username, user.Scope, d.server.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("create user: failed to mkdir user home dir: [%s]", userHome)
|
log.Printf("create user: failed to mkdir user home dir: [%s]", userHome)
|
||||||
|
@ -139,8 +137,7 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int,
|
||||||
}
|
}
|
||||||
user.Scope = userHome
|
user.Scope = userHome
|
||||||
log.Printf("new user: %s, home dir: [%s].", user.Username, userHome)
|
log.Printf("new user: %s, home dir: [%s].", user.Username, userHome)
|
||||||
settings.Config.UserDefaults.Apply(user)
|
err = d.store.Users.Save(&user)
|
||||||
err = d.store.Users.Save(user)
|
|
||||||
if err == errors.ErrExist {
|
if err == errors.ErrExist {
|
||||||
return http.StatusConflict, err
|
return http.StatusConflict, err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -157,7 +154,6 @@ var renewHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data
|
||||||
func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User) (int, error) {
|
func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User) (int, error) {
|
||||||
duration, err := time.ParseDuration(settings.Config.Auth.TokenExpirationTime)
|
duration, err := time.ParseDuration(settings.Config.Auth.TokenExpirationTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error parsing duration:", err)
|
|
||||||
duration = time.Hour * 2
|
duration = time.Hour * 2
|
||||||
}
|
}
|
||||||
claims := &authToken{
|
claims := &authToken{
|
||||||
|
|
|
@ -19,25 +19,25 @@ import (
|
||||||
var withHashFile = func(fn handleFunc) handleFunc {
|
var withHashFile = func(fn handleFunc) handleFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
id, path := ifPathWithName(r)
|
id, path := ifPathWithName(r)
|
||||||
fmt.Println(id, path)
|
|
||||||
link, err := d.store.Share.GetByHash(id)
|
link, err := d.store.Share.GetByHash(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
if link.Hash != "" {
|
if link.Hash != "" {
|
||||||
var status int
|
var status int
|
||||||
status, err = authenticateShareRequest(r, link) // Assign to the existing `err` variable
|
status, err = authenticateShareRequest(r, link)
|
||||||
if err != nil || status != 0 {
|
if err != nil || status != 0 {
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.user = &users.PublicUser
|
d.user = &users.PublicUser
|
||||||
realPath, err := files.GetRealPath(d.user.Scope, link.Path, path)
|
realPath, isDir, err := files.GetRealPath(d.user.Scope, link.Path, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
file, err := files.FileInfoFaster(files.FileOptions{
|
file, err := files.FileInfoFaster(files.FileOptions{
|
||||||
Path: realPath,
|
Path: realPath,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: true,
|
Expand: true,
|
||||||
ReadHeader: d.server.TypeDetectionByHeader,
|
ReadHeader: d.server.TypeDetectionByHeader,
|
||||||
|
|
|
@ -81,12 +81,13 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data)
|
||||||
if !d.user.Perm.Download {
|
if !d.user.Perm.Download {
|
||||||
return http.StatusAccepted, nil
|
return http.StatusAccepted, nil
|
||||||
}
|
}
|
||||||
realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
file, err := files.FileInfoFaster(files.FileOptions{
|
file, err := files.FileInfoFaster(files.FileOptions{
|
||||||
Path: realPath,
|
Path: realPath,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: false,
|
Expand: false,
|
||||||
ReadHeader: d.server.TypeDetectionByHeader,
|
ReadHeader: d.server.TypeDetectionByHeader,
|
||||||
|
|
|
@ -18,13 +18,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("unable to get real path", d.user.Scope, r.URL.Path)
|
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
file, err := files.FileInfoFaster(files.FileOptions{
|
file, err := files.FileInfoFaster(files.FileOptions{
|
||||||
Path: realPath,
|
Path: realPath,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: true,
|
Expand: true,
|
||||||
ReadHeader: d.server.TypeDetectionByHeader,
|
ReadHeader: d.server.TypeDetectionByHeader,
|
||||||
|
@ -34,10 +34,7 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
if file.IsDir {
|
if !file.IsDir {
|
||||||
file.Listing.Sorting = d.user.Sorting
|
|
||||||
return renderJSON(w, r, file)
|
|
||||||
}
|
|
||||||
if checksum := r.URL.Query().Get("checksum"); checksum != "" {
|
if checksum := r.URL.Query().Get("checksum"); checksum != "" {
|
||||||
err := file.Checksum(checksum)
|
err := file.Checksum(checksum)
|
||||||
if err == errors.ErrInvalidOption {
|
if err == errors.ErrInvalidOption {
|
||||||
|
@ -46,7 +43,7 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return renderJSON(w, r, file)
|
return renderJSON(w, r, file)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -55,12 +52,13 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
||||||
if r.URL.Path == "/" || !d.user.Perm.Delete {
|
if r.URL.Path == "/" || !d.user.Perm.Delete {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
fileOpts := files.FileOptions{
|
fileOpts := files.FileOptions{
|
||||||
Path: realPath,
|
Path: realPath,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: false,
|
Expand: false,
|
||||||
ReadHeader: d.server.TypeDetectionByHeader,
|
ReadHeader: d.server.TypeDetectionByHeader,
|
||||||
|
@ -90,12 +88,13 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||||
if !d.user.Perm.Create || !d.Check(r.URL.Path) {
|
if !d.user.Perm.Create || !d.Check(r.URL.Path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
fileOpts := files.FileOptions{
|
fileOpts := files.FileOptions{
|
||||||
Path: realPath,
|
Path: realPath,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: false,
|
Expand: false,
|
||||||
ReadHeader: d.server.TypeDetectionByHeader,
|
ReadHeader: d.server.TypeDetectionByHeader,
|
||||||
|
@ -109,7 +108,6 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||||
}
|
}
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := files.FileInfoFaster(fileOpts)
|
file, err := files.FileInfoFaster(fileOpts)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if r.URL.Query().Get("override") != "true" {
|
if r.URL.Query().Get("override") != "true" {
|
||||||
|
@ -141,12 +139,13 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
fileOpts := files.FileOptions{
|
fileOpts := files.FileOptions{
|
||||||
Path: realPath,
|
Path: realPath,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: false,
|
Expand: false,
|
||||||
ReadHeader: d.server.TypeDetectionByHeader,
|
ReadHeader: d.server.TypeDetectionByHeader,
|
||||||
|
@ -187,7 +186,6 @@ func resourcePatchHandler(fileCache FileCache) handleFunc {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
err = d.RunHook(func() error {
|
err = d.RunHook(func() error {
|
||||||
fmt.Println("hook", src, dst)
|
|
||||||
return patchAction(r.Context(), action, src, dst, d, fileCache)
|
return patchAction(r.Context(), action, src, dst, d, fileCache)
|
||||||
}, action, src, dst, d.user)
|
}, action, src, dst, d.user)
|
||||||
|
|
||||||
|
@ -237,16 +235,17 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach
|
||||||
}
|
}
|
||||||
src = path.Clean("/" + src)
|
src = path.Clean("/" + src)
|
||||||
dst = path.Clean("/" + dst)
|
dst = path.Clean("/" + dst)
|
||||||
realDest, err := files.GetRealPath(d.user.Scope, dst)
|
realDest, _, err := files.GetRealPath(d.user.Scope, dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
realSrc, err := files.GetRealPath(d.user.Scope, src)
|
realSrc, isDir, err := files.GetRealPath(d.user.Scope, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
file, err := files.FileInfoFaster(files.FileOptions{
|
file, err := files.FileInfoFaster(files.FileOptions{
|
||||||
Path: realSrc,
|
Path: realSrc,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: false,
|
Expand: false,
|
||||||
ReadHeader: false,
|
ReadHeader: false,
|
||||||
|
@ -274,12 +273,13 @@ type DiskUsageResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var diskUsage = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var diskUsage = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
file, err := files.FileInfoFaster(files.FileOptions{
|
file, err := files.FileInfoFaster(files.FileOptions{
|
||||||
Path: realPath,
|
Path: realPath,
|
||||||
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: false,
|
Expand: false,
|
||||||
ReadHeader: false,
|
ReadHeader: false,
|
||||||
|
|
|
@ -2,7 +2,6 @@ package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -131,19 +130,21 @@ var userPostHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *
|
||||||
return http.StatusBadRequest, errors.ErrEmptyPassword
|
return http.StatusBadRequest, errors.ErrEmptyPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newUser := users.ApplyDefaults(*req.Data)
|
||||||
|
|
||||||
userHome, err := d.settings.MakeUserDir(req.Data.Username, req.Data.Scope, d.server.Root)
|
userHome, err := d.settings.MakeUserDir(req.Data.Username, req.Data.Scope, d.server.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("create user: failed to mkdir user home dir: [%s]", userHome)
|
log.Printf("create user: failed to mkdir user home dir: [%s]", userHome)
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
req.Data.Scope = userHome
|
newUser.Scope = userHome
|
||||||
log.Printf("user: %s, home dir: [%s].", req.Data.Username, userHome)
|
log.Printf("user: %s, home dir: [%s].", req.Data.Username, userHome)
|
||||||
_, err = files.GetRealPath(d.server.Root, req.Data.Scope)
|
_, _, err = files.GetRealPath(d.server.Root, req.Data.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("user path is not valid", req.Data.Scope)
|
log.Println("user path is not valid", req.Data.Scope)
|
||||||
return http.StatusBadRequest, nil
|
return http.StatusBadRequest, nil
|
||||||
}
|
}
|
||||||
err = d.store.Users.Save(req.Data)
|
err = d.store.Users.Save(&newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
@ -161,7 +162,7 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
||||||
if req.Data.ID != d.raw.(uint) {
|
if req.Data.ID != d.raw.(uint) {
|
||||||
return http.StatusBadRequest, nil
|
return http.StatusBadRequest, nil
|
||||||
}
|
}
|
||||||
_, err = files.GetRealPath(d.server.Root, req.Data.Scope)
|
_, _, err = files.GetRealPath(d.server.Root, req.Data.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, nil
|
return http.StatusBadRequest, nil
|
||||||
}
|
}
|
||||||
|
@ -175,7 +176,9 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
field := t.Field(i)
|
field := t.Field(i)
|
||||||
if field.Name != "Password" && field.Name != "Fs" {
|
if field.Name == "Password" && req.Data.Password != "" {
|
||||||
|
req.Which = append(req.Which, field.Name)
|
||||||
|
} else if field.Name != "Password" && field.Name != "Fs" {
|
||||||
req.Which = append(req.Which, field.Name)
|
req.Which = append(req.Which, field.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ checkExit() {
|
||||||
if command -v go &> /dev/null
|
if command -v go &> /dev/null
|
||||||
then
|
then
|
||||||
printf "\n == Running tests == \n"
|
printf "\n == Running tests == \n"
|
||||||
go test -race -v ./...
|
go test -race -parallel -v ./...
|
||||||
checkExit
|
checkExit
|
||||||
else
|
else
|
||||||
echo "ERROR: unable to perform tests"
|
echo "ERROR: unable to perform tests"
|
||||||
|
|
|
@ -20,8 +20,8 @@ type Runner struct {
|
||||||
|
|
||||||
// RunHook runs the hooks for the before and after event.
|
// RunHook runs the hooks for the before and after event.
|
||||||
func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.User) error {
|
func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.User) error {
|
||||||
path, _ = files.GetRealPath(user.Scope, path)
|
path, _, _ = files.GetRealPath(user.Scope, path)
|
||||||
dst, _ = files.GetRealPath(user.Scope, dst)
|
dst, _, _ = files.GetRealPath(user.Scope, dst)
|
||||||
|
|
||||||
if r.Enabled {
|
if r.Enabled {
|
||||||
if val, ok := r.Commands["before_"+evt]; ok {
|
if val, ok := r.Commands["before_"+evt]; ok {
|
||||||
|
|
|
@ -3,10 +3,9 @@ package settings
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var Config Settings
|
var Config Settings
|
||||||
|
@ -19,7 +18,16 @@ func Initialize(configFile string) {
|
||||||
log.Fatalf("Error unmarshaling YAML data: %v", err)
|
log.Fatalf("Error unmarshaling YAML data: %v", err)
|
||||||
}
|
}
|
||||||
Config.UserDefaults.Perm = Config.UserDefaults.Permissions
|
Config.UserDefaults.Perm = Config.UserDefaults.Permissions
|
||||||
Config.Server.Root = strings.TrimSuffix(Config.Server.Root, "/")
|
// Convert relative path to absolute path
|
||||||
|
realRoot, err := filepath.Abs(Config.Server.Root)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error getting root path: %v", err)
|
||||||
|
}
|
||||||
|
_, err = os.Stat(realRoot)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("ERROR: Configured Root Path does not exist! %v", err)
|
||||||
|
}
|
||||||
|
Config.Server.Root = realRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfigFile(configFile string) []byte {
|
func loadConfigFile(configFile string) []byte {
|
||||||
|
@ -77,8 +85,9 @@ func setDefaults() Settings {
|
||||||
HideDotfiles: true,
|
HideDotfiles: true,
|
||||||
DarkMode: false,
|
DarkMode: false,
|
||||||
DisableSettings: false,
|
DisableSettings: false,
|
||||||
|
ViewMode: "normal",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
Permissions: users.Permissions{
|
Permissions: Permissions{
|
||||||
Create: false,
|
Create: false,
|
||||||
Rename: false,
|
Rename: false,
|
||||||
Modify: false,
|
Modify: false,
|
||||||
|
@ -90,19 +99,3 @@ func setDefaults() Settings {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply applies the default options to a user.
|
|
||||||
func (d *UserDefaults) Apply(u *users.User) {
|
|
||||||
u.StickySidebar = d.StickySidebar
|
|
||||||
u.DisableSettings = d.DisableSettings
|
|
||||||
u.DarkMode = d.DarkMode
|
|
||||||
u.Scope = d.Scope
|
|
||||||
u.Locale = d.Locale
|
|
||||||
u.ViewMode = d.ViewMode
|
|
||||||
u.SingleClick = d.SingleClick
|
|
||||||
u.Perm = d.Perm
|
|
||||||
u.Sorting = d.Sorting
|
|
||||||
u.Commands = d.Commands
|
|
||||||
u.HideDotfiles = d.HideDotfiles
|
|
||||||
u.DateFormat = d.DateFormat
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gtsteffaniak/filebrowser/rules"
|
"github.com/gtsteffaniak/filebrowser/rules"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
|
@ -82,9 +81,20 @@ type UserDefaults struct {
|
||||||
By string `json:"by"`
|
By string `json:"by"`
|
||||||
Asc bool `json:"asc"`
|
Asc bool `json:"asc"`
|
||||||
} `json:"sorting"`
|
} `json:"sorting"`
|
||||||
Perm users.Permissions `json:"perm"`
|
Perm Permissions `json:"perm"`
|
||||||
Permissions users.Permissions `json:"permissions"`
|
Permissions Permissions `json:"permissions"`
|
||||||
Commands []string `json:"commands,omitempty"`
|
Commands []string `json:"commands,omitempty"`
|
||||||
HideDotfiles bool `json:"hideDotfiles"`
|
HideDotfiles bool `json:"hideDotfiles"`
|
||||||
DateFormat bool `json:"dateFormat"`
|
DateFormat bool `json:"dateFormat"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Permissions struct {
|
||||||
|
Admin bool `json:"admin"`
|
||||||
|
Execute bool `json:"execute"`
|
||||||
|
Create bool `json:"create"`
|
||||||
|
Rename bool `json:"rename"`
|
||||||
|
Modify bool `json:"modify"`
|
||||||
|
Delete bool `json:"delete"`
|
||||||
|
Share bool `json:"share"`
|
||||||
|
Download bool `json:"download"`
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package users
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -123,7 +122,6 @@ func (s *Storage) DeleteRule(userID string, ruleID string) error {
|
||||||
|
|
||||||
// Save saves the user in a storage.
|
// Save saves the user in a storage.
|
||||||
func (s *Storage) Save(user *User) error {
|
func (s *Storage) Save(user *User) error {
|
||||||
log.Println("Saving new user:", user.Username)
|
|
||||||
return s.back.Save(user)
|
return s.back.Save(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,9 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/rules"
|
"github.com/gtsteffaniak/filebrowser/rules"
|
||||||
|
"github.com/gtsteffaniak/filebrowser/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Permissions struct {
|
|
||||||
Admin bool `json:"admin"`
|
|
||||||
Execute bool `json:"execute"`
|
|
||||||
Create bool `json:"create"`
|
|
||||||
Rename bool `json:"rename"`
|
|
||||||
Modify bool `json:"modify"`
|
|
||||||
Delete bool `json:"delete"`
|
|
||||||
Share bool `json:"share"`
|
|
||||||
Download bool `json:"download"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortingSettings represents the sorting settings.
|
// SortingSettings represents the sorting settings.
|
||||||
type Sorting struct {
|
type Sorting struct {
|
||||||
By string `json:"by"`
|
By string `json:"by"`
|
||||||
|
@ -36,7 +26,7 @@ type User struct {
|
||||||
LockPassword bool `json:"lockPassword"`
|
LockPassword bool `json:"lockPassword"`
|
||||||
ViewMode string `json:"viewMode"`
|
ViewMode string `json:"viewMode"`
|
||||||
SingleClick bool `json:"singleClick"`
|
SingleClick bool `json:"singleClick"`
|
||||||
Perm Permissions `json:"perm"`
|
Perm settings.Permissions `json:"perm"`
|
||||||
Commands []string `json:"commands"`
|
Commands []string `json:"commands"`
|
||||||
Sorting Sorting `json:"sorting"`
|
Sorting Sorting `json:"sorting"`
|
||||||
Rules []rules.Rule `json:"rules"`
|
Rules []rules.Rule `json:"rules"`
|
||||||
|
@ -51,7 +41,7 @@ var PublicUser = User{
|
||||||
Scope: "./",
|
Scope: "./",
|
||||||
ViewMode: "normal",
|
ViewMode: "normal",
|
||||||
LockPassword: true,
|
LockPassword: true,
|
||||||
Perm: Permissions{
|
Perm: settings.Permissions{
|
||||||
Create: false,
|
Create: false,
|
||||||
Rename: false,
|
Rename: false,
|
||||||
Modify: false,
|
Modify: false,
|
||||||
|
@ -81,3 +71,20 @@ func (u *User) CanExecute(command string) bool {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply applies the default options to a user.
|
||||||
|
func ApplyDefaults(u User) User {
|
||||||
|
u.StickySidebar = settings.Config.UserDefaults.StickySidebar
|
||||||
|
u.DisableSettings = settings.Config.UserDefaults.DisableSettings
|
||||||
|
u.DarkMode = settings.Config.UserDefaults.DarkMode
|
||||||
|
u.Scope = settings.Config.UserDefaults.Scope
|
||||||
|
u.Locale = settings.Config.UserDefaults.Locale
|
||||||
|
u.ViewMode = settings.Config.UserDefaults.ViewMode
|
||||||
|
u.SingleClick = settings.Config.UserDefaults.SingleClick
|
||||||
|
u.Perm = settings.Config.UserDefaults.Perm
|
||||||
|
u.Sorting = settings.Config.UserDefaults.Sorting
|
||||||
|
u.Commands = settings.Config.UserDefaults.Commands
|
||||||
|
u.HideDotfiles = settings.Config.UserDefaults.HideDotfiles
|
||||||
|
u.DateFormat = settings.Config.UserDefaults.DateFormat
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit" data-vite-ignore></script>
|
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit" data-vite-ignore></script>
|
||||||
[{[ end ]}]
|
[{[ end ]}]
|
||||||
|
|
||||||
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]FileBrowser Quantum[{[ end ]}]</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" sizes="256x256" href="[{[ .StaticURL ]}]/img/icons/favicon-256x256.png">
|
<link rel="icon" type="image/png" sizes="256x256" href="[{[ .StaticURL ]}]/img/icons/favicon-256x256.png">
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
||||||
var dynamicManifest = {
|
var dynamicManifest = {
|
||||||
"name": window.FileBrowser.Name || 'File Browser',
|
"name": window.FileBrowser.Name || 'FileBrowser Quantum',
|
||||||
"short_name": window.FileBrowser.Name || 'FileBrowser',
|
"short_name": window.FileBrowser.Name || 'FileBrowser',
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import { state } from "@/store";
|
import { state } from "@/store";
|
||||||
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
|
// Notify if errors occur
|
||||||
export async function fetch(url, content = false) {
|
export async function fetch(url, content = false) {
|
||||||
|
try {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
const res = await fetchURL(`/api/resources${url}?content=${content}`, {});
|
const res = await fetchURL(`/api/resources${url}?content=${content}`, {});
|
||||||
|
const data = await res.json();
|
||||||
let data = await res.json();
|
|
||||||
data.url = `/files${url}`;
|
data.url = `/files${url}`;
|
||||||
|
|
||||||
if (data.isDir) {
|
if (data.isDir) {
|
||||||
|
@ -25,9 +27,14 @@ export async function fetch(url,content=false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error fetching data");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resourceAction(url, method, content) {
|
async function resourceAction(url, method, content) {
|
||||||
|
try {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
let opts = { method };
|
let opts = { method };
|
||||||
|
@ -37,19 +44,33 @@ async function resourceAction(url, method, content) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetchURL(`/api/resources${url}`, opts);
|
const res = await fetchURL(`/api/resources${url}`, opts);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error performing resource action");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(url) {
|
export async function remove(url) {
|
||||||
return resourceAction(url, "DELETE");
|
try {
|
||||||
|
return await resourceAction(url, "DELETE");
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error deleting resource");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function put(url, content = "") {
|
export async function put(url, content = "") {
|
||||||
return resourceAction(url, "PUT", content);
|
try {
|
||||||
|
return await resourceAction(url, "PUT", content);
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error putting resource");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function download(format, ...files) {
|
export function download(format, ...files) {
|
||||||
|
try {
|
||||||
let url = `${baseURL}/api/raw`;
|
let url = `${baseURL}/api/raw`;
|
||||||
|
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
|
@ -75,9 +96,13 @@ export function download(format, ...files) {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.open(url);
|
window.open(url);
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error downloading files");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post(url, content = "", overwrite = false, onupload) {
|
export async function post(url, content = "", overwrite = false, onupload) {
|
||||||
|
try {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
let bufferContent;
|
let bufferContent;
|
||||||
|
@ -117,6 +142,10 @@ export async function post(url, content = "", overwrite = false, onupload) {
|
||||||
|
|
||||||
request.send(bufferContent || content);
|
request.send(bufferContent || content);
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error posting resource");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveCopy(items, copy = false, overwrite = false, rename = false) {
|
function moveCopy(items, copy = false, overwrite = false, rename = false) {
|
||||||
|
@ -131,7 +160,10 @@ function moveCopy(items, copy = false, overwrite = false, rename = false) {
|
||||||
promises.push(resourceAction(url, "PATCH"));
|
promises.push(resourceAction(url, "PATCH"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises).catch((err) => {
|
||||||
|
notify.showError(err.message || "Error moving/copying resources");
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function move(items, overwrite = false, rename = false) {
|
export function move(items, overwrite = false, rename = false) {
|
||||||
|
@ -143,28 +175,44 @@ export function copy(items, overwrite = false, rename = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checksum(url, algo) {
|
export async function checksum(url, algo) {
|
||||||
|
try {
|
||||||
const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
|
const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
|
||||||
return (await data.json()).checksums[algo];
|
return (await data.json()).checksums[algo];
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error fetching checksum");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDownloadURL(file, inline) {
|
export function getDownloadURL(file, inline) {
|
||||||
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
...(inline && { inline: "true" }),
|
...(inline && { inline: "true" }),
|
||||||
};
|
};
|
||||||
|
|
||||||
return createURL("api/raw" + file.path, params);
|
return createURL("api/raw" + file.path, params);
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error getting download URL");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreviewURL(file, size) {
|
export function getPreviewURL(file, size) {
|
||||||
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
inline: "true",
|
inline: "true",
|
||||||
key: Date.parse(file.modified),
|
key: Date.parse(file.modified),
|
||||||
};
|
};
|
||||||
|
|
||||||
return createURL("api/preview/" + size + file.path, params);
|
return createURL("api/preview/" + size + file.path, params);
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error getting preview URL");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubtitlesURL(file) {
|
export function getSubtitlesURL(file) {
|
||||||
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
inline: "true",
|
inline: "true",
|
||||||
};
|
};
|
||||||
|
@ -175,12 +223,20 @@ export function getSubtitlesURL(file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return subtitles;
|
return subtitles;
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error fetching subtitles URL");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function usage(url) {
|
export async function usage(url) {
|
||||||
|
try {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
const res = await fetchURL(`/api/usage${url}`, {});
|
const res = await fetchURL(`/api/usage${url}`, {});
|
||||||
|
|
||||||
return await res.json();
|
return await res.json();
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error fetching usage data");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,5 +84,6 @@ export function getDownloadURL(share, inline = false) {
|
||||||
if (share.path == undefined) {
|
if (share.path == undefined) {
|
||||||
share.path = ""
|
share.path = ""
|
||||||
}
|
}
|
||||||
return createURL("api/public/dl/" + share.hash + "/"+share.path, params, false);
|
const path = share.path.replace("/share/"+share.hash +"/","")
|
||||||
|
return createURL("api/public/dl/" + share.hash + "/"+path, params, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { fetchURL, removePrefix } from "./utils";
|
import { fetchURL, removePrefix } from "./utils";
|
||||||
import url from "../utils/url";
|
import url from "../utils/url";
|
||||||
|
import { notify } from "@/notify"; // Import notify for error handling
|
||||||
|
|
||||||
export default async function search(base, query) {
|
export default async function search(base, query) {
|
||||||
|
try {
|
||||||
base = removePrefix(base);
|
base = removePrefix(base);
|
||||||
query = encodeURIComponent(query);
|
query = encodeURIComponent(query);
|
||||||
|
|
||||||
|
@ -9,7 +11,7 @@ export default async function search(base, query) {
|
||||||
base += "/";
|
base += "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await fetchURL(`/api/search${base}?query=${query}`, {});
|
const res = await fetchURL(`/api/search${base}?query=${query}`, {});
|
||||||
|
|
||||||
let data = await res.json();
|
let data = await res.json();
|
||||||
|
|
||||||
|
@ -19,4 +21,8 @@ export default async function search(base, query) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error occurred during search");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
import { fetchURL, fetchJSON } from "@/api/utils";
|
import { fetchURL, fetchJSON } from "@/api/utils";
|
||||||
|
import { notify } from "@/notify"; // Import notify for error handling
|
||||||
|
|
||||||
export async function getAllUsers() {
|
export async function getAllUsers() {
|
||||||
|
try {
|
||||||
return await fetchJSON(`/api/users`, {});
|
return await fetchJSON(`/api/users`, {});
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Failed to fetch users");
|
||||||
|
throw err; // Re-throw to handle further if needed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(id) {
|
export async function get(id) {
|
||||||
return fetchJSON(`/api/users/${id}`, {});
|
try {
|
||||||
|
return await fetchJSON(`/api/users/${id}`, {});
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || `Failed to fetch user with ID: ${id}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(user) {
|
export async function create(user) {
|
||||||
|
try {
|
||||||
const res = await fetchURL(`/api/users`, {
|
const res = await fetchURL(`/api/users`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -20,15 +32,23 @@ export async function create(user) {
|
||||||
|
|
||||||
if (res.status === 201) {
|
if (res.status === 201) {
|
||||||
return res.headers.get("Location");
|
return res.headers.get("Location");
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to create user");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || "Error creating user");
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(user, which = ["all"]) {
|
export async function update(user, which = ["all"]) {
|
||||||
if (which[0] != "password") {
|
try {
|
||||||
user.password = "";
|
// List of keys to exclude from the "which" array
|
||||||
}
|
const excludeKeys = ["id", "name"];
|
||||||
if (user.username == "publicUser") {
|
// Filter out the keys from "which"
|
||||||
return
|
which = which.filter(item => !excludeKeys.includes(item));
|
||||||
|
if (user.username === "publicUser") {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
await fetchURL(`/api/users/${user.id}`, {
|
await fetchURL(`/api/users/${user.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
@ -38,10 +58,19 @@ export async function update(user, which = ["all"]) {
|
||||||
data: user,
|
data: user,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || `Failed to update user with ID: ${user.id}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(id) {
|
export async function remove(id) {
|
||||||
|
try {
|
||||||
await fetchURL(`/api/users/${id}`, {
|
await fetchURL(`/api/users/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
notify.showError(err.message || `Failed to delete user with ID: ${id}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { state } from "@/store";
|
||||||
import { renew, logout } from "@/utils/auth";
|
import { renew, logout } from "@/utils/auth";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import { encodePath } from "@/utils/url";
|
import { encodePath } from "@/utils/url";
|
||||||
import { showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
export async function fetchURL(url, opts, auth = true) {
|
export async function fetchURL(url, opts, auth = true) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
@ -51,7 +51,7 @@ export async function fetchJSON(url, opts) {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.json();
|
return res.json();
|
||||||
} else {
|
} else {
|
||||||
showError("unable to fetch : " + url + "status" + res.status);
|
notify.showError("unable to fetch : " + url + "status" + res.status);
|
||||||
throw new Error(res.status);
|
throw new Error(res.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<component :is="element" :to="link.url">{{ link.name }}</component>
|
<component :is="element" :to="link.url">{{ link.name }}</component>
|
||||||
</span>
|
</span>
|
||||||
<action style="display: contents" v-if="showShare" icon="share" show="share" />
|
<action style="display: contents" v-if="showShare" icon="share" show="share" />
|
||||||
<div v-if="isResizableView">
|
<div v-if="isCardView">
|
||||||
Size:
|
Size:
|
||||||
<input
|
<input
|
||||||
v-model="gallerySize"
|
v-model="gallerySize"
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { state, mutations, getters } from "@/store"; // Import mutations as well
|
import { state, mutations, getters } from "@/store"; // Import mutations as well
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/Action.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "breadcrumbs",
|
name: "breadcrumbs",
|
||||||
|
@ -51,8 +51,8 @@ export default {
|
||||||
},
|
},
|
||||||
props: ["base", "noLink"],
|
props: ["base", "noLink"],
|
||||||
computed: {
|
computed: {
|
||||||
isResizableView() {
|
isCardView() {
|
||||||
return getters.isResizableView();
|
return getters.isCardView();
|
||||||
},
|
},
|
||||||
items() {
|
items() {
|
||||||
const relativePath = state.route.path.replace(this.base, "");
|
const relativePath = state.route.path.replace(this.base, "");
|
||||||
|
@ -107,11 +107,6 @@ export default {
|
||||||
return state.user?.perm && state.user?.perm.share; // Access from state directly
|
return state.user?.perm && state.user?.perm.share; // Access from state directly
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: { },
|
||||||
// Example of a method using mutations
|
|
||||||
updateUserPermissions(newPerms) {
|
|
||||||
mutations.updateUser({ perm: newPerms });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
id="context-menu"
|
||||||
|
ref="contextMenu"
|
||||||
|
v-show="showContext"
|
||||||
|
:style="{
|
||||||
|
top: `${top}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
}"
|
||||||
|
class="button"
|
||||||
|
:class="{ 'dark-mode': isDarkMode, mobile: isMobile }"
|
||||||
|
>
|
||||||
|
<div v-if="selectedCount > 0" class="button selected-count-header">
|
||||||
|
<span>{{ selectedCount }} selected</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<action
|
||||||
|
v-if="!headerButtons.select"
|
||||||
|
icon="create_new_folder"
|
||||||
|
:label="$t('sidebar.newFolder')"
|
||||||
|
@action="showHover('newDir')"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="!headerButtons.select"
|
||||||
|
icon="note_add"
|
||||||
|
:label="$t('sidebar.newFile')"
|
||||||
|
@action="showHover('newFile')"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="!headerButtons.select"
|
||||||
|
icon="file_upload"
|
||||||
|
:label="$t('buttons.upload')"
|
||||||
|
@action="uploadFunc"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.select"
|
||||||
|
icon="info"
|
||||||
|
:label="$t('buttons.info')"
|
||||||
|
show="info"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="!isMultiple"
|
||||||
|
icon="check_circle"
|
||||||
|
:label="$t('buttons.selectMultiple')"
|
||||||
|
@action="toggleMultipleSelection"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.download"
|
||||||
|
icon="file_download"
|
||||||
|
:label="$t('buttons.download')"
|
||||||
|
@action="startDownload"
|
||||||
|
:counter="selectedCount"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.share"
|
||||||
|
icon="share"
|
||||||
|
:label="$t('buttons.share')"
|
||||||
|
show="share"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.rename"
|
||||||
|
icon="mode_edit"
|
||||||
|
:label="$t('buttons.rename')"
|
||||||
|
show="rename"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.copy"
|
||||||
|
icon="content_copy"
|
||||||
|
:label="$t('buttons.copyFile')"
|
||||||
|
show="copy"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.move"
|
||||||
|
icon="forward"
|
||||||
|
:label="$t('buttons.moveFile')"
|
||||||
|
show="move"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.delete"
|
||||||
|
icon="delete"
|
||||||
|
:label="$t('buttons.delete')"
|
||||||
|
show="delete"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import downloadFiles from "@/utils/download";
|
||||||
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
|
import Action from "@/components/Action.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ContextMenu",
|
||||||
|
components: {
|
||||||
|
Action,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
posX: 0,
|
||||||
|
posY: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isMultiple() {
|
||||||
|
return state.multiple;
|
||||||
|
},
|
||||||
|
user() {
|
||||||
|
return state.user;
|
||||||
|
},
|
||||||
|
isMobile() {
|
||||||
|
return getters.isMobile();
|
||||||
|
},
|
||||||
|
showContext() {
|
||||||
|
if (getters.currentPromptName() == "ContextMenu" && state.prompts != []) {
|
||||||
|
this.setPositions();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
top() {
|
||||||
|
// Ensure the context menu stays within the viewport
|
||||||
|
return Math.min(
|
||||||
|
this.posY,
|
||||||
|
|
||||||
|
window.innerHeight - (this.$refs.contextMenu?.clientHeight ?? 0)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
left() {
|
||||||
|
return Math.min(
|
||||||
|
this.posX,
|
||||||
|
|
||||||
|
window.innerWidth - (this.$refs.contextMenu?.clientWidth ?? 0)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isDarkMode() {
|
||||||
|
return getters.isDarkMode();
|
||||||
|
},
|
||||||
|
headerButtons() {
|
||||||
|
return {
|
||||||
|
select: state.selected.length > 0,
|
||||||
|
upload: state.user.perm?.create && state.selected.length > 0,
|
||||||
|
download: state.user.perm.download && state.selected.length > 0,
|
||||||
|
delete: state.selected.length > 0 && state.user.perm.delete,
|
||||||
|
rename: state.selected.length === 1 && state.user.perm.rename,
|
||||||
|
share: state.selected.length === 1 && state.user.perm.share,
|
||||||
|
move: state.selected.length > 0 && state.user.perm.rename,
|
||||||
|
copy: state.selected.length > 0 && state.user.perm?.create,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
selectedCount() {
|
||||||
|
return getters.selectedCount();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
uploadFunc() {
|
||||||
|
mutations.showHover("upload");
|
||||||
|
},
|
||||||
|
showHover(value) {
|
||||||
|
return mutations.showHover(value);
|
||||||
|
},
|
||||||
|
setPositions() {
|
||||||
|
const contextProps = getters.currentPrompt().props;
|
||||||
|
this.posX = contextProps.posX;
|
||||||
|
this.posY = contextProps.posY;
|
||||||
|
},
|
||||||
|
toggleMultipleSelection() {
|
||||||
|
mutations.setMultiple(!state.multiple);
|
||||||
|
mutations.closeHovers();
|
||||||
|
},
|
||||||
|
startDownload() {
|
||||||
|
downloadFiles();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#context-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: white;
|
||||||
|
max-width: 20em;
|
||||||
|
min-width: 15em;
|
||||||
|
min-height: 4em;
|
||||||
|
height: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-menu.mobile {
|
||||||
|
top: 50% !important;
|
||||||
|
left: 50% !important;
|
||||||
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-count-header {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
cursor: unset;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-menu .action {
|
||||||
|
width: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-menu > span {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 1em;
|
||||||
|
color: #6f6f6f;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-menu .action span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File selection */
|
||||||
|
#context-menu.dark-mode {
|
||||||
|
background: var(--surfaceSecondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-menu.dark-mode span {
|
||||||
|
color: var(--textPrimary) !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,168 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-if="selectedCount > 0" id="file-selection" :class="{ 'dark-mode': isDarkMode }">
|
|
||||||
<span>{{ selectedCount }} selected</span>
|
|
||||||
<div>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.select"
|
|
||||||
icon="info"
|
|
||||||
:label="$t('buttons.info')"
|
|
||||||
show="info"
|
|
||||||
/>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.select"
|
|
||||||
icon="check_circle"
|
|
||||||
:label="$t('buttons.selectMultiple')"
|
|
||||||
@action="toggleMultipleSelection"
|
|
||||||
/>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.download"
|
|
||||||
icon="file_download"
|
|
||||||
:label="$t('buttons.download')"
|
|
||||||
@action="download"
|
|
||||||
:counter="selectedCount"
|
|
||||||
/>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.share"
|
|
||||||
icon="share"
|
|
||||||
:label="$t('buttons.share')"
|
|
||||||
show="share"
|
|
||||||
/>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.rename"
|
|
||||||
icon="mode_edit"
|
|
||||||
:label="$t('buttons.rename')"
|
|
||||||
show="rename"
|
|
||||||
/>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.copy"
|
|
||||||
icon="content_copy"
|
|
||||||
:label="$t('buttons.copyFile')"
|
|
||||||
show="copy"
|
|
||||||
/>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.move"
|
|
||||||
icon="forward"
|
|
||||||
:label="$t('buttons.moveFile')"
|
|
||||||
show="move"
|
|
||||||
/>
|
|
||||||
<action
|
|
||||||
v-if="headerButtons.delete"
|
|
||||||
icon="delete"
|
|
||||||
:label="$t('buttons.delete')"
|
|
||||||
show="delete"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
|
||||||
import { files as api } from "@/api";
|
|
||||||
import Action from "@/components/header/Action.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "fileSelection",
|
|
||||||
components: {
|
|
||||||
Action,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isDarkMode() {
|
|
||||||
return getters.isDarkMode();
|
|
||||||
},
|
|
||||||
headerButtons() {
|
|
||||||
return {
|
|
||||||
select: state.selected.length > 0,
|
|
||||||
upload: state.user.perm?.create && state.selected.length > 0,
|
|
||||||
download: state.user.perm.download && state.selected.length > 0,
|
|
||||||
delete: state.selected.length > 0 && state.user.perm.delete,
|
|
||||||
rename: state.selected.length === 1 && state.user.perm.rename,
|
|
||||||
share: state.selected.length === 1 && state.user.perm.share,
|
|
||||||
move: state.selected.length > 0 && state.user.perm.rename,
|
|
||||||
copy: state.selected.length > 0 && state.user.perm?.create,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
selectedCount() {
|
|
||||||
return getters.selectedCount();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleMultipleSelection() {
|
|
||||||
mutations.setMultiple(!state.multiple);
|
|
||||||
mutations.closeHovers();
|
|
||||||
},
|
|
||||||
download() {
|
|
||||||
if (getters.isSingleFileSelected()) {
|
|
||||||
api.download(null, getters.selectedDownloadUrl());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mutations.showHover({
|
|
||||||
name: "download",
|
|
||||||
confirm: (format) => {
|
|
||||||
mutations.closeHovers();
|
|
||||||
let files = [];
|
|
||||||
if (state.selected.length > 0) {
|
|
||||||
for (let i of state.selected) {
|
|
||||||
files.push(state.req.items[i].url);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
files.push(state.route.path);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
api.download(format, ...files);
|
|
||||||
showSuccess("download started");
|
|
||||||
} catch (e) {
|
|
||||||
showError("error downloading", e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
#file-selection {
|
|
||||||
bottom: 4em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-selection .action {
|
|
||||||
border-radius: 50%;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-selection > span {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 1em;
|
|
||||||
color: #6f6f6f;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-selection .action span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File Selection */
|
|
||||||
#file-selection {
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.3) 0px 2em 50px 10px;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 4em;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
align-items: center;
|
|
||||||
background: #fff;
|
|
||||||
max-width: 30em;
|
|
||||||
z-index: 3;
|
|
||||||
border-radius: 1em;
|
|
||||||
display: flex;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
/* File selection */
|
|
||||||
#file-selection.dark-mode {
|
|
||||||
background: var(--surfaceSecondary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-selection.dark-mode span {
|
|
||||||
color: var(--textPrimary) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -16,6 +16,7 @@
|
||||||
<i v-else class="material-icons">search</i>
|
<i v-else class="material-icons">search</i>
|
||||||
<!-- Input field for search -->
|
<!-- Input field for search -->
|
||||||
<input
|
<input
|
||||||
|
id="main-input"
|
||||||
class="main-input"
|
class="main-input"
|
||||||
type="text"
|
type="text"
|
||||||
@keyup.exact="keyup"
|
@keyup.exact="keyup"
|
||||||
|
@ -194,7 +195,6 @@
|
||||||
import ButtonGroup from "./ButtonGroup.vue";
|
import ButtonGroup from "./ButtonGroup.vue";
|
||||||
import { search } from "@/api";
|
import { search } from "@/api";
|
||||||
import { getters, mutations, state } from "@/store";
|
import { getters, mutations, state } from "@/store";
|
||||||
import { showError } from "@/notify";
|
|
||||||
|
|
||||||
var boxes = {
|
var boxes = {
|
||||||
folder: { label: "folders", icon: "folder" },
|
folder: { label: "folders", icon: "folder" },
|
||||||
|
@ -248,13 +248,18 @@ export default {
|
||||||
this.submit();
|
this.submit();
|
||||||
},
|
},
|
||||||
active(active) {
|
active(active) {
|
||||||
|
// this is hear to allow for animation
|
||||||
const resultList = document.getElementById("result-list");
|
const resultList = document.getElementById("result-list");
|
||||||
if (!active) {
|
if (!active) {
|
||||||
resultList.classList.remove("active");
|
resultList.classList.remove("active");
|
||||||
|
this.value = "";
|
||||||
|
event.stopPropagation();
|
||||||
|
mutations.closeHovers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resultList.classList.add("active");
|
resultList.classList.add("active");
|
||||||
|
document.getElementById("main-input").focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
},
|
},
|
||||||
value() {
|
value() {
|
||||||
|
@ -394,11 +399,9 @@ export default {
|
||||||
}
|
}
|
||||||
let path = state.route.path;
|
let path = state.route.path;
|
||||||
this.ongoing = true;
|
this.ongoing = true;
|
||||||
try {
|
|
||||||
this.results = await search(path, searchTypesFull + this.value);
|
this.results = await search(path, searchTypesFull + this.value);
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
}
|
|
||||||
this.ongoing = false;
|
this.ongoing = false;
|
||||||
if (this.results.length == 0) {
|
if (this.results.length == 0) {
|
||||||
this.noneMessage = "No results found in indexed search.";
|
this.noneMessage = "No results found in indexed search.";
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations } from "@/store";
|
||||||
import throttle from "@/utils/throttle";
|
import throttle from "@/utils/throttle";
|
||||||
import { showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
src: String,
|
src: String,
|
||||||
|
@ -131,8 +131,7 @@ export default {
|
||||||
imgex.onload = () => URL.revokeObjectURL(imgex.src); // Clean up URL object after loading
|
imgex.onload = () => URL.revokeObjectURL(imgex.src); // Clean up URL object after loading
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError("Error decoding TIFF");
|
notify.showError("Error decoding TIFF");
|
||||||
console.error("Error decoding TIFF:", error);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMouseUp() {
|
onMouseUp() {
|
||||||
|
|
|
@ -1,37 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<component
|
||||||
:class="{ activebutton: this.isMaximized && this.isSelected }"
|
:is="isSelected || user.singleClick ? 'a' : 'div'"
|
||||||
class="item"
|
:href="isSelected || user.singleClick ? url : undefined"
|
||||||
|
:class="{
|
||||||
|
item: true,
|
||||||
|
activebutton: isMaximized && isSelected,
|
||||||
|
}"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
:draggable="isDraggable"
|
:draggable="isDraggable"
|
||||||
@dragstart="dragStart"
|
@dragstart="dragStart"
|
||||||
@dragover="dragOver"
|
@dragover="dragOver"
|
||||||
@drop="drop"
|
@drop="drop"
|
||||||
@click="itemClick"
|
|
||||||
:data-dir="isDir"
|
:data-dir="isDir"
|
||||||
:data-type="type"
|
:data-type="type"
|
||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
:aria-selected="isSelected"
|
:aria-selected="isSelected"
|
||||||
|
@click="isSelected || user.singleClick ? toggleClick() : itemClick($event)"
|
||||||
>
|
>
|
||||||
<div
|
<div @click="toggleClick" :class="{ activetitle: isMaximized && isSelected }">
|
||||||
@click="toggleClick"
|
|
||||||
:class="{ activetitle: this.isMaximized && this.isSelected }"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
|
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
|
||||||
v-lazy="thumbnailUrl"
|
v-lazy="thumbnailUrl"
|
||||||
:class="{ activeimg: this.isMaximized && this.isSelected }"
|
:class="{ activeimg: isMaximized && isSelected }"
|
||||||
ref="thumbnail"
|
ref="thumbnail"
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
:class="{ iconActive: this.isMaximized && this.isSelected }"
|
:class="{ iconActive: isMaximized && isSelected }"
|
||||||
v-else
|
v-else
|
||||||
class="material-icons"
|
class="material-icons"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text" :class="{ activecontent: this.isMaximized && this.isSelected }">
|
<div class="text" :class="{ activecontent: isMaximized && isSelected }">
|
||||||
<p class="name">{{ name }}</p>
|
<p class="name">{{ name }}</p>
|
||||||
<p v-if="isDir" class="size" data-order="-1">—</p>
|
<p v-if="isDir" class="size" data-order="-1">—</p>
|
||||||
<p v-else class="size" :data-order="humanSize()">{{ humanSize() }}</p>
|
<p v-else class="size" :data-order="humanSize()">{{ humanSize() }}</p>
|
||||||
|
@ -39,7 +40,7 @@
|
||||||
<time :datetime="modified">{{ humanTime() }}</time>
|
<time :datetime="modified">{{ humanTime() }}</time>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -73,7 +74,7 @@ import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "item",
|
name: "item",
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isThumbnailInView: false,
|
isThumbnailInView: false,
|
||||||
isMaximized: false,
|
isMaximized: false,
|
||||||
|
@ -98,27 +99,12 @@ export default {
|
||||||
selected() {
|
selected() {
|
||||||
return state.selected;
|
return state.selected;
|
||||||
},
|
},
|
||||||
req() {
|
|
||||||
return state.req;
|
|
||||||
},
|
|
||||||
jwt() {
|
|
||||||
return state.jwt;
|
|
||||||
},
|
|
||||||
selectedCount() {
|
|
||||||
return getters.selectedCount();
|
|
||||||
},
|
|
||||||
isClicked() {
|
isClicked() {
|
||||||
if (state.user.singleClick || !this.allowedView) {
|
if (state.user.singleClick || !this.allowedView) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !this.isMaximized;
|
return !this.isMaximized;
|
||||||
},
|
},
|
||||||
allowedView() {
|
|
||||||
return state.user.viewMode != "gallery" && state.user.viewMode != "normal";
|
|
||||||
},
|
|
||||||
singleClick() {
|
|
||||||
return this.readOnly == undefined && state.user.singleClick;
|
|
||||||
},
|
|
||||||
isSelected() {
|
isSelected() {
|
||||||
return this.selected.indexOf(this.index) !== -1;
|
return this.selected.indexOf(this.index) !== -1;
|
||||||
},
|
},
|
||||||
|
@ -181,18 +167,18 @@ export default {
|
||||||
toggleClick() {
|
toggleClick() {
|
||||||
this.isMaximized = this.isClicked;
|
this.isMaximized = this.isClicked;
|
||||||
},
|
},
|
||||||
humanSize: function () {
|
humanSize() {
|
||||||
return this.type == "invalid_link"
|
return this.type == "invalid_link"
|
||||||
? "invalid link"
|
? "invalid link"
|
||||||
: getHumanReadableFilesize(this.size);
|
: getHumanReadableFilesize(this.size);
|
||||||
},
|
},
|
||||||
humanTime: function () {
|
humanTime() {
|
||||||
if (this.readOnly == undefined && state.user.dateFormat) {
|
if (this.readOnly == undefined && state.user.dateFormat) {
|
||||||
return fromNow(this.modified, state.user.locale).format("L LT");
|
return fromNow(this.modified, state.user.locale).format("L LT");
|
||||||
}
|
}
|
||||||
return fromNow(this.modified, state.user.locale);
|
return fromNow(this.modified, state.user.locale);
|
||||||
},
|
},
|
||||||
dragStart: function () {
|
dragStart() {
|
||||||
if (getters.selectedCount() === 0) {
|
if (getters.selectedCount() === 0) {
|
||||||
mutations.addSelected(this.index);
|
mutations.addSelected(this.index);
|
||||||
return;
|
return;
|
||||||
|
@ -203,7 +189,7 @@ export default {
|
||||||
mutations.addSelected(this.index);
|
mutations.addSelected(this.index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dragOver: function (event) {
|
dragOver(event) {
|
||||||
if (!this.canDrop) return;
|
if (!this.canDrop) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -217,7 +203,7 @@ export default {
|
||||||
|
|
||||||
el.style.opacity = 1;
|
el.style.opacity = 1;
|
||||||
},
|
},
|
||||||
drop: async function (event) {
|
async drop(event) {
|
||||||
if (!this.canDrop) return;
|
if (!this.canDrop) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -276,11 +262,11 @@ export default {
|
||||||
|
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
itemClick: function (event) {
|
itemClick(event) {
|
||||||
if (this.singleClick && !state.multiple) this.open();
|
if (this.singleClick && !state.multiple) this.open();
|
||||||
else this.click(event);
|
else this.click(event);
|
||||||
},
|
},
|
||||||
click: function (event) {
|
click(event) {
|
||||||
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
|
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -321,7 +307,7 @@ export default {
|
||||||
mutations.resetSelected();
|
mutations.resetSelected();
|
||||||
mutations.addSelected(this.index);
|
mutations.addSelected(this.index);
|
||||||
},
|
},
|
||||||
open: function () {
|
open() {
|
||||||
this.$router.push({ path: this.url });
|
this.$router.push({ path: this.url });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,7 +52,7 @@ import FileList from "./FileList.vue";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
import { showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "copy",
|
name: "copy",
|
||||||
|
@ -102,7 +102,7 @@ export default {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
buttons.done("copy");
|
buttons.done("copy");
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
import { showError,showSuccess } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "delete",
|
name: "delete",
|
||||||
|
@ -59,7 +59,7 @@ export default {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
await api.remove(state.route.path);
|
await api.remove(state.route.path);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
showSuccess("Deleted item successfully")
|
showSuccess("Deleted item successfully");
|
||||||
|
|
||||||
this.currentPrompt?.confirm();
|
this.currentPrompt?.confirm();
|
||||||
this.closeHovers();
|
this.closeHovers();
|
||||||
|
@ -79,11 +79,11 @@ export default {
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
showSuccess("Deleted item successfully")
|
showSuccess("Deleted item successfully");
|
||||||
mutations.setReload(true); // Handle reload as needed
|
mutations.setReload(true); // Handle reload as needed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { users as api } from "@/api";
|
import { users as api } from "@/api";
|
||||||
import { showSuccess,showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
|
|
||||||
|
@ -40,12 +40,12 @@ export default {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
try {
|
try {
|
||||||
await api.remove(this.user.id);
|
await api.remove(this.user.id);
|
||||||
this.$router.push({ path: "/settings/users" });
|
this.$router.push({ path: "/settings",hash:"#users-main" });
|
||||||
showSuccess(this.$t("settings.userDeleted"));
|
notify.showSuccess(this.$t("settings.userDeleted"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.message === "403"
|
e.message === "403"
|
||||||
? showError(this.$t("errors.forbidden"), false)
|
? notify.showError(this.$t("errors.forbidden"), false)
|
||||||
: showError(e);
|
: notify.showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeHovers() {
|
closeHovers() {
|
||||||
|
@ -80,7 +80,7 @@ export default {
|
||||||
mutations.setReload(true); // Handle reload as needed
|
mutations.setReload(true); // Handle reload as needed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
import { state, mutations } from "@/store";
|
import { state, mutations } from "@/store";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files } from "@/api";
|
import { files } from "@/api";
|
||||||
import { showError } from "@/notify";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "file-list",
|
name: "file-list",
|
||||||
|
@ -86,7 +85,7 @@ export default {
|
||||||
// content.
|
// content.
|
||||||
let uri = event.currentTarget.dataset.url;
|
let uri = event.currentTarget.dataset.url;
|
||||||
|
|
||||||
files.fetch(uri).then(this.fillOptions).catch(showError);
|
files.fetch(uri).then(this.fillOptions);
|
||||||
},
|
},
|
||||||
touchstart(event) {
|
touchstart(event) {
|
||||||
let url = event.currentTarget.dataset.url;
|
let url = event.currentTarget.dataset.url;
|
||||||
|
|
|
@ -75,7 +75,6 @@ import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||||
import { formatTimestamp } from "@/utils/moment";
|
import { formatTimestamp } from "@/utils/moment";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
import { showError } from "@/notify";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "info",
|
name: "info",
|
||||||
|
@ -146,12 +145,8 @@ export default {
|
||||||
link = state.route.path;
|
link = state.route.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const hash = await api.checksum(link, algo);
|
const hash = await api.checksum(link, algo);
|
||||||
event.target.innerHTML = hash;
|
event.target.innerHTML = hash;
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,7 +52,7 @@ import FileList from "./FileList.vue";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
import { showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "move",
|
name: "move",
|
||||||
|
@ -95,7 +95,7 @@ export default {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
buttons.done("move");
|
buttons.done("move");
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,7 +121,6 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { getters, mutations, state } from "@/store"; // Import your custom store
|
import { getters, mutations, state } from "@/store"; // Import your custom store
|
||||||
import { showError } from "@/notify";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "new-dir",
|
name: "new-dir",
|
||||||
|
@ -87,7 +86,6 @@ export default {
|
||||||
uri += encodeURIComponent(this.name) + "/";
|
uri += encodeURIComponent(this.name) + "/";
|
||||||
uri = uri.replace("//", "/");
|
uri = uri.replace("//", "/");
|
||||||
|
|
||||||
try {
|
|
||||||
await api.post(uri);
|
await api.post(uri);
|
||||||
if (this.redirect) {
|
if (this.redirect) {
|
||||||
this.$router.push({ path: uri });
|
this.$router.push({ path: uri });
|
||||||
|
@ -95,9 +93,6 @@ export default {
|
||||||
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
||||||
mutations.updateRequest(res);
|
mutations.updateRequest(res);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
|
|
|
@ -73,12 +73,8 @@ export default {
|
||||||
uri += encodeURIComponent(this.name);
|
uri += encodeURIComponent(this.name);
|
||||||
uri = uri.replace("//", "/");
|
uri = uri.replace("//", "/");
|
||||||
|
|
||||||
try {
|
|
||||||
await api.post(uri);
|
await api.post(uri);
|
||||||
this.$router.push({ path: uri });
|
this.$router.push({ path: uri });
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
|
|
|
@ -98,7 +98,6 @@ export default {
|
||||||
|
|
||||||
newLink = url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
newLink = url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
||||||
|
|
||||||
try {
|
|
||||||
await api.move([{ from: oldLink, to: newLink }]);
|
await api.move([{ from: oldLink, to: newLink }]);
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
this.$router.push({ path: newLink });
|
this.$router.push({ path: newLink });
|
||||||
|
@ -106,9 +105,6 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
mutations.setReload(true);
|
mutations.setReload(true);
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
|
|
|
@ -120,7 +120,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { showSuccess, showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
import { share as api, pub as pub_api } from "@/api";
|
import { share as api, pub as pub_api } from "@/api";
|
||||||
import { fromNow } from "@/utils/moment";
|
import { fromNow } from "@/utils/moment";
|
||||||
|
@ -173,7 +173,6 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async beforeMount() {
|
async beforeMount() {
|
||||||
try {
|
|
||||||
const links = await api.get(this.url);
|
const links = await api.get(this.url);
|
||||||
this.links = links;
|
this.links = links;
|
||||||
this.sort();
|
this.sort();
|
||||||
|
@ -181,14 +180,11 @@ export default {
|
||||||
if (this.links.length === 0) {
|
if (this.links.length === 0) {
|
||||||
this.listing = false;
|
this.listing = false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.clip = new Clipboard(".copy-clipboard");
|
this.clip = new Clipboard(".copy-clipboard");
|
||||||
this.clip.on("success", () => {
|
this.clip.on("success", () => {
|
||||||
showSuccess(this.$t("success.linkCopied"));
|
notify.showSuccess(this.$t("success.linkCopied"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
@ -198,7 +194,6 @@ export default {
|
||||||
async submit() {
|
async submit() {
|
||||||
let isPermanent = !this.time || this.time === 0;
|
let isPermanent = !this.time || this.time === 0;
|
||||||
|
|
||||||
try {
|
|
||||||
let res = null;
|
let res = null;
|
||||||
|
|
||||||
if (isPermanent) {
|
if (isPermanent) {
|
||||||
|
@ -215,22 +210,15 @@ export default {
|
||||||
this.password = "";
|
this.password = "";
|
||||||
|
|
||||||
this.listing = true;
|
this.listing = true;
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async deleteLink(event, link) {
|
async deleteLink(event, link) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
try {
|
|
||||||
await api.remove(link.hash);
|
await api.remove(link.hash);
|
||||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||||
|
|
||||||
if (this.links.length === 0) {
|
if (this.links.length === 0) {
|
||||||
this.listing = false;
|
this.listing = false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
humanTime(time) {
|
humanTime(time) {
|
||||||
return fromNow(time, state.user.locale);
|
return fromNow(time, state.user.locale);
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default {
|
||||||
handleFiles(event);
|
handleFiles(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFiles = (event) => {
|
const handleFiles = async (event) => {
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
const files = event.target.files;
|
const files = event.target.files;
|
||||||
if (!files) return;
|
if (!files) return;
|
||||||
|
@ -94,21 +94,20 @@ export default {
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
mutations.showHover({
|
mutations.showHover({
|
||||||
name: "replace",
|
name: "replace",
|
||||||
action: (event) => {
|
action: async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
upload.handleFiles(uploadFiles, path, false);
|
await upload.handleFiles(uploadFiles, path, false);
|
||||||
},
|
},
|
||||||
confirm: (event) => {
|
confirm: async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
upload.handleFiles(uploadFiles, path, true);
|
await upload.handleFiles(uploadFiles, path, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
} else {
|
||||||
|
await upload.handleFiles(uploadFiles, path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
upload.handleFiles(uploadFiles, path, true);
|
|
||||||
mutations.setReload(true);
|
mutations.setReload(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
type="text"
|
type="text"
|
||||||
v-model="user.username"
|
v-model="user.username"
|
||||||
id="username"
|
id="username"
|
||||||
|
@input="emitUpdate"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -18,22 +19,24 @@
|
||||||
:placeholder="passwordPlaceholder"
|
:placeholder="passwordPlaceholder"
|
||||||
v-model="user.password"
|
v-model="user.password"
|
||||||
id="password"
|
id="password"
|
||||||
|
@input="emitUpdate"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="scope">{{ $t("settings.scope") }}</label>
|
<label for="scope">{{ $t("settings.scope") }}</label>
|
||||||
<input
|
<input
|
||||||
:disabled="createUserDirData"
|
:disabled="createUserDir"
|
||||||
:placeholder="scopePlaceholder"
|
:placeholder="scopePlaceholder"
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="user.scope"
|
v-model="user.scope"
|
||||||
id="scope"
|
id="scope"
|
||||||
|
@input="emitUpdate"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p class="small" v-if="displayHomeDirectoryCheckbox">
|
<p class="small" v-if="displayHomeDirectoryCheckbox">
|
||||||
<input type="checkbox" v-model="createUserDirData" />
|
<input type="checkbox" v-model="createUserDir" />
|
||||||
{{ $t("settings.createUserHomeDirectory") }}
|
{{ $t("settings.createUserHomeDirectory") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -43,31 +46,32 @@
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
id="locale"
|
id="locale"
|
||||||
v-model:locale="user.locale"
|
v-model:locale="user.locale"
|
||||||
|
@input="emitUpdate"
|
||||||
></languages>
|
></languages>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="!isDefault">
|
<p v-if="!isDefault">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:disabled="user.perm.admin"
|
:disabled="user.perm?.admin"
|
||||||
v-model="user.lockPassword"
|
v-model="user.lockPassword"
|
||||||
|
@input="emitUpdate"
|
||||||
/>
|
/>
|
||||||
{{ $t("settings.lockPassword") }}
|
{{ $t("settings.lockPassword") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<permissions :perm="user.perm" />
|
<permissions :perm="localUser.perm" />
|
||||||
<commands v-if="isExecEnabled" v-model:commands="user.commands" />
|
<commands v-if="isExecEnabled" v-model:commands="user.commands" />
|
||||||
|
|
||||||
<div v-if="!isDefault">
|
<div v-if="!isDefault">
|
||||||
<h3>{{ $t("settings.rules") }}</h3>
|
<h3>{{ $t("settings.rules") }}</h3>
|
||||||
<p class="small">{{ $t("settings.rulesHelp") }}</p>
|
<p class="small">{{ $t("settings.rulesHelp") }}</p>
|
||||||
<rules v-model:rules="user.rules" />
|
<rules v-model:rules="user.rules" @input="emitUpdate" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { state } from "@/store"
|
|
||||||
import Languages from "./Languages.vue";
|
import Languages from "./Languages.vue";
|
||||||
import Rules from "./Rules.vue";
|
import Rules from "./Rules.vue";
|
||||||
import Permissions from "./Permissions.vue";
|
import Permissions from "./Permissions.vue";
|
||||||
|
@ -75,35 +79,51 @@ import Commands from "./Commands.vue";
|
||||||
import { enableExec } from "@/utils/constants";
|
import { enableExec } from "@/utils/constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "user",
|
name: "UserForm",
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
createUserDirData: false,
|
|
||||||
originalUserScope: "/",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
Permissions,
|
Permissions,
|
||||||
Languages,
|
Languages,
|
||||||
Rules,
|
Rules,
|
||||||
Commands,
|
Commands,
|
||||||
},
|
},
|
||||||
props: [ "createUserDir", "isNew", "isDefault"],
|
data() {
|
||||||
created() {
|
return {
|
||||||
this.originalUserScope = state.user.scope;
|
createUserDir: false,
|
||||||
this.createUserDirData = this.createUserDir;
|
originalUserScope: ".",
|
||||||
|
localUser: { ...this.user },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
user: Object, // Define user as a prop
|
||||||
|
isDefault: Boolean,
|
||||||
|
isNew: Boolean,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
user: {
|
||||||
|
handler(newUser) {
|
||||||
|
console.log("UserForm: user changed", newUser);
|
||||||
|
this.localUser = { ...newUser }; // Watch for changes in the parent and update the local copy
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
"user.perm.admin": function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.user.lockPassword = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createUserDir(newVal) {
|
||||||
|
this.user.scope = newVal ? "" : this.originalUserScope;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user() {
|
|
||||||
return state.user;
|
|
||||||
},
|
|
||||||
passwordPlaceholder() {
|
passwordPlaceholder() {
|
||||||
return this.isNew ? "" : this.$t("settings.avoidChanges");
|
return this.isNew ? "" : this.$t("settings.avoidChanges");
|
||||||
},
|
},
|
||||||
scopePlaceholder() {
|
scopePlaceholder() {
|
||||||
return this.createUserDir
|
return this.createUserDir
|
||||||
? this.$t("settings.userScopeGenerationPlaceholder")
|
? this.$t("settings.userScopeGenerationPlaceholder")
|
||||||
: "";
|
: "./";
|
||||||
},
|
},
|
||||||
displayHomeDirectoryCheckbox() {
|
displayHomeDirectoryCheckbox() {
|
||||||
return this.isNew && this.createUserDir;
|
return this.isNew && this.createUserDir;
|
||||||
|
@ -112,10 +132,5 @@ export default {
|
||||||
return enableExec; // Removed arrow function
|
return enableExec; // Removed arrow function
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
createUserDirData(newVal) {
|
|
||||||
state.user.scope = newVal ? "" : this.originalUserScope;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
|
@ -44,35 +44,6 @@
|
||||||
|
|
||||||
<!-- Section for logged-in users -->
|
<!-- Section for logged-in users -->
|
||||||
<div v-if="isLoggedIn" class="sidebar-scroll-list">
|
<div v-if="isLoggedIn" class="sidebar-scroll-list">
|
||||||
<!-- Buttons visible if user has create permission -->
|
|
||||||
<div v-if="user.perm?.create">
|
|
||||||
<!-- New Folder button -->
|
|
||||||
<button
|
|
||||||
@click="showHover('newDir')"
|
|
||||||
class="action"
|
|
||||||
:aria-label="$t('sidebar.newFolder')"
|
|
||||||
:title="$t('sidebar.newFolder')"
|
|
||||||
>
|
|
||||||
<i class="material-icons">create_new_folder</i>
|
|
||||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
|
||||||
</button>
|
|
||||||
<!-- New File button -->
|
|
||||||
<button
|
|
||||||
@click="showHover('newFile')"
|
|
||||||
class="action"
|
|
||||||
:aria-label="$t('sidebar.newFile')"
|
|
||||||
:title="$t('sidebar.newFile')"
|
|
||||||
>
|
|
||||||
<i class="material-icons">note_add</i>
|
|
||||||
<span>{{ $t("sidebar.newFile") }}</span>
|
|
||||||
</button>
|
|
||||||
<!-- Upload button -->
|
|
||||||
<button id="upload-button" @click="uploadFunc" class="action">
|
|
||||||
<i class="material-icons">file_upload</i>
|
|
||||||
<span>Upload file</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isLoggedIn" class="sources card">
|
<div v-if="isLoggedIn" class="sources card">
|
||||||
<span>Sources</span>
|
<span>Sources</span>
|
||||||
<div class="inner-card">
|
<div class="inner-card">
|
||||||
|
@ -138,7 +109,6 @@ import { files } from "@/api";
|
||||||
import ProgressBar from "@/components/ProgressBar.vue";
|
import ProgressBar from "@/components/ProgressBar.vue";
|
||||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
import { showError } from "@/notify";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SidebarGeneral",
|
name: "SidebarGeneral",
|
||||||
|
@ -192,13 +162,13 @@ export default {
|
||||||
this.hoverText = "Quick Toggles"; // Reset to default hover text
|
this.hoverText = "Quick Toggles"; // Reset to default hover text
|
||||||
},
|
},
|
||||||
toggleClick() {
|
toggleClick() {
|
||||||
mutations.updateUser({ singleClick: !state.user.singleClick });
|
mutations.updateCurrentUser({ singleClick: !state.user.singleClick });
|
||||||
},
|
},
|
||||||
toggleDarkMode() {
|
toggleDarkMode() {
|
||||||
mutations.toggleDarkMode();
|
mutations.toggleDarkMode();
|
||||||
},
|
},
|
||||||
toggleSticky() {
|
toggleSticky() {
|
||||||
mutations.updateUser({ stickySidebar: !state.user.stickySidebar });
|
mutations.updateCurrentUser({ stickySidebar: !state.user.stickySidebar });
|
||||||
},
|
},
|
||||||
async updateUsage() {
|
async updateUsage() {
|
||||||
if (!getters.isLoggedIn()) {
|
if (!getters.isLoggedIn()) {
|
||||||
|
@ -209,21 +179,16 @@ export default {
|
||||||
if (this.disableUsedPercentage) {
|
if (this.disableUsedPercentage) {
|
||||||
return usageStats;
|
return usageStats;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
let usage = await files.usage(path);
|
let usage = await files.usage(path);
|
||||||
usageStats = {
|
usageStats = {
|
||||||
used: getHumanReadableFilesize(usage.used / 1024),
|
used: getHumanReadableFilesize(usage.used / 1024),
|
||||||
total: getHumanReadableFilesize(usage.total / 1024),
|
total: getHumanReadableFilesize(usage.total / 1024),
|
||||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
showError("Error fetching usage", error);
|
|
||||||
}
|
|
||||||
mutations.setUsage(usageStats);
|
mutations.setUsage(usageStats);
|
||||||
},
|
},
|
||||||
showHover(value) {
|
|
||||||
return mutations.showHover(value);
|
|
||||||
},
|
|
||||||
navigateTo(path) {
|
navigateTo(path) {
|
||||||
const hashIndex = path.indexOf("#");
|
const hashIndex = path.indexOf("#");
|
||||||
if (hashIndex !== -1) {
|
if (hashIndex !== -1) {
|
||||||
|
@ -241,9 +206,7 @@ export default {
|
||||||
help() {
|
help() {
|
||||||
mutations.showHover("help");
|
mutations.showHover("help");
|
||||||
},
|
},
|
||||||
uploadFunc() {
|
|
||||||
mutations.showHover("upload");
|
|
||||||
},
|
|
||||||
// Logout the user
|
// Logout the user
|
||||||
logout: auth.logout,
|
logout: auth.logout,
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,28 +7,38 @@
|
||||||
@click="setView(setting.id + '-main')"
|
@click="setView(setting.id + '-main')"
|
||||||
:class="{ 'active-settings': active(setting.id + '-main') }"
|
:class="{ 'active-settings': active(setting.id + '-main') }"
|
||||||
>
|
>
|
||||||
<div class="card-wrapper">{{ setting.label }}</div>
|
<div v-if="shouldShow(setting)" class="card-wrapper">{{ setting.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
import { settings } from "@/utils/constants";
|
import { settings } from "@/utils/constants";
|
||||||
|
import { router } from "@/router";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SidebarSettings",
|
name: "SidebarSettings",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
settings // Initialize the settings array in data
|
settings, // Initialize the settings array in data
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentHash: () => getters.currentHash(),
|
currentHash: () => getters.currentHash(),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
shouldShow(setting) {
|
||||||
|
const perm = setting?.perm || {};
|
||||||
|
// Check if all keys in setting.perm exist in state.user.perm and have truthy values
|
||||||
|
return Object.keys(perm).every((key) => state.user.perm[key]);
|
||||||
|
},
|
||||||
active: (view) => state.activeSettingsView === view,
|
active: (view) => state.activeSettingsView === view,
|
||||||
setView(view) {
|
setView(view) {
|
||||||
|
if (state.route.path != "/settings") {
|
||||||
|
router.push({ path: "/settings", hash: "#" + view }, () => {});
|
||||||
|
} else {
|
||||||
mutations.setActiveSettingsView(view);
|
mutations.setActiveSettingsView(view);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { version, commitSHA } from "@/utils/constants";
|
import { version, commitSHA } from "@/utils/constants";
|
||||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
import { getters, mutations } from "@/store"; // Import your custom store
|
||||||
import SidebarGeneral from "./General.vue";
|
import SidebarGeneral from "./General.vue";
|
||||||
import SidebarSettings from "./Settings.vue";
|
import SidebarSettings from "./Settings.vue";
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
version: () => version,
|
version: () => version,
|
||||||
|
commitSHA: () => commitSHA,
|
||||||
isDarkMode: () => getters.isDarkMode(),
|
isDarkMode: () => getters.isDarkMode(),
|
||||||
isLoggedIn: () => getters.isLoggedIn(),
|
isLoggedIn: () => getters.isLoggedIn(),
|
||||||
isSettings: () => getters.isSettings(),
|
isSettings: () => getters.isSettings(),
|
||||||
|
|
|
@ -92,7 +92,9 @@ main > div {
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs {
|
.breadcrumbs {
|
||||||
height: 3em;
|
overflow-x: auto;
|
||||||
|
height: auto;
|
||||||
|
min-height: 3em;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -162,23 +164,25 @@ button:disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
#popup-notification {
|
#popup-notification {
|
||||||
color: white;
|
border-radius: 1em;
|
||||||
|
color: #fff;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
height: 4em;
|
height: 4em;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: -20em; /* Start off-screen */
|
right: -20em;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 1em;
|
padding: 0.5em;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: right 1s ease; /* Animate the 'right' property */
|
transition: right 1s ease;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
margin: 1em;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#popup-notification-content {
|
#popup-notification-content {
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0;
|
padding: 1em;
|
||||||
padding-left: .5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#popup-notification.success {
|
#popup-notification.success {
|
||||||
|
|
|
@ -103,6 +103,10 @@
|
||||||
border-color: var(--divider) !important;
|
border-color: var(--divider) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark-mode #listingView.gallery .item .text {
|
||||||
|
text-shadow: 0 0 2px black;
|
||||||
|
}
|
||||||
|
|
||||||
/* Listing item modified text */
|
/* Listing item modified text */
|
||||||
.dark-mode #listingView .item .modified {
|
.dark-mode #listingView .item .modified {
|
||||||
color: var(--textSecondary);
|
color: var(--textSecondary);
|
||||||
|
|
|
@ -36,6 +36,7 @@ body.rtl #listingView {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView .item div:last-of-type {
|
#listingView .item div:last-of-type {
|
||||||
|
@ -140,7 +141,7 @@ body.rtl #listingView {
|
||||||
display:flex;
|
display:flex;
|
||||||
min-width: 12em;
|
min-width: 12em;
|
||||||
min-height: 12em;
|
min-height: 12em;
|
||||||
text-shadow: 0 0 2px black;
|
text-shadow: 0 0 2px white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.gallery .item div:last-of-type {
|
#listingView.gallery .item div:last-of-type {
|
||||||
|
@ -407,25 +408,3 @@ body.rtl #listingView {
|
||||||
#listingView.list .header .active {
|
#listingView.list .header .active {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView #multiple-selection {
|
|
||||||
position: fixed;
|
|
||||||
bottom: -4em;
|
|
||||||
left: 0;
|
|
||||||
z-index: 99999;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--blue);
|
|
||||||
height: 4em;
|
|
||||||
padding: 0.5em 0.5em 0.5em 1em;
|
|
||||||
justify-content: space-between;
|
|
||||||
transition: .2s ease bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView #multiple-selection.active {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView #multiple-selection p,
|
|
||||||
#listingView #multiple-selection i {
|
|
||||||
color: var(--item-selected);
|
|
||||||
}
|
|
|
@ -43,6 +43,8 @@
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
animation: .2s opac forwards;
|
animation: .2s opac forwards;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes opac {
|
@keyframes opac {
|
||||||
|
|
|
@ -174,6 +174,7 @@
|
||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"UserManagement": "User Management",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "Execute commands",
|
"allowCommands": "Execute commands",
|
||||||
|
|
|
@ -175,7 +175,7 @@
|
||||||
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
||||||
"branding": "Marka",
|
"branding": "Marka",
|
||||||
"brandingDirectoryPath": "Marka dizin yolu",
|
"brandingDirectoryPath": "Marka dizin yolu",
|
||||||
"brandingHelp": "Adını değiştirerek, logoyu değiştirerek, özel stiller ekleyerek ve hatta GitHub'a harici bağlantıları devre dışı bırakarak Filebrowser örneğinizin görünüşünü ve hissini özelleştirebilirsiniz.\nÖzel marka bilinci oluşturma hakkında daha fazla bilgi için lütfen {0} sayfasına göz atın.",
|
"brandingHelp": "Adını değiştirerek, logoyu değiştirerek, özel stiller ekleyerek ve hatta GitHub'a harici bağlantıları devre dışı bırakarak FileBrowser örneğinizin görünüşünü ve hissini özelleştirebilirsiniz.\nÖzel marka bilinci oluşturma hakkında daha fazla bilgi için lütfen {0} sayfasına göz atın.",
|
||||||
"changePassword": "Şifre Değiştir",
|
"changePassword": "Şifre Değiştir",
|
||||||
"commandRunner": "Komut satırı",
|
"commandRunner": "Komut satırı",
|
||||||
"commandRunnerHelp": "Burada, adlandırılmış olaylarda yürütülen komutları ayarlayabilirsiniz. Her satıra bir tane yazmalısınız. {0} ve {1} ortam değişkenleri, {1}'ye göre {0} olacak şekilde kullanılabilir olacaktır. Bu özellik ve mevcut ortam değişkenleri hakkında daha fazla bilgi için lütfen {2}'yi okuyun.",
|
"commandRunnerHelp": "Burada, adlandırılmış olaylarda yürütülen komutları ayarlayabilirsiniz. Her satıra bir tane yazmalısınız. {0} ve {1} ortam değişkenleri, {1}'ye göre {0} olacak şekilde kullanılabilir olacaktır. Bu özellik ve mevcut ortam değişkenleri hakkında daha fazla bilgi için lütfen {2}'yi okuyun.",
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
|
|
||||||
import { showSuccess, showError, closePopUp } from "./message.js";
|
import * as notify from "./message.js";
|
||||||
export {
|
export {
|
||||||
showSuccess,
|
notify,
|
||||||
showError,
|
|
||||||
closePopUp,
|
|
||||||
};
|
};
|
|
@ -1,20 +1,35 @@
|
||||||
|
import { mutations, state } from "@/store";
|
||||||
|
|
||||||
export function showPopup(type, message) {
|
export function showPopup(type, message) {
|
||||||
const [popup, popupContent] = getElements();
|
const [popup, popupContent] = getElements();
|
||||||
|
if (popup == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
popup.classList.remove('success', 'error'); // Clear previous types
|
popup.classList.remove('success', 'error'); // Clear previous types
|
||||||
popup.classList.add(type);
|
popup.classList.add(type);
|
||||||
popupContent.textContent = message;
|
popupContent.textContent = message;
|
||||||
|
|
||||||
// Start animation: bring the popup into view
|
|
||||||
popup.style.right = '1em';
|
popup.style.right = '1em';
|
||||||
|
|
||||||
|
// don't hide for actions
|
||||||
|
if (type == "action") {
|
||||||
|
popup.classList.add("success");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Start animation: bring the popup into view
|
||||||
// Automatically hide after 10 seconds
|
// Automatically hide after 10 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closePopUp()
|
closePopUp()
|
||||||
}, 10000);
|
}, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closePopUp() {
|
export function closePopUp() {
|
||||||
const [popup, popupContent] = getElements();
|
const [popup, popupContent] = getElements();
|
||||||
|
if (popupContent == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (popupContent.textContent == "Multiple Selection Enabled" && state.multiple) {
|
||||||
|
mutations.setMultiple(false)
|
||||||
|
}
|
||||||
popup.style.right = '-50em'; // Slide out
|
popup.style.right = '-50em'; // Slide out
|
||||||
popupContent.textContent = "no content";
|
popupContent.textContent = "no content";
|
||||||
}
|
}
|
||||||
|
@ -22,13 +37,11 @@ export function closePopUp() {
|
||||||
function getElements() {
|
function getElements() {
|
||||||
const popup = document.getElementById('popup-notification');
|
const popup = document.getElementById('popup-notification');
|
||||||
if (!popup) {
|
if (!popup) {
|
||||||
console.error('Popup notification element not found');
|
|
||||||
return [null, null];
|
return [null, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
const popupContent = popup.querySelector('#popup-notification-content');
|
const popupContent = popup.querySelector('#popup-notification-content');
|
||||||
if (!popupContent) {
|
if (!popupContent) {
|
||||||
console.error('Popup notification content element not found');
|
|
||||||
return [null, null];
|
return [null, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,3 +56,7 @@ export function showError(message) {
|
||||||
showPopup('error', message);
|
showPopup('error', message);
|
||||||
console.error(message)
|
console.error(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showMultipleSelection() {
|
||||||
|
showPopup("action","Multiple Selection Enabled");
|
||||||
|
}
|
|
@ -70,6 +70,14 @@ const routes = [
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
component: Settings,
|
component: Settings,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "users/:id",
|
||||||
|
name: "User",
|
||||||
|
component: Settings,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { state } from "./state.js";
|
import { state } from "./state.js";
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
isResizableView: () => (state.user.viewMode == "gallery" || state.user.viewMode == "normal" ) && getters.currentView() == "listingView" ,
|
isCardView: () => (state.user.viewMode == "gallery" || state.user.viewMode == "normal" ) && getters.currentView() == "listingView" ,
|
||||||
currentHash: () => state.route.hash.replace("#", ""),
|
currentHash: () => state.route.hash.replace("#", ""),
|
||||||
isMobile: () => state.isMobile,
|
isMobile: () => state.isMobile,
|
||||||
isLoading: () => Object.keys(state.loading).length > 0,
|
isLoading: () => Object.keys(state.loading).length > 0,
|
||||||
|
@ -19,6 +19,7 @@ export const getters = {
|
||||||
isFiles: () => state.route.name === "Files",
|
isFiles: () => state.route.name === "Files",
|
||||||
isListing: () => getters.isFiles() && state.req.isDir,
|
isListing: () => getters.isFiles() && state.req.isDir,
|
||||||
selectedCount: () => Array.isArray(state.selected) ? state.selected.length : 0,
|
selectedCount: () => Array.isArray(state.selected) ? state.selected.length : 0,
|
||||||
|
getFirstSelected: () => state.req.items[state.selected[0]],
|
||||||
isSingleFileSelected: () => getters.selectedCount() === 1 && !state.req.items[state.selected[0]]?.isDir,
|
isSingleFileSelected: () => getters.selectedCount() === 1 && !state.req.items[state.selected[0]]?.isDir,
|
||||||
selectedDownloadUrl() {
|
selectedDownloadUrl() {
|
||||||
let selectedItem = state.selected[0]
|
let selectedItem = state.selected[0]
|
||||||
|
@ -77,7 +78,7 @@ export const getters = {
|
||||||
return { dirs, files };
|
return { dirs, files };
|
||||||
},
|
},
|
||||||
isSidebarVisible: () => {
|
isSidebarVisible: () => {
|
||||||
let visible = state.showSidebar || getters.isStickySidebar()
|
let visible = (state.showSidebar || getters.isStickySidebar()) && state.user.username != "publicUser"
|
||||||
if (getters.currentView() == "settings") {
|
if (getters.currentView() == "settings") {
|
||||||
visible = !getters.isMobile();
|
visible = !getters.isMobile();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { state } from "./state.js";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { emitStateChanged } from './eventBus'; // Import the function from eventBus.js
|
import { emitStateChanged } from './eventBus'; // Import the function from eventBus.js
|
||||||
import { users } from "@/api";
|
import { users } from "@/api";
|
||||||
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
setGallerySize: (value) => {
|
setGallerySize: (value) => {
|
||||||
|
@ -31,13 +32,13 @@ export const mutations = {
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
toggleDarkMode() {
|
toggleDarkMode() {
|
||||||
mutations.updateUser({ "darkMode": !state.user.darkMode });
|
mutations.updateCurrentUser({ "darkMode": !state.user.darkMode });
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
toggleSidebar() {
|
toggleSidebar() {
|
||||||
if (state.user.stickySidebar) {
|
if (state.user.stickySidebar) {
|
||||||
localStorage.setItem("stickySidebar", "false");
|
localStorage.setItem("stickySidebar", "false");
|
||||||
mutations.updateUser({ "stickySidebar": false }); // turn off sticky when closed
|
mutations.updateCurrentUser({ "stickySidebar": false }); // turn off sticky when closed
|
||||||
state.showSidebar = false;
|
state.showSidebar = false;
|
||||||
} else {
|
} else {
|
||||||
state.showSidebar = !state.showSidebar;
|
state.showSidebar = !state.showSidebar;
|
||||||
|
@ -99,22 +100,18 @@ export const mutations = {
|
||||||
state.reload = value;
|
state.reload = value;
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
setUser: (value) => {
|
setCurrentUser: (value) => {
|
||||||
if (value === null) {
|
state.user = value;
|
||||||
state.user = null;
|
// If value is null or undefined, emit state change and exit early
|
||||||
|
if (!value) {
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Ensure locale exists and is valid
|
||||||
let locale = value.locale;
|
if (!value.locale) {
|
||||||
if (locale === "") {
|
value.locale = i18n.detectLocale(); // Default to detected locale if missing
|
||||||
value.locale = i18n.detectLocale();
|
|
||||||
}
|
|
||||||
let previousUser = state.user
|
|
||||||
state.user = value;
|
|
||||||
if (state.user != previousUser && state.user.username != "publicUser") {
|
|
||||||
users.update(state.user);
|
|
||||||
}
|
}
|
||||||
|
// Emit state change after setting the user and locale
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
setJWT: (value) => {
|
setJWT: (value) => {
|
||||||
|
@ -127,6 +124,11 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
setMultiple: (value) => {
|
setMultiple: (value) => {
|
||||||
state.multiple = value;
|
state.multiple = value;
|
||||||
|
if (value == true) {
|
||||||
|
notify.showMultipleSelection()
|
||||||
|
} else {
|
||||||
|
notify.closePopUp()
|
||||||
|
}
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
addSelected: (value) => {
|
addSelected: (value) => {
|
||||||
|
@ -144,22 +146,38 @@ export const mutations = {
|
||||||
mutations.setMultiple(false);
|
mutations.setMultiple(false);
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
updateUser: (value) => {
|
updateCurrentUser: (value) => {
|
||||||
if (typeof value !== "object") return;
|
// Ensure the input is a valid object
|
||||||
if (state.user === null) {
|
if (typeof value !== "object" || value === null) return;
|
||||||
|
|
||||||
|
// Initialize state.user if it's null
|
||||||
|
if (!state.user) {
|
||||||
state.user = {};
|
state.user = {};
|
||||||
}
|
}
|
||||||
let previousUser = state.user;
|
|
||||||
|
// Store previous state for comparison
|
||||||
|
const previousUser = { ...state.user };
|
||||||
|
|
||||||
|
// Merge the new values into the current user state
|
||||||
state.user = { ...state.user, ...value };
|
state.user = { ...state.user, ...value };
|
||||||
|
|
||||||
|
// Handle locale change
|
||||||
if (state.user.locale !== previousUser.locale) {
|
if (state.user.locale !== previousUser.locale) {
|
||||||
state.user.locale = i18n.detectLocale();
|
state.user.locale = i18n.detectLocale();
|
||||||
i18n.setLocale(state.user.locale);
|
i18n.setLocale(state.user.locale);
|
||||||
i18n.default.locale = state.user.locale;
|
i18n.default.locale = state.user.locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update localStorage if stickySidebar exists
|
||||||
|
if ('stickySidebar' in state.user) {
|
||||||
localStorage.setItem("stickySidebar", state.user.stickySidebar);
|
localStorage.setItem("stickySidebar", state.user.stickySidebar);
|
||||||
if (state.user != previousUser) {
|
|
||||||
users.update(state.user);
|
|
||||||
}
|
}
|
||||||
|
// Update users if there's any change in state.user
|
||||||
|
if (JSON.stringify(state.user) !== JSON.stringify(previousUser)) {
|
||||||
|
users.update(state.user,Object.keys(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit state change event
|
||||||
emitStateChanged();
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
updateRequest: (value) => {
|
updateRequest: (value) => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ export function parseToken(token) {
|
||||||
localStorage.setItem("jwt", token);
|
localStorage.setItem("jwt", token);
|
||||||
mutations.setJWT(token);
|
mutations.setJWT(token);
|
||||||
mutations.setSession(generateRandomCode(8));
|
mutations.setSession(generateRandomCode(8));
|
||||||
mutations.setUser(data.user);
|
mutations.setCurrentUser(data.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateLogin() {
|
export async function validateLogin() {
|
||||||
|
@ -89,7 +89,7 @@ export async function signupLogin(username, password) {
|
||||||
export function logout() {
|
export function logout() {
|
||||||
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
||||||
mutations.setJWT("");
|
mutations.setJWT("");
|
||||||
mutations.setUser(null);
|
mutations.setCurrentUser(null);
|
||||||
localStorage.setItem("jwt", null);
|
localStorage.setItem("jwt", null);
|
||||||
router.push({ path: "/login" });
|
router.push({ path: "/login" });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const name = window.FileBrowser.Name || "File Browser";
|
const name = window.FileBrowser.Name || "FileBrowser Quantum";
|
||||||
const disableExternal = window.FileBrowser.DisableExternal;
|
const disableExternal = window.FileBrowser.DisableExternal;
|
||||||
const disableUsedPercentage = window.FileBrowser.DisableUsedPercentage;
|
const disableUsedPercentage = window.FileBrowser.DisableUsedPercentage;
|
||||||
const baseURL = window.FileBrowser.BaseURL;
|
const baseURL = window.FileBrowser.BaseURL;
|
||||||
|
@ -20,9 +20,13 @@ const origin = window.location.origin;
|
||||||
|
|
||||||
const settings = [
|
const settings = [
|
||||||
{ id: 'profile', label: 'Profile Management', component: 'ProfileSettings' },
|
{ id: 'profile', label: 'Profile Management', component: 'ProfileSettings' },
|
||||||
{ id: 'shares', label: 'Share Management', component: 'SharesSettings' },
|
{
|
||||||
{ id: 'global', label: 'Global', component: 'GlobalSettings' },
|
id: 'shares', label: 'Share Management', component: 'SharesSettings', perm: {
|
||||||
{ id: 'user-defaults', label: 'User Defaults', component: 'UserDefaultSettings' },
|
share: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ id: 'global', label: 'Global', component: 'GlobalSettings', perm: { admin: true } },
|
||||||
|
{ id: 'users', label: 'User Management', component: 'UserManagement', perm: { admin: true } },
|
||||||
]
|
]
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { state, mutations, getters } from "@/store"
|
||||||
|
import { files as api } from "@/api";
|
||||||
|
import { notify } from "@/notify"
|
||||||
|
|
||||||
|
export default function download() {
|
||||||
|
if (getters.isSingleFileSelected()) {
|
||||||
|
api.download(null, getters.selectedDownloadUrl());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mutations.showHover({
|
||||||
|
name: "download",
|
||||||
|
confirm: (format) => {
|
||||||
|
mutations.closeHovers();
|
||||||
|
let files = [];
|
||||||
|
if (state.selected.length > 0) {
|
||||||
|
for (let i of state.selected) {
|
||||||
|
files.push(state.req.items[i].url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
files.push(state.route.path);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
api.download(format, ...files);
|
||||||
|
notify.showSuccess("download started");
|
||||||
|
} catch (e) {
|
||||||
|
notify.showError("error downloading", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -101,7 +101,7 @@ export function scanFiles(dt) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleFiles(files, base, overwrite = false) {
|
export async function handleFiles(files, base, overwrite = false) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const id = state.upload.id;
|
const id = state.upload.id;
|
||||||
let path = base;
|
let path = base;
|
||||||
|
@ -123,8 +123,7 @@ export function handleFiles(files, base, overwrite = false) {
|
||||||
overwrite,
|
overwrite,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Upload the file using your API
|
await api.post(item.path, item.file, item.overwrite, (event) => {
|
||||||
api.post(item.path, item.file, item.overwrite, (event) => {
|
|
||||||
console.log(`Upload progress: ${Math.round((event.loaded / event.total) * 100)}%`);
|
console.log(`Upload progress: ${Math.round((event.loaded / event.total) * 100)}%`);
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { state } from "@/store";
|
||||||
|
import { router } from "@/router";
|
||||||
const errors = {
|
const errors = {
|
||||||
0: {
|
0: {
|
||||||
icon: "cloud_off",
|
icon: "cloud_off",
|
||||||
|
@ -30,13 +31,26 @@ const errors = {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "errors",
|
name: "errors",
|
||||||
components: {
|
components: {},
|
||||||
},
|
|
||||||
props: ["errorCode", "showHeader"],
|
props: ["errorCode", "showHeader"],
|
||||||
computed: {
|
computed: {
|
||||||
info() {
|
info() {
|
||||||
return errors[this.errorCode] ? errors[this.errorCode] : errors[500];
|
return errors[this.errorCode] ? errors[this.errorCode] : errors[500];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener("keydown", this.keyEvent);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
keyEvent(event) {
|
||||||
|
const { key } = event;
|
||||||
|
if (key == "Backspace") {
|
||||||
|
// go back
|
||||||
|
let currentPath = state.route.path.replace(/\/+$/, "");
|
||||||
|
let newPath = currentPath.substring(0, currentPath.lastIndexOf("/"));
|
||||||
|
router.push({ path: newPath });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<i v-on:click="closePopUp" class="material-icons">close</i>
|
<i v-on:click="closePopUp" class="material-icons">close</i>
|
||||||
<div id="popup-notification-content">no info</div>
|
<div id="popup-notification-content">no info</div>
|
||||||
</div>
|
</div>
|
||||||
<fileSelection> </fileSelection>
|
<ContextMenu></ContextMenu>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import editorBar from "./bars/EditorBar.vue";
|
import editorBar from "./bars/EditorBar.vue";
|
||||||
|
@ -33,16 +33,16 @@ import listingBar from "./bars/ListingBar.vue";
|
||||||
import Prompts from "@/components/prompts/Prompts.vue";
|
import Prompts from "@/components/prompts/Prompts.vue";
|
||||||
import Sidebar from "@/components/sidebar/Sidebar.vue";
|
import Sidebar from "@/components/sidebar/Sidebar.vue";
|
||||||
import Search from "@/components/Search.vue";
|
import Search from "@/components/Search.vue";
|
||||||
import fileSelection from "@/components/FileSelection.vue";
|
import ContextMenu from "@/components/ContextMenu.vue";
|
||||||
|
|
||||||
import { closePopUp } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import { enableExec } from "@/utils/constants";
|
import { enableExec } from "@/utils/constants";
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "layout",
|
name: "layout",
|
||||||
components: {
|
components: {
|
||||||
fileSelection,
|
ContextMenu,
|
||||||
Search,
|
Search,
|
||||||
defaultBar,
|
defaultBar,
|
||||||
editorBar,
|
editorBar,
|
||||||
|
@ -72,7 +72,7 @@ export default {
|
||||||
return getters.isSidebarVisible() && getters.isStickySidebar();
|
return getters.isSidebarVisible() && getters.isStickySidebar();
|
||||||
},
|
},
|
||||||
closePopUp() {
|
closePopUp() {
|
||||||
return closePopUp;
|
return notify.closePopUp;
|
||||||
},
|
},
|
||||||
progress() {
|
progress() {
|
||||||
return getters.progress(); // Access getter directly from the store
|
return getters.progress(); // Access getter directly from the store
|
||||||
|
@ -130,6 +130,9 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
#layout-container {
|
||||||
|
padding-bottom: 30% !important;
|
||||||
|
}
|
||||||
main {
|
main {
|
||||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none; /* Firefox */
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="login" :class="{ recaptcha: recaptcha, 'dark-mode': isDarkMode }">
|
<div id="login" :class="{ recaptcha: recaptcha, 'dark-mode': isDarkMode }">
|
||||||
<form @submit="submit">
|
<form @submit="submit">
|
||||||
<img :src="logoURL" alt="File Browser" />
|
<img :src="logoURL" alt="FileBrowser Quantum" />
|
||||||
<h1>{{ name }}</h1>
|
<h1>{{ name }}</h1>
|
||||||
<div v-if="error !== ''" class="wrong">{{ error }}</div>
|
<div v-if="error !== ''" class="wrong">{{ error }}</div>
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import router from "@/router";
|
||||||
import { state } from "@/store";
|
import { state } from "@/store";
|
||||||
import { signupLogin, login } from "@/utils/auth";
|
import { signupLogin, login } from "@/utils/auth";
|
||||||
import {
|
import {
|
||||||
|
@ -113,7 +114,7 @@ export default {
|
||||||
await signupLogin(this.username, this.password);
|
await signupLogin(this.username, this.password);
|
||||||
}
|
}
|
||||||
await login(this.username, this.password, captcha);
|
await login(this.username, this.password, captcha);
|
||||||
this.$router.push({ path: redirect });
|
router.push({ path: redirect });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
if (e.message == 409) {
|
if (e.message == 409) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard" style="padding-bottom: 30vh">
|
||||||
<div class="settings-views">
|
<div v-if="isRootSettings" class="settings-views">
|
||||||
<div
|
<div
|
||||||
v-for="setting in settings"
|
v-for="setting in settings"
|
||||||
:key="setting.id + '-main'"
|
:key="setting.id + '-main'"
|
||||||
|
@ -12,7 +12,12 @@
|
||||||
@click="!active(setting.id + '-main') && setView(setting.id + '-main')"
|
@click="!active(setting.id + '-main') && setView(setting.id + '-main')"
|
||||||
>
|
>
|
||||||
<!-- Dynamically render the component based on the setting -->
|
<!-- Dynamically render the component based on the setting -->
|
||||||
<component :is="setting.component"></component>
|
<component v-if="shouldShow(setting)" :is="setting.component"></component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="settings-views">
|
||||||
|
<div class="active">
|
||||||
|
<UserSettings />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -33,17 +38,17 @@
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
import { settings } from "@/utils/constants";
|
import { settings } from "@/utils/constants";
|
||||||
import GlobalSettings from "@/views/settings/Global.vue";
|
import GlobalSettings from "@/views/settings/Global.vue";
|
||||||
import UserDefaultSettings from "@/views/settings/UserDefaults.vue";
|
|
||||||
import UserColumnSettings from "@/views/settings/UserColumn.vue";
|
|
||||||
import ProfileSettings from "@/views/settings/Profile.vue";
|
import ProfileSettings from "@/views/settings/Profile.vue";
|
||||||
import SharesSettings from "@/views/settings/Shares.vue";
|
import SharesSettings from "@/views/settings/Shares.vue";
|
||||||
|
import UserManagement from "@/views/settings/Users.vue";
|
||||||
|
import UserSettings from "@/views/settings/User.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "settings",
|
name: "settings",
|
||||||
components: {
|
components: {
|
||||||
|
UserManagement,
|
||||||
|
UserSettings,
|
||||||
GlobalSettings,
|
GlobalSettings,
|
||||||
UserDefaultSettings,
|
|
||||||
UserColumnSettings,
|
|
||||||
ProfileSettings,
|
ProfileSettings,
|
||||||
SharesSettings,
|
SharesSettings,
|
||||||
},
|
},
|
||||||
|
@ -53,6 +58,12 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isRootSettings() {
|
||||||
|
return state.route.path == "/settings";
|
||||||
|
},
|
||||||
|
newUserPage() {
|
||||||
|
return state.route.path == "/settings/users/new";
|
||||||
|
},
|
||||||
loading() {
|
loading() {
|
||||||
return getters.isLoading();
|
return getters.isLoading();
|
||||||
},
|
},
|
||||||
|
@ -67,6 +78,14 @@ export default {
|
||||||
mutations.setActiveSettingsView(getters.currentHash());
|
mutations.setActiveSettingsView(getters.currentHash());
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
shouldShow(setting) {
|
||||||
|
if (state.isMobile) {
|
||||||
|
const perm = setting?.perm || {};
|
||||||
|
// Check if all keys in setting.perm exist in state.user.perm and have truthy values
|
||||||
|
return Object.keys(perm).every((key) => state.user.perm[key]);
|
||||||
|
}
|
||||||
|
return this.active(setting.id + "-main");
|
||||||
|
},
|
||||||
active(id) {
|
active(id) {
|
||||||
return state.activeSettingsView === id;
|
return state.activeSettingsView === id;
|
||||||
},
|
},
|
||||||
|
@ -88,6 +107,7 @@ export default {
|
||||||
.settings-views {
|
.settings-views {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
padding-bottom: 35vh;
|
padding-bottom: 35vh;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.settings-views > .active > .card {
|
.settings-views > .active > .card {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
|
|
@ -119,20 +119,6 @@
|
||||||
readOnly
|
readOnly
|
||||||
>
|
>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<div :class="{ active: multiple }" id="multiple-selection">
|
|
||||||
<p>{{ $t("files.multipleSelectionEnabled") }}</p>
|
|
||||||
<div
|
|
||||||
@click="setMultipleFalse"
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
:title="$t('files.clear')"
|
|
||||||
:aria-label="$t('files.clear')"
|
|
||||||
class="action"
|
|
||||||
>
|
|
||||||
<i class="material-icons">clear</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -149,7 +135,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { showSuccess } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||||
import { pub as api } from "@/api";
|
import { pub as api } from "@/api";
|
||||||
import { fromNow } from "@/utils/moment";
|
import { fromNow } from "@/utils/moment";
|
||||||
|
@ -184,15 +170,14 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
const hash = state.route.params.path.at(-1);
|
this.hash = state.route.params.path.at(0);
|
||||||
this.hash = hash;
|
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener("keydown", this.keyEvent);
|
window.addEventListener("keydown", this.keyEvent);
|
||||||
this.clip = new Clipboard(".copy-clipboard");
|
this.clip = new Clipboard(".copy-clipboard");
|
||||||
this.clip.on("success", () => {
|
this.clip.on("success", () => {
|
||||||
showSuccess(this.$t("success.linkCopied"));
|
notify.showSuccess(this.$t("success.linkCopied"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
@ -226,10 +211,19 @@ export default {
|
||||||
return "insert_drive_file";
|
return "insert_drive_file";
|
||||||
},
|
},
|
||||||
link() {
|
link() {
|
||||||
return api.getDownloadURL(state.req);
|
return api.getDownloadURL({
|
||||||
|
hash: this.hash,
|
||||||
|
path: window.location.pathname,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
inlineLink() {
|
inlineLink() {
|
||||||
return api.getDownloadURL(state.req, true);
|
return api.getDownloadURL(
|
||||||
|
{
|
||||||
|
hash: this.hash,
|
||||||
|
path: window.location.pathname,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
},
|
},
|
||||||
humanSize() {
|
humanSize() {
|
||||||
if (state.req.isDir) {
|
if (state.req.isDir) {
|
||||||
|
@ -262,7 +256,7 @@ export default {
|
||||||
// Reset view information.
|
// Reset view information.
|
||||||
if (!getters.isLoggedIn()) {
|
if (!getters.isLoggedIn()) {
|
||||||
let userData = await api.getPublicUser();
|
let userData = await api.getPublicUser();
|
||||||
mutations.setUser(userData);
|
mutations.setCurrentUser(userData);
|
||||||
}
|
}
|
||||||
mutations.setReload(false);
|
mutations.setReload(false);
|
||||||
mutations.resetSelected();
|
mutations.resetSelected();
|
||||||
|
@ -324,3 +318,8 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.share {
|
||||||
|
padding-bottom: 35vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -15,11 +15,11 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import url from "@/utils/url"
|
import url from "@/utils/url";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/Action.vue";
|
||||||
import css from "@/utils/css";
|
import css from "@/utils/css";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -296,7 +296,8 @@ export default {
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
const currentIndex = this.viewModes.indexOf(state.user.viewMode);
|
const currentIndex = this.viewModes.indexOf(state.user.viewMode);
|
||||||
const nextIndex = (currentIndex + 1) % this.viewModes.length;
|
const nextIndex = (currentIndex + 1) % this.viewModes.length;
|
||||||
mutations.updateUser({ viewMode: this.viewModes[nextIndex] });
|
const newView = this.viewModes[nextIndex];
|
||||||
|
mutations.updateCurrentUser({ "viewMode": newView });
|
||||||
},
|
},
|
||||||
preventDefault(event) {
|
preventDefault(event) {
|
||||||
// Wrapper around prevent default.
|
// Wrapper around prevent default.
|
||||||
|
|
|
@ -30,9 +30,9 @@ import { state, mutations } from "@/store";
|
||||||
import { eventBus } from "@/store/eventBus";
|
import { eventBus } from "@/store/eventBus";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { showError, showSuccess } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/Action.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "editorBar",
|
name: "editorBar",
|
||||||
|
@ -108,10 +108,10 @@ export default {
|
||||||
try {
|
try {
|
||||||
eventBus.emit("handleEditorValueRequest", "data");
|
eventBus.emit("handleEditorValueRequest", "data");
|
||||||
buttons.success(button);
|
buttons.success(button);
|
||||||
showSuccess("File Saved!");
|
notify.showSuccess("File Saved!");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done(button);
|
buttons.done(button);
|
||||||
showError("Error saving file: ", e);
|
notify.showError("Error saving file: ", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
|
|
|
@ -26,8 +26,7 @@
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/Action.vue";
|
||||||
import { showError } from "@/notify";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "listingView",
|
name: "listingView",
|
||||||
|
@ -79,11 +78,7 @@ export default {
|
||||||
const currentIndex = this.viewModes.indexOf(state.user.viewMode);
|
const currentIndex = this.viewModes.indexOf(state.user.viewMode);
|
||||||
const nextIndex = (currentIndex + 1) % this.viewModes.length;
|
const nextIndex = (currentIndex + 1) % this.viewModes.length;
|
||||||
const newView = this.viewModes[nextIndex];
|
const newView = this.viewModes[nextIndex];
|
||||||
try {
|
mutations.updateCurrentUser({ "viewMode": newView });
|
||||||
mutations.updateUser({ viewMode: newView });
|
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { router } from "@/router";
|
||||||
import { eventBus } from "@/store/eventBus";
|
import { eventBus } from "@/store/eventBus";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, getters } from "@/store";
|
||||||
import { showError } from "@/notify";
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import ace from "ace-builds/src-min-noconflict/ace.js";
|
import ace from "ace-builds/src-min-noconflict/ace.js";
|
||||||
|
@ -62,7 +62,8 @@ export default {
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
// this is empty content string "empty-file-x6OlSil" which is used to represent empty text file
|
// this is empty content string "empty-file-x6OlSil" which is used to represent empty text file
|
||||||
const fileContent = state.req.content == "empty-file-x6OlSil" ? "" : state.req.content || "";
|
const fileContent =
|
||||||
|
state.req.content == "empty-file-x6OlSil" ? "" : state.req.content || "";
|
||||||
this.editor = ace.edit("editor", {
|
this.editor = ace.edit("editor", {
|
||||||
value: fileContent,
|
value: fileContent,
|
||||||
showPrintMargin: false,
|
showPrintMargin: false,
|
||||||
|
@ -79,31 +80,36 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleEditorValueRequest() {
|
handleEditorValueRequest() {
|
||||||
try {
|
|
||||||
api.put(state.route.path, this.editor.getValue());
|
api.put(state.route.path, this.editor.getValue());
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
back() {
|
back() {
|
||||||
let uri = url.removeLastDir(state.route.path) + "/";
|
let uri = url.removeLastDir(state.route.path) + "/";
|
||||||
this.$router.push({ path: uri });
|
this.$router.push({ path: uri });
|
||||||
},
|
},
|
||||||
keyEvent(event) {
|
keyEvent(event) {
|
||||||
if (!event.ctrlKey && !event.metaKey) {
|
const { key, ctrlKey, metaKey } = event;
|
||||||
|
if (getters.currentPromptName() != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (key == "Backspace") {
|
||||||
if (String.fromCharCode(event.which).toLowerCase() !== "s") {
|
// go back
|
||||||
|
let currentPath = state.route.path.replace(/\/+$/, "");
|
||||||
|
let newPath = currentPath.substring(0, currentPath.lastIndexOf("/"));
|
||||||
|
router.push({ path: newPath });
|
||||||
|
}
|
||||||
|
if (!ctrlKey && !metaKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
switch (key.toLowerCase()) {
|
||||||
|
case "s":
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.save();
|
this.save();
|
||||||
},
|
break;
|
||||||
close() {
|
|
||||||
mutations.replaceRequest({});
|
default:
|
||||||
let uri = url.removeLastDir(state.route.path) + "/";
|
// No action for other keys
|
||||||
this.$router.push({ path: uri });
|
return;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div style="padding-bottom: 5em">
|
<div style="padding-bottom: 35vh">
|
||||||
<div v-if="loading">
|
<div v-if="loading">
|
||||||
<h2 class="message delayed">
|
<h2 class="message delayed">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
|
@ -100,8 +100,7 @@
|
||||||
v-bind:type="item.type"
|
v-bind:type="item.type"
|
||||||
v-bind:size="item.size"
|
v-bind:size="item.size"
|
||||||
v-bind:path="item.path"
|
v-bind:path="item.path"
|
||||||
>
|
/>
|
||||||
</item>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="numFiles > 0">
|
<div v-if="numFiles > 0">
|
||||||
<div class="header-items">
|
<div class="header-items">
|
||||||
|
@ -120,8 +119,7 @@
|
||||||
v-bind:type="item.type"
|
v-bind:type="item.type"
|
||||||
v-bind:size="item.size"
|
v-bind:size="item.size"
|
||||||
v-bind:path="item.path"
|
v-bind:path="item.path"
|
||||||
>
|
/>
|
||||||
</item>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
@ -129,7 +127,7 @@
|
||||||
type="file"
|
type="file"
|
||||||
id="upload-input"
|
id="upload-input"
|
||||||
@change="uploadInput($event)"
|
@change="uploadInput($event)"
|
||||||
getMultiple
|
multiple
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
style="display: none"
|
style="display: none"
|
||||||
|
@ -137,34 +135,21 @@
|
||||||
id="upload-folder-input"
|
id="upload-folder-input"
|
||||||
@change="uploadInput($event)"
|
@change="uploadInput($event)"
|
||||||
webkitdirectory
|
webkitdirectory
|
||||||
getMultiple
|
multiple
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div :class="{ active: getMultiple }" id="multiple-selection">
|
|
||||||
<p>{{ $t("files.multipleSelectionEnabled") }}</p>
|
|
||||||
<div
|
|
||||||
@click="this.setMultiple(false)"
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
:title="$t('files.clear')"
|
|
||||||
:aria-label="$t('files.clear')"
|
|
||||||
class="action"
|
|
||||||
>
|
|
||||||
<i class="material-icons">clear</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import download from "@/utils/download";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
|
import { router } from "@/router";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
import css from "@/utils/css";
|
import css from "@/utils/css";
|
||||||
import throttle from "@/utils/throttle";
|
import throttle from "@/utils/throttle";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import { showError } from "@/notify";
|
|
||||||
|
|
||||||
import Item from "@/components/files/ListingItem.vue";
|
import Item from "@/components/files/ListingItem.vue";
|
||||||
export default {
|
export default {
|
||||||
|
@ -178,15 +163,39 @@ export default {
|
||||||
columnWidth: 250 + state.user.gallerySize * 50,
|
columnWidth: 250 + state.user.gallerySize * 50,
|
||||||
dragCounter: 0,
|
dragCounter: 0,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
|
lastSelected: {}, // Add this to track the currently focused item
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
gallerySize() {
|
gallerySize() {
|
||||||
this.columnWidth = 250 + state.user.gallerySize * 50; // Update columnWidth based on new gallery size\
|
this.columnWidth = 250 + state.user.gallerySize * 50;
|
||||||
this.colunmsResize();
|
this.colunmsResize();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
lastFolderIndex() {
|
||||||
|
const allItems = [...this.items.dirs, ...this.items.files];
|
||||||
|
for (let i = 0; i < allItems.length; i++) {
|
||||||
|
if (!allItems[i].isDir) {
|
||||||
|
return i - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allItems.length > 0) {
|
||||||
|
return allItems.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Return null if there are no files
|
||||||
|
},
|
||||||
|
numColumns() {
|
||||||
|
if (!getters.isCardView()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let columns = Math.floor(
|
||||||
|
document.querySelector("main").offsetWidth / this.columnWidth
|
||||||
|
);
|
||||||
|
if (columns === 0) columns = 1;
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
// Create a computed property that references the Vuex state
|
// Create a computed property that references the Vuex state
|
||||||
gallerySize() {
|
gallerySize() {
|
||||||
return state.user.gallerySize;
|
return state.user.gallerySize;
|
||||||
|
@ -270,6 +279,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.lastSelected = state.selected;
|
||||||
// Check the columns size for the first time.
|
// Check the columns size for the first time.
|
||||||
this.colunmsResize();
|
this.colunmsResize();
|
||||||
// Add the needed event listeners to the window and document.
|
// Add the needed event listeners to the window and document.
|
||||||
|
@ -278,66 +288,238 @@ export default {
|
||||||
window.addEventListener("resize", this.windowsResize);
|
window.addEventListener("resize", this.windowsResize);
|
||||||
|
|
||||||
if (!state.user.perm?.create) return;
|
if (!state.user.perm?.create) return;
|
||||||
document.addEventListener("dragover", this.preventDefault);
|
this.$el.addEventListener("dragover", this.preventDefault);
|
||||||
document.addEventListener("dragenter", this.dragEnter);
|
this.$el.addEventListener("dragenter", this.dragEnter);
|
||||||
document.addEventListener("dragleave", this.dragLeave);
|
this.$el.addEventListener("dragleave", this.dragLeave);
|
||||||
document.addEventListener("drop", this.drop);
|
this.$el.addEventListener("drop", this.drop);
|
||||||
|
this.$el.addEventListener("contextmenu", this.openContext);
|
||||||
|
this.$el.addEventListener("click", this.clickClear);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
// Remove event listeners before destroying this page.
|
// Remove event listeners before destroying this page.
|
||||||
window.removeEventListener("keydown", this.keyEvent);
|
window.removeEventListener("keydown", this.keyEvent);
|
||||||
window.removeEventListener("scroll", this.scrollEvent);
|
window.removeEventListener("scroll", this.scrollEvent);
|
||||||
window.removeEventListener("resize", this.windowsResize);
|
window.removeEventListener("resize", this.windowsResize);
|
||||||
|
|
||||||
if (state.user && !state.user.perm?.create) return;
|
|
||||||
document.removeEventListener("dragover", this.preventDefault);
|
|
||||||
document.removeEventListener("dragenter", this.dragEnter);
|
|
||||||
document.removeEventListener("dragleave", this.dragLeave);
|
|
||||||
document.removeEventListener("drop", this.drop);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
base64(name) {
|
base64(name) {
|
||||||
return window.btoa(unescape(encodeURIComponent(name)));
|
return window.btoa(unescape(encodeURIComponent(name)));
|
||||||
},
|
},
|
||||||
keyEvent(event) {
|
// Helper method to select the first item if nothing is selected
|
||||||
// Esc!
|
selectFirstItem() {
|
||||||
if (event.keyCode === 27) {
|
|
||||||
mutations.resetSelected();
|
mutations.resetSelected();
|
||||||
|
const allItems = [...this.items.dirs, ...this.items.files];
|
||||||
|
if (allItems.length > 0) {
|
||||||
|
mutations.addSelected(allItems[0].index);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Del!
|
// Helper method to select an item by index
|
||||||
if (event.keyCode === 46) {
|
selectItem(index) {
|
||||||
if (!state.user.perm.delete || state.selected.length === 0) return;
|
mutations.resetSelected();
|
||||||
mutations.showHover("delete");
|
mutations.addSelected(index);
|
||||||
}
|
},
|
||||||
|
// Helper method to handle selection based on arrow keys
|
||||||
|
navigateKeboardArrows(arrowKey) {
|
||||||
|
let selectedIndex = state.selected.length > 0 ? state.selected[0] : null;
|
||||||
|
|
||||||
// F2!
|
if (selectedIndex === null) {
|
||||||
if (event.keyCode === 113) {
|
// If nothing is selected, select the first item
|
||||||
if (!state.user.perm.rename || state.selected.length !== 1) return;
|
this.selectFirstItem();
|
||||||
mutations.showHover("rename");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ctrl is pressed
|
|
||||||
if (!event.ctrlKey && !event.metaKey) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = String.fromCharCode(event.which).toLowerCase();
|
const allItems = [...this.items.dirs, ...this.items.files]; // Combine files and directories
|
||||||
|
|
||||||
switch (key) {
|
// Find the current index of the selected item
|
||||||
case "f":
|
let currentIndex = allItems.findIndex((item) => item.index === selectedIndex);
|
||||||
event.preventDefault();
|
|
||||||
mutations.showHover("search");
|
// If no item is selected, select the first item
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
// Check if there are any items to select
|
||||||
|
if (allItems.length > 0) {
|
||||||
|
currentIndex = 0;
|
||||||
|
this.selectItem(allItems[currentIndex].index);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newSelected = null;
|
||||||
|
const fileSelected = currentIndex > this.lastFolderIndex;
|
||||||
|
const nextIsDir = currentIndex - this.numColumns <= this.lastFolderIndex;
|
||||||
|
const folderSelected = currentIndex <= this.lastFolderIndex;
|
||||||
|
const nextIsFile = currentIndex + this.numColumns > this.lastFolderIndex;
|
||||||
|
const nextHopExists = currentIndex + this.numColumns < allItems.length;
|
||||||
|
const thisColumnNum =
|
||||||
|
((currentIndex - this.lastFolderIndex - 1) % this.numColumns) + 1;
|
||||||
|
const lastFolderColumn = (this.lastFolderIndex % this.numColumns) + 1;
|
||||||
|
const thisColumnNum2 = (currentIndex + 1) % this.numColumns;
|
||||||
|
let firstRowColumnPos = this.lastFolderIndex + thisColumnNum2;
|
||||||
|
let newPos = currentIndex - lastFolderColumn;
|
||||||
|
switch (arrowKey) {
|
||||||
|
case "ArrowUp":
|
||||||
|
if (currentIndex - this.numColumns < 0) {
|
||||||
|
// do nothing
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
if (!getters.isCardView) {
|
||||||
|
newSelected = allItems[currentIndex - 1].index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// do normal move
|
||||||
|
if (!(fileSelected && nextIsDir)) {
|
||||||
|
newSelected = allItems[currentIndex - this.numColumns].index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// complex logic to move from files to folders
|
||||||
|
if (lastFolderColumn < thisColumnNum) {
|
||||||
|
newPos -= this.numColumns;
|
||||||
|
}
|
||||||
|
newSelected = allItems[newPos].index;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowDown":
|
||||||
|
if (currentIndex >= allItems.length) {
|
||||||
|
// do nothing - last item
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!getters.isCardView) {
|
||||||
|
newSelected = allItems[currentIndex + 1].index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!nextHopExists) {
|
||||||
|
// do nothing - next item is out of bounds
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(folderSelected && nextIsFile)) {
|
||||||
|
newSelected = allItems[currentIndex + this.numColumns].index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// complex logic for moving from folders to files
|
||||||
|
if (firstRowColumnPos <= this.lastFolderIndex) {
|
||||||
|
firstRowColumnPos += this.numColumns;
|
||||||
|
}
|
||||||
|
newSelected = allItems[firstRowColumnPos].index;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowLeft":
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
newSelected = allItems[currentIndex - 1].index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowRight":
|
||||||
|
if (currentIndex < allItems.length - 1) {
|
||||||
|
newSelected = allItems[currentIndex + 1].index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (newSelected != null) {
|
||||||
|
this.selectItem(newSelected);
|
||||||
|
setTimeout(() => {
|
||||||
|
// Find the element with class "item" and aria-selected="true"
|
||||||
|
const element = document.querySelector('.item[aria-selected="true"]');
|
||||||
|
// Scroll the element into view if it exists
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "end",
|
||||||
|
inline: "nearest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyEvent(event) {
|
||||||
|
const { key, ctrlKey, metaKey, which } = event;
|
||||||
|
// Check if the key is alphanumeric
|
||||||
|
const isAlphanumeric = /^[a-z0-9]$/i.test(key);
|
||||||
|
const noModifierKeys = !ctrlKey && !metaKey;
|
||||||
|
|
||||||
|
if (isAlphanumeric && noModifierKeys) {
|
||||||
|
this.alphanumericKeyPress(key); // Call the alphanumeric key press function
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle the space bar key
|
||||||
|
if (key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (getters.currentPromptName() == "search") {
|
||||||
|
mutations.closeHovers();
|
||||||
|
} else {
|
||||||
|
mutations.showHover("search");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getters.currentPromptName() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let currentPath = state.route.path.replace(/\/+$/, ""); // Remove trailing slashes
|
||||||
|
let newPath = currentPath.substring(0, currentPath.lastIndexOf("/"));
|
||||||
|
// Handle key events using a switch statement
|
||||||
|
switch (key) {
|
||||||
|
case "Enter":
|
||||||
|
if (this.selectedCount === 1) {
|
||||||
|
router.push({ path: getters.getFirstSelected().url });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Backspace":
|
||||||
|
// go back
|
||||||
|
router.push({ path: newPath });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Escape":
|
||||||
|
mutations.resetSelected();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Delete":
|
||||||
|
if (!state.user.perm.delete || state.selected.length === 0) return;
|
||||||
|
mutations.showHover("delete");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "F2":
|
||||||
|
if (!state.user.perm.rename || state.selected.length !== 1) return;
|
||||||
|
mutations.showHover("rename");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowUp":
|
||||||
|
case "ArrowDown":
|
||||||
|
case "ArrowLeft":
|
||||||
|
case "ArrowRight":
|
||||||
|
event.preventDefault();
|
||||||
|
this.navigateKeboardArrows(key);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Handle keys with ctrl or meta keys
|
||||||
|
if (!ctrlKey && !metaKey) return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const charKey = String.fromCharCode(which).toLowerCase();
|
||||||
|
|
||||||
|
switch (charKey) {
|
||||||
case "c":
|
case "c":
|
||||||
case "x":
|
case "x":
|
||||||
this.copyCut(event, key);
|
this.copyCut(event, charKey);
|
||||||
break;
|
break;
|
||||||
case "v":
|
case "v":
|
||||||
this.paste(event);
|
this.paste(event);
|
||||||
break;
|
break;
|
||||||
case "a":
|
case "a":
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
this.selectAll();
|
||||||
|
break;
|
||||||
|
case "s":
|
||||||
|
event.preventDefault();
|
||||||
|
download();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper method to select all files and directories
|
||||||
|
selectAll() {
|
||||||
for (let file of this.items.files) {
|
for (let file of this.items.files) {
|
||||||
if (state.selected.indexOf(file.index) === -1) {
|
if (state.selected.indexOf(file.index) === -1) {
|
||||||
mutations.addSelected(file.index);
|
mutations.addSelected(file.index);
|
||||||
|
@ -348,11 +530,50 @@ export default {
|
||||||
mutations.addSelected(dir.index);
|
mutations.addSelected(dir.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
},
|
||||||
case "s":
|
alphanumericKeyPress(key) {
|
||||||
event.preventDefault();
|
// Convert the key to uppercase to match the case-insensitive search
|
||||||
document.getElementById("download-button").click();
|
const searchLetter = key.toLowerCase();
|
||||||
break;
|
const currentSelected = getters.getFirstSelected();
|
||||||
|
let currentName = null;
|
||||||
|
let findNextWithName = false;
|
||||||
|
|
||||||
|
if (currentSelected != undefined) {
|
||||||
|
currentName = currentSelected.name.toLowerCase();
|
||||||
|
if (currentName.startsWith(searchLetter)) {
|
||||||
|
findNextWithName = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Combine directories and files (assuming they are stored in this.items.dirs and this.items.files)
|
||||||
|
const allItems = [...this.items.dirs, ...this.items.files];
|
||||||
|
let foundPrevious = false;
|
||||||
|
let firstFound = null;
|
||||||
|
// Iterate over all items to find the first one where the name starts with the searchLetter
|
||||||
|
for (let i = 0; i < allItems.length; i++) {
|
||||||
|
const itemName = allItems[i].name.toLowerCase();
|
||||||
|
if (!itemName.startsWith(searchLetter)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (firstFound == null) {
|
||||||
|
firstFound = allItems[i].index;
|
||||||
|
}
|
||||||
|
if (!findNextWithName) {
|
||||||
|
// return first you find
|
||||||
|
this.selectItem(allItems[i].index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (itemName == currentName) {
|
||||||
|
foundPrevious = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (foundPrevious) {
|
||||||
|
this.selectItem(allItems[i].index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// select the first item again
|
||||||
|
if (firstFound != null) {
|
||||||
|
this.selectItem(firstFound);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preventDefault(event) {
|
preventDefault(event) {
|
||||||
|
@ -395,23 +616,17 @@ export default {
|
||||||
}
|
}
|
||||||
mutations.setLoading("listing", true);
|
mutations.setLoading("listing", true);
|
||||||
let action = (overwrite, rename) => {
|
let action = (overwrite, rename) => {
|
||||||
api
|
api.copy(items, overwrite, rename).then(() => {
|
||||||
.copy(items, overwrite, rename)
|
|
||||||
.then(() => {
|
|
||||||
mutations.setLoading("listing", false);
|
mutations.setLoading("listing", false);
|
||||||
})
|
});
|
||||||
.catch(showError);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.clipboard.key === "x") {
|
if (this.clipboard.key === "x") {
|
||||||
action = (overwrite, rename) => {
|
action = (overwrite, rename) => {
|
||||||
api
|
api.move(items, overwrite, rename).then(() => {
|
||||||
.move(items, overwrite, rename)
|
|
||||||
.then(() => {
|
|
||||||
this.clipboard = {};
|
this.clipboard = {};
|
||||||
mutations.setLoading("listing", false);
|
mutations.setLoading("listing", false);
|
||||||
})
|
});
|
||||||
.catch(showError);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,12 +655,8 @@ export default {
|
||||||
action(false, false);
|
action(false, false);
|
||||||
},
|
},
|
||||||
colunmsResize() {
|
colunmsResize() {
|
||||||
let columns = Math.floor(
|
|
||||||
document.querySelector("main").offsetWidth / this.columnWidth
|
|
||||||
);
|
|
||||||
let items = css(["#listingView .item", "#listingView .item"]);
|
let items = css(["#listingView .item", "#listingView .item"]);
|
||||||
if (columns === 0) columns = 1;
|
items.style.width = `calc(${100 / this.numColumns}% - 1em)`;
|
||||||
items.style.width = `calc(${100 / columns}% - 1em)`;
|
|
||||||
if (state.user.viewMode == "gallery") {
|
if (state.user.viewMode == "gallery") {
|
||||||
items.style.height = `${this.columnWidth / 20}em`;
|
items.style.height = `${this.columnWidth / 20}em`;
|
||||||
} else {
|
} else {
|
||||||
|
@ -483,34 +694,44 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
let files = await upload.scanFiles(dt);
|
let files = await upload.scanFiles(dt);
|
||||||
|
const folderUpload = !!files[0].webkitRelativePath;
|
||||||
|
|
||||||
|
const uploadFiles = [];
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
const fullPath = folderUpload ? file.webkitRelativePath : undefined;
|
||||||
|
uploadFiles.push({
|
||||||
|
file, // File object directly
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
isDir: false,
|
||||||
|
fullPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
let items = state.req.items;
|
let items = state.req.items;
|
||||||
let path = getters.getRoutePath();
|
let path = getters.getRoutePath();
|
||||||
|
|
||||||
if (el !== null && el.classList.contains("item") && el.dataset.dir === "true") {
|
if (el !== null && el.classList.contains("item") && el.dataset.dir === "true") {
|
||||||
path = el.__vue__.url;
|
path = el.__vue__.url;
|
||||||
|
|
||||||
try {
|
|
||||||
items = (await api.fetch(path)).items;
|
items = (await api.fetch(path)).items;
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const conflict = upload.checkConflict(files, items);
|
const conflict = upload.checkConflict(uploadFiles, items);
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
mutations.showHover({
|
mutations.showHover({
|
||||||
name: "replace",
|
name: "replace",
|
||||||
confirm: (event) => {
|
confirm: async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
upload.handleFiles(files, path, true);
|
await upload.handleFiles(uploadFiles, path, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
} else {
|
||||||
|
await upload.handleFiles(uploadFiles, path);
|
||||||
}
|
}
|
||||||
|
mutations.setReload(true);
|
||||||
upload.handleFiles(files, path);
|
|
||||||
},
|
},
|
||||||
uploadInput(event) {
|
uploadInput(event) {
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
|
@ -564,6 +785,7 @@ export default {
|
||||||
},
|
},
|
||||||
setMultiple(val) {
|
setMultiple(val) {
|
||||||
mutations.setMultiple(val == true);
|
mutations.setMultiple(val == true);
|
||||||
|
showMultipleSelection();
|
||||||
},
|
},
|
||||||
openSearch() {
|
openSearch() {
|
||||||
this.currentPrompt = "search";
|
this.currentPrompt = "search";
|
||||||
|
@ -584,6 +806,23 @@ export default {
|
||||||
document.getElementById("upload-input").click();
|
document.getElementById("upload-input").click();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openContext(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
mutations.showHover({
|
||||||
|
name: "ContextMenu",
|
||||||
|
props: {
|
||||||
|
posX: event.clientX,
|
||||||
|
posY: event.clientY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clickClear() {
|
||||||
|
const sameAsBefore = state.selected == this.lastSelected;
|
||||||
|
if (sameAsBefore && !state.multiple) {
|
||||||
|
mutations.resetSelected();
|
||||||
|
}
|
||||||
|
this.lastSelected = state.selected;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -198,19 +198,26 @@ export default {
|
||||||
this.$router.replace({ path: this.nextLink });
|
this.$router.replace({ path: this.nextLink });
|
||||||
},
|
},
|
||||||
key(event) {
|
key(event) {
|
||||||
if (this.currentPrompt !== null) {
|
if (getters.currentPromptName() != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.which === 13 || event.which === 39) {
|
const { key } = event;
|
||||||
// right arrow
|
|
||||||
if (this.hasNext) this.next();
|
switch (key) {
|
||||||
} else if (event.which === 37) {
|
case "ArrowRight":
|
||||||
// left arrow
|
if (this.hasNext) {
|
||||||
if (this.hasPrevious) this.prev();
|
this.next();
|
||||||
} else if (event.which === 27) {
|
}
|
||||||
// esc
|
break;
|
||||||
|
case "ArrowLeft":
|
||||||
|
if (this.hasPrevious) {
|
||||||
|
this.prev();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ("Escape", "Backspace"):
|
||||||
this.close();
|
this.close();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async updatePreview() {
|
async updatePreview() {
|
||||||
|
@ -222,13 +229,9 @@ export default {
|
||||||
this.name = decodeURIComponent(dirs[dirs.length - 1]);
|
this.name = decodeURIComponent(dirs[dirs.length - 1]);
|
||||||
|
|
||||||
if (!this.listing) {
|
if (!this.listing) {
|
||||||
try {
|
|
||||||
const path = url.removeLastDir(state.route.path);
|
const path = url.removeLastDir(state.route.path);
|
||||||
const res = await api.fetch(path);
|
const res = await api.fetch(path);
|
||||||
this.listing = res.items;
|
this.listing = res.items;
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.previousLink = "";
|
this.previousLink = "";
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { showSuccess, showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import { settings as api } from "@/api";
|
import { settings as api } from "@/api";
|
||||||
import { enableExec } from "@/utils/constants";
|
import { enableExec } from "@/utils/constants";
|
||||||
|
@ -140,9 +140,9 @@ export default {
|
||||||
try {
|
try {
|
||||||
mutations.setSettings(this.selectedSettings);
|
mutations.setSettings(this.selectedSettings);
|
||||||
await api.update(state.settings);
|
await api.update(state.settings);
|
||||||
showSuccess(this.$t("settings.settingsUpdated"));
|
notify.showSuccess(this.$t("settings.settingsUpdated"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card" id="profile-main" :class="{ active: active }">
|
<div class="card" :class="{ active: active }">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("settings.profileSettings") }}</h2>
|
<h2>{{ $t("settings.profileSettings") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { showSuccess, showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import { state, mutations } from "@/store";
|
import { state, mutations } from "@/store";
|
||||||
import { users } from "@/api";
|
import { users } from "@/api";
|
||||||
import Languages from "@/components/settings/Languages.vue";
|
import Languages from "@/components/settings/Languages.vue";
|
||||||
|
@ -174,9 +174,9 @@ export default {
|
||||||
newUserSettings.id = state.user.id;
|
newUserSettings.id = state.user.id;
|
||||||
newUserSettings.password = this.password;
|
newUserSettings.password = this.password;
|
||||||
await users.update(newUserSettings, ["password"]);
|
await users.update(newUserSettings, ["password"]);
|
||||||
showSuccess(this.$t("settings.passwordUpdated"));
|
notify.showSuccess(this.$t("settings.passwordUpdated"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async updateSettings(event) {
|
async updateSettings(event) {
|
||||||
|
@ -203,13 +203,13 @@ export default {
|
||||||
"dateFormat",
|
"dateFormat",
|
||||||
"gallerySize",
|
"gallerySize",
|
||||||
]);
|
]);
|
||||||
mutations.updateUser(data);
|
mutations.updateCurrentUser(data);
|
||||||
if (shouldReload) {
|
if (shouldReload) {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
showSuccess(this.$t("settings.settingsUpdated"));
|
notify.showSuccess(this.$t("settings.settingsUpdated"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateViewMode(updatedMode) {
|
updateViewMode(updatedMode) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<errors v-if="error" :errorCode="error.status" />
|
<errors v-if="error" :errorCode="error.status" />
|
||||||
<div class="card" id="shares-main" :class="{ active: active }">
|
<div class="card" :class="{ active: active }">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("settings.shareManagement") }}</h2>
|
<h2>{{ $t("settings.shareManagement") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { showSuccess, showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import { share as api, users } from "@/api";
|
import { share as api, users } from "@/api";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import { fromNow } from "@/utils/moment";
|
import { fromNow } from "@/utils/moment";
|
||||||
|
@ -95,7 +95,7 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.clip = new Clipboard(".copy-clipboard");
|
this.clip = new Clipboard(".copy-clipboard");
|
||||||
this.clip.on("success", () => {
|
this.clip.on("success", () => {
|
||||||
showSuccess(this.$t("success.linkCopied"));
|
notify.showSuccess(this.$t("success.linkCopied"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
@ -126,9 +126,9 @@ export default {
|
||||||
try {
|
try {
|
||||||
api.remove(link.hash);
|
api.remove(link.hash);
|
||||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||||
showSuccess(this.$t("settings.shareDeleted"));
|
notify.showSuccess(this.$t("settings.shareDeleted"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<errors v-if="error" :errorCode="error.status" />
|
<errors v-if="error" :errorCode="error.status" />
|
||||||
<form @submit="save" id="user-main" class="card">
|
<form @submit="save" class="card active">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2>
|
<h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2>
|
||||||
<h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2>
|
<h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2>
|
||||||
|
@ -37,7 +37,7 @@ import { mutations, state } from "@/store";
|
||||||
import { users as api, settings } from "@/api";
|
import { users as api, settings } from "@/api";
|
||||||
import UserForm from "@/components/settings/UserForm.vue";
|
import UserForm from "@/components/settings/UserForm.vue";
|
||||||
import Errors from "@/views/Errors.vue";
|
import Errors from "@/views/Errors.vue";
|
||||||
import { showSuccess, showError } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "user",
|
name: "user",
|
||||||
|
@ -49,12 +49,13 @@ export default {
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
originalUser: null,
|
originalUser: null,
|
||||||
user: { perm: { admin: false } },
|
user: {
|
||||||
|
scope: ".",
|
||||||
|
username: "",
|
||||||
|
perm: { admin: false },
|
||||||
|
},
|
||||||
showDelete: false,
|
showDelete: false,
|
||||||
createUserDir: false,
|
createUserDir: false,
|
||||||
loading: false, // Replaces Vuex state `loading`
|
|
||||||
currentPrompt: null, // Replaces Vuex getter `currentPrompt`
|
|
||||||
currentPromptName: null, // Replaces Vuex getter `currentPromptName`
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -65,14 +66,14 @@ export default {
|
||||||
return state.settings;
|
return state.settings;
|
||||||
},
|
},
|
||||||
isNew() {
|
isNew() {
|
||||||
return state.route.path === "/settings/users/new";
|
return state.route.path.startsWith("/settings/users/new");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
$route: "fetchData",
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
|
if (!state.route.path.startsWith("/settings")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
mutations.setLoading("users", true);
|
mutations.setLoading("users", true);
|
||||||
try {
|
try {
|
||||||
if (this.isNew) {
|
if (this.isNew) {
|
||||||
|
@ -87,11 +88,13 @@ export default {
|
||||||
id: 0,
|
id: 0,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const id = state.route.params.id;
|
const id = Array.isArray(state.route.params.id)
|
||||||
|
? state.route.params.id.join("")
|
||||||
|
: state.route.params.id;
|
||||||
this.user = { ...(await api.get(id)) };
|
this.user = { ...(await api.get(id)) };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
this.error = e;
|
this.error = e;
|
||||||
} finally {
|
} finally {
|
||||||
mutations.setLoading("users", false);
|
mutations.setLoading("users", false);
|
||||||
|
@ -101,27 +104,19 @@ export default {
|
||||||
mutations.showHover({ name: "deleteUser", props: { user: this.user } });
|
mutations.showHover({ name: "deleteUser", props: { user: this.user } });
|
||||||
},
|
},
|
||||||
async save(event) {
|
async save(event) {
|
||||||
|
let user = this.user
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let user = {
|
|
||||||
...this.originalUser,
|
|
||||||
...this.user,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.isNew) {
|
if (this.isNew) {
|
||||||
const loc = await api.create(user);
|
const loc = await api.create(user);
|
||||||
this.$router.push({ path: loc });
|
this.$router.push({ path: loc });
|
||||||
showSuccess(this.$t("settings.userCreated"));
|
notify.showSuccess(this.$t("settings.userCreated"));
|
||||||
} else {
|
} else {
|
||||||
await api.update(user);
|
await api.update(user);
|
||||||
if (user.id === state.user.id) {
|
notify.showSuccess(this.$t("settings.userUpdated"));
|
||||||
consoel.log("set user");
|
|
||||||
mutations.setUser(user);
|
|
||||||
}
|
|
||||||
showSuccess(this.$t("settings.userUpdated"));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e);
|
notify.showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
<template>
|
|
||||||
<errors v-if="error" :errorCode="error.status" />
|
|
||||||
<div v-if="isExecEnabled" class="card" id="userColumn-main">
|
|
||||||
<form @submit.prevent="save">
|
|
||||||
<div class="card-title">
|
|
||||||
<h2>{{ $t("settings.commandRunner") }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-content">
|
|
||||||
<i18n path="settings.commandRunnerHelp" tag="p" class="small">
|
|
||||||
<code>FILE</code>
|
|
||||||
<code>SCOPE</code>
|
|
||||||
<a
|
|
||||||
class="link"
|
|
||||||
target="_blank"
|
|
||||||
href="https://filebrowser.org/configuration/command-runner"
|
|
||||||
>{{ $t("settings.documentation") }}</a
|
|
||||||
>
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-for="(command, index) in settings.commands"
|
|
||||||
:key="index"
|
|
||||||
class="collapsible"
|
|
||||||
>
|
|
||||||
<input :id="command.name" type="checkbox" />
|
|
||||||
<label :for="command.name">
|
|
||||||
<p>{{ capitalize(command.name) }}</p>
|
|
||||||
<i class="material-icons">arrow_drop_down</i>
|
|
||||||
</label>
|
|
||||||
<div class="collapse">
|
|
||||||
<textarea
|
|
||||||
class="input input--block input--textarea"
|
|
||||||
v-model.trim="command.value"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-action">
|
|
||||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { showSuccess } from "@/notify";
|
|
||||||
import { state, getters } from "@/store";
|
|
||||||
import { settings as api } from "@/api";
|
|
||||||
import { enableExec } from "@/utils/constants";
|
|
||||||
//import UserForm from "@/components/settings/UserForm.vue";
|
|
||||||
//import Rules from "@/components/settings/Rules.vue";
|
|
||||||
import Errors from "@/views/Errors.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "settings",
|
|
||||||
components: {
|
|
||||||
//UserForm,
|
|
||||||
//Rules,
|
|
||||||
Errors,
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
error: null,
|
|
||||||
originalSettings: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
settings() {
|
|
||||||
return state.settings;
|
|
||||||
},
|
|
||||||
loading() {
|
|
||||||
return getters.isLoading();
|
|
||||||
},
|
|
||||||
user() {
|
|
||||||
return state.user;
|
|
||||||
},
|
|
||||||
isExecEnabled: () => enableExec,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateRules(updatedRules) {
|
|
||||||
this.settings.rules = updatedRules;
|
|
||||||
},
|
|
||||||
capitalize(name, where = "_") {
|
|
||||||
if (where === "caps") where = /(?=[A-Z])/;
|
|
||||||
let splitted = name.split(where);
|
|
||||||
name = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < splitted.length; i++) {
|
|
||||||
name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
return name.slice(0, -1);
|
|
||||||
},
|
|
||||||
async save() {
|
|
||||||
try {
|
|
||||||
await api.update(state.settings);
|
|
||||||
showSuccess(this.$t("settings.settingsUpdated"));
|
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,87 +0,0 @@
|
||||||
<template>
|
|
||||||
<errors v-if="error" :errorCode="error.status" />
|
|
||||||
<div class="card" id="user-defaults-main">
|
|
||||||
<div class="card-title">
|
|
||||||
<h2>{{ $t("settings.userDefaults") }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="small">{{ $t("settings.defaultUserDescription") }}</p>
|
|
||||||
|
|
||||||
<user-form
|
|
||||||
:isNew="false"
|
|
||||||
:isDefault="true"
|
|
||||||
:user="settings.defaults"
|
|
||||||
@update:user="updateUser"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-action">
|
|
||||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { showSuccess } from "@/notify";
|
|
||||||
import { state, getters } from "@/store";
|
|
||||||
import { settings as api } from "@/api";
|
|
||||||
import { enableExec } from "@/utils/constants";
|
|
||||||
import UserForm from "@/components/settings/UserForm.vue";
|
|
||||||
//import Rules from "@/components/settings/Rules.vue";
|
|
||||||
import Errors from "@/views/Errors.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "settings",
|
|
||||||
components: {
|
|
||||||
UserForm,
|
|
||||||
//Rules,
|
|
||||||
Errors,
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
error: null,
|
|
||||||
originalSettings: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
settings() {
|
|
||||||
return state.settings;
|
|
||||||
},
|
|
||||||
loading() {
|
|
||||||
return getters.isLoading();
|
|
||||||
},
|
|
||||||
user() {
|
|
||||||
return state.user;
|
|
||||||
},
|
|
||||||
isExecEnabled: () => enableExec,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateRules(updatedRules) {
|
|
||||||
state.settings.rules = updatedRules;
|
|
||||||
},
|
|
||||||
updateUser(updatedUser) {
|
|
||||||
state.settings.defaults = updatedUser;
|
|
||||||
},
|
|
||||||
capitalize(name, where = "_") {
|
|
||||||
if (where === "caps") where = /(?=[A-Z])/;
|
|
||||||
let splitted = name.split(where);
|
|
||||||
name = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < splitted.length; i++) {
|
|
||||||
name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
return name.slice(0, -1);
|
|
||||||
},
|
|
||||||
async save() {
|
|
||||||
try {
|
|
||||||
await api.update(state.settings);
|
|
||||||
showSuccess(this.$t("settings.settingsUpdated"));
|
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<errors v-if="error" :errorCode="error.status" />
|
<errors v-if="error" :errorCode="error.status" />
|
||||||
<div class="card" id="users-main">
|
<div class="card">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("settings.users") }}</h2>
|
<h2>{{ $t("settings.users") }}</h2>
|
||||||
<router-link to="/settings/users/new"
|
<router-link to="/settings/users/new"
|
||||||
|
@ -36,12 +36,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import { getAllUsers } from "@/api/users";
|
import { getAllUsers } from "@/api/users";
|
||||||
import Errors from "@/views/Errors.vue";
|
import Errors from "@/views/Errors.vue";
|
||||||
import { showError } from "@/notify";
|
|
||||||
mutations.setLoading("users", true);
|
|
||||||
export default {
|
export default {
|
||||||
name: "users",
|
name: "users",
|
||||||
components: {
|
components: {
|
||||||
|
@ -54,18 +54,10 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
|
mutations.setLoading("users", true);
|
||||||
// Set loading state to true
|
// Set loading state to true
|
||||||
|
|
||||||
try {
|
|
||||||
// Fetch all users from the API
|
|
||||||
this.users = await getAllUsers();
|
this.users = await getAllUsers();
|
||||||
} catch (e) {
|
|
||||||
showError(e);
|
|
||||||
// Handle errors
|
|
||||||
this.error = e;
|
|
||||||
} finally {
|
|
||||||
mutations.setLoading("users", false);
|
mutations.setLoading("users", false);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
settings() {
|
settings() {
|
||||||
|
|
|
@ -10,7 +10,7 @@ test("redirect to login", async ({ page }) => {
|
||||||
|
|
||||||
test("login", async ({ authPage, page, context }) => {
|
test("login", async ({ authPage, page, context }) => {
|
||||||
await authPage.goto();
|
await authPage.goto();
|
||||||
await expect(page).toHaveTitle(/Login - File Browser$/);
|
await expect(page).toHaveTitle(/Login - FileBrowser Quantum$/);
|
||||||
|
|
||||||
await authPage.loginAs("fake", "fake");
|
await authPage.loginAs("fake", "fake");
|
||||||
await expect(authPage.wrongCredentials).toBeVisible();
|
await expect(authPage.wrongCredentials).toBeVisible();
|
||||||
|
@ -18,14 +18,14 @@ test("login", async ({ authPage, page, context }) => {
|
||||||
await authPage.loginAs();
|
await authPage.loginAs();
|
||||||
await expect(authPage.wrongCredentials).toBeHidden();
|
await expect(authPage.wrongCredentials).toBeHidden();
|
||||||
// await page.waitForURL("**/files/", { timeout: 5000 });
|
// await page.waitForURL("**/files/", { timeout: 5000 });
|
||||||
await expect(page).toHaveTitle(/.*Files - File Browser$/);
|
await expect(page).toHaveTitle(/.*Files - FileBrowser Quantum$/);
|
||||||
|
|
||||||
let cookies = await context.cookies();
|
let cookies = await context.cookies();
|
||||||
expect(cookies.find((c) => c.name == "auth")?.value).toBeDefined();
|
expect(cookies.find((c) => c.name == "auth")?.value).toBeDefined();
|
||||||
|
|
||||||
// await authPage.logout();
|
// await authPage.logout();
|
||||||
// await page.waitForURL("**/login", { timeout: 5000 });
|
// await page.waitForURL("**/login", { timeout: 5000 });
|
||||||
// await expect(page).toHaveTitle(/Login - File Browser$/);
|
// await expect(page).toHaveTitle(/Login - FileBrowser Quantum$/);
|
||||||
// cookies = await context.cookies();
|
// cookies = await context.cookies();
|
||||||
// expect(cookies.find((c) => c.name == "auth")?.value).toBeUndefined();
|
// expect(cookies.find((c) => c.name == "auth")?.value).toBeUndefined();
|
||||||
});
|
});
|
|
@ -3,15 +3,17 @@
|
||||||
next 0.2.x release:
|
next 0.2.x release:
|
||||||
|
|
||||||
- Theme configuration from settings
|
- Theme configuration from settings
|
||||||
- Better media and file viewer support
|
- File syncronization improvements
|
||||||
|
- right-click context menu
|
||||||
|
|
||||||
initial 0.3.0 release :
|
initial 0.3.0 release :
|
||||||
|
|
||||||
- drop in replace backend db with pocketbas
|
- database changes
|
||||||
|
- introduce jobs as replacement to runners.
|
||||||
- Add Job status to the sidebar
|
- Add Job status to the sidebar
|
||||||
- index status.
|
- index status.
|
||||||
- Job status from users
|
- Job status from users
|
||||||
|
- upload status
|
||||||
|
|
||||||
Future releases:
|
Future releases:
|
||||||
- Replace http routes for gorilla/mux with pocketbase
|
- Replace http routes for gorilla/mux with pocketbase
|
||||||
|
|
Loading…
Reference in New Issue