Compare commits

..
73 changed files with 1146 additions and 1824 deletions

No files matched your search

+50
View File
@@ -2,6 +2,56 @@
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).
## v1.2.2-stable
**Security**:
- Patched Stored XSS in public share page via unsanitized share metadata (text/template misuse)
- Patched Incomplete Remediation of CVE-2026-27611: Password-Protected Share Bypass via /public/api/share/info GHSA-525j-95gf-766f
**New Features**:
- "Divider" option in sidebar links to add a text or divider between links (#1875)
- shares offer a "go to source Location" sidebar link and button when editing a share.
**Notes**:
- Share edit/delete permissions are scoped to the user's shares rather than global (#2050)
- OIDC group claims accepted as map (#2084)
**BugFixes**:
- fixed the requirement that the database path needed to be set in the config file, now it loads `FILEBROWSER_DATABASE` value by default, fallback to config file property.
- Error downloading zipped directory: no such file or directory (users with scope) #2015
## v1.2.1-stable
**BugFixes**:
- startup icon generation order issue (#2019)
- login button on shares not showing (#2028)
- removed file watcher link in context menu for shares (#2025)
- Direct download link for single-file shares generates incorrect URL (#2026)
- Deleting files causes error V1.2.0 #2039
## v1.2.7-beta
**BugFixes**:
- head > title > infinitely duplicating titles (#2016)
- Proxy auth not working with Nginx, stuck at logo (#2013)
## v1.2.6-beta
**Security**:
- resolves https://github.com/gtsteffaniak/filebrowser/security/advisories/GHSA-8vrh-3pm2-v4v6
## v1.2.5-beta
**Notes**:
- Dependency Updates for frontend and backend packages
- Upgrade to go 1.26
- file list prompt interaction behavior, single-click always selects, double-click navigates. (#1911)
**BugFixes**:
- .ssa and .ass subtitle files get their styling removed during conversion (#1933)
- custom icons issue
- new database message for noauth (#1935)
## v1.2.4-beta
**Notes**:
-1
View File
@@ -6,7 +6,6 @@ WORKDIR /app
COPY ./backend ./
RUN apk update && apk add --no-cache gcc musl-dev upx
ENV CGO_ENABLED=1
ENV GOEXPERIMENT=greenteagc
RUN go build -tags mupdf,musl -ldflags="-w -s \
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.Version=${VERSION}' \
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.CommitSHA=${REVISION}'" \
-1
View File
@@ -4,7 +4,6 @@ ARG REVISION
WORKDIR /app
COPY ./backend ./
RUN apk update && apk add upx
ENV GOEXPERIMENT=greenteagc
RUN go build -ldflags="-w -s \
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.Version=${VERSION}' \
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.CommitSHA=${REVISION}'" \
+4 -4
View File
@@ -182,7 +182,7 @@ func rootCMD(ctx context.Context, store *bolt.BoltStore, serverConfig *settings.
cacheDir := settings.Config.Server.CacheDir
numWorkers := settings.Config.Server.NumImageProcessors
ffmpeg.SetFFmpegPaths()
// Initialize asset filesystem before starting services
if settings.Env.EmbeddedFs {
embeddedAssets := fbhttp.GetEmbeddedAssets()
@@ -194,7 +194,7 @@ func rootCMD(ctx context.Context, store *bolt.BoltStore, serverConfig *settings.
} else {
fileutils.InitAssetFS(nil, false)
}
// Start preview service
err := preview.StartPreviewGenerator(numWorkers, cacheDir)
if err != nil {
@@ -202,12 +202,12 @@ func rootCMD(ctx context.Context, store *bolt.BoltStore, serverConfig *settings.
}
logger.Debugf("MuPDF Enabled : %v", settings.Env.MuPdfAvailable)
logger.Debugf("Media Enabled : %v", settings.MediaEnabled())
// Generate PWA icons after preview service is initialized
if err := icons.GeneratePWAIcons(); err != nil {
logger.Warningf("Failed to generate PWA icons: %v", err)
}
fbhttp.StartHttp(ctx, store, shutdownComplete)
return nil
}
+1 -1
View File
@@ -119,7 +119,7 @@ func updateShowFirstLogin(user *users.User) bool {
// func to convert legacy user with perm key to permissions
func updatePermissions(user *users.User) bool {
if user.Version == 1 {
if user.Version >= 1 {
return false
}
updateUser := true
+1 -25
View File
@@ -278,8 +278,6 @@ func setupFrontend(generate bool) {
}
// Load custom favicon if configured
loadCustomFavicon()
// Generate PWA icons from custom favicon if needed
generatePWAIcons()
}
func setupMedia() {
@@ -636,14 +634,9 @@ func setDefaults(generate bool) Settings {
database := os.Getenv("FILEBROWSER_DATABASE")
if database == "" {
database = "database.db"
} else {
// check if database file exists
if _, err := os.Stat(database); os.IsNotExist(err) {
database = "database.db"
}
}
if _, err := os.Stat(database); os.IsNotExist(err) {
logger.Warning("database file could not be found. If this is unexpected, the default path is `./database.db`, but it can be configured in the config file under `server.database`.")
logger.Warning("database file could not be found. If this is unexpected, please set the FILEBROWSER_DATABASE environment variable to the correct path.")
}
s := Settings{
Server: Server{
@@ -788,23 +781,6 @@ func loadCustomFavicon() {
logger.Infof("Using custom favicon: %s", Env.FaviconPath)
}
// generatePWAIcons sets up PWA icon paths (actual generation happens after preview service starts)
func generatePWAIcons() {
// Set PWA icons directory in cache
Env.PWAIconsDir = filepath.Join(Config.Server.CacheDir, "icons")
// Initialize with default paths (all icons will be generated)
Env.PWAIcon192 = "icons/pwa-icon-192.png"
Env.PWAIcon256 = "icons/pwa-icon-256.png"
Env.PWAIcon512 = "icons/pwa-icon-512.png"
// Create icons directory with configured permissions
// Note: Parent cache directory should already exist from testCacheDirSpeed()
if err := os.MkdirAll(Env.PWAIconsDir, fileutils.PermDir); err != nil {
logger.Warningf("Failed to create PWA icons directory %s: %v", Env.PWAIconsDir, err)
}
}
func loadLoginIcon() {
const imageName = "login icon"
allowedFormats := []string{".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico"}
+94 -51
View File
@@ -14,6 +14,7 @@ import (
"sort"
"strings"
"golang.org/x/tools/go/packages"
"gopkg.in/yaml.v3"
)
@@ -111,74 +112,93 @@ func CollectCommentsAndSecrets(srcPath string) (CommentsMap, SecretFieldsMap, De
return comments, secrets, deprecated, nil
}
// parseDirectoryComments parses a single directory for comments, secrets, and deprecated fields
// parseDirectoryComments parses a single directory for comments, secrets, and deprecated fields.
func parseDirectoryComments(dir string) (CommentsMap, SecretFieldsMap, DeprecatedFieldsMap, error) {
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments)
absDir, err := filepath.Abs(dir)
if err != nil {
return nil, nil, nil, err
}
var files []*ast.File
cfg := &packages.Config{
Mode: packages.NeedSyntax | packages.NeedFiles,
Dir: absDir,
ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
return parser.ParseFile(fset, filename, src, parser.ParseComments)
},
}
pkgs, err := packages.Load(cfg, ".")
if err == nil && len(pkgs) > 0 {
for _, pkg := range pkgs {
files = append(files, pkg.Syntax...)
}
}
if len(files) == 0 {
// Fallback for directories outside a module (e.g. test temp dirs): parse .go files directly
files, err = parseDirGoFiles(absDir)
if err != nil || len(files) == 0 {
return nil, nil, nil, err
}
}
comments := make(CommentsMap)
secrets := make(SecretFieldsMap)
deprecated := make(DeprecatedFieldsMap)
for _, pkg := range pkgs {
for _, file := range pkg.Files {
for _, decl := range file.Decls {
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.TYPE {
for _, file := range files {
for _, decl := range file.Decls {
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.TYPE {
continue
}
for _, spec := range gen.Specs {
ts, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}
for _, spec := range gen.Specs {
ts, ok := spec.(*ast.TypeSpec)
if !ok {
st, ok := ts.Type.(*ast.StructType)
if !ok {
continue
}
typeName := ts.Name.Name
commentMap := make(map[string]string)
secretMap := make(map[string]bool)
deprecatedMap := make(map[string]bool)
comments[typeName] = commentMap
secrets[typeName] = secretMap
deprecated[typeName] = deprecatedMap
for _, field := range st.Fields.List {
if len(field.Names) == 0 {
continue
}
st, ok := ts.Type.(*ast.StructType)
if !ok {
continue
name := field.Names[0].Name
var parts []string
var fullComment string
if field.Doc != nil {
docText := strings.TrimSpace(field.Doc.Text())
parts = append(parts, docText)
fullComment += docText + " "
}
if field.Comment != nil {
commentText := strings.TrimSpace(field.Comment.Text())
parts = append(parts, commentText)
fullComment += commentText
}
typeName := ts.Name.Name
commentMap := make(map[string]string)
secretMap := make(map[string]bool)
deprecatedMap := make(map[string]bool)
comments[typeName] = commentMap
secrets[typeName] = secretMap
deprecated[typeName] = deprecatedMap
for _, field := range st.Fields.List {
if len(field.Names) == 0 {
continue
}
name := field.Names[0].Name
var parts []string
var fullComment string
if len(parts) > 0 {
commentMap[name] = strings.Join(parts, " : ")
}
if field.Doc != nil {
docText := strings.TrimSpace(field.Doc.Text())
parts = append(parts, docText)
fullComment += docText + " "
}
if field.Comment != nil {
commentText := strings.TrimSpace(field.Comment.Text())
parts = append(parts, commentText)
fullComment += commentText
}
// Check if field should be treated as secret
if strings.Contains(strings.ToLower(fullComment), "secret:") {
secretMap[name] = true
}
if len(parts) > 0 {
commentMap[name] = strings.Join(parts, " : ")
}
// Check if field should be treated as secret
if strings.Contains(strings.ToLower(fullComment), "secret:") {
secretMap[name] = true
}
// Check if field should be treated as deprecated
if strings.Contains(strings.ToLower(fullComment), "deprecated:") {
deprecatedMap[name] = true
}
// Check if field should be treated as deprecated
if strings.Contains(strings.ToLower(fullComment), "deprecated:") {
deprecatedMap[name] = true
}
}
}
@@ -187,6 +207,29 @@ func parseDirectoryComments(dir string) (CommentsMap, SecretFieldsMap, Deprecate
return comments, secrets, deprecated, nil
}
// parseDirGoFiles parses all .go files in dir and returns their ASTs. Used when the directory
// is not inside a Go module (e.g. test temp dirs) so packages.Load would fail.
func parseDirGoFiles(dir string) ([]*ast.File, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
fset := token.NewFileSet()
var files []*ast.File
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".go") {
continue
}
path := filepath.Join(dir, e.Name())
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
return nil, err
}
files = append(files, f)
}
return files, nil
}
// PathToTypeField represents a mapping from YAML path to Go type and field info
type PathToTypeField struct {
TypeName string
+23 -4
View File
@@ -1,11 +1,12 @@
package share
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
"github.com/gtsteffaniak/filebrowser/backend/database/users"
)
// IsSingleFileShare determines if this share is for a single file (not a directory).
@@ -144,10 +145,28 @@ func (l *Link) HasReachedUserLimit(username string) bool {
return count >= l.DownloadsLimit
}
func (l *Link) GetSourceName() (string, error) {
func (l *Link) GetSourceName() string {
sourceInfo, ok := settings.Config.Server.SourceMap[l.Source]
if !ok {
return "", fmt.Errorf("source not found")
return ""
}
return sourceInfo.Name, nil
return sourceInfo.Name
}
func (l *Link) UserCanEdit(user *users.User) bool {
return l.UserID == user.ID || user.Permissions.Admin
}
func (l *Link) SourceURL(user *users.User) string {
sourceName := l.GetSourceName()
// get user scope path from share
userScope, err := user.GetScopeForSourceName(sourceName)
if err != nil {
return ""
}
if !strings.HasPrefix(l.Path, userScope) {
return ""
}
scopedPath := strings.TrimPrefix(l.Path, userScope)
return "/" + filepath.Join("files", sourceName, scopedPath)
}
+4 -2
View File
@@ -43,8 +43,10 @@ type CommonShare struct {
AllowReplacements bool `json:"allowReplacements,omitempty"` // allow replacements of files
SidebarLinks []users.SidebarLink `json:"sidebarLinks"` // customizable sidebar links
HasPassword bool `json:"hasPassword,omitempty"`
ShowHidden bool `json:"showHidden,omitempty"` // show hidden files in share (true = show, false = hide)
DisableLoginOption bool `json:"disableLoginOption,omitempty"` // disable login option in share (true = hide, false = show)
ShowHidden bool `json:"showHidden,omitempty"` // show hidden files in share (true = show, false = hide)
DisableLoginOption bool `json:"disableLoginOption"` // disable login option in share (true = hide, false = show)
SourceURL string `json:"sourceURL,omitempty"`
CanEdit bool `json:"canEdit,omitempty"`
}
type CreateBody struct {
CommonShare
+1 -1
View File
@@ -541,7 +541,7 @@ func (db *IndexDB) ExplainQueryPlan(query string, args ...interface{}) (string,
if err := rows.Scan(&id, &parent, &notused, &detail); err != nil {
return "", err
}
plan.WriteString(fmt.Sprintf("%d|%d|%s\n", id, parent, detail))
fmt.Fprintf(&plan, "%d|%d|%s\n", id, parent, detail)
}
return plan.String(), rows.Err()
}
+1
View File
@@ -18,6 +18,7 @@ import (
var userStore *users.Storage
func InitializeDb(path string) (*bolt.BoltStore, bool, error) {
logger.Debugf("Initializing database: %v", path)
exists, err := dbExists(path)
if err != nil {
panic(err)
+46 -46
View File
@@ -15,22 +15,23 @@ require (
github.com/google/go-cmp v0.7.0
github.com/gtsteffaniak/go-cache v1.1.0
github.com/gtsteffaniak/go-logger v1.0.2
github.com/kovidgoyal/imaging v1.8.19
github.com/mattn/go-sqlite3 v1.14.33
github.com/kovidgoyal/imaging v1.8.20
github.com/mattn/go-sqlite3 v1.14.34
github.com/pquerna/otp v1.5.0
github.com/spf13/afero v1.15.0
github.com/stretchr/testify v1.11.1
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.16.6
golang.org/x/crypto v0.47.0
golang.org/x/image v0.35.0
golang.org/x/mod v0.32.0
golang.org/x/net v0.49.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sys v0.40.0
golang.org/x/crypto v0.48.0
golang.org/x/image v0.36.0
golang.org/x/mod v0.33.0
golang.org/x/net v0.51.0
golang.org/x/oauth2 v0.35.0
golang.org/x/sys v0.41.0
golang.org/x/time v0.14.0
golang.org/x/tools v0.42.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.44.3
modernc.org/sqlite v1.46.1
)
require (
@@ -52,16 +53,17 @@ require (
github.com/Djarvur/go-err113 v0.1.1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/MirrexOne/unqueryvet v1.4.0 // indirect
github.com/MirrexOne/unqueryvet v1.5.3 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
github.com/air-verse/air v1.64.2 // indirect
github.com/alecthomas/chroma/v2 v2.21.1 // indirect
github.com/air-verse/air v1.64.5 // indirect
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
github.com/alexkohler/prealloc v1.0.1 // indirect
github.com/alexkohler/prealloc v1.0.2 // indirect
github.com/alfatraining/structtag v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/alingse/nilnesserr v0.2.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect
github.com/ashanbrown/makezero/v2 v2.1.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
@@ -71,7 +73,7 @@ require (
github.com/bkielbasa/cyclop v1.2.3 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
github.com/bombsimon/wsl/v5 v5.3.0 // indirect
github.com/bombsimon/wsl/v5 v5.6.0 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/breml/bidichk v0.3.3 // indirect
github.com/breml/errchkjson v0.4.1 // indirect
@@ -83,7 +85,7 @@ require (
github.com/charithe/durationcheck v0.0.11 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/ckaznocha/intrange v0.3.1 // indirect
@@ -97,7 +99,7 @@ require (
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
@@ -105,20 +107,20 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/ghostiam/protogetter v0.3.18 // indirect
github.com/ghostiam/protogetter v0.3.20 // indirect
github.com/go-critic/go-critic v0.14.3 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-openapi/jsonpointer v0.22.4 // indirect
github.com/go-openapi/jsonreference v0.21.4 // indirect
github.com/go-openapi/spec v0.22.3 // indirect
github.com/go-openapi/swag/conv v0.25.4 // indirect
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
github.com/go-openapi/swag/loading v0.25.4 // indirect
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/jsonreference v0.21.5 // indirect
github.com/go-openapi/spec v0.22.4 // indirect
github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
github.com/go-openapi/swag/loading v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
@@ -128,21 +130,21 @@ require (
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/godoc-lint/godoc-lint v0.11.1 // indirect
github.com/godoc-lint/godoc-lint v0.11.2 // indirect
github.com/gofrs/flock v0.13.0 // indirect
github.com/gohugoio/hugo v0.149.1 // indirect
github.com/golang/geo v0.0.0-20260129164528-943061e2742c // indirect
github.com/golang/geo v0.0.0-20260302211937-87f5a40ea07a // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golangci/asciicheck v0.5.0 // indirect
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
github.com/golangci/go-printf-func-name v0.1.1 // indirect
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
github.com/golangci/golangci-lint/v2 v2.8.0 // indirect
github.com/golangci/golines v0.14.0 // indirect
github.com/golangci/misspell v0.7.0 // indirect
github.com/golangci/golangci-lint/v2 v2.10.1 // indirect
github.com/golangci/golines v0.15.0 // indirect
github.com/golangci/misspell v0.8.0 // indirect
github.com/golangci/plugin-module-register v0.1.2 // indirect
github.com/golangci/revgrep v0.8.0 // indirect
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
@@ -164,7 +166,7 @@ require (
github.com/jjti/go-spancheck v0.6.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/julz/importas v0.2.0 // indirect
github.com/jupiterrider/ffi v0.5.1 // indirect
github.com/jupiterrider/ffi v0.6.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
github.com/kisielk/errcheck v1.9.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
@@ -194,7 +196,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgechev/revive v1.13.0 // indirect
github.com/mgechev/revive v1.14.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
github.com/moricho/tparallel v0.3.2 // indirect
@@ -203,7 +205,7 @@ require (
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.21.2 // indirect
github.com/nunnatsa/ginkgolinter v0.23.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -228,8 +230,8 @@ require (
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
github.com/securego/gosec/v2 v2.22.11 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/securego/gosec/v2 v2.23.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sonatard/noctx v0.4.0 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
@@ -262,23 +264,21 @@ require (
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.14.0 // indirect
go-simpler.org/sloglint v0.11.1 // indirect
go.augendre.info/arangolint v0.3.1 // indirect
go.augendre.info/arangolint v0.4.0 // indirect
go.augendre.info/fatcontext v0.9.0 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/tools v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
honnef.co/go/tools v0.6.1 // indirect
modernc.org/libc v1.67.7 // indirect
honnef.co/go/tools v0.7.0 // indirect
modernc.org/libc v1.69.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
mvdan.cc/gofumpt v0.9.2 // indirect
+112 -115
View File
@@ -73,18 +73,18 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/MirrexOne/unqueryvet v1.4.0 h1:6KAkqqW2KUnkl9Z0VuTphC3IXRPoFqEkJEtyxxHj5eQ=
github.com/MirrexOne/unqueryvet v1.4.0/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg=
github.com/MirrexOne/unqueryvet v1.5.3 h1:LpT3rsH+IY3cQddWF9bg4C7jsbASdGnrOSofY8IPEiw=
github.com/MirrexOne/unqueryvet v1.5.3/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
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/air-verse/air v1.64.2 h1:H49xKRa4r3uFUycmCCA+JectVuZLNMPGdypLFpoHVcY=
github.com/air-verse/air v1.64.2/go.mod h1:SOwv08NTvKextanAY819AK0FK6XzQO5/tC6s1R+rk8o=
github.com/air-verse/air v1.64.5 h1:+gs/NgTzYYe+gGPyfHy3XxpJReQWC1pIsiKIg0LgNt4=
github.com/air-verse/air v1.64.5/go.mod h1:OaJZSfZqf7wyjS2oP/CcEVyIt0JmZuPh5x1gdtklmmY=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.21.1 h1:FaSDrp6N+3pphkNKU6HPCiYLgm8dbe5UXIXcoBhZSWA=
github.com/alecthomas/chroma/v2 v2.21.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
@@ -96,14 +96,16 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=
github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
github.com/alexkohler/prealloc v1.0.1 h1:A9P1haqowqUxWvU9nk6tQ7YktXIHf+LQM9wPRhuteEE=
github.com/alexkohler/prealloc v1.0.1/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig=
github.com/alexkohler/prealloc v1.0.2 h1:MPo8cIkGkZytq7WNH9UHv3DIX1mPz1RatPXnZb0zHWQ=
github.com/alexkohler/prealloc v1.0.2/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig=
github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=
github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=
github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M=
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
@@ -154,8 +156,8 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
github.com/bombsimon/wsl/v5 v5.3.0 h1:nZWREJFL6U3vgW/B1lfDOigl+tEF6qgs6dGGbFeR0UM=
github.com/bombsimon/wsl/v5 v5.3.0/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I=
github.com/bombsimon/wsl/v5 v5.6.0 h1:4z+/sBqC5vUmSp1O0mS+czxwH9+LKXtCWtHH9rZGQL8=
github.com/bombsimon/wsl/v5 v5.6.0/go.mod h1:Uqt2EfrMj2NV8UGoN1f1Y3m0NpUVCsUdrNCdet+8LvU=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
@@ -182,8 +184,8 @@ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4p
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
@@ -242,8 +244,8 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -273,8 +275,8 @@ github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghostiam/protogetter v0.3.18 h1:yEpghRGtP9PjKvVXtEzGpYfQj1Wl/ZehAfU6fr62Lfo=
github.com/ghostiam/protogetter v0.3.18/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=
github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0=
github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=
github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog=
github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@@ -296,33 +298,33 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc=
github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -355,8 +357,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
@@ -365,8 +367,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godoc-lint/godoc-lint v0.11.1 h1:z9as8Qjiy6miRIa3VRymTa+Gt2RLnGICVikcvlUVOaA=
github.com/godoc-lint/godoc-lint v0.11.1/go.mod h1:BAqayheFSuZrEAqCRxgw9MyvsM+S/hZwJbU1s/ejRj8=
github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM=
github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -391,8 +393,8 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
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-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20260129164528-943061e2742c h1:ysO2h2Odnl1AJM1I2Lm/fa6JvO0pECMSt2CwBaa+ITo=
github.com/golang/geo v0.0.0-20260129164528-943061e2742c/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
github.com/golang/geo v0.0.0-20260302211937-87f5a40ea07a h1:7f/cr+n7Jtd7mzCLrx47sdVIGabpSSxCHrf6fJczDp0=
github.com/golang/geo v0.0.0-20260302211937-87f5a40ea07a/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -432,12 +434,12 @@ github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarog
github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
github.com/golangci/golangci-lint/v2 v2.8.0 h1:wJnr3hJWY3eVzOUcfwbDc2qbi2RDEpvLmQeNFaPSNYA=
github.com/golangci/golangci-lint/v2 v2.8.0/go.mod h1:xl+HafQ9xoP8rzw0z5AwnO5kynxtb80e8u02Ej/47RI=
github.com/golangci/golines v0.14.0 h1:xt9d3RKBjhasA3qpoXs99J2xN2t6eBlpLHt0TrgyyXc=
github.com/golangci/golines v0.14.0/go.mod h1:gf555vPG2Ia7mmy2mzmhVQbVjuK8Orw0maR1G4vVAAQ=
github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c=
github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=
github.com/golangci/golangci-lint/v2 v2.10.1 h1:flhw5Px6ojbLyEFzXvJn5B2HEdkkRlkhE1SnmCbQBiE=
github.com/golangci/golangci-lint/v2 v2.10.1/go.mod h1:dBsrOk6zj0vDhlTv+IiJGqkDokR24IVTS7W3EVfPTQY=
github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0=
github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10=
github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg=
github.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=
github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=
github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=
github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
@@ -471,8 +473,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -543,8 +545,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
github.com/jupiterrider/ffi v0.5.1 h1:l7ANXU+Ex33LilVa283HNaf/sTzCrrht7D05k6T6nlc=
github.com/jupiterrider/ffi v0.5.1/go.mod h1:x7xdNKo8h0AmLuXfswDUBxUsd2OqUP4ekC8sCnsmbvo=
github.com/jupiterrider/ffi v0.6.0 h1:UX378KcZvH5c8qgLi9KL/bL82SZTHdRspZ+jj7bvBng=
github.com/jupiterrider/ffi v0.6.0/go.mod h1:PqZ5Go6X9by8CIXgfprxfMPYmn8oT5m2O7AA56s64bY=
github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0=
github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
@@ -558,8 +560,8 @@ github.com/kovidgoyal/go-parallel v1.1.1 h1:1OzpNjtrUkBPq3UaqrnvOoB2F9RttSt811ui
github.com/kovidgoyal/go-parallel v1.1.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
github.com/kovidgoyal/go-shm v1.0.0 h1:HJEel9D1F9YhULvClEHJLawoRSj/1u/EDV7MJbBPgQo=
github.com/kovidgoyal/go-shm v1.0.0/go.mod h1:Yzb80Xf9L3kaoB2RGok9hHwMIt7Oif61kT6t3+VnZds=
github.com/kovidgoyal/imaging v1.8.19 h1:zWJdQqF2tfSKjvoB7XpLRhVGbYsze++M0iaqZ4ZkhNk=
github.com/kovidgoyal/imaging v1.8.19/go.mod h1:I0q8RdoEuyc4G8GFOF9CaluTUHQSf68d6TmsqpvfRI8=
github.com/kovidgoyal/imaging v1.8.20 h1:74GZ7C2rIm3rqmGEjK1GvvPOOnJ0SS5iDOa6Flfo0b0=
github.com/kovidgoyal/imaging v1.8.20/go.mod h1:d3phGYkTChGYkY4y++IjpHgUGhWGELDc2NEQAqxwZZg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -622,12 +624,12 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgechev/revive v1.13.0 h1:yFbEVliCVKRXY8UgwEO7EOYNopvjb1BFbmYqm9hZjBM=
github.com/mgechev/revive v1.13.0/go.mod h1:efJfeBVCX2JUumNQ7dtOLDja+QKj9mYGgEZA7rt5u+0=
github.com/mgechev/revive v1.14.0 h1:CC2Ulb3kV7JFYt+izwORoS3VT/+Plb8BvslI/l1yZsc=
github.com/mgechev/revive v1.14.0/go.mod h1:MvnujelCZBZCaoDv5B3foPo6WWgULSSFxvfxp7GsPfo=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -659,8 +661,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
github.com/nunnatsa/ginkgolinter v0.21.2 h1:khzWfm2/Br8ZemX8QM1pl72LwM+rMeW6VUbQ4rzh0Po=
github.com/nunnatsa/ginkgolinter v0.21.2/go.mod h1:GItSI5fw7mCGLPmkvGYrr1kEetZe7B593jcyOpyabsY=
github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8=
github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
@@ -671,10 +673,10 @@ github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8=
github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
@@ -698,8 +700,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
@@ -759,8 +759,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
github.com/securego/gosec/v2 v2.22.11 h1:tW+weM/hCM/GX3iaCV91d5I6hqaRT2TPsFM1+USPXwg=
github.com/securego/gosec/v2 v2.22.11/go.mod h1:KE4MW/eH0GLWztkbt4/7XpyH0zJBBnu7sYB4l6Wn7Mw=
github.com/securego/gosec/v2 v2.23.0 h1:h4TtF64qFzvnkqvsHC/knT7YC5fqyOCItlVR8+ptEBo=
github.com/securego/gosec/v2 v2.23.0/go.mod h1:qRHEgXLFuYUDkI2T7W7NJAmOkxVhkR0x9xyHOIcMNZ0=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
@@ -769,8 +769,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=
@@ -802,7 +802,6 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
@@ -853,6 +852,8 @@ github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pH
github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
@@ -878,8 +879,8 @@ go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=
go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=
go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=
go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=
go.augendre.info/arangolint v0.3.1 h1:n2E6p8f+zfXSFLa2e2WqFPp4bfvcuRdd50y6cT65pSo=
go.augendre.info/arangolint v0.3.1/go.mod h1:6ZKzEzIZuBQwoSvlKT+qpUfIbBfFCE5gbAoTg0/117g=
go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50=
go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA=
go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE=
go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
@@ -890,8 +891,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
@@ -909,8 +908,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -921,16 +920,16 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 h1:HDjDiATsGqvuqvkDvgJjD1IgPrVekcSXVVE21JwvzGE=
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk=
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -956,8 +955,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1000,16 +999,16 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1071,7 +1070,6 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1080,8 +1078,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -1098,8 +1096,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1155,8 +1153,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
@@ -1262,7 +1260,6 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1272,22 +1269,22 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/ccgo/v4 v4.31.0 h1:/bsaxqdgX3gy/0DboxcvWrc3NpzH+6wpFfI/ZaA/hrg=
modernc.org/ccgo/v4 v4.31.0/go.mod h1:jKe8kPBjIN/VdGTVqARTQ8N1gAziBmiISY8j5HoKwjg=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.7 h1:H+gYQw2PyidyxwxQsGTwQw6+6H+xUk+plvOKW7+d3TI=
modernc.org/libc v1.67.7/go.mod h1:UjCSJFl2sYbJbReVQeVpq/MgzlbmDM4cRHIYFelnaDk=
modernc.org/libc v1.69.0 h1:YQJ5QMSReTgQ3QFmI0dudfjXIjCcYTUxcH8/9P9f0D8=
modernc.org/libc v1.69.0/go.mod h1:YfLLduUEbodNV2xLU5JOnRHBTAHVHsVW3bVYGw0ZCV4=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -1296,8 +1293,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+1 -1
View File
@@ -6,10 +6,10 @@ import (
"embed"
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net"
"net/http"
"text/template"
"time"
_ "net/http/pprof"
+3
View File
@@ -4,6 +4,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"maps"
"net/http"
"net/url"
"slices"
@@ -44,6 +45,8 @@ func (u *userInfoUnmarshaller) UnmarshalJSON(data []byte) error {
if u.groupsClaim != "" {
if v, ok := raw[u.groupsClaim]; ok {
switch val := v.(type) {
case map[string]interface{}:
u.userInfo.Groups = slices.Collect(maps.Keys(val))
case []interface{}:
groups := make([]string, len(val))
for i, g := range val {
+8 -8
View File
@@ -78,7 +78,7 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
// Get all "file" parameter values (supports repeated params)
files := r.URL.Query()["file"]
if len(files) == 0 {
return http.StatusBadRequest, fmt.Errorf("no files specified")
files = []string{"/"}
}
// Get the actual source name from the share's source mapping
@@ -246,8 +246,8 @@ func publicPutHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
if !d.share.AllowModify {
return http.StatusForbidden, fmt.Errorf("create is not allowed for this share")
}
source, err := d.share.GetSourceName()
if err != nil {
sourceName := d.share.GetSourceName()
if sourceName == "" {
return http.StatusNotFound, fmt.Errorf("source not available")
}
@@ -264,7 +264,7 @@ func publicPutHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
}
resolvedPath := utils.JoinPathAsUnix(d.share.Path, cleanPath)
err = files.WriteFile(source, resolvedPath, r.Body)
err = files.WriteFile(sourceName, resolvedPath, r.Body)
// hide the error
if err != nil {
logger.Errorf("public put handler: error updating resource with error %v", err)
@@ -344,8 +344,8 @@ func publicPatchHandler(w http.ResponseWriter, r *http.Request, d *requestContex
}
// Get the source from the share
source, err := d.share.GetSourceName()
if err != nil {
sourceName := d.share.GetSourceName()
if sourceName == "" {
return http.StatusNotFound, fmt.Errorf("source not available")
}
@@ -371,9 +371,9 @@ func publicPatchHandler(w http.ResponseWriter, r *http.Request, d *requestContex
// Note: Share paths are absolute, so we don't strip user scope here
// resourcePatchHandler will skip adding scope for shares
for i := range req.Items {
req.Items[i].FromSource = source
req.Items[i].FromSource = sourceName
req.Items[i].FromPath = utils.JoinPathAsUnix(d.share.Path, req.Items[i].FromPath)
req.Items[i].ToSource = source
req.Items[i].ToSource = sourceName
req.Items[i].ToPath = utils.JoinPathAsUnix(d.share.Path, req.Items[i].ToPath)
}
d.Data = req
+15 -35
View File
@@ -14,7 +14,6 @@ import (
"path/filepath"
"strings"
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/fileutils"
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
"github.com/gtsteffaniak/filebrowser/backend/indexing"
@@ -143,16 +142,6 @@ func addFile(source string, path string, d *requestContext, tarWriter *tar.Write
}
}
// Verify file exists
_, err := files.FileInfoFaster(utils.FileOptions{
Path: path,
Source: source,
Expand: false,
FollowSymlinks: true,
}, store.Access, d.user)
if err != nil {
return err
}
realPath, _, _ := idx.GetRealPath(path)
info, err := os.Stat(realPath)
if err != nil {
@@ -284,7 +273,7 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
return http.StatusForbidden, fmt.Errorf("user is not allowed to download")
}
if len(fileList) == 0 {
if len(fileList) == 0 && d.share == nil {
return http.StatusBadRequest, fmt.Errorf("no files specified")
}
@@ -298,6 +287,7 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
var documentId string
var logContext *OnlyOfficeLogContext
// modify all filepaths for user scope
if d.share == nil {
userscope, err = d.user.GetScopeForSourceName(source)
if err != nil {
@@ -319,8 +309,11 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
}
return http.StatusForbidden, err
}
firstFilePath = utils.JoinPathAsUnix(userscope, firstFilePath)
for i, filePath := range fileList {
fileList[i] = utils.JoinPathAsUnix(userscope, filePath)
}
}
firstFilePath = fileList[0]
// For shares, the path is already correctly resolved by publicRawHandler
idx := indexing.GetIndex(source)
if idx == nil {
@@ -505,7 +498,7 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
_, err = io.Copy(w, reader)
os.Remove(archiveData) // Remove the file after streaming
if err != nil {
logger.Errorf("Failed to copy archive data to response: %v", err)
logger.Debugf("client likely aborted: %v, failed to copy archive data to response: %v", r.Context().Err(), err)
return http.StatusInternalServerError, err
}
@@ -519,30 +512,17 @@ func computeArchiveSize(source string, fileList []string, d *requestContext) (in
return 0, fmt.Errorf("source %s is not available", source)
}
var userScope string
var err error
if d.share == nil {
userScope, err = d.user.GetScopeForSourceName(source)
if err != nil {
return 0, fmt.Errorf("source %s is not available for user %s", source, d.user.Username)
}
}
for _, path := range fileList {
var fullPath string
if d.share == nil {
fullPath = utils.JoinPathAsUnix(userScope, path)
// Check access control for each file in the archive
// Silently skip if access is denied (as if the file doesn't exist)
if store.Access != nil && !store.Access.Permitted(idx.Path, fullPath, d.user.Username) {
if store.Access != nil && !store.Access.Permitted(idx.Path, path, d.user.Username) {
continue // Skip this file and continue with the next one
}
} else {
fullPath = path
}
// For shares, the path is already correctly resolved by publicRawHandler
realPath, isDir, err := idx.GetRealPath(fullPath)
realPath, isDir, err := idx.GetRealPath(path)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -568,11 +548,11 @@ func createZip(d *requestContext, source string, tmpDirPath string, filenames ..
zipWriter := zip.NewWriter(file)
for _, fname := range filenames {
err := addFile(source, fname, d, nil, zipWriter, false)
for _, filepath := range filenames {
err := addFile(source, filepath, d, nil, zipWriter, false)
if err != nil {
// Access control failures return nil, so any error here is a real error
logger.Errorf("Failed to add %s to ZIP: %v", fname, err)
logger.Errorf("Failed to add %s to ZIP: %v", filepath, err)
return err
}
}
@@ -595,10 +575,10 @@ func createTarGz(d *requestContext, source string, tmpDirPath string, filenames
gzWriter := gzip.NewWriter(file)
tarWriter := tar.NewWriter(gzWriter)
for _, fname := range filenames {
err := addFile(source, fname, d, tarWriter, nil, false)
for _, filepath := range filenames {
err := addFile(source, filepath, d, tarWriter, nil, false)
if err != nil {
logger.Errorf("Failed to add %s to TAR.GZ: %v", fname, err)
logger.Errorf("Failed to add %s to TAR.GZ: %v", filepath, err)
return err
}
}
+5 -5
View File
@@ -252,12 +252,12 @@ func resourceBulkDeleteHandler(w http.ResponseWriter, r *http.Request, d *reques
}
if d.share != nil {
source, err := d.share.GetSourceName()
if err != nil {
sourceName := d.share.GetSourceName()
if sourceName == "" {
return http.StatusNotFound, fmt.Errorf("source not available")
}
// get user scope path from share
userScope, err := d.shareUser.GetScopeForSourceName(source)
userScope, err := d.shareUser.GetScopeForSourceName(sourceName)
if err != nil {
response.Failed = append(response.Failed, BulkDeleteItem{
Source: item.Source,
@@ -272,7 +272,7 @@ func resourceBulkDeleteHandler(w http.ResponseWriter, r *http.Request, d *reques
fileInfo, err := files.FileInfoFaster(utils.FileOptions{
FollowSymlinks: true,
Path: indexPath,
Source: source,
Source: sourceName,
ShowHidden: true,
}, store.Access, filePermUser)
if err != nil {
@@ -280,7 +280,7 @@ func resourceBulkDeleteHandler(w http.ResponseWriter, r *http.Request, d *reques
}
// Delete the file/directory
err = files.DeleteFiles(source, fileInfo.RealPath, fileInfo.Type == "directory")
err = files.DeleteFiles(sourceName, fileInfo.RealPath, fileInfo.Type == "directory")
if err != nil {
logger.Errorf("resource bulk delete handler: error deleting file/directory: %v", err)
response.Failed = append(response.Failed, BulkDeleteItem{
+41 -14
View File
@@ -33,14 +33,10 @@ type ShareResponse struct {
}
// convertToFrontendShareResponse converts shares to response format with usernames
func convertToFrontendShareResponse(r *http.Request, shares []*share.Link) ([]*ShareResponse, error) {
func convertToFrontendShareResponse(r *http.Request, shares []*share.Link, user *users.User) ([]*ShareResponse, error) {
responses := make([]*ShareResponse, 0, len(shares))
for _, s := range shares {
user, err := store.Users.Get(s.UserID)
username := ""
if err == nil {
username = user.Username
}
username := user.Username
// Get source info to convert path to name for frontend
sourceInfo, ok := config.Server.SourceMap[s.Source]
@@ -65,6 +61,9 @@ func convertToFrontendShareResponse(r *http.Request, shares []*share.Link) ([]*S
s.CommonShare.HasPassword = s.HasPassword()
s.DownloadURL = getShareURL(r, s.Hash, true, s.Token)
s.ShareURL = getShareURL(r, s.Hash, false, s.Token)
if s.UserCanEdit(user) {
s.CommonShare.SourceURL = s.SourceURL(user)
}
// Create response with source name (overrides the embedded Link's source field)
responses = append(responses, &ShareResponse{
Link: s,
@@ -97,7 +96,7 @@ func shareListHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
return http.StatusInternalServerError, err
}
shares = utils.NonNilSlice(shares)
sharesWithUsernames, err := convertToFrontendShareResponse(r, shares)
sharesWithUsernames, err := convertToFrontendShareResponse(r, shares, d.user)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -137,7 +136,7 @@ func shareGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("error getting share info from server")
}
sharesWithUsernames, err := convertToFrontendShareResponse(r, s)
sharesWithUsernames, err := convertToFrontendShareResponse(r, s, d.user)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -162,7 +161,16 @@ func shareDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContex
return http.StatusBadRequest, nil
}
err := store.Share.Delete(hash)
// only allow users to delete their own shares
thisShare, err := store.Share.GetByHash(hash)
if err != nil {
return http.StatusBadRequest, fmt.Errorf("share not found")
}
if thisShare.UserID != d.user.ID && !d.user.Permissions.Admin {
return http.StatusForbidden, fmt.Errorf("you are not allowed to delete this share")
}
err = store.Share.Delete(hash)
if err != nil {
return errToStatus(err), err
}
@@ -196,8 +204,16 @@ func sharePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext
return http.StatusBadRequest, fmt.Errorf("hash and path are required")
}
// only allow users to update their own shares
thisShare, err := store.Share.GetByHash(body.Hash)
if err != nil {
return http.StatusBadRequest, fmt.Errorf("share not found")
}
if thisShare.UserID != d.user.ID && !d.user.Permissions.Admin {
return http.StatusForbidden, fmt.Errorf("you are not allowed to update this share")
}
// Update the share path
err := store.Share.UpdateSharePath(body.Hash, body.Path)
err = store.Share.UpdateSharePath(body.Hash, body.Path)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -209,7 +225,7 @@ func sharePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext
}
// Convert to response format
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{updatedShare})
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{updatedShare}, d.user)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -389,7 +405,7 @@ func sharePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
if err = store.Share.Save(s); err != nil {
return http.StatusInternalServerError, err
}
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{s})
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{s}, d.user)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -610,8 +626,7 @@ func shareInfoHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
return http.StatusNotFound, fmt.Errorf("share hash not found")
}
commonShare := shareLink.CommonShare
commonShare.DownloadURL = getShareURL(r, hash, true, shareLink.Token)
commonShare.ShareURL = getShareURL(r, hash, false, shareLink.Token)
commonShare.ShareURL = getShareURL(r, hash, false, "")
_, _, err = getShareImagePartsHelper(shareLink, true)
if err == nil {
commonShare.BannerUrl = fmt.Sprintf("%spublic/api/share/image?banner=true&hash=%s", config.Server.BaseURL, hash)
@@ -622,6 +637,7 @@ func shareInfoHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
}
commonShare.Source = ""
commonShare.Path = ""
commonShare.DownloadURL = ""
commonShare.SidebarLinks = []users.SidebarLink{}
for _, link := range shareLink.SidebarLinks {
if link.Category == "download" && shareLink.ShareType == "upload" {
@@ -630,6 +646,17 @@ func shareInfoHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
commonShare.SidebarLinks = append(commonShare.SidebarLinks, link)
}
}
commonShare.SourceURL = shareLink.SourceURL(d.user)
commonShare.CanEdit = shareLink.UserCanEdit(d.user)
if commonShare.SourceURL != "" {
link := users.SidebarLink{
Name: "sourceLocation",
Category: "custom",
Target: commonShare.SourceURL,
}
commonShare.SidebarLinks = append(commonShare.SidebarLinks, link)
}
return renderJSON(w, r, commonShare)
}
+17 -9
View File
@@ -3,12 +3,12 @@ package http
import (
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
@@ -25,10 +25,18 @@ type TemplateRenderer struct {
// Render renders a template document with headers and data
func (t *TemplateRenderer) Render(w http.ResponseWriter, name string, data interface{}) error {
// If in dev mode, reload templates on every render.
templates := t.templates
// If in dev mode, reload templates on every render by creating a fresh instance
if t.devMode {
var err error
t.templates, err = t.templates.ParseFS(assetFs, "public/index.html")
templates = template.New("").Funcs(template.FuncMap{
"marshal": func(v interface{}) (string, error) {
var a []byte
a, err = json.Marshal(v)
return string(a), err
},
})
templates, err = templates.ParseFS(assetFs, "public/index.html")
if err != nil {
return fmt.Errorf("error reloading template: %w", err)
}
@@ -39,7 +47,7 @@ func (t *TemplateRenderer) Render(w http.ResponseWriter, name string, data inter
w.Header().Set("X-Accel-Expires", "0")
w.Header().Set("Transfer-Encoding", "identity")
// Execute the template with the provided data
return t.templates.ExecuteTemplate(w, name, data)
return templates.ExecuteTemplate(w, name, data)
}
func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestContext, file, contentType string) (int, error) {
@@ -180,8 +188,8 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
data["htmlVars"] = map[string]interface{}{
"title": title,
"customCSS": config.Frontend.Styling.CustomCSSRaw,
"userSelectedTheme": userSelectedTheme,
"customCSS": template.CSS(config.Frontend.Styling.CustomCSSRaw),
"userSelectedTheme": template.CSS(userSelectedTheme),
"lightBackground": config.Frontend.Styling.LightBackground,
"darkBackground": config.Frontend.Styling.DarkBackground,
"staticURL": staticURL,
@@ -192,7 +200,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
"winIcon": staticURL + "/mstile-256x256.png",
"appIcon": staticURL + "/apple-touch-icon.png",
"description": description,
"loadingSpinnersCSS": loadingSpinnersCSS,
"loadingSpinnersCSS": template.CSS(loadingSpinnersCSS),
"banner": banner,
"image": ogImage,
"url": fullURL,
@@ -238,8 +246,8 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
return http.StatusInternalServerError, fmt.Errorf("error marshaling globalVars: %w", err)
}
// Replace with JSON strings for direct template usage
data["globalVars"] = string(globalVarsJSON)
// Mark as JS for html/template to avoid escaping
data["globalVars"] = template.JS(globalVarsJSON)
// Render the template with global variables
if err := templateRenderer.Render(w, file, data); err != nil {
+12
View File
@@ -233,7 +233,19 @@ func generateSingleIcon(previewService *preview.Service, sourceData []byte, icon
// All icons are generated as PNG for maximum compatibility (Apple devices, PWA, older browsers)
func GeneratePWAIcons() error {
logger.Info("Generating PWA icons...")
// Set PWA icons directory in cache
settings.Env.PWAIconsDir = filepath.Join(settings.Config.Server.CacheDir, "icons")
// Initialize with default paths (all icons will be generated)
settings.Env.PWAIcon192 = "icons/pwa-icon-192.png"
settings.Env.PWAIcon256 = "icons/pwa-icon-256.png"
settings.Env.PWAIcon512 = "icons/pwa-icon-512.png"
// Create icons directory with configured permissions
// Note: Parent cache directory should already exist from testCacheDirSpeed()
if err := os.MkdirAll(settings.Env.PWAIconsDir, fileutils.PermDir); err != nil {
logger.Warningf("Failed to create PWA icons directory %s: %v", settings.Env.PWAIconsDir, err)
}
// Load and prepare source data as PNG
sourceData, err := loadFaviconSource()
if err != nil {
+2 -1
View File
@@ -196,7 +196,7 @@ func TestProcessDirectoryItem(t *testing.T) {
},
}
for _, tt := range tests {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
itemInfo, size, shouldCount := idx.processDirectoryItem(
tt.file, tt.combinedPath,
@@ -212,6 +212,7 @@ func TestProcessDirectoryItem(t *testing.T) {
if itemInfo == nil {
t.Fatalf("expected non-nil itemInfo")
return
}
if size != tt.expectSize {
+24
View File
@@ -3614,6 +3614,9 @@ const docTemplate = `{
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -3725,6 +3728,9 @@ const docTemplate = `{
"description": "Override embedded field to show source name",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
@@ -5062,6 +5068,9 @@ const docTemplate = `{
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -5158,6 +5167,9 @@ const docTemplate = `{
"description": "backend source is path to maintain between name changes",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
@@ -5200,6 +5212,9 @@ const docTemplate = `{
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -5305,6 +5320,9 @@ const docTemplate = `{
"description": "backend source is path to maintain between name changes",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
@@ -5350,6 +5368,9 @@ const docTemplate = `{
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -5458,6 +5479,9 @@ const docTemplate = `{
"description": "backend source is path to maintain between name changes",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
+24
View File
@@ -3603,6 +3603,9 @@
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -3714,6 +3717,9 @@
"description": "Override embedded field to show source name",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
@@ -5051,6 +5057,9 @@
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -5147,6 +5156,9 @@
"description": "backend source is path to maintain between name changes",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
@@ -5189,6 +5201,9 @@
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -5294,6 +5309,9 @@
"description": "backend source is path to maintain between name changes",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
@@ -5339,6 +5357,9 @@
"bannerUrl": {
"type": "string"
},
"canEdit": {
"type": "boolean"
},
"description": {
"type": "string"
},
@@ -5447,6 +5468,9 @@
"description": "backend source is path to maintain between name changes",
"type": "string"
},
"sourceURL": {
"type": "string"
},
"themeColor": {
"type": "string"
},
+16
View File
@@ -122,6 +122,8 @@ definitions:
type: string
bannerUrl:
type: string
canEdit:
type: boolean
description:
type: string
disableAnonymous:
@@ -199,6 +201,8 @@ definitions:
source:
description: Override embedded field to show source name
type: string
sourceURL:
type: string
themeColor:
type: string
title:
@@ -1199,6 +1203,8 @@ definitions:
type: string
bannerUrl:
type: string
canEdit:
type: boolean
description:
type: string
disableAnonymous:
@@ -1266,6 +1272,8 @@ definitions:
source:
description: backend source is path to maintain between name changes
type: string
sourceURL:
type: string
themeColor:
type: string
title:
@@ -1296,6 +1304,8 @@ definitions:
type: string
bannerUrl:
type: string
canEdit:
type: boolean
description:
type: string
disableAnonymous:
@@ -1369,6 +1379,8 @@ definitions:
source:
description: backend source is path to maintain between name changes
type: string
sourceURL:
type: string
themeColor:
type: string
title:
@@ -1401,6 +1413,8 @@ definitions:
type: string
bannerUrl:
type: string
canEdit:
type: boolean
description:
type: string
disableAnonymous:
@@ -1476,6 +1490,8 @@ definitions:
source:
description: backend source is path to maintain between name changes
type: string
sourceURL:
type: string
themeColor:
type: string
title:
+282 -1325
View File
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -22,12 +22,11 @@
"dependencies": {
"@onlyoffice/document-editor-vue": "^1.4.0",
"ace-builds": "^1.24.2",
"axios": "^1.7.9",
"axios": "^1.13.5",
"clipboard": "^2.0.4",
"css-vars-ponyfill": "^2.4.3",
"dompurify": "^3.2.4",
"epubjs": "^0.3.93",
"file-loader": "^6.2.0",
"glob": "^9.3.5",
"highlight.js": "^11.11.1",
"mammoth": "^1.9.1",
@@ -41,6 +40,9 @@
"vue-lazyload": "^3.0.0",
"vue-router": "^4.3.0"
},
"overrides": {
"minimatch": "^10.2.1"
},
"devDependencies": {
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
"@intlify/unplugin-vue-i18n": "^4.0.0",
+1 -1
View File
@@ -5,7 +5,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>{{ .htmlVars.title }}</title>
<title>"{{ .htmlVars.title }}"</title>
<link rel="icon" type="image/png" sizes="256x256" href="{{ .htmlVars.favicon }}">
-12
View File
@@ -1,12 +0,0 @@
import { globalVars } from "@/utils/constants";
const ssl = window.location.protocol === "https:";
const protocol = ssl ? "wss:" : "ws:";
export default function command(url, command, onmessage, onclose) {
url = `${protocol}//${window.location.host}${globalVars.baseURL}api/command${url}`;
let conn = new window.WebSocket(url);
conn.onopen = () => conn.send(command);
conn.onmessage = onmessage;
conn.onclose = onclose;
}
+1
View File
@@ -1,6 +1,7 @@
import { fetchURL, fetchJSON } from '@/api/utils'
import { getApiPath, getPublicApiPath } from '@/utils/url.js'
import { notify } from '@/notify' // Import notify for error handling
export async function getAllUsers() {
try {
const apiPath = getApiPath('api/users')
+4 -1
View File
@@ -148,7 +148,7 @@
<action v-if="showGoToRaw" icon="open_in_new" :label="$t('general.openFile')" @action="goToRaw()" />
<action v-if="shouldShowParentFolder()" icon="folder" :label="$t('buttons.openParentFolder')" @action="openParentFolder" />
<action v-if="isPreview && permissions.modify" icon="mode_edit" :label="$t('general.rename')" @action="showRenameHoverForPreview" />
<action v-if="hasDownload && !req.isDir" icon="visibility" :label="$t('buttons.watchFile')" @action="watchFile()" />
<action v-if="showWatch" icon="visibility" :label="$t('buttons.watchFile')" @action="watchFile()" />
<action v-if="hasDownload" icon="file_download" :label="$t('general.download')" @action="startDownload" />
<action v-if="showEdit" icon="edit" :label="$t('general.edit')" @action="edit()" />
<action v-if="showSave" icon="save" :label="$t('general.save')" @action="save()" />
@@ -187,6 +187,9 @@ export default {
},
},
computed: {
showWatch() {
return this.hasDownload && !this.req.isDir && !this.isShare;
},
showGoToItem() {
return this.isDuplicateFinder && this.selectedCount == 1;
},
+23 -2
View File
@@ -57,6 +57,7 @@ import { notify } from "@/notify";
import { getTypeInfo } from "@/utils/mimetype";
import ListingItem from "@/components/files/ListingItem.vue";
import { eventBus } from "@/store/eventBus";
import url from "@/utils/url";
export default {
name: "delete",
@@ -230,7 +231,7 @@ export default {
const failedCount = response.failed ? response.failed.length : 0;
if (failedCount === 0) {
// All succeeded - close prompt and reload
// All succeeded - close prompt and handle navigation
buttons.success("delete");
notify.showSuccessToast(this.$t('prompts.deleted'));
@@ -245,7 +246,27 @@ export default {
if (!this.items) {
mutations.resetSelected();
}
mutations.setReload(true);
// Smart navigation: if in preview mode with single item deletion, navigate intelligently
if (getters.isPreviewView() && this.itemsToDelete.length === 1) {
// Try next, then previous, then parent directory
if (state.navigation.nextItem) {
// Use goToItem with undefined to use router.replace (no history entry)
url.goToItem(state.navigation.nextItem.source, state.navigation.nextItem.path, undefined);
} else if (state.navigation.previousItem) {
// Use goToItem with undefined to use router.replace (no history entry)
url.goToItem(state.navigation.previousItem.source, state.navigation.previousItem.path, undefined);
} else {
// Navigate to parent directory of deleted item
const deletedItemPath = this.itemsToDelete[0].path;
const parentPath = url.removeLastDir(deletedItemPath);
const deletedItemSource = this.itemsToDelete[0].source;
url.goToItem(deletedItemSource, parentPath, {});
}
} else {
// For listing view or multiple deletions, just reload
mutations.setReload(true);
}
} else if (succeededCount > 0) {
// Partial success - keep prompt open to show errors
buttons.done("delete");
+17 -11
View File
@@ -412,13 +412,14 @@ export default {
handleItemClick(item, _index, event) {
event.preventDefault();
event.stopPropagation();
// Display mode: single click navigates directly to the file
if (this.isDisplayMode) {
this.navigateToItem(item);
return;
}
// Create a synthetic event-like object for compatibility with existing methods
// Browse mode: single click selects only, emit selected, do not navigate
const syntheticEvent = {
currentTarget: {
dataset: {
@@ -428,18 +429,17 @@ export default {
preventDefault: () => {},
stopPropagation: () => {},
};
if (state.user.singleClick) {
this.next(syntheticEvent);
} else {
this.select(syntheticEvent);
}
this.select(syntheticEvent);
},
handleItemDblClick(item, _index, event) {
event.preventDefault();
event.stopPropagation();
// Create a synthetic event for double-click
// Double click: navigate (into folder or open file)
if (this.isDisplayMode) {
this.navigateToItem(item);
return;
}
const syntheticEvent = {
currentTarget: {
dataset: {
@@ -457,9 +457,12 @@ export default {
if (this.selected === path) {
this.selected = null;
this.selectedSource = null;
this.selectedType = null;
this.$emit("update:selected", {
path: this.current,
source: this.source
source: this.source,
type: 'directory',
isValid: !this.requireFileSelection,
});
return;
}
@@ -468,9 +471,12 @@ export default {
this.selected = path;
let clickedItem = this.items.find(item => item.path === path);
this.selectedSource = clickedItem ? clickedItem.source : this.source;
this.selectedType = clickedItem ? clickedItem.type : null;
this.$emit("update:selected", {
path: this.selected,
source: this.selectedSource
source: this.selectedSource,
type: this.selectedType,
isValid: this.selectedType === 'directory' ? !this.requireFileSelection : true,
});
},
createDir: async function () {
+9 -1
View File
@@ -34,8 +34,13 @@
</div>
<div v-else-if="!isPickingFile">
<div aria-label="share-path" class="searchContext button"> {{ $t('general.path', { suffix: ':' }) }} {{
<div v-if="isFiles" aria-label="share-path" class="searchContext button"> {{ $t('general.path', { suffix: ':' }) }} {{
displayPath }}</div>
<div v-else>
<a :href="link.sourceURL" class="button button--block">
{{ $t('buttons.goToSource') }}
</a>
</div>
<p> {{ $t('share.notice') }} </p>
<div v-if="listing">
@@ -474,6 +479,9 @@ export default {
isListing() {
return getters.isListing(); // Access getter directly from the store
},
isFiles() {
return getters.isFiles(); // Access getter directly from the store
},
url() {
if (state.isSearchActive) {
const file = /** @type {FilebrowserFile} */ (this.selected[0]);
@@ -20,7 +20,10 @@
class="link-drag-handle">
<i class="material-icons">drag_indicator</i>
</div>
<div class="link-icon">
<div v-if="link.category === 'divider'" class="link-icon">
<i class="material-icons">horizontal_rule</i>
</div>
<div v-else class="link-icon">
<i :class="getIconClass(link.icon)">{{ link.icon }}</i>
</div>
<div class="link-details">
@@ -66,6 +69,7 @@
<option value="share">{{ $t('general.share') }}</option>
<option v-if="context === 'user'" value="tool">{{ $t('general.tool') }}</option>
<option value="custom">{{ $t('sidebar.customLink') }}</option>
<option value="divider">{{ $t('general.divider') }}</option>
<option v-if="context === 'share'" value="shareInfo">{{ $t('share.shareInfo') }}</option>
<option v-if="context === 'share'" value="download">{{ $t('general.download') }}</option>
</select>
@@ -148,6 +152,13 @@
<p>{{ $t('share.downloadDescription') }}</p>
</div>
<!-- Divider - special visual separator -->
<div v-if="newLink.category === 'divider'" class="form-group">
<p>{{ $t('sidebar.linkName') }}</p>
<input aria-label="Link Name" v-model="newLink.name" type="text" class="input"
:placeholder="$t('sidebar.dividerNamePlaceholder')" />
</div>
<!-- Custom Link Input -->
<div v-if="newLink.category === 'custom'" class="form-group">
<p>{{ $t('sidebar.linkName') }}</p>
@@ -159,8 +170,8 @@
:placeholder="$t('sidebar.linkUrlPlaceholder')" />
</div>
<!-- Icon Selection - Available for ALL link types -->
<div v-if="newLink.category" class="form-group">
<!-- Icon Selection - Available for ALL link types except divider -->
<div v-if="newLink.category && newLink.category !== 'divider'" class="form-group">
<p>{{ $t('sidebar.linkIcon') }}</p>
<div class="icon-input-group">
<input v-model="newLink.icon" type="text" class="input icon-input"
@@ -285,8 +296,10 @@ export default {
isNewLinkValid() {
if (!this.newLink.category) return false;
// Special link types for shares don't need additional validation
if (this.newLink.category === "shareInfo" || this.newLink.category === "download") {
// Special link types that don't need additional validation
if (this.newLink.category === "shareInfo" ||
this.newLink.category === "download" ||
this.newLink.category === "divider") {
return true;
}
@@ -392,10 +405,16 @@ export default {
share: this.$t('general.share'),
shareInfo: this.$t('share.shareInfo'),
download: this.$t('general.download'),
divider: this.$t('general.divider'),
};
return labels[category] || category;
},
getLinkDisplayName(link) {
// For dividers, show the name or a default
if (link.category === 'divider') {
return link.name || this.$t('general.divider');
}
// Check if the name looks like a translation key that needs translating
if (link.category === 'shareInfo' && link.name === 'share.shareInfo') {
return this.$t('share.shareInfo');
@@ -546,6 +565,11 @@ export default {
} else if (this.newLink.category === "download") {
// Download is a special action link
linkData.target = "#";
} else if (this.newLink.category === "divider") {
// Divider is a visual separator with no action
linkData.target = "#";
linkData.name = this.newLink.name || ""; // Keep the custom name if provided
linkData.icon = ""; // Dividers don't need an icon
} else if (this.newLink.category === "custom") {
linkData.target = this.processCustomUrl(this.newLink.target);
} else if (this.newLink.category === "source") {
+3 -4
View File
@@ -31,7 +31,7 @@
</div>
<div v-if="!disableQuickToggles" class="card-wrapper" @mouseleave="hideTooltip">
<div class="quick-toggles" :class="{ 'extra-padding': !hasCreateOptions }">
<div class="quick-toggles" :class="{ 'extra-padding': hasCreateOptions }">
<div class="clickable" :class="{ active: user?.singleClick }" @click="toggleClick"
@mouseenter="showTooltip($event, $t('index.toggleClick'))" @mouseleave="hideTooltip" v-if="!isInvalidShare">
<i class="material-icons">ads_click</i>
@@ -97,7 +97,7 @@ export default {
},
hasCreateOptions() {
if (getters.isShare()) {
return state.shareInfo?.allowCreate
return state.shareInfo?.allowCreate == true
}
return state.user?.permissions?.create || state.user?.permissions?.share || state.user?.permissions?.admin;
},
@@ -128,7 +128,7 @@ export default {
shouldShowLogin() {
if (getters.isShare()) {
// Don't show login until shareInfo is fully loaded
if (state.shareInfo && state.shareInfo.disableLoginOption == undefined) {
if (state.shareInfo && state.shareInfo?.disableLoginOption) {
return false;
}
}
@@ -146,7 +146,6 @@ export default {
},
},
methods: {
openContextMenu() {
mutations.resetSelected();
mutations.showHover({
+64 -26
View File
@@ -7,17 +7,33 @@
</div>
<!-- Links section header -->
<div class="sidebar-links-header" :class="{ 'no-edit-options': isShare, 'with-top-spacing': isShare && !disableShareCard }">
<i v-if="!isShare" @click="goHome()" class="material-icons action">home</i>
<div class="sidebar-links-header"
:class="{ 'with-top-spacing': isShare && !disableShareCard }">
<i :class="{ 'disabled': !isLoggedIn }" @click="goHome()" class="material-icons action">home</i>
<span>{{ $t("general.links") }}</span>
<i v-if="!isShare" @mouseenter="showTooltip($event, $t('sidebar.customizeLinks'))" @mouseleave="hideTooltip"
<i v-if="isShare" aria-label="Edit Share" @mouseenter="showTooltip($event, editShareText())" @mouseleave="hideTooltip"
:class="{ 'disabled': !canEdit }"
@click="showEditShareHover" class="material-icons action">edit</i>
<i v-else @mouseenter="showTooltip($event, $t('sidebar.customizeLinks'))" @mouseleave="hideTooltip"
@click="openSidebarLinksPrompt" class="material-icons action">edit</i>
</div>
<transition-group name="expand" tag="div" class="inner-card">
<template v-for="(link, index) in sidebarLinksToDisplay" :key="`link-${index}-${link.category}`">
<!-- Divider: renders as hr line or text label -->
<div v-if="link.category === 'divider'" class="sidebar-divider-container">
<hr v-if="!link.name || link.name.toLowerCase() === 'divider'" class="sidebar-divider" />
<span v-else class="sidebar-divider-text">{{ link.name }}</span>
</div>
<a v-else-if="link.category == 'custom' && link.name == 'sourceLocation'" :href="link.target"
:aria-label="link.name" class="action button sidebar-link-button" @click.prevent="handleLinkClick(link)">
<div class="link-container">
<i class="material-icons link-icon">open_in_new</i>
<span>{{ $t('buttons.goToSource') }}</span>
</div>
</a>
<!-- Source-type links: styled exactly like original sources -->
<a v-if="link.category === 'source'" :href="getLinkHref(link)"
<a v-else-if="link.category === 'source'" :href="getLinkHref(link)"
class="action button source-button sidebar-link-button" :class="{
active: isLinkActive(link),
disabled: !isLinkAccessible(link)
@@ -46,12 +62,11 @@
</i>
</div>
<div v-if="hasUsageInfo(link)" class="usage-info">
<ProgressBar
<ProgressBar
:key="`progress-${link.sourceName}-${sourceInfo[link.sourceName]?.used || 0}-${sourceInfo[link.sourceName]?.total || 0}`"
:val="getProgressBarValue(sourceInfo[link.sourceName] || {})"
:max="(sourceInfo[link.sourceName] || {}).total || 1"
:status="getProgressBarStatus(sourceInfo[link.sourceName] || {})"
unit="bytes">
:val="getProgressBarValue(sourceInfo[link.sourceName] || {})"
:max="(sourceInfo[link.sourceName] || {}).total || 1"
:status="getProgressBarStatus(sourceInfo[link.sourceName] || {})" unit="bytes">
</ProgressBar>
</div>
</a>
@@ -59,22 +74,12 @@
<!-- Non-source links: tool and custom links with simple icon style -->
<a v-else :aria-label="link.name" :href="getLinkHref(link)" class="action button sidebar-link-button"
:class="{ active: isLinkActive(link) }" @click.prevent="handleLinkClick(link)">
<div class="link-container">
<div class="link-container">
<i :class="getIconClass(link.icon) + ' link-icon'">{{ link.icon }}</i>
<span>{{ link.name }}</span>
</div>
</a>
</template>
<!-- Edit Share link - only shown when viewing a share and user has share permissions -->
<a v-if="isShare && canEditShare" :aria-label="$t('general.edit', { suffix: ' ' + $t('general.share') })" href="#"
class="action button sidebar-link-button"
@click.prevent="showEditShareHover">
<div class="link-container">
<i class="material-icons link-icon">edit</i>
<span>{{ $t("general.edit", { suffix: " " + $t("general.share") }) }}</span>
</div>
</a>
</transition-group>
</div>
</transition>
@@ -97,11 +102,17 @@ export default {
ShareInfo,
},
computed: {
canEdit() {
return state.shareInfo?.canEdit || false;
},
isLoggedIn() {
return state.user?.username !== 'anonymous';
},
isShare: () => getters.isShare(),
hasLinks() {
return this.sidebarLinksToDisplay?.length > 0;
},
user: () => (state.user || {username: 'anonymous'}),
user: () => (state.user || { username: 'anonymous' }),
sourceInfo() {
// Access state.sources.info to create reactive dependency
return state.sources.info;
@@ -117,10 +128,6 @@ export default {
// Check if user has customized their links
return this.user?.sidebarLinks && this.user.sidebarLinks.length > 0;
},
canEditShare() {
// Check if user is logged in and has share permissions
return state.user && state.user.permissions && state.user.permissions.share;
},
// Share info card props
disableShareCard() {
return state.shareInfo?.disableShareCard;
@@ -151,6 +158,9 @@ export default {
},
methods: {
getIconClass,
editShareText() {
return this.$t("general.edit") + " " + this.$t("general.share");
},
hasUsageInfo(link) {
// Check if usage info should be displayed for this link
// Returns true when link is accessible and has usage > 0
@@ -185,6 +195,9 @@ export default {
return baseURL + target;
},
goHome() {
if (!this.isLoggedIn) {
return;
}
this.$router.push('/');
},
getDefaultLinks() {
@@ -320,6 +333,9 @@ export default {
mutations.closeHovers();
},
openSidebarLinksPrompt() {
if (!this.isLoggedIn) {
return;
}
mutations.showHover({
name: "SidebarLinks",
});
@@ -407,12 +423,12 @@ export default {
</script>
<style scoped>
.no-edit-options {
justify-content: center !important;
}
.share-info-section {
margin-top: 0 !important;
margin-bottom: 1em;
padding-bottom: 0.5em;
border-bottom: 1px solid var(--borderColor);
@@ -627,6 +643,7 @@ a.sidebar-link-button {
.realtime-pulse.warning>.center {
fill: rgb(255, 157, 0);
}
.vue-simple-progress {
margin-top: 0 !important;
}
@@ -640,4 +657,25 @@ a.sidebar-link-button {
.edit-share-button .link-icon {
color: var(--primaryColor);
}
.sidebar-divider-container {
width: 100%;
margin-top: 0.5em;
}
.sidebar-divider {
width: 50%;
border: none;
border-top: 0.1em solid var(--divider);
}
.sidebar-divider-text {
display: flex;
font-size: .85em;
color: var(--textSecondary);
letter-spacing: .05em;
justify-content: center;
align-content: center;
align-items: center;
}
</style>
+1
View File
@@ -8,6 +8,7 @@
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
transition: .1s ease all;
text-align: center;
}
.button--block {
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "الانتقال إلى العنصر",
"watchFile": "ملف المشاهدة",
"showMore": "إظهار الخيارات المتقدمة",
"showLess": "إخفاء الخيارات المتقدمة"
"showLess": "إخفاء الخيارات المتقدمة",
"goToSource": "الانتقال إلى موقع المصدر"
},
"download": {
"downloadFile": "Download File",
@@ -371,7 +372,8 @@
"pickIconDescription": "بحث وتحديد أيقونة من مكتبة الأيقونات المادية",
"shareLinksUpdatedSuccess": "تم تحديث روابط الشريط الجانبي المشاركة بنجاح!",
"shareHome": "مشاركة الصفحة الرئيسية",
"linkIconPlaceholder": "تحديد اسم رمز المادة اختياريًا"
"linkIconPlaceholder": "تحديد اسم رمز المادة اختياريًا",
"dividerNamePlaceholder": "نص المقسم (اتركه فارغًا لإظهار سطر)"
},
"success": {
"linkCopied": "تم نسخ الملف"
@@ -627,7 +629,8 @@
"lastUpdate": "آخر تحديث{suffix}",
"latency": "الكمون{suffix}",
"all": "الكل{suffix}",
"expiration": "انتهاء الصلاحية{suffix}"
"expiration": "انتهاء الصلاحية{suffix}",
"divider": "مقسم{suffix}"
},
"access": {
"rules": "قواعد الوصول",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Přejít na položku",
"watchFile": "Sledovat soubor",
"showMore": "Zobrazit rozšířené možnosti",
"showLess": "Skrytí pokročilých možností"
"showLess": "Skrytí pokročilých možností",
"goToSource": "Přejít na zdrojové umístění"
},
"download": {
"downloadFile": "Stáhnout soubor",
@@ -508,7 +509,8 @@
"pickIconDescription": "Vyhledejte a vyberte ikonu z knihovny Material Icons.",
"shareLinksUpdatedSuccess": "Odkazy na sdílení postranního panelu byly úspěšně aktualizovány!",
"shareHome": "Sdílet Domů",
"linkIconPlaceholder": "Volitelně zadejte název ikony materiálu"
"linkIconPlaceholder": "Volitelně zadejte název ikony materiálu",
"dividerNamePlaceholder": "Text oddělovače (pro zobrazení řádku nechte prázdný)"
},
"success": {
"linkCopied": "Odkaz zkopírován!"
@@ -707,7 +709,8 @@
"lastUpdate": "Poslední aktualizace{suffix}",
"latency": "Zpoždění{suffix}",
"all": "Všechny{suffix}",
"expiration": "Expirace{suffix}"
"expiration": "Expirace{suffix}",
"divider": "Rozdělovač{suffix}"
},
"access": {
"rules": "Pravidla přístupu",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Weiter zu Artikel",
"watchFile": "Datei beobachten",
"showMore": "Erweiterte Optionen anzeigen",
"showLess": "Erweiterte Optionen ausblenden"
"showLess": "Erweiterte Optionen ausblenden",
"goToSource": "Zum Quellort gehen"
},
"download": {
"downloadFile": "Datei herunterladen",
@@ -371,7 +372,8 @@
"pickIconDescription": "Suchen und wählen Sie ein Symbol aus der Bibliothek der Materialsymbole",
"shareLinksUpdatedSuccess": "Links in der Seitenleiste erfolgreich aktualisiert!",
"shareHome": "Startseite teilen",
"linkIconPlaceholder": "Optional können Sie einen Materialsymbolnamen angeben"
"linkIconPlaceholder": "Optional können Sie einen Materialsymbolnamen angeben",
"dividerNamePlaceholder": "Teilertext (leer lassen, um eine Zeile anzuzeigen)"
},
"success": {
"linkCopied": "Link wurde kopiert!"
@@ -627,7 +629,8 @@
"lastUpdate": "Letzte Aktualisierung{suffix}",
"latency": "Latenzzeit{suffix}",
"all": "Alle{suffix}",
"expiration": "Verfallsdatum{suffix}"
"expiration": "Verfallsdatum{suffix}",
"divider": "Teiler{suffix}"
},
"access": {
"rules": "Zugangsregeln",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Πηγαίνετε στο στοιχείο",
"watchFile": "Παρακολουθήστε το αρχείο",
"showMore": "Εμφάνιση προηγμένων επιλογών",
"showLess": "Απόκρυψη προηγμένων επιλογών"
"showLess": "Απόκρυψη προηγμένων επιλογών",
"goToSource": "Πηγαίνετε στην τοποθεσία της πηγής"
},
"download": {
"downloadFile": "Λήψη αρχείου",
@@ -374,7 +375,8 @@
"pickIconDescription": "Αναζήτηση και επιλογή ενός εικονιδίου από τη βιβλιοθήκη Material Icons",
"shareLinksUpdatedSuccess": "Οι σύνδεσμοι της πλαϊνής μπάρας κοινής χρήσης ενημερώθηκαν με επιτυχία!",
"shareHome": "Μοιραστείτε το Home",
"linkIconPlaceholder": "Προαιρετικά καθορίστε ένα όνομα εικονιδίου υλικού"
"linkIconPlaceholder": "Προαιρετικά καθορίστε ένα όνομα εικονιδίου υλικού",
"dividerNamePlaceholder": "Κείμενο διαχωριστικού (αφήστε το κενό για να εμφανιστεί μια γραμμή)"
},
"success": {
"linkCopied": "Ο σύνδεσμος αντιγράφηκε!"
@@ -627,7 +629,8 @@
"lastUpdate": "Τελευταία ενημέρωση{suffix}",
"latency": "Καθυστέρηση{suffix}",
"all": "Όλα{suffix}",
"expiration": "Λήξη{suffix}"
"expiration": "Λήξη{suffix}",
"divider": "Διαχωριστικό{suffix}"
},
"access": {
"rules": "Κανόνες πρόσβασης",
+4 -1
View File
@@ -8,6 +8,7 @@
"incompleteResultsDetails": "Showing max results ({max}). Try narrowing your search to get more results under {max}."
},
"general": {
"divider": "Divider{suffix}",
"expiration": "Expiration{suffix}",
"all": "All{suffix}",
"name": "Name{suffix}",
@@ -150,7 +151,8 @@
"saveAndExit": "Save and Exit",
"keepEditing": "Keep Editing",
"closeAnyway": "Close Anyways",
"goToItem": "Go to Item"
"goToItem": "Go to Item",
"goToSource": "Go to Source Location"
},
"download": {
"downloadFile": "Download File",
@@ -592,6 +594,7 @@
"adminOptions": "Admin user options"
},
"sidebar": {
"dividerNamePlaceholder": "Divider text (leave blank to show a line)",
"siteSettings": "Site Settings",
"updateIsAvailable": "An update is available!",
"fileActions": "File actions",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Ir al elemento",
"watchFile": "Ver archivo",
"showMore": "Mostrar opciones avanzadas",
"showLess": "Ocultar opciones avanzadas"
"showLess": "Ocultar opciones avanzadas",
"goToSource": "Ir a la ubicación de origen"
},
"download": {
"downloadFile": "Descargar archivo",
@@ -371,7 +372,8 @@
"pickIconDescription": "Aquí puedes explorar los iconos más populates de la biblioteca \"Material Icons\"",
"shareLinksUpdatedSuccess": "¡Se actualizaron los accesos directos del recurso compartido!",
"shareHome": "Recurso compartido",
"linkIconPlaceholder": "Opcionalmente, puedes elegir un Material Icon que más te guste"
"linkIconPlaceholder": "Opcionalmente, puedes elegir un Material Icon que más te guste",
"dividerNamePlaceholder": "Texto divisor (déjelo en blanco para mostrar una línea)"
},
"success": {
"linkCopied": "¡Enlace copiado!"
@@ -627,7 +629,8 @@
"lastUpdate": "Última actualización{suffix}",
"latency": "Latencia{suffix}",
"all": "Todos{suffix}",
"expiration": "Caducidad{suffix}"
"expiration": "Caducidad{suffix}",
"divider": "Divisor{suffix}"
},
"access": {
"rules": "Reglas de acceso",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Aller à l'article",
"watchFile": "Regarder le dossier",
"showMore": "Afficher les options avancées",
"showLess": "Masquer les options avancées"
"showLess": "Masquer les options avancées",
"goToSource": "Aller à l'emplacement de la source"
},
"download": {
"downloadFile": "Télécharger le fichier",
@@ -371,7 +372,8 @@
"pickIconDescription": "Rechercher et sélectionner une icône dans la bibliothèque des icônes matérielles",
"shareLinksUpdatedSuccess": "Les liens de la barre latérale de partage ont été mis à jour avec succès !",
"shareHome": "Partager l'accueil",
"linkIconPlaceholder": "Spécifier éventuellement un nom d'icône de matériau"
"linkIconPlaceholder": "Spécifier éventuellement un nom d'icône de matériau",
"dividerNamePlaceholder": "Texte du séparateur (laisser en blanc pour montrer une ligne)"
},
"success": {
"linkCopied": "Lien copié !"
@@ -627,7 +629,8 @@
"lastUpdate": "Dernière mise à jour{suffix}",
"latency": "Temps de latence{suffix}",
"all": "Tous{suffix}",
"expiration": "Expiration{suffix}"
"expiration": "Expiration{suffix}",
"divider": "Diviseur{suffix}"
},
"access": {
"rules": "Règles d'accès",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "עבור לפריט",
"watchFile": "צפה בקובץ",
"showMore": "הצג אפשרויות מתקדמות",
"showLess": "הסתר אפשרויות מתקדמות"
"showLess": "הסתר אפשרויות מתקדמות",
"goToSource": "עבור למיקום המקור"
},
"download": {
"downloadFile": "הורד קובץ",
@@ -371,7 +372,8 @@
"pickIconDescription": "חפש ובחר סמל מספריית Material Icons.",
"shareLinksUpdatedSuccess": "קישורי הסרגל הצדדי שותפו בהצלחה!",
"shareHome": "שתף בית",
"linkIconPlaceholder": "אפשר לציין שם סמל חומר"
"linkIconPlaceholder": "אפשר לציין שם סמל חומר",
"dividerNamePlaceholder": "טקסט מפריד (השאר ריק כדי להציג קו)"
},
"success": {
"linkCopied": "הקישור הועתק!"
@@ -627,7 +629,8 @@
"lastUpdate": "עדכון אחרון{suffix}",
"latency": "חביון{suffix}",
"all": "כל{suffix}",
"expiration": "תוקף{suffix}"
"expiration": "תוקף{suffix}",
"divider": "מפריד{suffix}"
},
"access": {
"rules": "כללי גישה",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Tovább a tételhez",
"watchFile": "Nézze meg a fájlt",
"showMore": "Speciális beállítások megjelenítése",
"showLess": "Speciális beállítások elrejtése"
"showLess": "Speciális beállítások elrejtése",
"goToSource": "Menj a forráshelyre"
},
"download": {
"downloadFile": "Fájl letöltése",
@@ -371,7 +372,8 @@
"pickIconDescription": "Keressen és válasszon ki egy ikont az Anyagi ikonok könyvtárból.",
"shareLinksUpdatedSuccess": "Megosztás oldalsáv linkek sikeresen frissítve!",
"shareHome": "Megosztás",
"linkIconPlaceholder": "Opcionálisan megadhatja az anyag ikon nevét"
"linkIconPlaceholder": "Opcionálisan megadhatja az anyag ikon nevét",
"dividerNamePlaceholder": "Választószöveg (üresen hagyva egy sor megjelenítéséhez)"
},
"success": {
"linkCopied": "Link másolva!"
@@ -627,7 +629,8 @@
"lastUpdate": "Utolsó frissítés{suffix}",
"latency": "Késleltetés{suffix}",
"all": "Minden{suffix}",
"expiration": "Lejárat{suffix}"
"expiration": "Lejárat{suffix}",
"divider": "Választó{suffix}"
},
"access": {
"rules": "Hozzáférési szabályok",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Vai all'articolo",
"watchFile": "Guarda il file",
"showMore": "Mostra opzioni avanzate",
"showLess": "Nascondere le opzioni avanzate"
"showLess": "Nascondere le opzioni avanzate",
"goToSource": "Vai alla posizione della fonte"
},
"download": {
"downloadFile": "Scarica file",
@@ -371,7 +372,8 @@
"pickIconDescription": "Cercare e selezionare un'icona dalla libreria Material Icons.",
"shareLinksUpdatedSuccess": "I link della barra laterale di condivisione sono stati aggiornati con successo!",
"shareHome": "Condividi Casa",
"linkIconPlaceholder": "Specificare facoltativamente il nome dell'icona del materiale"
"linkIconPlaceholder": "Specificare facoltativamente il nome dell'icona del materiale",
"dividerNamePlaceholder": "Testo del divisore (lasciare vuoto per mostrare una riga)"
},
"success": {
"linkCopied": "Link copiato!"
@@ -627,7 +629,8 @@
"lastUpdate": "Ultimo aggiornamento{suffix}",
"latency": "Latenza{suffix}",
"all": "Tutti{suffix}",
"expiration": "Scadenza{suffix}"
"expiration": "Scadenza{suffix}",
"divider": "Divisore{suffix}"
},
"access": {
"rules": "Regole di accesso",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "アイテムへ",
"watchFile": "ウォッチファイル",
"showMore": "詳細オプションを表示",
"showLess": "詳細オプションを隠す"
"showLess": "詳細オプションを隠す",
"goToSource": "ソース・ロケーションへ"
},
"download": {
"downloadFile": "ファイルをダウンロード",
@@ -371,7 +372,8 @@
"pickIconDescription": "マテリアル・アイコン・ライブラリからアイコンを検索して選択します。",
"shareLinksUpdatedSuccess": "サイドバーの共有リンクが正常に更新されました!",
"shareHome": "シェア・ホーム",
"linkIconPlaceholder": "オプションで素材アイコン名を指定"
"linkIconPlaceholder": "オプションで素材アイコン名を指定",
"dividerNamePlaceholder": "分割テキスト(行を表示するには空白にする)"
},
"success": {
"linkCopied": "リンクがコピーされました!"
@@ -627,7 +629,8 @@
"lastUpdate": "最終更新{suffix}",
"latency": "レイテンシー{suffix}",
"all": "すべて{suffix}",
"expiration": "有効期限{suffix}"
"expiration": "有効期限{suffix}",
"divider": "ディバイダー{suffix}"
},
"access": {
"rules": "アクセスルール",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "항목으로 이동",
"watchFile": "파일 보기",
"showMore": "고급 옵션 표시",
"showLess": "고급 옵션 숨기기"
"showLess": "고급 옵션 숨기기",
"goToSource": "소스 위치로 이동"
},
"download": {
"downloadFile": "파일 다운로드",
@@ -371,7 +372,8 @@
"pickIconDescription": "머티리얼 아이콘 라이브러리에서 아이콘을 검색하고 선택합니다.",
"shareLinksUpdatedSuccess": "공유 사이드바 링크가 성공적으로 업데이트되었습니다!",
"shareHome": "홈 공유",
"linkIconPlaceholder": "선택적으로 머티리얼 아이콘 이름을 지정합니다."
"linkIconPlaceholder": "선택적으로 머티리얼 아이콘 이름을 지정합니다.",
"dividerNamePlaceholder": "디바이더 텍스트(줄을 표시하려면 비워둡니다)"
},
"success": {
"linkCopied": "링크가 복사되었습니다!"
@@ -627,7 +629,8 @@
"lastUpdate": "최근 업데이트{suffix}",
"latency": "지연 시간{suffix}",
"all": "모두{suffix}",
"expiration": "만료{suffix}"
"expiration": "만료{suffix}",
"divider": "디바이더{suffix}"
},
"access": {
"rules": "액세스 규칙",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Ga naar item",
"watchFile": "Bestand bekijken",
"showMore": "Geavanceerde opties weergeven",
"showLess": "Geavanceerde opties verbergen"
"showLess": "Geavanceerde opties verbergen",
"goToSource": "Ga naar de bronlocatie"
},
"download": {
"downloadFile": "Bestand downloaden",
@@ -371,7 +372,8 @@
"pickIconDescription": "Zoek en selecteer een pictogram uit de bibliotheek met materiaalpictogrammen",
"shareLinksUpdatedSuccess": "Zijbalk links delen succesvol bijgewerkt!",
"shareHome": "Thuis delen",
"linkIconPlaceholder": "Geef optioneel een materiaalpictogramnaam op"
"linkIconPlaceholder": "Geef optioneel een materiaalpictogramnaam op",
"dividerNamePlaceholder": "Scheidingstekst (leeg laten om een lijn weer te geven)"
},
"success": {
"linkCopied": "Link gekopieerd!"
@@ -707,7 +709,8 @@
"lastUpdate": "Laatste update{suffix}",
"latency": "Latency{suffix}",
"all": "Alle{suffix}",
"expiration": "Vervaldatum{suffix}"
"expiration": "Vervaldatum{suffix}",
"divider": "Verdeler{suffix}"
},
"access": {
"rules": "Toegangsregels",
+7 -4
View File
@@ -116,7 +116,8 @@
"lines": "Regels{suffix}",
"lastUpdate": "Laatste update{suffix}",
"latency": "Vertraging{suffix}",
"expiration": "Vervaldatum{suffix}"
"expiration": "Vervaldatum{suffix}",
"divider": "Verdeler{suffix}"
},
"buttons": {
"generateNewOtp": "Herstellen en nieuwe twee-factorcode genereren",
@@ -150,7 +151,8 @@
"saveAndExit": "Opslaan en afsluiten",
"keepEditing": "Blijven bewerken",
"closeAnyway": "Sluiten",
"goToItem": "Ga naar item"
"goToItem": "Ga naar item",
"goToSource": "Ga naar de bronlocatie"
},
"download": {
"downloadFile": "Bestand downloaden",
@@ -627,7 +629,8 @@
"linksUpdateFailed": "Bijwerken van koppelingen in zijbalk is mislukt",
"sourceNotAccessible": "Deze bron is niet langer toegankelijk voor jou",
"noLinksYet": "Er zijn nog geen links geconfigureerd. Voeg hieronder uw eerste link toe.",
"shareHome": "Thuis delen"
"shareHome": "Thuis delen",
"dividerNamePlaceholder": "Scheidingstekst (leeg laten om een lijn weer te geven)"
},
"success": {
"linkCopied": "Koppeling gekopieerd!"
@@ -737,7 +740,7 @@
"green": "groen",
"blue": "blauw",
"yellow": "geel",
"violet": "violet",
"violet": "paars",
"orange": "oranje"
},
"editor": {
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Przejdź do pozycji",
"watchFile": "Oglądaj plik",
"showMore": "Pokaż opcje zaawansowane",
"showLess": "Ukryj opcje zaawansowane"
"showLess": "Ukryj opcje zaawansowane",
"goToSource": "Przejdź do lokalizacji źródłowej"
},
"download": {
"downloadFile": "Pobierz plik",
@@ -371,7 +372,8 @@
"pickIconDescription": "Wyszukaj i wybierz ikonę z biblioteki Material Icons",
"shareLinksUpdatedSuccess": "Linki do paska bocznego zostały pomyślnie zaktualizowane!",
"shareHome": "Share Home",
"linkIconPlaceholder": "Opcjonalnie można określić nazwę ikony materiału"
"linkIconPlaceholder": "Opcjonalnie można określić nazwę ikony materiału",
"dividerNamePlaceholder": "Tekst podziału (pozostaw pusty, aby wyświetlić linię)"
},
"success": {
"linkCopied": "Link Skopiowany!"
@@ -627,7 +629,8 @@
"lastUpdate": "Ostatnia aktualizacja{suffix}",
"latency": "Opóźnienie{suffix}",
"all": "Wszystkie{suffix}",
"expiration": "Wygaśnięcie{suffix}"
"expiration": "Wygaśnięcie{suffix}",
"divider": "Dzielnik{suffix}"
},
"access": {
"rules": "Zasady dostępu",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Ir para o item",
"watchFile": "Assista ao arquivo",
"showMore": "Mostrar opções avançadas",
"showLess": "Ocultar opções avançadas"
"showLess": "Ocultar opções avançadas",
"goToSource": "Ir para o local de origem"
},
"download": {
"downloadFile": "Baixar arquivo",
@@ -371,7 +372,8 @@
"pickIconDescription": "Pesquise e selecione um ícone na biblioteca de ícones de materiais",
"shareLinksUpdatedSuccess": "Os links da barra lateral de compartilhamento foram atualizados com sucesso!",
"shareHome": "Compartilhar casa",
"linkIconPlaceholder": "Opcionalmente, especifique um nome de ícone de material"
"linkIconPlaceholder": "Opcionalmente, especifique um nome de ícone de material",
"dividerNamePlaceholder": "Texto do divisor (deixe em branco para mostrar uma linha)"
},
"success": {
"linkCopied": "Link copiado!"
@@ -627,7 +629,8 @@
"lastUpdate": "Última atualização{suffix}",
"latency": "Latência{suffix}",
"all": "Todos{suffix}",
"expiration": "Vencimento{suffix}"
"expiration": "Vencimento{suffix}",
"divider": "Divisor{suffix}"
},
"access": {
"rules": "Regras de acesso",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Ir para o item",
"watchFile": "Ver ficheiro",
"showMore": "Mostrar opções avançadas",
"showLess": "Ocultar opções avançadas"
"showLess": "Ocultar opções avançadas",
"goToSource": "Ir para o local de origem"
},
"download": {
"downloadFile": "Descarregar ficheiro",
@@ -371,7 +372,8 @@
"pickIconDescription": "Procurar e selecionar um ícone na biblioteca de ícones de materiais",
"shareLinksUpdatedSuccess": "As ligações da barra lateral de partilha foram actualizadas com êxito!",
"shareHome": "Partilhar casa",
"linkIconPlaceholder": "Especificar opcionalmente um nome de ícone de material"
"linkIconPlaceholder": "Especificar opcionalmente um nome de ícone de material",
"dividerNamePlaceholder": "Texto do separador (deixar em branco para mostrar uma linha)"
},
"success": {
"linkCopied": "Link copiado!"
@@ -707,7 +709,8 @@
"lastUpdate": "Última atualização{suffix}",
"latency": "Latência{suffix}",
"all": "Todos{suffix}",
"expiration": "Prazo de validade{suffix}"
"expiration": "Prazo de validade{suffix}",
"divider": "Divisor{suffix}"
},
"access": {
"rules": "Regras de acesso",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Du-te la articol",
"watchFile": "Fișier de vizionare",
"showMore": "Afișare opțiuni avansate",
"showLess": "Ascundeți opțiunile avansate"
"showLess": "Ascundeți opțiunile avansate",
"goToSource": "Du-te la locația sursei"
},
"download": {
"downloadFile": "Descarcă fișier",
@@ -371,7 +372,8 @@
"pickIconDescription": "Căutați și selectați o pictogramă din biblioteca Material Icons",
"shareLinksUpdatedSuccess": "Link-urile din bara laterală de partajare au fost actualizate cu succes!",
"shareHome": "Share Home",
"linkIconPlaceholder": "Opțional, specificați un nume de pictogramă de material"
"linkIconPlaceholder": "Opțional, specificați un nume de pictogramă de material",
"dividerNamePlaceholder": "Textul separatorului (lăsați gol pentru a afișa o linie)"
},
"success": {
"linkCopied": "Legătură copiată!"
@@ -627,7 +629,8 @@
"lastUpdate": "Ultima actualizare{suffix}",
"latency": "Latență{suffix}",
"all": "Toate{suffix}",
"expiration": "Expirare{suffix}"
"expiration": "Expirare{suffix}",
"divider": "Divizor{suffix}"
},
"access": {
"rules": "Reguli de acces",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Перейти к пункту",
"watchFile": "Смотреть файл",
"showMore": "Показать дополнительные параметры",
"showLess": "Скрыть дополнительные параметры"
"showLess": "Скрыть дополнительные параметры",
"goToSource": "Перейти к исходному местоположению"
},
"download": {
"downloadFile": "Скачать файл",
@@ -371,7 +372,8 @@
"pickIconDescription": "Найдите и выберите значок в библиотеке Material Icons.",
"shareLinksUpdatedSuccess": "Ссылки на боковой панели успешно обновлены!",
"shareHome": "Поделиться Домой",
"linkIconPlaceholder": "Опционально укажите имя значка материала"
"linkIconPlaceholder": "Опционально укажите имя значка материала",
"dividerNamePlaceholder": "Текст разделителя (оставьте пустым, чтобы показать строку)"
},
"success": {
"linkCopied": "Ссылка скопирована!"
@@ -627,7 +629,8 @@
"lastUpdate": "Последнее обновление{suffix}",
"latency": "Латентность{suffix}",
"all": "Все{suffix}",
"expiration": "Срок действия{suffix}"
"expiration": "Срок действия{suffix}",
"divider": "Разделитель{suffix}"
},
"access": {
"rules": "Правила доступа",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Prejsť na položku",
"watchFile": "Sledovať súbor",
"showMore": "Zobraziť rozšírené možnosti",
"showLess": "Skryť rozšírené možnosti"
"showLess": "Skryť rozšírené možnosti",
"goToSource": "Prejsť na zdrojové miesto"
},
"download": {
"downloadFile": "Stiahnuť súbor",
@@ -371,7 +372,8 @@
"pickIconDescription": "Vyhľadajte a vyberte ikonu z knižnice Material Icons",
"shareLinksUpdatedSuccess": "Zdieľanie bočného panela odkazy úspešne aktualizované!",
"shareHome": "Zdieľať Domov",
"linkIconPlaceholder": "Voliteľne zadajte názov ikony materiálu"
"linkIconPlaceholder": "Voliteľne zadajte názov ikony materiálu",
"dividerNamePlaceholder": "Text oddeľovača (nechajte prázdny, aby sa zobrazil riadok)"
},
"success": {
"linkCopied": "Odkaz skopírovaný!"
@@ -627,7 +629,8 @@
"lastUpdate": "Posledná aktualizácia{suffix}",
"latency": "Latencia{suffix}",
"all": "Všetky{suffix}",
"expiration": "Uplynutie platnosti{suffix}"
"expiration": "Uplynutie platnosti{suffix}",
"divider": "Rozdeľovač{suffix}"
},
"access": {
"rules": "Pravidlá prístupu",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Gå till artikel",
"watchFile": "Titta på filen",
"showMore": "Visa avancerade alternativ",
"showLess": "Dölj avancerade alternativ"
"showLess": "Dölj avancerade alternativ",
"goToSource": "Gå till källplatsen"
},
"download": {
"downloadFile": "Ladda ner fil",
@@ -371,7 +372,8 @@
"pickIconDescription": "Sök och välj en ikon från Material Icons-biblioteket",
"shareLinksUpdatedSuccess": "Dela sidofältets länkar uppdaterades framgångsrikt!",
"shareHome": "Dela hem",
"linkIconPlaceholder": "Ange eventuellt ett namn på materialikonen"
"linkIconPlaceholder": "Ange eventuellt ett namn på materialikonen",
"dividerNamePlaceholder": "Delningstext (lämna tom för att visa en rad)"
},
"success": {
"linkCopied": "Länk kopierad"
@@ -707,7 +709,8 @@
"lastUpdate": "Senaste uppdatering{suffix}",
"latency": "Fördröjning{suffix}",
"all": "Alla{suffix}",
"expiration": "Förfallodag{suffix}"
"expiration": "Förfallodag{suffix}",
"divider": "Uppdelare{suffix}"
},
"access": {
"rules": "Regler för åtkomst",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Öğeye git",
"watchFile": "Dosyayı izle",
"showMore": "Gelişmiş Seçenekleri Göster",
"showLess": "Gelişmiş Seçenekleri Gizle"
"showLess": "Gelişmiş Seçenekleri Gizle",
"goToSource": "Kaynak Konuma Git"
},
"download": {
"downloadFile": "Dosyayı indir",
@@ -371,7 +372,8 @@
"pickIconDescription": "Materyal Simgeleri kitaplığından bir simge arayın ve seçin",
"shareLinksUpdatedSuccess": "Kenar çubuğu bağlantılarını paylaşma başarıyla güncellendi!",
"shareHome": "Ev Paylaşımı",
"linkIconPlaceholder": "İsteğe bağlı olarak bir malzeme simgesi adı belirtin"
"linkIconPlaceholder": "İsteğe bağlı olarak bir malzeme simgesi adı belirtin",
"dividerNamePlaceholder": "Bölücü metin (bir çizgi göstermek için boş bırakın)"
},
"success": {
"linkCopied": "Link kopyalandı!"
@@ -627,7 +629,8 @@
"lastUpdate": "Son Güncelleme{suffix}",
"latency": "Gecikme{suffix}",
"all": "Tümü{suffix}",
"expiration": "Son kullanma tarihi{suffix}"
"expiration": "Son kullanma tarihi{suffix}",
"divider": "Bölücü{suffix}"
},
"access": {
"rules": "Erişim kuralları",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "Перейти до пункту",
"watchFile": "Переглянути файл",
"showMore": "Показати додаткові параметри",
"showLess": "Приховати додаткові параметри"
"showLess": "Приховати додаткові параметри",
"goToSource": "Перейдіть до Розташування джерела"
},
"download": {
"downloadFile": "Завантажити файл",
@@ -371,7 +372,8 @@
"pickIconDescription": "Знайдіть і виберіть піктограму з бібліотеки піктограм матеріалів",
"shareLinksUpdatedSuccess": "Посилання на бічну панель обміну успішно оновлено!",
"shareHome": "Поділитися Додому",
"linkIconPlaceholder": "За бажанням вкажіть назву іконки матеріалу"
"linkIconPlaceholder": "За бажанням вкажіть назву іконки матеріалу",
"dividerNamePlaceholder": "Текст роздільника (залиште порожнім, щоб показати лінію)"
},
"success": {
"linkCopied": "Посилання скопійоване!"
@@ -707,7 +709,8 @@
"lastUpdate": "Останнє оновлення{suffix}",
"latency": "Затримка{suffix}",
"all": "Усе.{suffix}",
"expiration": "Закінчення терміну дії{suffix}"
"expiration": "Закінчення терміну дії{suffix}",
"divider": "Перегородка{suffix}"
},
"access": {
"rules": "Правила доступу",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "转到项目",
"watchFile": "观看文件",
"showMore": "显示高级选项",
"showLess": "隐藏高级选项"
"showLess": "隐藏高级选项",
"goToSource": "转到源位置"
},
"download": {
"downloadFile": "下载文件",
@@ -371,7 +372,8 @@
"pickIconDescription": "从材质图标库中搜索并选择图标",
"shareLinksUpdatedSuccess": "共享侧边栏链接更新成功!",
"shareHome": "共享主页",
"linkIconPlaceholder": "可选择指定材料图标名称"
"linkIconPlaceholder": "可选择指定材料图标名称",
"dividerNamePlaceholder": "分隔线文本(留空以显示一行)"
},
"success": {
"linkCopied": "链接已复制!"
@@ -707,7 +709,8 @@
"lastUpdate": "最新更新{suffix}",
"latency": "延迟{suffix}",
"all": "全部{suffix}",
"expiration": "到期{suffix}"
"expiration": "到期{suffix}",
"divider": "分割器{suffix}"
},
"access": {
"rules": "访问规则",
+6 -3
View File
@@ -31,7 +31,8 @@
"goToItem": "前往項目",
"watchFile": "觀看檔案",
"showMore": "顯示進階選項",
"showLess": "隱藏進階選項"
"showLess": "隱藏進階選項",
"goToSource": "前往來源位置"
},
"download": {
"downloadFile": "下載檔案",
@@ -371,7 +372,8 @@
"pickIconDescription": "從 Material Icons 圖示庫搜尋並選取圖示",
"shareLinksUpdatedSuccess": "分享側邊欄連結更新成功!",
"shareHome": "分享首頁",
"linkIconPlaceholder": "可選擇指定材質圖示名稱"
"linkIconPlaceholder": "可選擇指定材質圖示名稱",
"dividerNamePlaceholder": "分割線文字(留空顯示一行)"
},
"success": {
"linkCopied": "連結已複製!"
@@ -707,7 +709,8 @@
"lastUpdate": "最後更新{suffix}",
"latency": "延遲{suffix}",
"all": "全部{suffix}",
"expiration": "到期日{suffix}"
"expiration": "到期日{suffix}",
"divider": "分割器{suffix}"
},
"access": {
"rules": "存取規則",
+3 -2
View File
@@ -11,10 +11,10 @@ import { mutations } from "@/store";
import { validateLogin } from "@/utils/auth";
import i18n from "@/i18n";
const titles = {
Login: i18n.global.t("general.login"),
Share: i18n.global.t("general.share"),
PublicShare: i18n.global.t("general.share"),
Files: i18n.global.t("general.files"),
Tools: i18n.global.t("general.tool"),
Settings: i18n.global.t("general.settings"),
@@ -180,11 +180,12 @@ router.beforeResolve(async (to, from, next) => {
to.matched.some((record) => record.meta.requiresAuth) ||
to.matched.some((record) => record.meta.optionalAuth)
) {
const isPublicRoute = to.path.startsWith("/public");
if (state?.user?.username) {
// do nothing, user is already set
} else {
try {
await validateLogin();
await validateLogin(isPublicRoute);
} catch (error) {
mutations.setCurrentUser(getters.anonymous());
}
-1
View File
@@ -76,7 +76,6 @@ export interface UserObject {
rules?: any[];
lockPassword?: boolean;
hideDotfiles?: boolean;
commands?: string[];
sorting?: {
by: string;
asc: boolean;
+5 -2
View File
@@ -2,9 +2,12 @@ import { mutations, getters,state } from "@/store";
import { getApiPath, getPublicApiPath } from "@/utils/url.js";
import { globalVars } from "@/utils/constants";
export async function validateLogin() {
export async function validateLogin(isPublicRoute = false) {
// Use direct fetch to avoid automatic logout on 401
let apiPath = getPublicApiPath('users', { id: 'self' });
// Public routes (e.g. /public/share/...) use the public API base path
const apiPath = isPublicRoute
? getPublicApiPath('users', { id: 'self' })
: getApiPath('/api/users', { id: 'self' });
const res = await fetch(apiPath, {
credentials: 'same-origin', // Ensure cookies are sent with the request
headers: {
+13 -1
View File
@@ -1,3 +1,15 @@
/**
* Strips ASS/SSA override blocks from dialogue text (e.g. {\fad(100,100)\blur3}).
* Used only when converting .ass / .ssa to VTT. Other formats keep their styling.
*/
export function stripAssOverrideTags (text) {
if (typeof text !== 'string') return text
return text
.replace(/\{[^}]*\}/g, '')
.replace(/\s+/g, ' ')
.trim()
}
export function convertToVTT (ext, text) {
if (ext == '.vtt') {
return text
@@ -73,7 +85,7 @@ export function convertToVTT (ext, text) {
let parts = line.split(',')
let startTime = formatAssTime(parts[1].trim())
let endTime = formatAssTime(parts[2].trim())
let dialogue = parts.slice(9).join(',').trim()
let dialogue = stripAssOverrideTags(parts.slice(9).join(',').trim())
return `${startTime} --> ${endTime}\n ${dialogue}` // Add leading space before dialogue
})
.join('\n\n')
+2 -3
View File
@@ -356,8 +356,7 @@ export default {
// Display initial data immediately
mutations.replaceRequest(file);
document.title = `${document.title} - ${file.name}`;
document.title = globalVars.name + " - " + this.$t("general.share") + " - " + file.name;
// Second pass: If directory has metadata available, fetch again with metadata IN THE BACKGROUND
if (file.type === "directory" && file.hasMetadata) {
this.loadingProgress = 90;
@@ -476,7 +475,7 @@ export default {
if (state.sources.count > 1) {
mutations.setCurrentSource(data.source);
}
document.title = `${document.title} - ${res.name}`;
document.title = globalVars.name + " - " + this.$t("general.files") + " - " + res.name;
// Display initial data immediately and clear loading spinner
mutations.replaceRequest(data);
+2 -1
View File
@@ -44,6 +44,7 @@ import { filesApi } from "@/api";
import { state, getters, mutations } from "@/store";
import { events, notify } from "@/notify";
import { generateRandomCode } from "@/utils/auth";
import { globalVars } from "@/utils/constants";
export default {
name: "layout",
@@ -180,7 +181,7 @@ export default {
if (maxUploads > 10 || maxUploads < 1) {
mutations.setMaxConcurrentUpload(1);
}
if ( state.user.showFirstLogin) {
if (state.user.showFirstLogin && !globalVars.noAuth) {
mutations.showHover({
name: "generic",
props: {
-21
View File
@@ -206,31 +206,11 @@ export default {
req() {
return state.req;
},
deletedItem() {
return state.deletedItem;
},
disableFileViewer() {
return state.shareInfo?.disableFileViewer;
},
},
watch: {
deletedItem() {
if (!state.deletedItem) {
return;
}
this.isDeleted = true;
this.listing = null; // Invalidate the listing to force a refresh
// Let the navigation component handle next/previous logic
if (state.navigation.nextLink) {
this.$router.replace({ path: state.navigation.nextLink });
} else if (state.navigation.previousLink) {
this.$router.replace({ path: state.navigation.previousLink });
} else {
this.close();
}
mutations.setDeletedItem(false);
},
async req() {
if (!getters.isLoggedIn()) {
return;
@@ -257,7 +237,6 @@ export default {
} else if (state.req.items) {
this.listing = state.req.items;
}
mutations.setDeletedItem(false);
window.addEventListener("keydown", this.keyEvent);
this.subtitlesList = await this.subtitles();
this.updatePreview();
@@ -50,3 +50,27 @@ test("navigate from search item", async({ page, checkForErrors, context }) => {
await expect(page.locator('.topTitle')).toHaveText('for testing.md');
checkForErrors()
});
test("use quick jump", async({ page, checkForErrors, context }) => {
await page.goto("/files/playwright%20%2B%20files/myfolder/testdata/gray-sample.jpg");
await expect(page).toHaveTitle("Graham's Filebrowser - Files - gray-sample.jpg");
// drag next button to the left to open quick jump list
const nextButton = page.locator('button[aria-label="Next"]');
await nextButton.waitFor({ state: "visible" });
const box = await nextButton.boundingBox();
expect(box).toBeTruthy();
const startX = box!.x + box!.width / 2;
const startY = box!.y + box!.height / 2;
await page.mouse.move(startX, startY);
await page.mouse.down();
await page.mouse.move(startX - 200, startY);
await page.mouse.up();
await expect(page.locator('div[aria-label="file-list-prompt"]')).toBeVisible();
await page.locator('div[aria-label="20130612_142406.jpg"]').click();
await expect(page).toHaveTitle("Graham's Filebrowser - Files - 20130612_142406.jpg");
await expect(page.locator('.topTitle')).toHaveText('20130612_142406.jpg');
await expect(page.locator('div[aria-label="file-list-prompt"]')).toBeHidden();
checkForErrors();
})
@@ -31,9 +31,10 @@ test("Create first new file with basic auth", async ({ page, checkForErrors, co
// test public share route (no auth required)
await page.goto("/subpath/public/share/" + shareHash);
await expect(page).toHaveTitle("Graham's Filebrowser - Share - demo-127.0.0.1");
await expect(page.locator('.listing-items .file-items')).toHaveCount(1);
await page.locator('a[aria-label="test.txt"]').dblclick();
checkForErrors(0,1);
checkForErrors(0,1); // expect expect redirect error
});
test("Verify basic auth is required for protected route", async ({ page }) => {
@@ -34,12 +34,12 @@ test("check previously created share has correct sidebar links", async ({ page,
// navigate to the share sidebar
await page.goto("/public/share/" + shareHash);
await expect(page).toHaveTitle("Graham's Filebrowser - Share - text-files");
// sidebar should have three items (ShareInfo, Download, Edit Share)
// sidebar should have 3 items (ShareInfo, Download, Go to source location)
await expect(page.locator('.sidebar-links .inner-card').locator('a')).toHaveCount(3);
// check items exist
await page.locator('a[aria-label="Share QR Code and Info"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="Download"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="Edit Share"]').waitFor({ state: 'visible' });
await page.locator('i[aria-label="Edit Share"]').waitFor({ state: 'visible' });
checkForErrors();
});
@@ -76,12 +76,11 @@ test("edit previously created links and ensure they are updated", async ({ page,
// navigate to the share sidebar
await page.goto("/public/share/" + shareHash);
await expect(page).toHaveTitle("Graham's Filebrowser - Share - text-files");
// sidebar should have four items (ShareInfo, Download, New Custom Link, Edit Share)
// sidebar should have 4 items (ShareInfo, Download, New Custom Link, Go to source location)
await expect(page.locator('.sidebar-links .inner-card').locator('a')).toHaveCount(4);
// check items exist
await page.locator('a[aria-label="Share QR Code and Info"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="Download"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="New Custom Link"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="Edit Share"]').waitFor({ state: 'visible' });
checkForErrors();
});