Compare commits

...
96 changed files with 3896 additions and 1526 deletions

No files matched your search

+32
View File
@@ -2,6 +2,38 @@
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
## v0.7.3-beta
Note: OIDC changes require config update.
**New Features**
- Added code highlights to text editor and enabled text editor for all asci files under 25MB
- Motion previews for videos -- cycles screenshots of vidoes. https://github.com/gtsteffaniak/filebrowser/issues/588
- Optionally reset default admin username/password on startup, to guarentee a username/password on startup if needed. Use by setting `auth.resetAdminOnStart` true https://github.com/gtsteffaniak/filebrowser/issues/625
**Notes**:
- Updated translations everywhere. https://github.com/gtsteffaniak/filebrowser/issues/627
- Office viewer is now full-screen with floating close button. https://github.com/gtsteffaniak/filebrowser/issues/542
- OIDC config additions
- `issuerUrl` required now to get relevant oidc configurations.
- `disableVerifyTLS` optionally, disable verifying HTTPS provider endpoints.
- `logoutRedirectUrl` optionally, redirect the user to this URL on logout.
- other URL config parameters are no longer accepted -- replace with issuerUrl.
- Aadmins allowed to change user login methods in user settings when creating or updating users.
- https://github.com/gtsteffaniak/filebrowser/issues/618
- https://github.com/gtsteffaniak/filebrowser/issues/617
- Hide header when showing only office https://github.com/gtsteffaniak/filebrowser/issues/542
**BugFixes**:
- Editor save shows notification
- Preview settings resetting on startup
- Not all languages show correctly https://github.com/gtsteffaniak/filebrowser/issues/623
- scopes sometimes reset on startup https://github.com/gtsteffaniak/filebrowser/issues/636
- Update save password option
- https://github.com/gtsteffaniak/filebrowser/issues/587
- https://github.com/gtsteffaniak/filebrowser/issues/619
- https://github.com/gtsteffaniak/filebrowser/issues/615
## v0.7.2-beta
The `media` tags introduced in 0.7.0 have been removed -- all docker images have media enabled now.
+7 -7
View File
@@ -100,19 +100,19 @@ See the [Migration Wiki](https://github.com/gtsteffaniak/filebrowser/wiki/Migrat
Application Name | <img width="48" src="https://github.com/user-attachments/assets/59986a2a-f960-4536-aa35-4a9a7c98ad48" > Quantum | <img width="48" src="https://github.com/filebrowser/filebrowser/blob/master/frontend/public/img/logo.svg" > Filebrowser | <img width="48" src="https://github.com/mickael-kerjean/filestash/blob/master/public/assets/logo/app_icon.png?raw=true" > Filestash | <img width="48" src="https://avatars.githubusercontent.com/u/19211038?s=200&v=4" > Nextcloud | <img width="48" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Google_Drive_logo.png/480px-Google_Drive_logo.png" > Google_Drive | <img width="48" src="https://avatars.githubusercontent.com/u/6422152?v=4" > FileRun
--- | --- | --- | --- | --- | --- | --- |
Filesystem support | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Filesystem support | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
Linux | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
Windows | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Mac | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Windows | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
Mac | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
Self hostable | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
Has Stable Release? | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
S3 support | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
S3 support | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
webdav support | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
FTP support | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
Dedicated docs site? | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
Multiple sources at once | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
Docker image size | 200 MB (with ffmpeg) | 31 MB | 240 MB (main image) | 250 MB | ❌ | > 2 GB |
Min. Memory Requirements | 256 MB | 128 MB | 128 MB (main image) | 128 MB | ❌ | 4 GB |
Min. Memory Requirements | 256 MB | 128 MB | 128 MB (main image) | 512 MB | ❌ | 512 MB |
has standalone binary | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
price | free | free | free | free tier | free tier | $99+ |
rich media preview | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
@@ -127,7 +127,7 @@ LDAP sign-on support | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
Long-live API key support | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
API documentation page | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
Mobile App | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
open source? | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
open source? | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
tags support | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ |
shareable web links? | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Event-based notifications | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
@@ -148,4 +148,4 @@ Can archive selected files | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Can browse archive files | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Can convert documents | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Can convert videos | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Can convert photos | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Can convert photos | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+42 -10
View File
@@ -68,16 +68,16 @@ func FileInfoFaster(opts iteminfo.FileOptions) (iteminfo.ExtendedFileInfo, error
if !exists {
return response, fmt.Errorf("could not get metadata for path: %v", opts.Path)
}
if opts.Content && strings.HasPrefix(info.Type, "text") {
// Check file size
if info.Size > 50*1024*1024 { // 50 megabytes in bytes
logger.Debug(fmt.Sprintf("Reading large text file contents: "+info.Path, info.Name))
if opts.Content {
if info.Size < 20*1024*1024 { // 20 megabytes in bytes
content, err := getContent(realPath)
if err != nil {
return response, err
}
response.Content = content
} else {
logger.Debug(fmt.Sprintf("skipping large text file contents (20MB limit): "+info.Path, info.Name))
}
content, err := getContent(realPath)
if err != nil {
return response, err
}
response.Content = content
}
response.FileInfo = *info
response.RealPath = realPath
@@ -268,12 +268,35 @@ func WriteFile(opts iteminfo.FileOptions, in io.Reader) error {
return idx.RefreshFileInfo(opts)
}
// addContent reads and sets content based on the file type.
// getContent reads and returns the file content only if it's ASCII-readable.
func getContent(realPath string) (string, error) {
const headerSize = 4096
// Open file
f, err := os.Open(realPath)
if err != nil {
return "", err
}
defer f.Close()
// Read header
buf := make([]byte, headerSize)
n, err := f.Read(buf)
if err != nil && err != io.EOF {
return "", err
}
// Check if header is ASCII
if !isASCII(buf[:n]) {
return "", nil
}
// Now read full file
content, err := os.ReadFile(realPath)
if err != nil {
return "", err
}
stringContent := string(content)
if !utf8.ValidString(stringContent) {
return "", nil
@@ -284,6 +307,15 @@ func getContent(realPath string) (string, error) {
return stringContent, nil
}
func isASCII(data []byte) bool {
for _, b := range data {
if b < 0x09 || (b > 0x0D && b < 0x20) || b > 0x7E {
return false
}
}
return true
}
func IsNamedPipe(mode os.FileMode) bool {
return mode&os.ModeNamedPipe != 0
}
+3 -2
View File
@@ -32,7 +32,6 @@ type HookAuth struct {
// Auth authenticates the user via a json in content body.
func (a *HookAuth) Auth(r *http.Request, usr *users.Storage) (*users.User, error) {
var cred hookCred
if r.Body == nil {
return nil, os.ErrPermission
}
@@ -52,6 +51,8 @@ func (a *HookAuth) Auth(r *http.Request, usr *users.Storage) (*users.User, error
if err != nil {
return nil, err
}
logger.Debug(fmt.Sprintf("hook auth %v", action))
switch action {
case "auth":
u, err := a.SaveUser()
@@ -167,7 +168,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
}
u = a.GetUser(d)
err = a.Users.Save(u, false)
err = a.Users.Save(u, false, false)
if err != nil {
return nil, err
}
+70 -53
View File
@@ -2,65 +2,82 @@
== Running benchmark ==
/usr/local/go/bin/go
? github.com/gtsteffaniak/filebrowser/backend [no test files]
PASS
ok github.com/gtsteffaniak/filebrowser/backend/adapters/fs/diskcache 0.004s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files 0.006s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/adapters/fs/fileutils 0.003s
? github.com/gtsteffaniak/filebrowser/backend/auth [no test files]
? github.com/gtsteffaniak/filebrowser/backend/cmd [no test files]
PASS
ok github.com/gtsteffaniak/filebrowser/backend/adapters/fs/diskcache 0.005s
? github.com/gtsteffaniak/filebrowser/backend/common/cache [no test files]
? github.com/gtsteffaniak/filebrowser/backend/common/errors [no test files]
/home/graham/git/filebrowser /home/graham/git/filebrowser
/home/graham/git/filebrowser/files/file.go /home/graham/git/filebrowser
/home/graham/git/filebrowser/mnt/doesnt/exist /home/graham/git/filebrowser
2025/01/04 14:04:55 Initializing index and assessing file system complexity
2025/01/04 14:04:55 Starting full scan
2025/01/04 14:04:55 Index assessment : complexity=simple directories=0 files=0
2025/01/04 14:04:55 Time Spent Indexing : 0 seconds
2025/01/04 14:04:55 Next scan in 1m0s
2025/01/04 14:04:56 Initializing index and assessing file system complexity
2025/01/04 14:04:56 Starting full scan
2025/01/04 14:04:56 Index assessment : complexity=simple directories=0 files=0
2025/01/04 14:04:56 Time Spent Indexing : 0 seconds
2025/01/04 14:04:56 Next scan in 1m0s
goos: linux
goarch: amd64
pkg: github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files
cpu: 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz
BenchmarkFillIndex-8 2025/01/04 14:04:57 Initializing index and assessing file system complexity
2025/01/04 14:04:57 Starting full scan
2025/01/04 14:04:57 Index assessment : complexity=simple directories=0 files=0
2025/01/04 14:04:57 Time Spent Indexing : 0 seconds
2025/01/04 14:04:57 Next scan in 1m0s
10 3515090 ns/op 34273 B/op 451 allocs/op
BenchmarkCheckIndexExclude-8 10 156.2 ns/op 0 B/op 0 allocs/op
BenchmarkCheckIndexConditionsInclude-8 10 98.00 ns/op 0 B/op 0 allocs/op
2025/01/04 14:04:58 Initializing index and assessing file system complexity
2025/01/04 14:04:58 Starting full scan
2025/01/04 14:04:58 Index assessment : complexity=simple directories=0 files=0
2025/01/04 14:04:58 Time Spent Indexing : 0 seconds
2025/01/04 14:04:58 Next scan in 1m0s
BenchmarkSearchAllIndexes-8 2025/01/04 14:04:59 Initializing index and assessing file system complexity
2025/01/04 14:04:59 Starting full scan
2025/01/04 14:04:59 Index assessment : complexity=simple directories=0 files=0
2025/01/04 14:04:59 Time Spent Indexing : 0 seconds
2025/01/04 14:04:59 Next scan in 1m0s
10 766822 ns/op 34230 B/op 900 allocs/op
? github.com/gtsteffaniak/filebrowser/backend/common/logger [no test files]
2025/05/11 20:42:56 Using OnlyOffice secret from FILEBROWSER_ONLYOFFICE_SECRET environment variable
2025/05/11 20:42:56 Using OnlyOffice secret from FILEBROWSER_ONLYOFFICE_SECRET environment variable
PASS
ok github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files 5.094s
ok github.com/gtsteffaniak/filebrowser/backend/common/settings 0.006s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/adapters/fs/fileutils 0.002s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/http 0.184s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/preview/img 0.123s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/runner 0.004s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/common/settings 0.005s
ok github.com/gtsteffaniak/filebrowser/backend/common/utils 0.003s
? github.com/gtsteffaniak/filebrowser/backend/common/version [no test files]
? github.com/gtsteffaniak/filebrowser/backend/database/share [no test files]
? github.com/gtsteffaniak/filebrowser/backend/database/storage [no test files]
? github.com/gtsteffaniak/filebrowser/backend/database/storage/bolt [no test files]
PASS
ok github.com/gtsteffaniak/filebrowser/backend/database/users 0.002s
? github.com/gtsteffaniak/filebrowser/backend/events [no test files]
2025/05/11 20:42:56 [DEBUG] Saving user [admin] changepass: true
2025/05/11 20:42:56 [DEBUG] Saving user [non-admin] changepass: true
PASS
ok github.com/gtsteffaniak/filebrowser/backend/http 0.197s
2025/05/11 20:42:57 initializing index: [test]
2025/05/11 20:42:57 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:42:57 [DEBUG] Starting full scan for [test]
2025/05/11 20:42:57 Index assessment : [test] complexity=simple directories=0 files=0
2025/05/11 20:42:57 [DEBUG] Time spent indexing [test]: 0 seconds
2025/05/11 20:42:57 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:42:57 [DEBUG] Next scan in 1m0s
2025/05/11 20:42:58 initializing index: [test]
2025/05/11 20:42:58 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:42:58 [DEBUG] Starting full scan for [test]
2025/05/11 20:42:58 Index assessment : [test] complexity=simple directories=0 files=0
2025/05/11 20:42:58 [DEBUG] Time spent indexing [test]: 0 seconds
2025/05/11 20:42:58 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:42:58 [DEBUG] Next scan in 1m0s
goos: linux
goarch: amd64
pkg: github.com/gtsteffaniak/filebrowser/backend/indexing
cpu: 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz
BenchmarkFillIndex-8 2025/05/11 20:42:59 initializing index: [test]
2025/05/11 20:42:59 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:42:59 [DEBUG] Starting full scan for [test]
2025/05/11 20:42:59 Index assessment : [test] complexity=simple directories=0 files=0
2025/05/11 20:42:59 [DEBUG] Time spent indexing [test]: 0 seconds
2025/05/11 20:42:59 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:42:59 [DEBUG] Next scan in 1m0s
10 3548759 ns/op 38032 B/op 452 allocs/op
BenchmarkCheckIndexExclude-8 10 172.6 ns/op 0 B/op 0 allocs/op
BenchmarkCheckIndexConditionsInclude-8 10 118.2 ns/op 0 B/op 0 allocs/op
2025/05/11 20:43:01 initializing index: [test]
2025/05/11 20:43:01 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:43:01 [DEBUG] Starting full scan for [test]
2025/05/11 20:43:01 Index assessment : [test] complexity=simple directories=0 files=0
2025/05/11 20:43:01 [DEBUG] Time spent indexing [test]: 0 seconds
2025/05/11 20:43:01 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:43:01 [DEBUG] Next scan in 1m0s
BenchmarkSearchAllIndexes-8 2025/05/11 20:43:02 initializing index: [test]
2025/05/11 20:43:02 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:43:02 [DEBUG] Starting full scan for [test]
2025/05/11 20:43:02 Index assessment : [test] complexity=simple directories=0 files=0
2025/05/11 20:43:02 [DEBUG] Time spent indexing [test]: 0 seconds
2025/05/11 20:43:02 [DEBUG] Skipping source update event for mock index.
2025/05/11 20:43:02 [DEBUG] Next scan in 1m0s
10 781119 ns/op 38515 B/op 900 allocs/op
PASS
ok github.com/gtsteffaniak/filebrowser/backend/indexing 5.097s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/indexing/iteminfo 0.005s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/preview 0.128s
? github.com/gtsteffaniak/filebrowser/backend/swagger/docs [no test files]
PASS
ok github.com/gtsteffaniak/filebrowser/backend/database/users 0.003s
PASS
ok github.com/gtsteffaniak/filebrowser/backend/common/utils 0.002s
? github.com/gtsteffaniak/filebrowser/backend/common/version [no test files]
? github.com/gtsteffaniak/filebrowser/backend/tools [no test files]
+1 -1
View File
@@ -107,7 +107,7 @@ func runCLI() bool {
if asAdmin {
user.Permissions.Admin = true
}
err = store.Users.Save(user, true)
err = store.Users.Save(user, true, false)
if err != nil {
logger.Error(fmt.Sprintf("could not update user: %v", err))
}
+46 -72
View File
@@ -2,7 +2,7 @@ package cmd
import (
"fmt"
"strings"
"reflect"
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/fileutils"
"github.com/gtsteffaniak/filebrowser/backend/common/logger"
@@ -44,98 +44,72 @@ func validateUserInfo() {
logger.Fatal(fmt.Sprintf("Unable to create automatic backup of database due to error: %v", err))
}
}
err := store.Users.Save(user, false)
err := store.Users.Save(user, false, true)
if err != nil {
logger.Error(fmt.Sprintf("could not update user: %v", err))
}
}
}
if settings.Config.Auth.ResetAdminOnStart {
logger.Info("Resetting admin user to default username and password.")
adminUser, err := store.Users.Get(1)
if err != nil {
logger.Fatal(fmt.Sprintf("could not load admin user: %v", err))
}
adminUser.Username = settings.Config.Auth.AdminUsername
adminUser.Password = settings.Config.Auth.AdminPassword
adminUser.Permissions.Admin = true
err = store.Users.Save(adminUser, true, true)
if err != nil {
logger.Error(fmt.Sprintf("could not Save admin user: %v", err))
}
}
}
func updateUserScopes(user *users.User) bool {
updateUser := false
finalScopes := []users.SourceScope{}
seenNames := make(map[string]bool)
newScopes := []users.SourceScope{}
seen := make(map[string]bool)
// Step 1: Start by including all existing scopes, updating Name if source.Path matches
for _, existingScope := range user.Scopes {
if existingScope.Scope != "/" {
existingScope.Scope = strings.TrimSuffix(existingScope.Scope, "/")
}
if existingScope.Scope == "" {
updateUser = true
continue
}
if !seenNames[existingScope.Name] {
finalScopes = append(finalScopes, existingScope)
seenNames[existingScope.Name] = true
}
// Build map for existing scopes by Name
existing := make(map[string]users.SourceScope)
for _, s := range user.Scopes {
existing[s.Name] = s
}
// Step 2: Add missing scopes from SourceMap if admin or DefaultEnabled
for _, source := range settings.Config.Server.SourceMap {
if seenNames[source.Path] {
// Preserve order by using Config.Server.Sources
for _, src := range settings.Config.Server.Sources {
realsource, ok := settings.Config.Server.NameToSource[src.Name]
if !ok {
continue
}
// Check if scope exists already via path
scopePath, _ := settings.GetScopeFromSourcePath(user.Scopes, source.Path)
if scopePath == "" {
// must be new scope for user
// If user has no access, skip unless default-enabled
if !source.Config.DefaultEnabled {
continue
}
// Determine scope path
if scopePath == "" {
scopePath = source.Config.DefaultUserScope
}
if scopePath == "" {
scopePath = "/"
}
if scopePath != "/" {
scopePath = strings.TrimSuffix(scopePath, "/")
existingScope, ok := existing[realsource.Path]
if ok {
// If scope is empty and there's a default, apply default
if existingScope.Scope == "" {
existingScope.Scope = src.Config.DefaultUserScope
}
} else if realsource.Config.DefaultEnabled {
existingScope.Scope = realsource.Config.DefaultUserScope
} else {
continue
}
// Add new scope
finalScopes = append(finalScopes, users.SourceScope{
Scope: scopePath,
Name: source.Path,
newScopes = append(newScopes, users.SourceScope{
Name: realsource.Path,
Scope: existingScope.Scope,
})
seenNames[source.Path] = true
updateUser = true
seen[realsource.Path] = true
}
// Step 3: If no scopes, apply default
if len(finalScopes) == 0 {
defaultScope := users.SourceScope{
Scope: settings.Config.Server.DefaultSource.Config.DefaultUserScope,
Name: settings.Config.Server.DefaultSource.Path,
}
finalScopes = append(finalScopes, defaultScope)
updateUser = true
}
// Step 4: Assign only if different
if !scopesEqual(user.Scopes, finalScopes) {
user.Scopes = finalScopes
updateUser = true
}
return updateUser
}
// scopesEqual does a deep comparison to avoid unnecessary writes
func scopesEqual(a, b []users.SourceScope) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].Scope != b[i].Scope || a[i].Name != b[i].Name {
return false
// Preserve user-defined scopes not matching current sources, append to end
for _, s := range user.Scopes {
if !seen[s.Name] {
newScopes = append(newScopes, s)
}
}
return true
changed := !reflect.DeepEqual(user.Scopes, newScopes)
user.Scopes = newScopes
return changed
}
// func to convert legacy user with perm key to permissions
+184
View File
@@ -0,0 +1,184 @@
package cmd
import (
"reflect"
"testing"
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
"github.com/gtsteffaniak/filebrowser/backend/database/users"
"github.com/stretchr/testify/assert"
)
func TestUpdateUserScopes_Phases(t *testing.T) {
sourceA := settings.Source{Path: "/pathA", Name: "sourceA", Config: settings.SourceConfig{
DefaultEnabled: false,
DefaultUserScope: "/defaultA",
}}
sourceB := settings.Source{Path: "/pathB", Name: "sourceB", Config: settings.SourceConfig{DefaultEnabled: true, DefaultUserScope: "/defaultB"}}
settings.Config.Server.Sources = []settings.Source{sourceA, sourceB}
settings.Config.Server.DefaultSource = sourceB
settings.Config.Server.SourceMap = map[string]settings.Source{
"/pathA": sourceA,
"/pathB": sourceB,
}
settings.Config.Server.NameToSource = map[string]settings.Source{
"sourceA": sourceA,
"sourceB": sourceB,
}
// ---------------------
// Test Cases Definition
// ---------------------
testCases := []struct {
name string
user *users.User
expectedPhase1 []users.SourceScope
expectedPhase2 []users.SourceScope
}{
{
name: "Single valid scope",
user: &users.User{
Scopes: []users.SourceScope{
{Scope: "/home", Name: "/pathA"},
},
},
expectedPhase1: []users.SourceScope{
{Scope: "/home", Name: "/pathA"},
{Scope: "/defaultB", Name: "/pathB"},
},
expectedPhase2: []users.SourceScope{
{Scope: "/home", Name: "/pathA"},
{Scope: "/defaultB", Name: "/pathB"},
},
},
{
name: "Single empty scope path",
user: &users.User{
Scopes: []users.SourceScope{
{Scope: "", Name: "/pathB"},
},
},
expectedPhase1: []users.SourceScope{
{Scope: "/defaultB", Name: "/pathB"},
},
expectedPhase2: []users.SourceScope{
{Scope: "/defaultA", Name: "/pathA"},
{Scope: "/defaultB", Name: "/pathB"},
},
},
{
name: "Two scopes, one includes username in path",
user: &users.User{
Username: "user123",
Scopes: []users.SourceScope{
{Scope: "/home/user123", Name: "/pathA"},
{Scope: "/data", Name: "/pathB"},
},
},
expectedPhase1: []users.SourceScope{
{Scope: "/home/user123", Name: "/pathA"},
{Scope: "/data", Name: "/pathB"},
},
expectedPhase2: []users.SourceScope{
{Scope: "/home/user123", Name: "/pathA"},
{Scope: "/data", Name: "/pathB"},
},
},
{
name: "Two scopes, one with empty name",
user: &users.User{
Scopes: []users.SourceScope{
{Scope: "/home", Name: "/pathB"},
{Scope: "/data", Name: "/somethingElse"},
},
},
expectedPhase1: []users.SourceScope{
{Scope: "/home", Name: "/pathB"},
{Scope: "/data", Name: "/somethingElse"},
},
expectedPhase2: []users.SourceScope{
{Scope: "/defaultA", Name: "/pathA"},
{Scope: "/home", Name: "/pathB"},
{Scope: "/data", Name: "/somethingElse"},
},
},
{
name: "No scopes at all",
user: &users.User{
Scopes: []users.SourceScope{},
},
expectedPhase1: []users.SourceScope{
{Scope: "/defaultB", Name: "/pathB"},
},
expectedPhase2: []users.SourceScope{
{Scope: "/defaultA", Name: "/pathA"},
{Scope: "/defaultB", Name: "/pathB"},
},
},
{
name: "All user Scope and source change",
user: &users.User{
Username: "user123",
Scopes: []users.SourceScope{
{Scope: "/defaultC/user123", Name: "/pathC"},
{Scope: "/defaultA/user123", Name: "/pathA"},
{Scope: "/defaultB/user123", Name: "/pathB"},
},
},
expectedPhase1: []users.SourceScope{
{Scope: "/defaultA/user123", Name: "/pathA"},
{Scope: "/defaultB/user123", Name: "/pathB"},
{Scope: "/defaultC/user123", Name: "/pathC"},
},
expectedPhase2: []users.SourceScope{
{Scope: "/defaultA/user123", Name: "/pathA"},
{Scope: "/defaultB/user123", Name: "/pathB"},
{Scope: "/defaultC/user123", Name: "/pathC"},
},
},
}
// ---------------------
// Phase 1 Tests
// ---------------------
for _, tc := range testCases {
t.Run(tc.name+"_Phase1", func(t *testing.T) {
originalScopes := tc.user.Scopes
updated := updateUserScopes(tc.user)
assert.Equal(t, tc.expectedPhase1, tc.user.Scopes, "Phase1 scope mismatch for test case: %s", tc.name)
assert.Equal(t, updated, !reflect.DeepEqual(originalScopes, tc.user.Scopes), "Phase2 scope change detection failed:\t %s vs expected:\t %s", tc.user.Scopes, tc.expectedPhase1)
})
}
// ---------------------
// Phase 2: rename sources but keep same paths
// ---------------------
sourceA = settings.Source{Path: "/pathA", Name: "sourceA", Config: settings.SourceConfig{
DefaultEnabled: true,
DefaultUserScope: "/defaultA",
CreateUserDir: true,
}}
sourceB = settings.Source{Path: "/pathB", Name: "sourceB", Config: settings.SourceConfig{DefaultEnabled: true, DefaultUserScope: "/defaultB"}}
settings.Config.Server.Sources = []settings.Source{sourceA, sourceB}
settings.Config.Server.DefaultSource = sourceB
settings.Config.Server.SourceMap = map[string]settings.Source{
"/pathA": sourceA,
"/pathB": sourceB,
}
settings.Config.Server.NameToSource = map[string]settings.Source{
"sourceA": sourceA,
"sourceB": sourceB,
}
// Run again without resetting user objects to test idempotency + renaming
for _, tc := range testCases {
t.Run(tc.name+"_Phase2", func(t *testing.T) {
originalScopes := tc.user.Scopes
updated := updateUserScopes(tc.user)
assert.Equal(t, tc.expectedPhase2, tc.user.Scopes, "Phase2 scope mismatch for test case: %s", tc.name)
assert.Equal(t, updated, !reflect.DeepEqual(originalScopes, tc.user.Scopes), "Phase2 scope change detection failed:\t %s vs expected:\t %s", tc.user.Scopes, tc.expectedPhase1)
})
}
}
+9 -6
View File
@@ -3,6 +3,7 @@ package logger
import (
"fmt"
"log"
"os"
"slices"
"time"
)
@@ -21,7 +22,7 @@ const (
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
GRAY = "\033[37m"
GRAY = "\033[2;37m"
)
var (
@@ -72,9 +73,6 @@ func Log(level string, msg string, prefix, api bool, color string) {
continue
}
}
if logger.stdout && level == "FATAL" {
continue
}
writeOut := msg
formattedTime := time.Now().Format("2006/01/02 15:04:05")
if logger.colors && color != "" {
@@ -92,12 +90,16 @@ func Log(level string, msg string, prefix, api bool, color string) {
if err != nil {
log.Printf("failed to log message '%v' with error `%v`", msg, err)
}
if level == levels.FATAL {
os.Exit(1)
}
}
}
func Api(msg string, statusCode int) {
// redirects are not warnings anymore
if statusCode > 302 && statusCode < 500 {
// content not modified is not a warning anymore
if statusCode > 304 && statusCode < 500 {
Log(levels.WARNING, msg, false, true, YELLOW)
} else if statusCode >= 500 {
Log(levels.ERROR, msg, false, true, RED)
@@ -142,6 +144,7 @@ func Error(msg string) {
func Fatal(msg string) {
if len(loggers) > 0 {
Log(levels.FATAL, msg, true, false, RED)
} else {
log.Fatal("[FATAL] ", msg)
}
log.Fatal("[FATAL] ", msg)
}
+66 -12
View File
@@ -1,11 +1,23 @@
package settings
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gtsteffaniak/filebrowser/backend/common/logger"
)
type Auth struct {
TokenExpirationHours int `json:"tokenExpirationHours"` // the number of hours until the token expires. Default is 2 hours.
Methods LoginMethods `json:"methods"`
Key string `json:"key"` // the key used to sign the JWT tokens. If not set, a random key will be generated.
AdminUsername string `json:"adminUsername"` // the username of the admin user. If not set, the default is "admin".
AdminPassword string `json:"adminPassword"` // the password of the admin user. If not set, the default is "admin".
Key string `json:"key"` // the key used to sign the JWT tokens. If not set, a random key will be generated.
AdminUsername string `json:"adminUsername"` // the username of the admin user. If not set, the default is "admin".
AdminPassword string `json:"adminPassword"` // the password of the admin user. If not set, the default is "admin".
ResetAdminOnStart bool `json:"resetAdminOnStart"` // if set to true, the admin user will be reset to the default username and password on startup.
AuthMethods []string `json:"-"`
}
@@ -37,13 +49,55 @@ type Recaptcha struct {
// OpenID OAuth2.0
type OidcConfig struct {
Enabled bool `json:"enabled"` // whether to enable OIDC authentication
ClientID string `json:"clientId" validate:"required"` // client id of the OIDC application
ClientSecret string `json:"clientSecret" validate:"required"` // client secret of the OIDC application
AuthorizationUrl string `json:"authorizationUrl" validate:"required"` // authorization URL of the OIDC provider
TokenUrl string `json:"tokenUrl" validate:"required"` // token URL of the OIDC provider
UserInfoUrl string `json:"userInfoUrl" validate:"required"` // user info URL of the OIDC provider
Scopes string `json:"scopes" validate:"required"` // space separated list of scopes to request from the OIDC provider
UserIdentifier string `json:"userIdentifier"` // optional: which attribute should be used as the username? options: email, username, name, phone_number, sub
JwksUrl string `json:"jwksUrl"` // currently not used by filebrowser
Enabled bool `json:"enabled"` // whether to enable OIDC authentication
ClientID string `json:"clientId"` // client id of the OIDC application
ClientSecret string `json:"clientSecret"` // client secret of the OIDC application
IssuerUrl string `json:"issuerUrl"` // authorization URL of the OIDC provider
Scopes string `json:"scopes"` // scopes to request from the OIDC provider
UserIdentifier string `json:"userIdentifier"` // the user identifier to use for authentication. Default is "username", can be "email" or "username", or "phone"
DisableVerifyTLS bool `json:"disableVerifyTLS"` // disable TLS verification for the OIDC provider. This is insecure and should only be used for testing.
LogoutRedirectUrl string `json:"logoutRedirectUrl"` // if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected.
Provider *oidc.Provider `json:"-"` // OIDC provider
Verifier *oidc.IDTokenVerifier `json:"-"` // OIDC verifier
}
// ValidateOidcAuth processes the OIDC callback and retrieves user identity
func validateOidcAuth() error {
oidcCfg := &Config.Auth.Methods.OidcAuth // Use a pointer to modify the original config
if !oidcCfg.Enabled {
return errors.New("OIDC is not enabled")
}
if oidcCfg.UserIdentifier == "" {
oidcCfg.UserIdentifier = "username"
}
ctx := context.Background()
// If disableVerifyTLS is true, create a custom HTTP client
// and set it in the context for the OIDC provider.
if oidcCfg.DisableVerifyTLS {
// Create a custom transport with InsecureSkipVerify set to true
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
customClient := &http.Client{
Transport: transport,
}
// Use oidc.ClientContext to pass the custom client to the OIDC library
ctx = oidc.ClientContext(ctx, customClient)
// Log that TLS verification is disabled (important for security awareness)
logger.Warning("OIDC TLS verification is disabled.")
}
provider, err := oidc.NewProvider(ctx, oidcCfg.IssuerUrl)
if err != nil {
return fmt.Errorf("url '%v' failed to create OIDC provider: %w", oidcCfg.IssuerUrl, err)
}
oidcCfg.Provider = provider
oidcCfg.Verifier = provider.Verifier(&oidc.Config{ClientID: oidcCfg.ClientID})
if oidcCfg.Scopes == "" {
oidcCfg.Scopes = "openid email profile"
}
return nil
}
+17 -8
View File
@@ -24,10 +24,9 @@ func Initialize(configFile string) {
}
err = ValidateConfig(Config)
if err != nil {
errmsg := "the provided config file failed validation. "
errmsg := "The provided config file failed validation. "
errmsg += "If you are seeing this on a config that worked previeously, "
errmsg += "this is because v0.6.8 requires a fully validated config to run. "
errmsg += "Please review your config for typos and invalid keys which are no longer supported. "
errmsg += "please review your config for typos and invalid keys which are no longer supported. "
errmsg += "visit https://github.com/gtsteffaniak/filebrowser/wiki/Full-Config-Example for more information."
logger.Error(errmsg)
logger.Fatal(err.Error())
@@ -145,23 +144,33 @@ func setupUrls() {
func setupAuth() {
if Config.Auth.Methods.PasswordAuth.Enabled {
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "Password")
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "password")
}
if Config.Auth.Methods.ProxyAuth.Enabled {
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "Proxy")
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "proxy")
}
if Config.Auth.Methods.OidcAuth.Enabled {
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "OIDC")
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "oidc")
}
if Config.Auth.Methods.NoAuth {
logger.Warning("Configured with no authentication, this is not recommended.")
Config.Auth.AuthMethods = []string{"Disabled"}
Config.Auth.AuthMethods = []string{"disabled"}
}
if Config.Auth.Methods.OidcAuth.Enabled {
err := validateOidcAuth()
if err != nil {
logger.Fatal(fmt.Sprintf("Error validating OIDC auth: %v", err))
}
logger.Info("OIDC Auth configured successfully")
}
// use password auth as default if no auth methods are set
if len(Config.Auth.AuthMethods) == 0 {
Config.Auth.Methods.PasswordAuth.Enabled = true
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "Password")
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "password")
}
Config.UserDefaults.LoginMethod = Config.Auth.AuthMethods[0]
}
func setupLogging() {
+1
View File
@@ -46,6 +46,7 @@ func ApplyUserDefaults(u *users.User) {
u.ViewMode = Config.UserDefaults.ViewMode
u.SingleClick = Config.UserDefaults.SingleClick
u.Permissions = Config.UserDefaults.Permissions
u.Preview = Config.UserDefaults.Preview
u.ShowHidden = Config.UserDefaults.ShowHidden
u.DateFormat = Config.UserDefaults.DateFormat
u.DisableOnlyOfficeExt = Config.UserDefaults.DisableOnlyOfficeExt
+17 -16
View File
@@ -110,20 +110,21 @@ type ExternalLink struct {
// UserDefaults is a type that holds the default values
// for some fields on User.
type UserDefaults struct {
PreviewOptions users.PreviewOptions `json:"preview"`
StickySidebar bool `json:"stickySidebar"`
DarkMode bool `json:"darkMode"`
LockPassword bool `json:"lockPassword"`
DisableSettings bool `json:"disableSettings,omitempty"`
Locale string `json:"locale"`
ViewMode string `json:"viewMode"`
GallerySize int `json:"gallerySize"`
SingleClick bool `json:"singleClick"`
Permissions users.Permissions `json:"permissions"`
ShowHidden bool `json:"showHidden"`
DateFormat bool `json:"dateFormat"`
ThemeColor string `json:"themeColor"`
QuickDownload bool `json:"quickDownload"`
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"`
DefaultScopes []users.SourceScope `json:"-"`
StickySidebar bool `json:"stickySidebar"` // keep sidebar open when navigating
DarkMode bool `json:"darkMode"` // should dark mode be enabled
Locale string `json:"locale"` // language to use: eg. de, en, or fr
ViewMode string `json:"viewMode"` // view mode to use: eg. normal, list, grid, or compact
SingleClick bool `json:"singleClick"` // open directory on single click, also enables middle click to open in new tab
ShowHidden bool `json:"showHidden"` // show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files
DateFormat bool `json:"dateFormat"` // when false, the date is relative, when true, the date is an exact timestamp
GallerySize int `json:"gallerySize"` // 0-9 - the size of the gallery thumbnails
ThemeColor string `json:"themeColor"` // theme color to use: eg. #ff0000, or var(--red), var(--purple), etc
QuickDownload bool `json:"quickDownload"` // show icon to download in one click
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"` // comma separated list of file extensions to disable onlyoffice preview for
LockPassword bool `json:"lockPassword"` // disable the user from changing their password
DisableSettings bool `json:"disableSettings,omitempty"` // disable the user from viewing the settings page
Preview users.Preview `json:"preview"`
DefaultScopes []users.SourceScope `json:"-"`
Permissions users.Permissions `json:"permissions"`
LoginMethod string `json:"loginMethod,omitempty"` // login method to use: eg. password, proxy, oidc
}
+4 -3
View File
@@ -32,6 +32,7 @@ func (st usersBackend) GetBy(i interface{}) (user *users.User, err error) {
arg = "ID"
case int:
val = uint(i)
arg = "ID"
case string:
arg = "Username"
val = i
@@ -136,7 +137,7 @@ func (st usersBackend) Update(user *users.User, actorIsAdmin bool, fields ...str
return nil
}
func (st usersBackend) Save(user *users.User, changePass bool) error {
func (st usersBackend) Save(user *users.User, changePass, disableScopeChange bool) error {
if user.LoginMethod == "" {
user.LoginMethod = users.LoginMethodPassword
}
@@ -159,13 +160,13 @@ func (st usersBackend) Save(user *users.User, changePass bool) error {
return err
}
user.Scopes = adjustedScopes
err = files.MakeUserDirs(user, false)
err = files.MakeUserDirs(user, disableScopeChange)
if err != nil {
logger.Error(err.Error())
}
err = st.db.Save(user)
if err == storm.ErrAlreadyExists {
return errors.ErrExist
return fmt.Errorf("user with provided username already exists")
}
return err
}
+21 -6
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
storm "github.com/asdine/storm/v3"
"github.com/gtsteffaniak/filebrowser/backend/auth"
@@ -33,13 +34,16 @@ func InitializeDb(path string) (*Storage, bool, error) {
panic(err)
}
db, err := storm.Open(path)
utils.CheckErr(fmt.Sprintf("storm.Open path %v", path), err)
if err != nil {
if strings.Contains(err.Error(), "timeout") {
logger.Fatal("the database is locked, please close all other instances of filebrowser before starting.")
}
logger.Fatal(fmt.Sprintf("could not open database: %v", err))
}
authStore, userStore, shareStore, settingsStore, err := bolt.NewStorage(db)
if err != nil {
return nil, exists, err
}
err = bolt.Save(db, "version", 2)
if err != nil {
return nil, exists, err
@@ -98,15 +102,26 @@ func quickSetup(store *Storage) {
user.LockPassword = false
user.Permissions = settings.AdminPerms()
logger.Debug(fmt.Sprintf("Creating user as admin: %v %v", user.Username, user.Password))
err = store.Users.Save(user, true)
err = store.Users.Save(user, true, true)
utils.CheckErr("store.Users.Save", err)
}
// create new user
func CreateUser(userInfo users.User, asAdmin bool) error {
newUser := &userInfo
if userInfo.LoginMethod == "password" {
if userInfo.Password != "" {
return errors.ErrInvalidRequestParams
}
} else {
hashpass, err := users.HashPwd(userInfo.Username)
if err != nil {
return err
}
userInfo.Password = hashpass
}
// must have username or password to create
if userInfo.Username == "" || userInfo.Password == "" {
if userInfo.Username == "" {
return errors.ErrInvalidRequestParams
}
logger.Debug(fmt.Sprintf("Creating user: %v %v", userInfo.Username, userInfo.Scopes))
@@ -115,7 +130,7 @@ func CreateUser(userInfo users.User, asAdmin bool) error {
userInfo.Permissions = settings.AdminPerms()
}
// create new home directories
err := store.Users.Save(newUser, true)
err := store.Users.Save(newUser, true, false)
if err != nil {
return err
}
+4 -4
View File
@@ -11,7 +11,7 @@ import (
type StorageBackend interface {
GetBy(interface{}) (*User, error)
Gets() ([]*User, error)
Save(u *User, changePass bool) error
Save(u *User, changePass bool, disableScopeChange bool) error
Update(u *User, adminActor bool, fields ...string) error
DeleteByID(uint) error
DeleteByUsername(string) error
@@ -22,7 +22,7 @@ type Store interface {
Get(id interface{}) (user *User, err error)
Gets() ([]*User, error)
Update(user *User, adminActor bool, fields ...string) error
Save(user *User, changePass bool) error
Save(user *User, changePass bool, disableScopeChange bool) error
Delete(id interface{}) error
LastUpdate(id uint) int64
AddApiKey(username uint, name string, key AuthToken) error
@@ -114,8 +114,8 @@ func (s *Storage) DeleteApiKey(userID uint, name string) error {
}
// Save saves the user in a storage.
func (s *Storage) Save(user *User, changePass bool) error {
return s.back.Save(user, changePass)
func (s *Storage) Save(user *User, changePass, disableScopeChange bool) error {
return s.back.Save(user, changePass, disableScopeChange)
}
// Delete allows you to delete a user by its name or username. The provided
+22 -20
View File
@@ -40,12 +40,13 @@ type Sorting struct {
Asc bool `json:"asc"`
}
type PreviewOptions struct {
HighQuality bool `json:"highQuality"`
Image bool `json:"image"`
Video bool `json:"video"`
Office bool `json:"office"`
PopUp bool `json:"popup"`
type Preview struct {
HighQuality bool `json:"highQuality"` // generate high quality preview images
Image bool `json:"image"` // show real image as icon instead of generic photo icon
Video bool `json:"video"` // show preview image for video files
MotionVideoPreview bool `json:"motionVideoPreview"` // show multiple frames for videos in preview when hovering
Office bool `json:"office"` // show preview image for office files
PopUp bool `json:"popup"` // show larger popup preview when hovering
}
// User describes a user.
@@ -69,21 +70,22 @@ type SourceScope struct {
Scope string `json:"scope"`
}
// json tags must match variable name with smaller case first letter
type NonAdminEditable struct {
Preview PreviewOptions `json:"preview"`
StickySidebar bool `json:"stickySidebar"`
DarkMode bool `json:"darkMode"`
Password string `json:"password,omitempty"`
Locale string `json:"locale"`
ViewMode string `json:"viewMode"`
SingleClick bool `json:"singleClick"`
Sorting Sorting `json:"sorting"`
ShowHidden bool `json:"showHidden"`
DateFormat bool `json:"dateFormat"`
GallerySize int `json:"gallerySize"`
ThemeColor string `json:"themeColor"`
QuickDownload bool `json:"quickDownload"`
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"`
Preview Preview `json:"preview"`
StickySidebar bool `json:"stickySidebar"` // keep sidebar open when navigating
DarkMode bool `json:"darkMode"` // should dark mode be enabled
Password string `json:"password,omitempty"`
Locale string `json:"locale"` // language to use: eg. de, en, or fr
ViewMode string `json:"viewMode"` // view mode to use: eg. normal, list, grid, or compact
SingleClick bool `json:"singleClick"` // open directory on single click, also enables middle click to open in new tab
Sorting Sorting `json:"sorting"`
ShowHidden bool `json:"showHidden"` // show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files
DateFormat bool `json:"dateFormat"` // when false, the date is relative, when true, the date is an exact timestamp
GallerySize int `json:"gallerySize"` // 0-9 - the size of the gallery thumbnails
ThemeColor string `json:"themeColor"` // theme color to use: eg. #ff0000, or var(--red), var(--purple), etc
QuickDownload bool `json:"quickDownload"` // show icon to download in one click
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"` // comma separated list of file extensions to disable onlyoffice preview for
}
var PublicUser = User{
+3
View File
@@ -57,6 +57,7 @@ require (
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/ckaznocha/intrange v0.3.0 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/curioswitch/go-reassign v0.3.0 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
@@ -75,6 +76,7 @@ require (
github.com/ghostiam/protogetter v0.3.9 // indirect
github.com/go-critic/go-critic v0.12.0 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
@@ -229,6 +231,7 @@ require (
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
+6
View File
@@ -130,6 +130,8 @@ github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd
github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -198,6 +200,8 @@ github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -835,6 +839,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
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.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
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=
+165 -218
View File
@@ -1,21 +1,21 @@
package http
import (
"crypto/tls"
"encoding/json"
libError "errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
jwt "github.com/golang-jwt/jwt/v4"
"github.com/golang-jwt/jwt/v4/request"
"github.com/lestrrat-go/jwx/v3/jwk"
"golang.org/x/crypto/bcrypt"
"golang.org/x/oauth2"
"github.com/gtsteffaniak/filebrowser/backend/common/cache"
"github.com/gtsteffaniak/filebrowser/backend/common/errors"
"github.com/gtsteffaniak/filebrowser/backend/common/logger"
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
@@ -79,16 +79,9 @@ func setupProxyUser(r *http.Request, data *requestContext, proxyUser string) (*u
return nil, err
}
if config.Auth.Methods.ProxyAuth.CreateUser {
hashpass, err := users.HashPwd(proxyUser)
if err != nil {
return nil, err
}
err = storage.CreateUser(users.User{
LoginMethod: users.LoginMethodProxy,
Username: proxyUser,
NonAdminEditable: users.NonAdminEditable{
Password: hashpass, // hashed password that can't actually be used
},
}, false)
if err != nil {
return nil, err
@@ -153,7 +146,6 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
// Authenticate the user based on the request
user, err := auther.Auth(r, store.Users)
if err != nil {
logger.Debug(err.Error())
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
@@ -175,6 +167,29 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
}
}
// logoutHandler handles user logout, specifically used for OIDC.
// @Summary User Logout
// @Description logs a user out of the application.
// @Tags Auth
// @Success 302 {string} string "Redirect to redirect URL if configured in oidc config."
// @Router /api/auth/logout [get]
func logoutHandler(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if origin == "" {
origin = fmt.Sprintf("%s://%s", getScheme(r), r.Host)
}
logoutUrl := fmt.Sprintf("%s/login", origin)
oidcCfg := settings.Config.Auth.Methods.OidcAuth
if oidcCfg.Enabled && oidcCfg.LogoutRedirectUrl != "" {
logoutUrl = oidcCfg.LogoutRedirectUrl
http.Redirect(w, r, logoutUrl, http.StatusFound)
return
}
http.Redirect(w, r, logoutUrl, http.StatusFound)
}
type signupBody struct {
Username string `json:"username"`
Password string `json:"password"`
@@ -220,7 +235,7 @@ func signupHandler(w http.ResponseWriter, r *http.Request) {
user.Username = info.Username
user.Password = info.Password
err = store.Users.Save(user, true)
err = store.Users.Save(user, true, false)
if err == errors.ErrExist {
http.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict)
return
@@ -332,155 +347,84 @@ type userInfo struct {
Phone string `json:"phone_number"`
}
// getKeySet fetches the JWKS from the given URL, using the provided cache.
func getKeySet(jwksUrl string) (jwk.Set, error) {
// Check cache first
value := cache.JwtCache.Get(jwksUrl)
jwks, ok := value.(jwk.Set)
if ok {
return jwks, nil
}
logger.Debug("JWKS not found in cache for, fetching...")
// Fetch the JWKS from the URL
resp, err := http.Get(jwksUrl)
if err != nil {
return nil, fmt.Errorf("failed to fetch JWKS from %s: %v", jwksUrl, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("failed to fetch JWKS from %s: received status code %d with body: %s", jwksUrl, resp.StatusCode, string(bodyBytes))
}
// Decode the JWKS using jwk.ParseReader
jwks, err = jwk.ParseReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to decode JWKS: %v", err)
}
// Store in cache. Assuming your cache handles expiration (e.g., 1 hour).
cache.JwtCache.Set(jwksUrl, jwks) // Store the jwk.Set
return jwks, nil // Return the jwk.Set after type assertion
}
// oidcCallbackHandler handles the OIDC callback after the user authenticates with the provider.
// It exchanges the authorization code for tokens, then attempts to verify the ID token using JWKS
// if configured, falling back to the UserInfo endpoint if necessary.
// It exchanges the authorization code for tokens, attempts to verify the ID token.
// If ID token verification or essential claims extraction fails, it falls back to the UserInfo endpoint.
func oidcCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
ctx := r.Context()
oidcCfg := settings.Config.Auth.Methods.OidcAuth
if oidcCfg.Provider == nil || oidcCfg.Verifier == nil {
// Ensure Provider and Verifier are initialized on application startup
// This check is good, keep it.
logger.Error("OIDC provider or verifier not initialized.")
return http.StatusInternalServerError, fmt.Errorf("OIDC provider or verifier not initialized")
}
// If disableVerifyTLS is true, create a custom HTTP client
// and set it in the context for the OIDC provider.
if oidcCfg.DisableVerifyTLS {
// Create a custom transport with InsecureSkipVerify set to true
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
customClient := &http.Client{
Transport: transport,
}
ctx = oidc.ClientContext(ctx, customClient)
}
code := r.URL.Query().Get("code")
// state := r.URL.Query().Get("state") // You might want to validate the state parameter for CSRF protection
origin := r.Header.Get("Origin")
if origin == "" {
origin = fmt.Sprintf("%s://%s", getScheme(r), r.Host)
// The redirect URI MUST match the one registered with the OIDC provider
// and used in the initial /api/auth/oidc/login handler.
// Using r.Host here might be tricky if running behind a proxy.
// Consider using a fixed redirect URL from settings if possible.
redirectURL := fmt.Sprintf("%s://%s/api/auth/oidc/callback", getScheme(r), r.Host)
oauth2Config := &oauth2.Config{
ClientID: oidcCfg.ClientID,
ClientSecret: oidcCfg.ClientSecret,
Endpoint: oidcCfg.Provider.Endpoint(), // Use endpoint from discovered provider
RedirectURL: redirectURL, // Use the dynamically determined redirect URL
Scopes: strings.Split(oidcCfg.Scopes, " "),
}
redirectURI := fmt.Sprintf("%s/api/auth/oidc/callback", origin)
// Step 1: Exchange the code for tokens
data := url.Values{}
data.Set("grant_type", "authorization_code")
data.Set("code", code)
data.Set("client_id", config.Auth.Methods.OidcAuth.ClientID)
data.Set("client_secret", config.Auth.Methods.OidcAuth.ClientSecret)
data.Set("redirect_uri", redirectURI)
req, _ := http.NewRequest("POST", config.Auth.Methods.OidcAuth.TokenUrl, strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{} // Consider using a client with a timeout
resp, err := client.Do(req)
// Exchange the authorization code for tokens
token, err := oauth2Config.Exchange(ctx, code)
if err != nil {
logger.Debug(fmt.Sprintf("failed to send token request: %v", err))
return 500, fmt.Errorf("token request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
logger.Debug(fmt.Sprintf("failed to fetch token: %v %v", resp.StatusCode, string(body)))
return 500, fmt.Errorf("failed to fetch token: %v %v", resp.StatusCode, string(body))
logger.Error(fmt.Sprintf("failed to exchange token: %v", err))
return http.StatusInternalServerError, fmt.Errorf("failed to exchange token: %v", err)
}
// Decode the token response, expecting access_token and potentially id_token
var tokenResp struct {
AccessToken string `json:"access_token"`
IDToken string `json:"id_token"` // OIDC providers include id_token here
}
rawIDToken, ok := token.Extra("id_token").(string)
// accessToken := token.AccessToken // Access token is needed for UserInfo, already in 'token'
if err = json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
logger.Debug(fmt.Sprintf("failed to parse token response: %v", err))
return 500, fmt.Errorf("failed to parse token response: %v", err)
}
var userdata userInfo // Declare userdata here to be populated by either source
claimsFromIDToken := false // Flag to track if we successfully got claims from ID token
loginUsername := "" // Variable to hold the login username
var userdata userInfo // This will hold the user claims, using the original struct
useUserInfoEndpoint := true // Flag to determine if we need to call the UserInfo endpoint
// --- Attempt to process ID Token ---
if ok && rawIDToken != "" {
logger.Debug("ID token found in token response, attempting verification.")
// Step 2: Process ID Token if available and JWKS URL is configured
if tokenResp.IDToken != "" && config.Auth.Methods.OidcAuth.JwksUrl != "" {
logger.Debug(fmt.Sprintf("ID token received `%v` attempting to verify using JWKS URL", tokenResp.IDToken))
// getKeySet now returns jwk.Set (interface)
var jwks jwk.Set
jwks, err = getKeySet(config.Auth.Methods.OidcAuth.JwksUrl)
// Verify the ID token
// This uses the verifier initialized with the provider's JWKS endpoint and client ID
idToken, err := oidcCfg.Verifier.Verify(ctx, rawIDToken)
if err != nil {
// Log the error but don't fail yet, try UserInfo endpoint as a fallback
logger.Warning("Failed to fetch or decode JWKS: %v. Falling back to UserInfo endpoint. Ensure your OidcAuth.JwksUrl is correct.")
logger.Warning(fmt.Sprintf("failed to verify ID token: %v. Falling back to UserInfo endpoint.", err))
// Verification failed, claimsFromIDToken remains false
} else {
var token *jwt.Token
// Parse the ID token. We need to provide a key function to `jwt.Parse`
// that looks up the key in the JWKS based on the token's `kid`.
token, err = jwt.Parse(tokenResp.IDToken, func(token *jwt.Token) (interface{}, error) {
switch alg := token.Method.Alg(); alg {
case "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "EdDSA":
// accepted
default:
logger.Debug(fmt.Sprintf("unsupported signing method recieved `%v`", alg))
return nil, fmt.Errorf("unsupported signing method recieved: %v", alg)
}
// Find the key in the JWKS that matches the token's kid (Key ID)
keyID, ok := token.Header["kid"].(string)
if !ok {
logger.Debug("kid header not found in ID token")
return nil, fmt.Errorf("kid header not found in ID token")
}
// Use the jwk.Set interface to find the key
// LookupKeyID returns a slice of jwk.Key and a boolean indicating if any keys were found.
key, found := jwks.LookupKeyID(keyID) // Called on the jwk.Set interface
if !found {
logger.Debug(fmt.Sprintf("key with kid %s not found in JWKS", keyID))
return nil, fmt.Errorf("key with kid %s not found in JWKS", keyID)
}
// Return the public key from the first matching JWK
// In a real scenario, you might need more sophisticated key selection logic.
// The jwk.Key interface has a PublicKey() method to get the public key.
publicKey, publicKeyErr := key.PublicKey()
if publicKeyErr != nil {
logger.Debug(fmt.Sprintf("failed to get public key from JWK: %v", publicKeyErr))
return nil, fmt.Errorf("failed to get public key from JWK: %v", err)
}
return publicKey, nil
})
var claims map[string]interface{}
// Decode the ID token claims into a map to handle arbitrary structure
// This is where the JWE unmarshalling error occurs if the token is encrypted
if err := idToken.Claims(&claims); err != nil {
logger.Warning(fmt.Sprintf("failed to decode ID token claims: %v. Falling back to UserInfo endpoint.", err))
// Claims decoding failed, claimsFromIDToken remains false
} else {
// Successfully verified and decoded ID token claims
logger.Debug(fmt.Sprintf("ID Token verified and claims decoded: %+v", claims))
if err != nil {
// Log the verification error but fall back to UserInfo endpoint
logger.Error(fmt.Sprintf("failed to parse or verify ID token: %v. Falling back to UserInfo endpoint.", err))
} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
logger.Debug("ID token verified successfully, claims extracted.")
// --- IMPORTANT OIDC VALIDATION ---
// In a production application, you MUST perform additional validations on the ID token claims:
// - Issuer (iss): Must match the expected issuer URL.
// - Audience (aud): Must contain your client ID.
// - Expiration Time (exp): Token must not be expired (jwt.Parse handles this if clock skew is accounted for).
// - Issued At Time (iat): Token must not be issued in the future.
// - Nonce (nonce): If a nonce was sent in the authorization request, it must match the 'nonce' claim in the ID token.
// - Authorized Party (azp): If 'aud' has multiple values, 'azp' must be present and equal to your client ID.
// - at_hash and c_hash: If the access token or authorization code were issued, validate their hashes if present in the ID token.
// A dedicated OIDC library handles these validations correctly and securely.
// This simplified example only verifies the signature and basic validity.
// --- END IMPORTANT OIDC VALIDATION ---
// Populate the userInfo struct from the ID token claims (map)
// Populate userdata from ID token claims
if name, ok := claims["name"].(string); ok {
userdata.Name = name
}
@@ -496,110 +440,113 @@ func oidcCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestConte
if phone, ok := claims["phone_number"].(string); ok {
userdata.Phone = phone
}
useUserInfoEndpoint = false // Successfully processed ID token, no need for UserInfo endpoint
} else {
// ID token signature verification failed or token is invalid for other reasons
logger.Error("id token signature verification failed or token is invalid. Falling back to UserInfo endpoint.")
// Decide if we rely on ID token claims or still need UserInfo
// Even if parsing succeeded, if essential claims are missing, use UserInfo
switch oidcCfg.UserIdentifier {
case "email":
if userdata.Email != "" {
claimsFromIDToken = true
loginUsername = userdata.Email
}
case "username":
if userdata.PreferredUsername != "" {
claimsFromIDToken = true
loginUsername = userdata.PreferredUsername
}
case "phone":
if userdata.Phone != "" {
claimsFromIDToken = true
loginUsername = userdata.Phone
}
}
}
}
} else {
logger.Debug("No ID token found in token response or it was empty. Falling back to UserInfo endpoint.")
// claimsFromIDToken remains false
}
// Step 3: Use the access token to fetch user info from the UserInfo URL if needed
// This step is skipped if the ID token was successfully processed and verified.
if useUserInfoEndpoint {
userInfoURL := config.Auth.Methods.OidcAuth.UserInfoUrl
req, _ = http.NewRequest("GET", userInfoURL, nil)
req.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken)
resp, err = client.Do(req)
// --- Fallback to UserInfo endpoint if ID token processing did not provide essential claims ---
if !claimsFromIDToken {
// Use the access token obtained from the initial exchange
// oauth2Config.TokenSource creates a token source that uses the provided token.
userInfoResp, err := oidcCfg.Provider.UserInfo(ctx, oauth2Config.TokenSource(ctx, token))
if err != nil {
return 500, fmt.Errorf("failed to fetch user info: %v", err)
logger.Error(fmt.Sprintf("failed to fetch user info from endpoint: %v", err))
return http.StatusInternalServerError, fmt.Errorf("failed to fetch user info from endpoint: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return 500, fmt.Errorf("failed to fetch user info: received status code %d with body: %s", resp.StatusCode, string(bodyBytes))
// Decode the UserInfo response directly into the userdata struct
// The UserInfo endpoint is expected to return standard JSON
if err := userInfoResp.Claims(&userdata); err != nil {
logger.Error(fmt.Sprintf("failed to decode user info from endpoint: %v", err))
return http.StatusInternalServerError, fmt.Errorf("failed to decode user info from endpoint: %v", err)
}
// Decode the UserInfo response directly into the original userdata struct
if err = json.NewDecoder(resp.Body).Decode(&userdata); err != nil {
return 500, fmt.Errorf("failed to parse user info: %v", err)
// Decide if we rely on ID token claims or still need UserInfo
// Even if parsing succeeded, if essential claims are missing, use UserInfo
switch oidcCfg.UserIdentifier {
case "email":
loginUsername = userdata.Email
case "username":
loginUsername = userdata.PreferredUsername
case "phone":
loginUsername = userdata.Phone
}
}
// Now userdata contains claims from either the verified ID token or the UserInfo endpoint.
// Proceed with logging in the user based on the extracted claims.
return loginWithOidcUser(w, r, userdata)
if loginUsername == "" {
logger.Error("No valid username found in ID token or UserInfo response.")
return http.StatusInternalServerError, fmt.Errorf("no valid username found in ID token or UserInfo response from claims")
}
// Proceed to log the user in with the OIDC data
// userdata struct now contains info from either verified ID token or UserInfo endpoint
return loginWithOidcUser(w, r, loginUsername)
}
// oidcLoginHandler redirects the user to the OIDC provider's authorization endpoint.
// This function remains largely the same, but includes the 'fb_redirect' parameter
// to redirect the user back to the original page after successful login.
func oidcLoginHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
if config.Auth.Methods.OidcAuth.Enabled {
origin := r.Header.Get("Origin")
if origin == "" {
origin = fmt.Sprintf("%s://%s", getScheme(r), r.Host)
}
// Generate a nonce for CSRF protection (optional but recommended)
// You should store this nonce and validate it in the callback handler.
nonce := utils.InsecureRandomIdentifier(16)
// For state parameter, you might include the nonce and the original redirect URL
state := fmt.Sprintf("%s:%s", nonce, r.URL.Query().Get("fb_redirect")) // Example state parameter structure
redirectURI := fmt.Sprintf("%s/api/auth/oidc/callback", origin)
// Construct the authorization URL
authURL := fmt.Sprintf("%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s&redirect_uris=%s&state=%s",
config.Auth.Methods.OidcAuth.AuthorizationUrl,
url.QueryEscape(config.Auth.Methods.OidcAuth.ClientID),
url.QueryEscape(config.Auth.Methods.OidcAuth.Scopes),
url.QueryEscape(redirectURI),
url.QueryEscape(redirectURI),
url.QueryEscape(state), // Use the state parameter
)
// Redirect the user to the OIDC provider
http.Redirect(w, r, authURL, http.StatusFound)
return 0, nil // Indicate that the request has been handled by the redirect
oidcCfg := settings.Config.Auth.Methods.OidcAuth
if !oidcCfg.Enabled {
return http.StatusForbidden, fmt.Errorf("oidc authentication is not enabled")
}
// OIDC authentication is not enabled
return http.StatusForbidden, fmt.Errorf("oidc authentication is not enabled")
origin := r.Header.Get("Origin")
if origin == "" {
origin = fmt.Sprintf("%s://%s", getScheme(r), r.Host)
}
oauth2Config := &oauth2.Config{
ClientID: oidcCfg.ClientID,
ClientSecret: oidcCfg.ClientSecret,
Endpoint: oidcCfg.Provider.Endpoint(),
RedirectURL: fmt.Sprintf("%s/api/auth/oidc/callback", origin),
Scopes: strings.Split(oidcCfg.Scopes, " "),
}
nonce := utils.InsecureRandomIdentifier(16)
fbRedirect := r.URL.Query().Get("fb_redirect")
state := fmt.Sprintf("%s:%s", nonce, fbRedirect)
authURL := oauth2Config.AuthCodeURL(state)
http.Redirect(w, r, authURL, http.StatusFound)
return 0, nil
}
// loginWithOidcUser extracts the username from the user claims (userInfo)
// based on the configured UserIdentifier and logs the user into the application.
// It creates a new user if one doesn't exist.
func loginWithOidcUser(w http.ResponseWriter, r *http.Request, userInfo userInfo) (int, error) {
username := userInfo.PreferredUsername
switch config.Auth.Methods.OidcAuth.UserIdentifier {
case "email":
username = userInfo.Email
case "username":
username = userInfo.PreferredUsername
}
func loginWithOidcUser(w http.ResponseWriter, r *http.Request, username string) (int, error) {
logger.Debug(fmt.Sprintf("Successfully authenticated OIDC username: %s", username))
// Retrieve the user from the store and store it in the context
user, err := store.Users.Get(username)
if err != nil {
if err.Error() != "the resource does not exist" {
return http.StatusInternalServerError, err
}
hashpass := ""
hashpass, err = users.HashPwd(username) // hashed password that can't actually be used, gets double hashed
if err != nil {
return http.StatusInternalServerError, err
}
err = storage.CreateUser(users.User{
LoginMethod: users.LoginMethodOidc,
Username: username,
NonAdminEditable: users.NonAdminEditable{
Password: hashpass, // hashed password that can't actually be used
},
}, false)
if err != nil {
return http.StatusInternalServerError, err
+2
View File
@@ -73,6 +73,8 @@ func StartHttp(ctx context.Context, storage *storage.Storage, shutdownComplete c
// Auth routes
api.HandleFunc("POST /auth/login", loginHandler)
api.HandleFunc("GET /auth/logout", logoutHandler)
api.HandleFunc("GET /auth/signup", signupHandler)
api.HandleFunc("POST /auth/renew", withUser(renewHandler))
api.HandleFunc("PUT /auth/token", withUser(createApiKeyHandler))
+2 -2
View File
@@ -77,10 +77,10 @@ func TestWithAdminHelper(t *testing.T) {
Permissions: users.Permissions{Admin: false}, // Non-admin user
}
// Save the users to the mock database
if err := store.Users.Save(adminUser, true); err != nil {
if err := store.Users.Save(adminUser, true, true); err != nil {
t.Fatal("failed to save admin user:", err)
}
if err := store.Users.Save(nonAdminUser, true); err != nil {
if err := store.Users.Save(nonAdminUser, true, true); err != nil {
t.Fatal("failed to save non-admin user:", err)
}
// Test cases for different scenarios
+30 -10
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"os"
"strconv"
"strings"
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
@@ -39,6 +40,7 @@ type FileCache interface {
// @Failure 501 {object} map[string]string "Preview generation not implemented"
// @Router /api/preview [get]
func previewHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
if config.Server.DisablePreviews {
return http.StatusNotImplemented, fmt.Errorf("preview is disabled")
}
@@ -88,6 +90,7 @@ func rawFileHandler(w http.ResponseWriter, r *http.Request, file iteminfo.Extend
}
func previewHelperFunc(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
previewSize := r.URL.Query().Get("size")
if previewSize != "small" {
previewSize = "large"
@@ -106,18 +109,35 @@ func previewHelperFunc(w http.ResponseWriter, r *http.Request, d *requestContext
}
return http.StatusNotImplemented, fmt.Errorf("can't create preview for %s type", d.fileInfo.Type)
}
pathUrl := fmt.Sprintf("/api/raw?files=%s::%s", d.fileInfo.Source, d.fileInfo.Path)
pathUrl = pathUrl + "&auth=" + d.token
if settings.Config.Server.InternalUrl != "" {
pathUrl = config.Server.InternalUrl + pathUrl
} else {
scheme := "http"
if r.TLS != nil {
scheme = "https"
seekPercentage := 0
percentage := r.URL.Query().Get("atPercentage")
if percentage != "" {
var err error
// convert string to int
seekPercentage, err = strconv.Atoi(percentage)
if err != nil {
seekPercentage = 10
}
if seekPercentage < 0 || seekPercentage > 100 {
seekPercentage = 10
}
pathUrl = scheme + "://" + r.Host + pathUrl
}
previewImg, err := preview.GetPreviewForFile(d.fileInfo, previewSize, pathUrl)
officeUrl := ""
if d.fileInfo.OnlyOfficeId != "" {
pathUrl := fmt.Sprintf("/api/raw?files=%s::%s", d.fileInfo.Source, d.fileInfo.Path)
pathUrl = pathUrl + "&auth=" + d.token
if settings.Config.Server.InternalUrl != "" {
officeUrl = config.Server.InternalUrl + pathUrl
} else {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
officeUrl = scheme + "://" + r.Host + pathUrl
}
}
previewImg, err := preview.GetPreviewForFile(d.fileInfo, previewSize, officeUrl, seekPercentage)
if err != nil {
return http.StatusInternalServerError, err
}
-1
View File
@@ -48,7 +48,6 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, file, contentT
"StaticURL": config.Server.BaseURL + "static",
"Signup": settings.Config.Auth.Methods.PasswordAuth.Signup,
"NoAuth": config.Auth.Methods.NoAuth,
"PasswordAuth": config.Auth.Methods.PasswordAuth,
"LoginPage": auther.LoginPage(),
"CSS": false,
"EnableThumbs": !config.Server.DisablePreviews,
+1 -1
View File
@@ -158,7 +158,7 @@ func usersPostHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
return http.StatusBadRequest, nil
}
if req.Data.Password == "" {
if req.Data.Password == "" && req.Data.LoginMethod == "password" {
return http.StatusBadRequest, errors.ErrEmptyPassword
}
+16 -12
View File
@@ -73,20 +73,21 @@ func Start(concurrencyLimit int, ffmpegPath, cacheDir string) error {
return nil
}
func GetPreviewForFile(file iteminfo.ExtendedFileInfo, previewSize, rawUrl string) ([]byte, error) {
func GetPreviewForFile(file iteminfo.ExtendedFileInfo, previewSize, url string, seekPercentage int) ([]byte, error) {
if !AvailablePreview(file) {
return nil, ErrUnsupportedMedia
}
cacheKey := CacheKey(file.RealPath, previewSize, file.ItemInfo.ModTime)
cacheKey := CacheKey(file.RealPath, previewSize, file.ItemInfo.ModTime, seekPercentage)
if data, found, err := service.fileCache.Load(context.Background(), cacheKey); err != nil {
return nil, fmt.Errorf("failed to load from cache: %w", err)
} else if found {
return data, nil
}
return GeneratePreview(file, previewSize, rawUrl)
return GeneratePreview(file, previewSize, url, seekPercentage)
}
func GeneratePreview(file iteminfo.ExtendedFileInfo, previewSize, rawUrl string) ([]byte, error) {
func GeneratePreview(file iteminfo.ExtendedFileInfo, previewSize, officeUrl string, seekPercentage int) ([]byte, error) {
ext := strings.ToLower(filepath.Ext(file.Name))
var (
err error
@@ -95,7 +96,7 @@ func GeneratePreview(file iteminfo.ExtendedFileInfo, previewSize, rawUrl string)
// Generate an image from office document
if file.OnlyOfficeId != "" {
imageBytes, err = service.GenerateOfficePreview(filepath.Ext(file.Name), file.OnlyOfficeId, file.Name, rawUrl)
imageBytes, err = service.GenerateOfficePreview(filepath.Ext(file.Name), file.OnlyOfficeId, file.Name, officeUrl)
if err != nil {
return nil, fmt.Errorf("failed to create image for office file: %w", err)
}
@@ -106,10 +107,13 @@ func GeneratePreview(file iteminfo.ExtendedFileInfo, previewSize, rawUrl string)
return nil, fmt.Errorf("failed to read image file: %w", err)
}
} else if strings.HasPrefix(file.Type, "video") {
if seekPercentage == 0 {
seekPercentage = 10
}
// Generate thumbnail image from video
outPathPattern := filepath.Join(settings.Config.Server.CacheDir, "thumbnails", "video", CacheKey(file.RealPath, previewSize, file.ItemInfo.ModTime)+".jpg")
outPathPattern := filepath.Join(settings.Config.Server.CacheDir, "thumbnails", "video", CacheKey(file.RealPath, previewSize, file.ItemInfo.ModTime, seekPercentage)+".jpg")
defer os.Remove(outPathPattern) // cleanup
if err = service.GenerateVideoPreview(file.RealPath, outPathPattern); err != nil {
if err = service.GenerateVideoPreview(file.RealPath, outPathPattern, seekPercentage); err != nil {
return nil, fmt.Errorf("failed to generate video preview: %w", err)
}
imageBytes, err = os.ReadFile(outPathPattern)
@@ -124,7 +128,7 @@ func GeneratePreview(file iteminfo.ExtendedFileInfo, previewSize, rawUrl string)
return nil, fmt.Errorf("failed to resize preview image: %w", err)
}
// Cache and return
cacheKey := CacheKey(file.RealPath, previewSize, file.ItemInfo.ModTime)
cacheKey := CacheKey(file.RealPath, previewSize, file.ItemInfo.ModTime, seekPercentage)
if err := service.fileCache.Store(context.Background(), cacheKey, resizedBytes); err != nil {
logger.Error(fmt.Sprintf("failed to cache resized image: %v", err))
}
@@ -159,14 +163,14 @@ func (s *Service) CreatePreview(data []byte, previewSize string) ([]byte, error)
return output.Bytes(), nil
}
func CacheKey(realPath, previewSize string, modTime time.Time) string {
return fmt.Sprintf("%x%x%x", realPath, modTime.Unix(), previewSize)
func CacheKey(realPath, previewSize string, modTime time.Time, percentage int) string {
return fmt.Sprintf("%x%x%x%x", realPath, modTime.Unix(), previewSize, percentage)
}
func DelThumbs(ctx context.Context, file iteminfo.ExtendedFileInfo) {
errSmall := service.fileCache.Delete(ctx, CacheKey(file.RealPath, "small", file.ItemInfo.ModTime))
errSmall := service.fileCache.Delete(ctx, CacheKey(file.RealPath, "small", file.ItemInfo.ModTime, 0))
if errSmall != nil {
errLarge := service.fileCache.Delete(ctx, CacheKey(file.RealPath, "large", file.ItemInfo.ModTime))
errLarge := service.fileCache.Delete(ctx, CacheKey(file.RealPath, "large", file.ItemInfo.ModTime, 0))
if errLarge != nil {
logger.Debug(fmt.Sprintf("Could not delete thumbnail: %v", file.Name))
}
+8 -5
View File
@@ -12,7 +12,7 @@ import (
// videoPath: path to the input video file.
// outputPath: path where the generated preview image will be saved (e.g., "/tmp/preview.jpg").
// seekTime: how many seconds into the video to seek before capturing the frame.
func (s *Service) GenerateVideoPreview(videoPath, outputPath string) error {
func (s *Service) GenerateVideoPreview(videoPath, outputPath string, percentageSeek int) error {
// Step 1: Get video stream duration (v:0)
probeCmd := exec.Command(
s.ffprobePath,
@@ -37,12 +37,15 @@ func (s *Service) GenerateVideoPreview(videoPath, outputPath string) error {
return fmt.Errorf("invalid duration: %v", err)
}
// Step 2: Truncate and compute 10% of video length
// Step 2: Get the duration of the video in whole seconds
duration := int(durationFloat)
seekSeconds := duration / 10
seekTime := strconv.Itoa(seekSeconds)
// Step 3: Extract frame at seek time
// Step 3: Calculate seek time based on percentageSeek (percentage value)
seekSeconds := duration * percentageSeek / 100
// Step 4: Convert seekSeconds to string for ffmpeg command
seekTime := strconv.Itoa(seekSeconds)
// Step 5: Extract frame at seek time
cmd := exec.Command(
s.ffmpegPath,
"-ss", seekTime,
+73 -27
View File
@@ -15,6 +15,23 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/auth/logout": {
"get": {
"description": "logs a user out of the application.",
"tags": [
"Auth"
],
"summary": "User Logout",
"responses": {
"302": {
"description": "Redirect to redirect URL if configured in oidc config.",
"schema": {
"type": "string"
}
}
}
}
},
"/api/createApiKey": {
"post": {
"description": "Create an API key with specified name, duration, and permissions.",
@@ -1653,6 +1670,10 @@ const docTemplate = `{
"methods": {
"$ref": "#/definitions/settings.LoginMethods"
},
"resetAdminOnStart": {
"description": "if set to true, the admin user will be reset to the default username and password on startup.",
"type": "boolean"
},
"tokenExpirationHours": {
"description": "the number of hours until the token expires. Default is 2 hours.",
"type": "integer"
@@ -1794,19 +1815,7 @@ const docTemplate = `{
},
"settings.OidcConfig": {
"type": "object",
"required": [
"authorizationUrl",
"clientId",
"clientSecret",
"scopes",
"tokenUrl",
"userInfoUrl"
],
"properties": {
"authorizationUrl": {
"description": "authorization URL of the OIDC provider",
"type": "string"
},
"clientId": {
"description": "client id of the OIDC application",
"type": "string"
@@ -1815,28 +1824,28 @@ const docTemplate = `{
"description": "client secret of the OIDC application",
"type": "string"
},
"disableVerifyTLS": {
"description": "disable TLS verification for the OIDC provider. This is insecure and should only be used for testing.",
"type": "boolean"
},
"enabled": {
"description": "whether to enable OIDC authentication",
"type": "boolean"
},
"jwksUrl": {
"description": "currently not used by filebrowser",
"issuerUrl": {
"description": "authorization URL of the OIDC provider",
"type": "string"
},
"logoutRedirectUrl": {
"description": "if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected.",
"type": "string"
},
"scopes": {
"description": "space separated list of scopes to request from the OIDC provider",
"type": "string"
},
"tokenUrl": {
"description": "token URL of the OIDC provider",
"description": "scopes to request from the OIDC provider",
"type": "string"
},
"userIdentifier": {
"description": "optional: which attribute should be used as the username? options: email, username, name, phone_number, sub",
"type": "string"
},
"userInfoUrl": {
"description": "user info URL of the OIDC provider",
"description": "the user identifier to use for authentication. Default is \"username\", can be \"email\" or \"username\", or \"phone\"",
"type": "string"
}
}
@@ -2099,48 +2108,65 @@ const docTemplate = `{
"type": "object",
"properties": {
"darkMode": {
"description": "should dark mode be enabled",
"type": "boolean"
},
"dateFormat": {
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"disableOnlyOfficeExt": {
"description": "comma separated list of file extensions to disable onlyoffice preview for",
"type": "string"
},
"disableSettings": {
"description": "disable the user from viewing the settings page",
"type": "boolean"
},
"gallerySize": {
"description": "0-9 - the size of the gallery thumbnails",
"type": "integer"
},
"locale": {
"description": "language to use: eg. de, en, or fr",
"type": "string"
},
"lockPassword": {
"description": "disable the user from changing their password",
"type": "boolean"
},
"loginMethod": {
"description": "login method to use: eg. password, proxy, oidc",
"type": "string"
},
"permissions": {
"$ref": "#/definitions/users.Permissions"
},
"preview": {
"$ref": "#/definitions/users.PreviewOptions"
"$ref": "#/definitions/users.Preview"
},
"quickDownload": {
"description": "show icon to download in one click",
"type": "boolean"
},
"showHidden": {
"description": "show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files",
"type": "boolean"
},
"singleClick": {
"description": "open directory on single click, also enables middle click to open in new tab",
"type": "boolean"
},
"stickySidebar": {
"description": "keep sidebar open when navigating",
"type": "boolean"
},
"themeColor": {
"description": "theme color to use: eg. #ff0000, or var(--red), var(--purple), etc",
"type": "string"
},
"viewMode": {
"description": "view mode to use: eg. normal, list, grid, or compact",
"type": "string"
}
}
@@ -2242,22 +2268,31 @@ const docTemplate = `{
}
}
},
"users.PreviewOptions": {
"users.Preview": {
"type": "object",
"properties": {
"highQuality": {
"description": "generate high quality preview images",
"type": "boolean"
},
"image": {
"description": "show real image as icon instead of generic photo icon",
"type": "boolean"
},
"motionVideoPreview": {
"description": "show multiple frames for videos in preview when hovering",
"type": "boolean"
},
"office": {
"description": "show preview image for office files",
"type": "boolean"
},
"popup": {
"description": "show larger popup preview when hovering",
"type": "boolean"
},
"video": {
"description": "show preview image for video files",
"type": "boolean"
}
}
@@ -2294,24 +2329,29 @@ const docTemplate = `{
}
},
"darkMode": {
"description": "should dark mode be enabled",
"type": "boolean"
},
"dateFormat": {
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"disableOnlyOfficeExt": {
"description": "comma separated list of file extensions to disable onlyoffice preview for",
"type": "string"
},
"disableSettings": {
"type": "boolean"
},
"gallerySize": {
"description": "0-9 - the size of the gallery thumbnails",
"type": "integer"
},
"id": {
"type": "integer"
},
"locale": {
"description": "language to use: eg. de, en, or fr",
"type": "string"
},
"lockPassword": {
@@ -2335,9 +2375,10 @@ const docTemplate = `{
"$ref": "#/definitions/users.Permissions"
},
"preview": {
"$ref": "#/definitions/users.PreviewOptions"
"$ref": "#/definitions/users.Preview"
},
"quickDownload": {
"description": "show icon to download in one click",
"type": "boolean"
},
"scope": {
@@ -2350,24 +2391,29 @@ const docTemplate = `{
}
},
"showHidden": {
"description": "show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files",
"type": "boolean"
},
"singleClick": {
"description": "open directory on single click, also enables middle click to open in new tab",
"type": "boolean"
},
"sorting": {
"$ref": "#/definitions/users.Sorting"
},
"stickySidebar": {
"description": "keep sidebar open when navigating",
"type": "boolean"
},
"themeColor": {
"description": "theme color to use: eg. #ff0000, or var(--red), var(--purple), etc",
"type": "string"
},
"username": {
"type": "string"
},
"viewMode": {
"description": "view mode to use: eg. normal, list, grid, or compact",
"type": "string"
}
}
+73 -27
View File
@@ -4,6 +4,23 @@
"contact": {}
},
"paths": {
"/api/auth/logout": {
"get": {
"description": "logs a user out of the application.",
"tags": [
"Auth"
],
"summary": "User Logout",
"responses": {
"302": {
"description": "Redirect to redirect URL if configured in oidc config.",
"schema": {
"type": "string"
}
}
}
}
},
"/api/createApiKey": {
"post": {
"description": "Create an API key with specified name, duration, and permissions.",
@@ -1642,6 +1659,10 @@
"methods": {
"$ref": "#/definitions/settings.LoginMethods"
},
"resetAdminOnStart": {
"description": "if set to true, the admin user will be reset to the default username and password on startup.",
"type": "boolean"
},
"tokenExpirationHours": {
"description": "the number of hours until the token expires. Default is 2 hours.",
"type": "integer"
@@ -1783,19 +1804,7 @@
},
"settings.OidcConfig": {
"type": "object",
"required": [
"authorizationUrl",
"clientId",
"clientSecret",
"scopes",
"tokenUrl",
"userInfoUrl"
],
"properties": {
"authorizationUrl": {
"description": "authorization URL of the OIDC provider",
"type": "string"
},
"clientId": {
"description": "client id of the OIDC application",
"type": "string"
@@ -1804,28 +1813,28 @@
"description": "client secret of the OIDC application",
"type": "string"
},
"disableVerifyTLS": {
"description": "disable TLS verification for the OIDC provider. This is insecure and should only be used for testing.",
"type": "boolean"
},
"enabled": {
"description": "whether to enable OIDC authentication",
"type": "boolean"
},
"jwksUrl": {
"description": "currently not used by filebrowser",
"issuerUrl": {
"description": "authorization URL of the OIDC provider",
"type": "string"
},
"logoutRedirectUrl": {
"description": "if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected.",
"type": "string"
},
"scopes": {
"description": "space separated list of scopes to request from the OIDC provider",
"type": "string"
},
"tokenUrl": {
"description": "token URL of the OIDC provider",
"description": "scopes to request from the OIDC provider",
"type": "string"
},
"userIdentifier": {
"description": "optional: which attribute should be used as the username? options: email, username, name, phone_number, sub",
"type": "string"
},
"userInfoUrl": {
"description": "user info URL of the OIDC provider",
"description": "the user identifier to use for authentication. Default is \"username\", can be \"email\" or \"username\", or \"phone\"",
"type": "string"
}
}
@@ -2088,48 +2097,65 @@
"type": "object",
"properties": {
"darkMode": {
"description": "should dark mode be enabled",
"type": "boolean"
},
"dateFormat": {
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"disableOnlyOfficeExt": {
"description": "comma separated list of file extensions to disable onlyoffice preview for",
"type": "string"
},
"disableSettings": {
"description": "disable the user from viewing the settings page",
"type": "boolean"
},
"gallerySize": {
"description": "0-9 - the size of the gallery thumbnails",
"type": "integer"
},
"locale": {
"description": "language to use: eg. de, en, or fr",
"type": "string"
},
"lockPassword": {
"description": "disable the user from changing their password",
"type": "boolean"
},
"loginMethod": {
"description": "login method to use: eg. password, proxy, oidc",
"type": "string"
},
"permissions": {
"$ref": "#/definitions/users.Permissions"
},
"preview": {
"$ref": "#/definitions/users.PreviewOptions"
"$ref": "#/definitions/users.Preview"
},
"quickDownload": {
"description": "show icon to download in one click",
"type": "boolean"
},
"showHidden": {
"description": "show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files",
"type": "boolean"
},
"singleClick": {
"description": "open directory on single click, also enables middle click to open in new tab",
"type": "boolean"
},
"stickySidebar": {
"description": "keep sidebar open when navigating",
"type": "boolean"
},
"themeColor": {
"description": "theme color to use: eg. #ff0000, or var(--red), var(--purple), etc",
"type": "string"
},
"viewMode": {
"description": "view mode to use: eg. normal, list, grid, or compact",
"type": "string"
}
}
@@ -2231,22 +2257,31 @@
}
}
},
"users.PreviewOptions": {
"users.Preview": {
"type": "object",
"properties": {
"highQuality": {
"description": "generate high quality preview images",
"type": "boolean"
},
"image": {
"description": "show real image as icon instead of generic photo icon",
"type": "boolean"
},
"motionVideoPreview": {
"description": "show multiple frames for videos in preview when hovering",
"type": "boolean"
},
"office": {
"description": "show preview image for office files",
"type": "boolean"
},
"popup": {
"description": "show larger popup preview when hovering",
"type": "boolean"
},
"video": {
"description": "show preview image for video files",
"type": "boolean"
}
}
@@ -2283,24 +2318,29 @@
}
},
"darkMode": {
"description": "should dark mode be enabled",
"type": "boolean"
},
"dateFormat": {
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"disableOnlyOfficeExt": {
"description": "comma separated list of file extensions to disable onlyoffice preview for",
"type": "string"
},
"disableSettings": {
"type": "boolean"
},
"gallerySize": {
"description": "0-9 - the size of the gallery thumbnails",
"type": "integer"
},
"id": {
"type": "integer"
},
"locale": {
"description": "language to use: eg. de, en, or fr",
"type": "string"
},
"lockPassword": {
@@ -2324,9 +2364,10 @@
"$ref": "#/definitions/users.Permissions"
},
"preview": {
"$ref": "#/definitions/users.PreviewOptions"
"$ref": "#/definitions/users.Preview"
},
"quickDownload": {
"description": "show icon to download in one click",
"type": "boolean"
},
"scope": {
@@ -2339,24 +2380,29 @@
}
},
"showHidden": {
"description": "show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files",
"type": "boolean"
},
"singleClick": {
"description": "open directory on single click, also enables middle click to open in new tab",
"type": "boolean"
},
"sorting": {
"$ref": "#/definitions/users.Sorting"
},
"stickySidebar": {
"description": "keep sidebar open when navigating",
"type": "boolean"
},
"themeColor": {
"description": "theme color to use: eg. #ff0000, or var(--red), var(--purple), etc",
"type": "string"
},
"username": {
"type": "string"
},
"viewMode": {
"description": "view mode to use: eg. normal, list, grid, or compact",
"type": "string"
}
}
+76 -24
View File
@@ -100,6 +100,10 @@ definitions:
type: string
methods:
$ref: '#/definitions/settings.LoginMethods'
resetAdminOnStart:
description: if set to true, the admin user will be reset to the default username
and password on startup.
type: boolean
tokenExpirationHours:
description: the number of hours until the token expires. Default is 2 hours.
type: integer
@@ -199,41 +203,33 @@ definitions:
type: object
settings.OidcConfig:
properties:
authorizationUrl:
description: authorization URL of the OIDC provider
type: string
clientId:
description: client id of the OIDC application
type: string
clientSecret:
description: client secret of the OIDC application
type: string
disableVerifyTLS:
description: disable TLS verification for the OIDC provider. This is insecure
and should only be used for testing.
type: boolean
enabled:
description: whether to enable OIDC authentication
type: boolean
jwksUrl:
description: currently not used by filebrowser
issuerUrl:
description: authorization URL of the OIDC provider
type: string
logoutRedirectUrl:
description: if provider logout url is provided, filebrowser will also redirect
to logout url. Custom logout query params are respected.
type: string
scopes:
description: space separated list of scopes to request from the OIDC provider
type: string
tokenUrl:
description: token URL of the OIDC provider
description: scopes to request from the OIDC provider
type: string
userIdentifier:
description: 'optional: which attribute should be used as the username? options:
email, username, name, phone_number, sub'
description: the user identifier to use for authentication. Default is "username",
can be "email" or "username", or "phone"
type: string
userInfoUrl:
description: user info URL of the OIDC provider
type: string
required:
- authorizationUrl
- clientId
- clientSecret
- scopes
- tokenUrl
- userInfoUrl
type: object
settings.OnlyOffice:
properties:
@@ -421,34 +417,55 @@ definitions:
settings.UserDefaults:
properties:
darkMode:
description: should dark mode be enabled
type: boolean
dateFormat:
description: when false, the date is relative, when true, the date is an exact
timestamp
type: boolean
disableOnlyOfficeExt:
description: comma separated list of file extensions to disable onlyoffice
preview for
type: string
disableSettings:
description: disable the user from viewing the settings page
type: boolean
gallerySize:
description: 0-9 - the size of the gallery thumbnails
type: integer
locale:
description: 'language to use: eg. de, en, or fr'
type: string
lockPassword:
description: disable the user from changing their password
type: boolean
loginMethod:
description: 'login method to use: eg. password, proxy, oidc'
type: string
permissions:
$ref: '#/definitions/users.Permissions'
preview:
$ref: '#/definitions/users.PreviewOptions'
$ref: '#/definitions/users.Preview'
quickDownload:
description: show icon to download in one click
type: boolean
showHidden:
description: show hidden files in the UI. On windows this includes files starting
with a dot and windows hidden files
type: boolean
singleClick:
description: open directory on single click, also enables middle click to
open in new tab
type: boolean
stickySidebar:
description: keep sidebar open when navigating
type: boolean
themeColor:
description: 'theme color to use: eg. #ff0000, or var(--red), var(--purple),
etc'
type: string
viewMode:
description: 'view mode to use: eg. normal, list, grid, or compact'
type: string
type: object
share.CreateBody:
@@ -519,17 +536,25 @@ definitions:
share:
type: boolean
type: object
users.PreviewOptions:
users.Preview:
properties:
highQuality:
description: generate high quality preview images
type: boolean
image:
description: show real image as icon instead of generic photo icon
type: boolean
motionVideoPreview:
description: show multiple frames for videos in preview when hovering
type: boolean
office:
description: show preview image for office files
type: boolean
popup:
description: show larger popup preview when hovering
type: boolean
video:
description: show preview image for video files
type: boolean
type: object
users.Sorting:
@@ -553,18 +578,25 @@ definitions:
$ref: '#/definitions/users.AuthToken'
type: object
darkMode:
description: should dark mode be enabled
type: boolean
dateFormat:
description: when false, the date is relative, when true, the date is an exact
timestamp
type: boolean
disableOnlyOfficeExt:
description: comma separated list of file extensions to disable onlyoffice
preview for
type: string
disableSettings:
type: boolean
gallerySize:
description: 0-9 - the size of the gallery thumbnails
type: integer
id:
type: integer
locale:
description: 'language to use: eg. de, en, or fr'
type: string
lockPassword:
type: boolean
@@ -579,8 +611,9 @@ definitions:
permissions:
$ref: '#/definitions/users.Permissions'
preview:
$ref: '#/definitions/users.PreviewOptions'
$ref: '#/definitions/users.Preview'
quickDownload:
description: show icon to download in one click
type: boolean
scope:
type: string
@@ -589,23 +622,42 @@ definitions:
$ref: '#/definitions/users.SourceScope'
type: array
showHidden:
description: show hidden files in the UI. On windows this includes files starting
with a dot and windows hidden files
type: boolean
singleClick:
description: open directory on single click, also enables middle click to
open in new tab
type: boolean
sorting:
$ref: '#/definitions/users.Sorting'
stickySidebar:
description: keep sidebar open when navigating
type: boolean
themeColor:
description: 'theme color to use: eg. #ff0000, or var(--red), var(--purple),
etc'
type: string
username:
type: string
viewMode:
description: 'view mode to use: eg. normal, list, grid, or compact'
type: string
type: object
info:
contact: {}
paths:
/api/auth/logout:
get:
description: logs a user out of the application.
responses:
"302":
description: Redirect to redirect URL if configured in oidc config.
schema:
type: string
summary: User Logout
tags:
- Auth
/api/createApiKey:
post:
consumes:
+5 -4
View File
@@ -116,12 +116,13 @@ func buildNode(v reflect.Value, comm commentsMap) (*yaml.Node, error) {
// attach validate and comments inline
var parts []string
if vt := sf.Tag.Get("validate"); vt != "" {
parts = append(parts, fmt.Sprintf("validate:%s", vt))
}
if cm := comm[typeName][sf.Name]; cm != "" {
parts = append(parts, fmt.Sprintf("comments:\"%s\"", cm))
parts = append(parts, fmt.Sprintf("\"%s\"", cm))
}
if vt := sf.Tag.Get("validate"); vt != "" {
parts = append(parts, fmt.Sprintf(" validate:%s", vt))
}
if len(parts) > 0 {
keyNode.LineComment = strings.Join(parts, " ")
}
+40 -1
View File
@@ -6,9 +6,48 @@
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript"
"@vue/eslint-config-typescript",
"plugin:@intlify/vue-i18n/recommended"
],
"settings": {
"vue-i18n": {
// Path to your locale message directory or a glob pattern.
// The plugin will use these files to determine available keys.
// Make sure 'en.json' is present here.
"localeDir": "./src/i18n/*.json"
// If your i18n instance is initialized with a fallbackLocale,
// the linter might use that. Ensure 'en' is effectively the master.
// fallbackLocale: 'en', // This might be part of your i18n setup, not ESLint settings
}
},
"rules": {
// vue-i18n rules:
// This rule will check if the key used in $t() exists in your locale messages.
// By default, it checks against all locales. We want it to primarily use 'en.json'
// as the source of truth. The plugin often infers this from your i18n setup,
// but we want to be explicit or ensure our sync script makes 'en.json' the master.
"@intlify/vue-i18n/no-missing-keys": "error",
// Optional: Warn about unused keys in your 'en.json'
// This requires configuring the `localeDir` setting below.
"@intlify/vue-i18n/no-unused-keys": ["warn", {
"src": "./src", // Path to your source files
"extensions": [".js", ".vue"]
// Important: This tells the rule to check unused keys specifically in en.json
// by making it the single source of truth for what "should" exist.
// However, the rule usually checks keys NOT used in your Vue code.
// The primary goal is `no-missing-keys` for keys used in the template.
}],
"@intlify/vue-i18n/no-raw-text": [
"warn",
{
"ignoreNodes": ["i", "v-icon"]
}
],
// If you have your sync script, you might not need this one for other locales.
"@intlify/vue-i18n/no-missing-keys-in-other-locales": "off",
"vue/multi-word-component-names": "off",
"vue/no-mutating-props": [
"error",
+4
View File
@@ -16,6 +16,7 @@
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
"lint": "eslint --ext .js,.vue,ts src",
"lint:fix": "eslint --fix src/",
"i18n:sync": "node ./scripts/sync-translations.js",
"format": "prettier --write .",
"test": "vitest run "
},
@@ -37,13 +38,16 @@
"vue-router": "^4.3.0"
},
"devDependencies": {
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@playwright/test": "^1.49.1",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-typescript": "^13.0.0",
"deepl-node": "^1.18.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.24.0",
"fs-extra": "^11.3.0",
"jsdom": "^25.0.1",
"vite": "^6.2.0",
"vite-plugin-compression2": "^1.0.0",
+114
View File
@@ -0,0 +1,114 @@
server:
numImageProcessors: 0 # "number of concurrent image processing jobs"
socket: "" # "socket to listen on"
tlsKey: "" # "path to TLS key"
tlsCert: "" # "path to TLS cert"
disablePreviews: false # "disable all previews thumbnails, simple icons will be used"
disablePreviewResize: false # "disable resizing of previews for faster loading over slow connections"
disableTypeDetectionByHeader: false # "disable type detection by header, useful if filesystem is slow."
port: 0 # "port to listen on"
baseURL: "" # "base URL for the server, the subpath that the server is running on."
logging:
- levels: "" # "separated list of log levels to enable. (eg. "info|warning|error|debug")"
apiLevels: "" # "separated list of log levels to enable for the API. (eg. "info|warning|error")"
output: "" # "output location. (eg. "stdout" or "path/to/file.log")"
noColors: false # "disable colors in the output"
json: false # "output in json format, currently not supported"
database: "" # "path to the database file"
sources: # validate:required,dive
- path: "" # "file system path. (Can be relative)" validate:required
name: "" # "display name"
config:
indexingInterval: 0 # "optional manual overide interval in seconds to re-index the source"
disabled: false # "disable the indexing of this source"
maxWatchers: 0 # "number of concurrent watchers to use for this source, currently not supported"
neverWatchPaths: []
ignoreHidden: false # "ignore hidden files and folders."
ignoreZeroSizeFolders: false # "ignore folders with 0 size"
exclude: # "exclude files and folders from indexing, if include is not set"
files: []
folders: []
fileEndsWith: []
include: # "include files and folders from indexing, if exclude is not set"
files: []
folders: []
fileEndsWith: []
defaultUserScope: "" # "default "/" should match folders under path"
defaultEnabled: false # "should be added as a default source for new users?"
createUserDir: false # "create a user directory for each user"
externalUrl: "" # "used by share links if set"
internalUrl: "" # "used by integrations if set, this is the url that an integration service will use to communicate with filebrowser"
cacheDir: "" # "path to the cache directory, used for thumbnails and other cached files"
maxArchiveSize: 0 # "max pre-archive combined size of files/folder that are allowed to be archived (in GB)"
auth:
tokenExpirationHours: 0 # "the number of hours until the token expires. Default is 2 hours."
methods:
proxy: # validate:omitempty
enabled: false
createUser: false # "create user if not exists"
header: "" # "required header to use for authentication. Security Warning: FileBrowser blindly accepts the header value as username."
noauth: false # "if set to true, overrides all other auth methods and disables authentication"
password: # validate:omitempty
enabled: false
minLength: 0 # validate:omitempty,min=5
signup: false # "currently not used by filebrowser" validate:omitempty
recaptcha: # "recaptcha config, only used if signup is enabled" validate:omitempty
host: "" # validate:required
key: "" # validate:required
secret: "" # validate:required
oidc: # validate:omitempty
enabled: false # "whether to enable OIDC authentication"
clientId: "" # "client id of the OIDC application"
clientSecret: "" # "client secret of the OIDC application"
issuerUrl: "" # "authorization URL of the OIDC provider"
scopes: "" # "scopes to request from the OIDC provider"
userIdentifier: "" # "the user identifier to use for authentication. Default is "username", can be "email" or "username", or "phone""
disableVerifyTLS: false # "disable TLS verification for the OIDC provider. This is insecure and should only be used for testing."
logoutRedirectUrl: "" # "if provider logout url is provided, filebrowser will also redirect to logout url. Custom logout query params are respected."
key: "" # "the key used to sign the JWT tokens. If not set, a random key will be generated."
adminUsername: "" # "the username of the admin user. If not set, the default is "admin"."
adminPassword: "" # "the password of the admin user. If not set, the default is "admin"."
resetAdminOnStart: false # "if set to true, the admin user will be reset to the default username and password on startup."
frontend:
name: "" # "display name"
disableDefaultLinks: false # "disable default links in the sidebar"
disableUsedPercentage: false # "disable used percentage for the sources in the sidebar"
externalLinks:
- text: "" # "the text to display on the link" validate:required
title: "" # "the title to display on hover"
url: "" # "the url to link to" validate:required
userDefaults:
stickySidebar: false # "keep sidebar open when navigating"
darkMode: false # "should dark mode be enabled"
locale: "" # "language to use: eg. de, en, or fr"
viewMode: "" # "view mode to use: eg. normal, list, grid, or compact"
singleClick: false # "open directory on single click, also enables middle click to open in new tab"
showHidden: false # "show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files"
dateFormat: false # "when false, the date is relative, when true, the date is an exact timestamp"
gallerySize: 0 # "0-9 - the size of the gallery thumbnails"
themeColor: "" # "theme color to use: eg. #ff0000, or var(--red), var(--purple), etc"
quickDownload: false # "show icon to download in one click"
disableOnlyOfficeExt: "" # "comma separated list of file extensions to disable onlyoffice preview for"
lockPassword: false # "disable the user from changing their password"
disableSettings: false # "disable the user from viewing the settings page"
preview:
highQuality: false
image: false
video: false
motionVideoPreview: false
office: false
popup: false
permissions:
api: false
admin: false
modify: false
share: false
realtime: false
loginMethod: "" # "login method to use: eg. password, proxy, oidc"
integrations:
office: # validate:omitempty
url: "" # "The URL to the OnlyOffice Document Server, needs to be accessible to the user." validate:required
internalUrl: "" # "An optional internal address that the filebrowser server can use to communicate with the OnlyOffice Document Server, could be useful to bypass proxy."
secret: "" # validate:required
media: # validate:omitempty
ffmpegPath: "" # "path to ffmpeg directory with ffmpeg and ffprobe (eg. /usr/local/bin)"
+152
View File
@@ -0,0 +1,152 @@
// scripts/sync-translations.js
import fs from 'fs-extra';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import glob from 'glob';
import * as deepl from 'deepl-node';
// --- Configuration ---
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const localesDir = path.resolve(__dirname, '../src/i18n');
const masterLocaleFile = path.join(localesDir, 'en.json');
const masterLanguageCode = 'en';
const targetLocaleFiles = glob.sync(path.join(localesDir, '*.json'))
.filter(file => path.basename(file) !== `${masterLanguageCode}.json`);
const DEEPL_API_KEY = process.env.DEEPL_API_KEY;
if (!DEEPL_API_KEY) {
console.error("❌ Missing DEEPL_API_KEY in environment.");
process.exit(1);
}
const translator = new deepl.Translator(DEEPL_API_KEY);
// --- Translation Function using DeepL ---
async function translateText(text, targetLanguage, keyPath = '') {
if (!text || typeof text !== 'string' || text.trim() === '') {
console.warn(`Skipping translation for empty or non-string text: "${text}"`);
return text;
}
if (keyPath === 'languages' || keyPath.startsWith('languages.')) {
return text;
}
if (/\{[^}]+\}/.test(text)) {
console.log(`Skipping translation for placeholder string: "${text}"`);
return text;
}
try {
console.log(`Translating "${text}" from '${masterLanguageCode}' to '${targetLanguage}'...`);
const result = await translator.translateText(text, masterLanguageCode, targetLanguage.toUpperCase());
// Delay to avoid rate-limiting
await new Promise(resolve => setTimeout(resolve, 100));
return result.text;
} catch (err) {
console.error(`⚠️ Translation failed for "${text}" (${keyPath}):`, err?.message || err);
return ``;
}
}
// --- Recursive key processor ---
async function processKeys(masterObj, targetObj, targetLangCode, currentPathParts = []) {
let changesMade = false;
for (const key in masterObj) {
if (Object.prototype.hasOwnProperty.call(masterObj, key)) {
const currentPathPartsNext = [...currentPathParts, key];
const currentKeyPath = currentPathPartsNext.join('.');
if (currentPathParts[0] === 'languages') continue;
const masterValue = masterObj[key];
if (typeof masterValue === 'object' && masterValue !== null && !Array.isArray(masterValue)) {
if (!targetObj[key] || typeof targetObj[key] !== 'object') {
console.log(`Creating missing object structure for "${currentKeyPath}" in ${targetLangCode}.json`);
targetObj[key] = {};
changesMade = true;
}
const result = await processKeys(masterValue, targetObj[key], targetLangCode, currentPathPartsNext);
if (result == "UNSUPPORTED") {
console.log(`Skipping translation for "${targetLangCode}" due to unsupported structure.`);
return "UNSUPPORTED";
}
if (result) {
changesMade = true;
}
} else if (typeof masterValue === 'string') {
if (!targetObj.hasOwnProperty(key) || targetObj[key] === '' || targetObj[key] === null) {
const result = await translateText(masterValue, targetLangCode, currentKeyPath);
if (result == "") {
return "UNSUPPORTED";
}
targetObj[key] = result;
changesMade = true;
}
} else {
if (!targetObj.hasOwnProperty(key)) {
console.log(`Key "${currentKeyPath}" (non-string) missing in ${targetLangCode}.json. Copying from English.`);
targetObj[key] = masterValue;
changesMade = true;
}
}
}
}
return changesMade;
}
// --- Main synchronization ---
async function syncAllTranslations() {
console.warn("--- Using DeepL API for translation ---");
if (!await fs.pathExists(masterLocaleFile)) {
console.error(`Master locale file not found: ${masterLocaleFile}`);
process.exit(1);
}
const masterContent = await fs.readJson(masterLocaleFile);
console.log(`Loaded master translations from ${masterLocaleFile}`);
for (const targetFile of targetLocaleFiles) {
const targetLangCode = path.basename(targetFile, '.json');
let targetContent = {};
let fileExisted = await fs.pathExists(targetFile);
if (fileExisted) {
try {
targetContent = await fs.readJson(targetFile);
console.log(`\nProcessing target language: ${targetLangCode} (from ${targetFile})`);
} catch (e) {
console.warn(`Warning: Could not parse ${targetFile}. Starting fresh. Error: ${e.message}`);
targetContent = {};
}
} else {
console.log(`\nTarget file ${targetFile} not found. Will create for language: ${targetLangCode}.`);
}
const wasUpdated = await processKeys(masterContent, targetContent, targetLangCode);
if (wasUpdated || !fileExisted) {
try {
await fs.writeJson(targetFile, targetContent, { spaces: 2 });
console.log(`Successfully ${wasUpdated ? 'updated' : 'created'} ${targetFile}`);
} catch (error) {
console.error(`Error writing to ${targetFile}:`, error);
}
} else {
console.log(`No changes needed for ${targetFile}`);
}
}
console.log('\n✅ Translation synchronization complete (via DeepL).');
}
syncAllTranslations().catch(error => {
console.error("\n❌ An error occurred during translation synchronization:", error);
process.exit(1);
});
+1 -1
View File
@@ -230,7 +230,7 @@ export function getPreviewURL(source, path, modified) {
const params = {
path: encodeURIComponent(path),
size: state.user.preview.highQuality ? 'large' : 'small',
key: Date.parse(modified),
key: Date.parse(modified), // Use modified date as cache key
source: source,
inline: 'true'
}
+1 -1
View File
@@ -49,7 +49,7 @@ export default {
return state.req;
},
multistate() {
if (!state.multiButtonState) {
if (!state.multiButtonState && getters.currentView() == "settings") {
return "close";
}
return state.multiButtonState;
+1 -1
View File
@@ -19,7 +19,7 @@
</li>
</ul>
<div v-if="isCardView" class="gallery-size">
Size:
{{ $t('files.size') }}:<span class="sr-only">:</span> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<input
v-model="gallerySize"
type="range"
+3 -2
View File
@@ -11,7 +11,7 @@
:class="{ 'dark-mode': isDarkMode, centered: centered }"
>
<div v-if="selectedCount > 0" class="button selected-count-header">
<span>{{ selectedCount }} selected</span>
<span>{{ selectedCount }} {{ $t('prompts.selected') }} </span>
</div>
<action
@@ -265,7 +265,8 @@ export default {
buttons.done(button);
notify.showError("Error saving file: ", e);
}
mutations.closeHovers();
mutations.closeHovers();
},
},
};
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="card" id="popup-notification">
<i v-on:click="closePopUp" class="material-icons">close</i>
<canvas class="notification-spinner hidden" width="100" height="100"></canvas>
<div id="popup-notification-content">no info</div>
<div id="popup-notification-content"></div>
</div>
</template>
+8 -23
View File
@@ -81,7 +81,7 @@
<!-- Inputs for filtering by file size -->
<div class="sizeConstraints">
<div class="sizeInputWrapper">
<p>Smaller Than:</p>
<p> {{ $t('search.smallerThan') }} </p>
<input
class="sizeInput"
v-model="smallerThan"
@@ -89,17 +89,17 @@
min="0"
placeholder="number"
/>
<p>MB</p>
<p>MB</p> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</div>
<div class="sizeInputWrapper">
<p>Larger Than:</p>
<p> {{ $t('search.largerThan') }} </p>
<input
class="sizeInput"
v-model="largerThan"
type="number"
placeholder="number"
/>
<p>MB</p>
<p>MB</p> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</div>
</div>
</div>
@@ -112,27 +112,12 @@
<!-- Message when no results are found -->
<div class="searchPrompt" v-show="isEmpty && !isRunning">
<p>{{ noneMessage }}</p>
<div class="helpButton" @click="toggleHelp()">Help</div>
<div class="helpButton" @click="toggleHelp()"> {{ $t('sidebar.help') }} </div>
</div>
<!-- Help text section -->
<div class="helpText" v-if="showHelp">
<p>
Search occurs on each character you type (3 character minimum for search
terms).
</p>
<p>
<b>The index:</b> Search utilizes the index which automatically gets updated
on the configured interval (default: 5 minutes). Searching when the program
has just started may result in incomplete results.
</p>
<p>
<b>Filter by type:</b> You can have multiple type filters by adding
<code>type:condition</code> followed by search terms.
</p>
<p>
<b>Multiple Search terms:</b> Additional terms separated by <code>|</code>,
for example <code>"test|not"</code> searches for both terms independently.
</p>
<p>{{ $t('search.helpText1') }}</p>
<p>{{ $t('search.helpText2') }}</p>
</div>
<!-- List of search results -->
<ul v-show="results.length > 0">
@@ -146,7 +131,7 @@
<a :href="getRelative(s.path)" @contextmenu="addSelected(event, s)">
<Icon :mimetype="s.type" />
<span class="text-container">
{{ basePath(s.path, s.type == "directory") }}/{{ baseName(s.path) }}/
{{ basePath(s.path, s.type == "directory") }}/{{ baseName(s.path) }}/ <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</span>
<div class="filesize">{{ humanSize(s.size) }}</div>
</a>
@@ -1,7 +1,7 @@
<template>
<div class="image-ex-container" ref="container" @touchstart="touchStart" @touchmove="touchMove" @dblclick="zoomAuto"
@mousedown="mousedownStart" @mousemove="mouseMove" @mouseup="mouseUp" @wheel="wheelMove">
<div v-if="!isLoaded">Loading image...</div>
<div v-if="!isLoaded">{{ $t('files.loading') }}</div>
<img v-if="!isTiff && isLoaded" :src="src" class="image-ex-img" ref="imgex" @load="onLoad" />
<canvas v-else-if="isLoaded" ref="imgex" class="image-ex-img"></canvas>
+86 -2
View File
@@ -1,9 +1,16 @@
<template>
<span v-if="isPreviewImg">
<i
v-if="hasMotion"
class="material-icons"
:class="{ larger: showLarger, smaller: !showLarger }"
>animation</i
>
<img
@mouseenter="handleMouseEnter($event)"
@mouseleave="handleMouseLeave($event)"
v-lazy="thumbnailUrl"
:src="currentThumbnail"
class="icon"
ref="thumbnail"
/>
@@ -38,9 +45,22 @@ export default {
materialIcon: "",
classes: "",
svgPath: "",
previewTimeouts: [], // Store timeout IDs
currentThumbnail: this.thumbnailUrl,
};
},
computed: {
showLarger() {
return state.user.viewMode === "gallery" || state.user.viewMode === "normal";
},
hasMotion() {
return (
this.getIconForType().simpleType === "video" &&
state.user.preview?.video &&
mediaAvailable &&
state.user.preview.motionVideoPreview
);
},
isMaterialIcon() {
return this.materialIcon !== "";
},
@@ -81,14 +101,64 @@ export default {
},
methods: {
handleMouseEnter() {
if (state.user.viewMode == "gallery" && !state.user.preview.highQuality) {
// skip popup for gallary view with small images
if (state.user.viewMode === "gallery" && !state.user.preview.highQuality) {
return;
}
mutations.setPreviewSource(this.thumbnailUrl);
if (!state.user.preview.motionVideoPreview) {
return;
}
const sequence = [
this.thumbnailUrl,
this.thumbnailUrl + "&atPercentage=25",
this.thumbnailUrl + "&atPercentage=50",
this.thumbnailUrl + "&atPercentage=75",
];
let index = 0;
const updateThumbnailUrl = () => {
if (state.popupPreviewSource === "") {
this.previewTimeouts.forEach(clearTimeout);
this.previewTimeouts = [];
return;
}
const currentUrl = sequence[index];
const img = new Image();
img.onload = () => {
// Set the thumbnail or popup preview
if (state.user.preview.popup) {
mutations.setPreviewSource(currentUrl);
} else {
this.currentThumbnail = currentUrl;
}
// Preload the next image if it exists
const nextIndex = (index + 1) % sequence.length;
const nextUrl = sequence[nextIndex];
const preloadImg = new Image();
preloadImg.src = nextUrl;
// Schedule next update
index = nextIndex;
const timeoutId = setTimeout(updateThumbnailUrl, 750);
this.previewTimeouts.push(timeoutId);
};
img.src = currentUrl;
};
updateThumbnailUrl();
},
handleMouseLeave() {
this.previewTimeouts.forEach(clearTimeout);
this.previewTimeouts = [];
mutations.setPreviewSource("");
this.currentThumbnail = this.thumbnailUrl;
},
getIconForType() {
return getTypeInfo(this.mimetype);
@@ -105,6 +175,20 @@ export default {
</script>
<style>
.larger {
position: absolute;
opacity: 0.5;
padding: 0.1em !important;
font-size: 2em !important;
}
.smaller {
position: absolute;
opacity: 0.5;
padding: 0.1em !important;
font-size: 1em !important;
}
.file-icons [aria-label^="."] {
opacity: 0.33;
}
+1 -1
View File
@@ -20,7 +20,7 @@
</div>
<div v-else class="thumb-letters no-select">
<i class="material-icons" :class="{ 'primary-icons': isFolder }">
{{ isFolder ? "folder" : "description" }}
{{ isFolder ? "folder" : "description" }} <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</i>
</div>
</div>
@@ -1,7 +1,7 @@
<template>
<div class="card floating">
<div class="card-title">
<h2>API Key Details</h2>
<h2>{{ $t('api.title') }}</h2>
</div>
<div class="card-content">
@@ -11,20 +11,20 @@
:aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')"
>
API Key Name : {{ name }}
{{ $t('api.keyName') }}{{ name }}
<i class="material-icons">content_paste</i>
</button>
<h3>Created At :</h3>
<h3>{{ $t('api.createdAt') }}</h3>
{{ formatTime(info.created) }}
<h3>Expires At :</h3>
<h3>{{ $t('api.expiresAt') }}</h3>
{{ formatTime(info.expires) }}
<h3>Permissions:</h3>
<h3>{{ $t('api.permissions') }}</h3>
<table>
<tbody>
<tr v-for="(isEnabled, permission) in info.Permissions" :key="permission">
<td>{{ permission }}</td>
<td>{{ isEnabled ? "✓" : "✗" }}</td>
<td>{{ isEnabled ? '✓' : '✗' }}</td> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</tr>
</tbody>
</table>
@@ -37,14 +37,14 @@
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')"
>
{{ $t("buttons.close") }}
{{ $t('buttons.close') }}
</button>
<button
class="button button--flat button--red"
@click="deleteApi"
:title="$t('buttons.delete')"
>
{{ $t("buttons.delete") }}
{{ $t('buttons.delete') }}
</button>
</div>
</div>
+10 -13
View File
@@ -1,40 +1,37 @@
<template>
<div class="card floating create-api__prompt__card" id="create-api">
<div class="card-title">
<h2>Create API Key</h2>
<h2>{{ $t('api.createTitle') }}</h2>
</div>
<div class="card-content">
<!-- API Key Name Input -->
<p>API Key Name</p>
<p>{{ $t('api.keyName') }}</p>
<input
class="input input--block"
type="text"
v-model.trim="apiName"
placeholder="enter a uinque api key name"
:placeholder="$t('api.keyNamePlaceholder')"
/>
<!-- Duration Input -->
<p>Token Duration</p>
<p>{{ $t('api.tokenDuration') }}</p>
<div class="inputWrapper">
<input
class="sizeInput roundedInputLeft input"
v-model.number="duration"
type="number"
min="1"
placeholder="number"
:placeholder="$t('api.durationNumberPlaceholder')"
/>
<select v-model="unit" class="roundedInputRight input">
<option value="days">days</option>
<option value="months">months</option>
<option value="days">{{ $t('api.days') }}</option>
<option value="months">{{ $t('api.months') }}</option>
</select>
</div>
<!-- Permissions Input -->
<p>
Choose at least one permission for the key. Your User must also have the
permission.
</p>
<p>{{ $t('api.permissionNote') }}</p>
<div class="settings-items">
<ToggleSwitch
v-for="(isEnabled, permission) in permissions"
@@ -114,10 +111,10 @@ export default {
};
await usersApi.createApiKey(params);
notify.showSuccess("successfully created!");
notify.showSuccess($t("api.createKeySuccess"));
window.location.reload();
} catch (error) {
notify.showError(this.$t("errors.createKeyFailed"));
notify.showError($t("api.createKeyFailed"));
}
},
},
@@ -1,7 +1,7 @@
<template>
<div class="card floating">
<div class="card-content">
<p>Are you sure you want to delete this user?</p>
<p>{{$t('prompts.deleteUserMessage')}}</p>
</div>
<div class="card-action">
<button
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div>
<div aria-label="filelist-path" class="searchContext">Path: {{ nav }}</div>
<div aria-label="filelist-path" class="searchContext">{{$t('search.path')}} {{ nav }}</div>
<ul class="file-list">
<li
@click="itemClick"
+9 -9
View File
@@ -6,15 +6,15 @@
<div class="card-content">
<ul>
<li><strong>F1</strong> - {{ $t("help.f1") }}</li>
<li><strong>F2</strong> - {{ $t("help.f2") }}</li>
<li><strong>DEL</strong> - {{ $t("help.del") }}</li>
<li><strong>ESC</strong> - {{ $t("help.esc") }}</li>
<li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li>
<li><strong>CTRL + F</strong> - {{ $t("help.ctrl.f") }}</li>
<li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li>
<li><strong>Click</strong> - {{ $t("help.click") }}</li>
<li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li>
<li><strong>F1</strong> - {{ $t("help.f1") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>F2</strong> - {{ $t("help.f2") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>DEL</strong> - {{ $t("help.del") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>ESC</strong> - {{ $t("help.esc") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>CTRL + F</strong> - {{ $t("help.ctrl.f") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>Click</strong> - {{ $t("help.click") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</ul>
</div>
+13 -13
View File
@@ -13,48 +13,48 @@
<strong>{{ $t("prompts.displayName") }}</strong> {{ name }}
</p>
<p v-if="!dir || selected.length > 1">
<strong>{{ $t("prompts.size") }}:</strong>
<strong>{{ $t("prompts.size") }}:</strong> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<span id="content_length"></span> {{ humanSize }}
</p>
<p v-if="!dir || selected.length > 1">
<strong>Type:</strong>
<strong>{{$t('prompts.typeName')}}</strong>
<span id="content_length"></span> {{ type }}
</p>
<p v-if="selected.length < 2" :title="modTime">
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }} <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</p>
<template v-if="dir && selected.length === 0">
<p>
<strong>{{ $t("prompts.numberFiles") }}:</strong> {{ req.numFiles }}
<strong>{{ $t("prompts.numberFiles") }}:</strong> {{ req.numFiles }} <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</p>
<p>
<strong>{{ $t("prompts.numberDirs") }}:</strong> {{ req.numDirs }}
<strong>{{ $t("prompts.numberDirs") }}:</strong> {{ req.numDirs }} <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</p>
</template>
<template v-if="!dir">
<p>
<strong>MD5: </strong
><code
<strong>MD5: </strong> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<code
><a @click="checksum($event, 'md5')">{{ $t("prompts.show") }}</a></code
>
</p>
<p>
<strong>SHA1: </strong
><code
<strong>SHA1: </strong> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<code
><a @click="checksum($event, 'sha1')">{{ $t("prompts.show") }}</a></code
>
</p>
<p>
<strong>SHA256: </strong
><code
<strong>SHA256: </strong> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<code
><a @click="checksum($event, 'sha256')">{{ $t("prompts.show") }}</a></code
>
</p>
<p>
<strong>SHA512: </strong
><code
<strong>SHA512: </strong> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<code
><a @click="checksum($event, 'sha512')">{{ $t("prompts.show") }}</a></code
>
</p>
+1 -2
View File
@@ -6,8 +6,7 @@
<div class="card-content">
<p>
{{ $t("prompts.renameMessage") }} <code>{{ oldName() }}</code
>:
{{ $t("prompts.renameMessage") }} <code>{{ oldName() }}</code>: <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</p>
<input
class="input input--block"
+3 -6
View File
@@ -3,17 +3,14 @@
<div class="card-title">
<h2>{{ $t("buttons.share") }}</h2>
</div>
<div aria-label="share-path" class="searchContext">Path: {{ subpath }}</div>
<p>
Note: anyone who has access to the link (and optional password) can access the
shared files. There is no requirement to be logged in.
</p>
<div aria-label="share-path" class="searchContext"> {{$t('search.path')}} {{ subpath }}</div>
<p> {{ $t('share.notice') }} </p>
<template v-if="listing">
<div class="card-content">
<table>
<tbody>
<tr>
<th>#</th>
<th>#</th> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<th>{{ $t("settings.shareDuration") }}</th>
<th></th>
<th></th>
@@ -1,7 +1,7 @@
<template>
<select @change="change" :value="locale">
<option v-for="(value, label) in locales" :key="label" :value="label">
{{ $t("languages." + label) }}
{{ $t("languages." + value) }}
</option>
</select>
</template>
+98 -37
View File
@@ -1,18 +1,43 @@
<template>
<div
v-if="!stateUser.permissions.admin && !isNew && stateUser.loginMethod == 'password'"
<h2
class="message"
v-if="user.loginMethod != 'password' && !stateUser.permissions.admin"
>
<i class="material-icons">sentiment_dissatisfied</i>
<span>{{ $t("files.lonely") }}</span>
</h2>
<div v-if="user.loginMethod == 'password' && passwordAvailable && !isNew">
<label for="password">{{ $t("settings.password") }}</label>
<input
class="input input--block"
type="password"
placeholder="enter new password"
v-model="user.password"
id="password"
@input="setUpdatePassword"
/>
<div class="form-group">
<input
class="input input--block form-form"
:class="{ 'invalid-form': invalidPassword }"
type="password"
:placeholder="$t('settings.enterPassword')"
v-model="passwordRef"
/>
</div>
<div class="form-group">
<input
class="input input--block form-form"
:class="{ 'flat-right': !isNew, 'invalid-form': invalidPassword }"
type="password"
:placeholder="$t('settings.enterPasswordAgain')"
v-model="user.password"
id="password"
/>
<button
v-if="!isNew"
type="button"
class="button form-button"
@click="submitUpdatePassword"
>
{{ $t("buttons.update") }}
</button>
</div>
<hr />
</div>
<div v-else>
<div v-if="stateUser.permissions.admin">
<p v-if="isNew">
<label for="username">{{ $t("settings.username") }}</label>
<input
@@ -24,26 +49,41 @@
/>
</p>
<p v-if="stateUser.loginMethod == 'password'">
<label for="password">{{ $t("settings.password") }}</label>
<div v-if="user.loginMethod == 'password' && passwordAvailable && isNew">
<label for="password">{{ $t("settings.password") }}</label>
<div class="form-group">
<input
class="input input--block"
class="input input--block form-form"
:class="{ 'invalid-form': invalidPassword }"
type="password"
:placeholder="passwordPlaceholder"
:placeholder="$t('settings.enterPassword')"
v-model="passwordRef"
/>
</div>
<div class="form-group">
<input
class="input input--block form-form"
:class="{ 'flat-right': !isNew, 'invalid-form': invalidPassword }"
type="password"
:placeholder="$t('settings.enterPasswordAgain')"
v-model="user.password"
id="password"
@input="emitUpdate"
/>
</p>
<button
v-if="!isNew"
type="button"
class="button form-button"
@click="submitUpdatePassword"
>
{{ $t("buttons.update") }}
</button>
</div>
</div>
<div class="settings-items">
<ToggleSwitch
class="item"
v-if="user.loginMethod === 'password'"
:modelValue="updatePassword"
@update:modelValue="$emit('update:updatePassword', $event)"
:name="$t('settings.changePassword')"
/>
<div
v-if="user.loginMethod == 'password' && passwordAvailable"
class="settings-items"
>
<ToggleSwitch
v-if="user.loginMethod === 'password' && stateUser.permissions?.admin"
class="item"
@@ -53,7 +93,7 @@
/>
</div>
<div v-if="stateUser.permissions.admin">
<div style="padding-bottom: 1em" v-if="stateUser.permissions.admin">
<label for="scopes">{{ $t("settings.scopes") }}</label>
<div
class="scope-list"
@@ -110,7 +150,14 @@
@input="emitUpdate"
></languages>
</p>
<div v-if="stateUser.permissions.admin">
<label for="loginMethod">{{ $t("settings.loginMethodDescription") }}</label>
<select v-model="user.loginMethod" class="input input--block" id="loginMethod">
<option value="password">Password</option>
<option value="oidc">OIDC</option>
<option value="proxy">Proxy</option>
</select>
</div>
<permissions v-if="stateUser.permissions.admin" :permissions="user.permissions" />
</div>
</template>
@@ -119,8 +166,10 @@
import Languages from "./Languages.vue";
import Permissions from "./Permissions.vue";
import { state } from "@/store";
import { settingsApi } from "@/api";
import ToggleSwitch from "@/components/settings/ToggleSwitch.vue";
import { notify } from "@/notify";
import { usersApi, settingsApi } from "@/api";
import { passwordAvailable } from "@/utils/constants";
export default {
name: "UserForm",
@@ -131,7 +180,6 @@ export default {
},
props: {
user: Object,
updatePassword: Boolean,
isNew: Boolean,
},
data() {
@@ -141,6 +189,7 @@ export default {
sourceList: [],
availableSources: [],
selectedSources: [],
passwordRef: "",
};
},
async mounted() {
@@ -150,6 +199,7 @@ export default {
this.sourceList = await settingsApi.get("sources");
}
this.user.password = this.user.password || "";
this.selectedSources = this.user.scopes || [];
this.availableSources = this.sourceList.filter(
(s) => !this.selectedSources.some((sel) => sel.name === s.name)
@@ -170,6 +220,11 @@ export default {
},
},
computed: {
invalidPassword() {
const matching = this.user.password != this.passwordRef && this.user.password.length > 0 ;
return matching;
},
passwordAvailable: () => passwordAvailable,
duplicateSources() {
const names = this.selectedSources.map((s) => s.name);
return names.filter((name, idx) => names.indexOf(name) !== idx);
@@ -188,6 +243,19 @@ export default {
},
},
methods: {
async submitUpdatePassword() {
event.preventDefault();
if (this.invalidPassword) {
notify.showError(this.$t("settings.passwordsDoNotMatch"));
return;
}
try {
await usersApi.update(this.user, ["password"]);
notify.showSuccess(this.$t("settings.userUpdated"));
} catch (e) {
notify.showError(e);
}
},
emitUserUpdate() {
this.$emit("update:user", { ...this.user, scopes: this.selectedSources });
},
@@ -239,14 +307,7 @@ export default {
.scope-list {
display: flex;
}
.flat-right {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.flat-left {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.scope-input {
width: 100%;
}
+26 -24
View File
@@ -1,9 +1,10 @@
<template>
<div class="tooltip">
<span class="tooltiptext-first" :class="{ visible: hoverText != '' }">{{
this.hoverText
}}</span>
<span class="tooltiptext-first" :class="{ visible: hoverText != '' }">
{{ hoverText }}
</span>
</div>
<div v-if="hasSourceInfo" class="tooltip-sources">
<div
class="tooltiptext-sources"
@@ -18,37 +19,38 @@
</thead>
<tbody>
<tr>
<td>Status</td>
<td>{{ $t("index.status") }}</td>
<td>{{ sourceInfoTooltip.status }}</td>
</tr>
<tr>
<td>Assessment</td>
<td>{{ $t("index.assessment") }}</td>
<td>{{ sourceInfoTooltip.assessment }}</td>
</tr>
<tr>
<td>Files</td>
<td>{{ $t("index.files") }}</td>
<td>{{ sourceInfoTooltip.files }}</td>
</tr>
<tr>
<td>Folders</td>
<td>{{ $t("index.folders") }}</td>
<td>{{ sourceInfoTooltip.folders }}</td>
</tr>
<tr>
<td>Last Scanned</td>
<td>{{ $t("index.lastScanned") }}</td>
<td>{{ gethumanReadable }}</td>
</tr>
<tr>
<td>Quick Scan Seconds</td>
<td>{{ $t("index.quickScan") }}</td>
<td>{{ humanReadableQuickScan }}</td>
</tr>
<tr>
<td>Full Scan Seconds</td>
<td>{{ $t("index.fullScan") }}</td>
<td>{{ humanReadableFullScan }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card headline-card">
<div class="card-wrapper user-card">
<div
@@ -58,12 +60,12 @@
>
<button
class="person-button action"
@mouseover="updateHoverText('Settings For User')"
@mouseover="updateHoverText($t('index.settingsHover'))"
@mouseleave="resetHoverTextToDefault"
>
<i class="material-icons">person</i>
{{ user.username }}
<i aria-label="settings" class="material-icons"> settings</i>
<i aria-label="settings" class="material-icons">settings</i>
</button>
</div>
<div v-else class="inner-card">
@@ -77,19 +79,20 @@
<button
aria-label="logout-button"
class="logout-button action"
@mouseover="updateHoverText('Logout')"
@mouseover="updateHoverText($t('index.logout'))"
@mouseleave="resetHoverTextToDefault"
>
<i v-if="canLogout" class="material-icons">exit_to_app</i>
</button>
</div>
</div>
<div class="card-wrapper" @mouseleave="resetHoverTextToDefault">
<div class="quick-toggles">
<div
:class="{ active: user?.singleClick }"
@click="toggleClick"
@mouseover="updateHoverText('Toggle Single Click')"
@mouseover="updateHoverText($t('index.toggleClick'))"
@mouseleave="resetHoverTextToDefault"
>
<i class="material-icons">ads_click</i>
@@ -97,7 +100,7 @@
<div
:class="{ active: user?.darkMode }"
@click="toggleDarkMode"
@mouseover="updateHoverText('Toggle Dark Mode')"
@mouseover="updateHoverText($t('index.toggleDark'))"
@mouseleave="resetHoverTextToDefault"
>
<i class="material-icons">dark_mode</i>
@@ -105,7 +108,7 @@
<div
:class="{ active: isStickySidebar }"
@click="toggleSticky"
@mouseover="updateHoverText('Toggle Sticky Mode')"
@mouseover="updateHoverText($t('index.toggleSticky'))"
@mouseleave="resetHoverTextToDefault"
v-if="!isMobile"
>
@@ -114,11 +117,10 @@
</div>
</div>
</div>
<!-- Section for logged-in users -->
<div v-if="loginCheck" class="sidebar-scroll-list">
<div class="sources card">
<span>Sources</span>
<span> {{ $t("sidebar.sources") }}</span>
<div class="inner-card">
<!-- My Files button -->
<button
@@ -147,18 +149,18 @@
<span>{{ name }}</span>
</div>
<div v-if="hasSourceInfo" class="usage-info">
<progress-bar
<ProgressBar
:val="info.usedPercentage"
text-position="inside"
:text="info.usedPercentage + '%'"
size="large"
text-fg-color="white"
></progress-bar>
></ProgressBar>
<div class="usage-info">
<span
>{{ getHumanReadableFilesize(info.used) }} of
{{ getHumanReadableFilesize(info.total) }} used</span
>
<span>
{{ getHumanReadableFilesize(info.used) }} {{ $t("index.of") }}
{{ getHumanReadableFilesize(info.total) }} {{ $t("index.used") }}
</span>
</div>
</div>
</button>
+116 -20
View File
@@ -31,17 +31,24 @@
"switchView": "تغيير العرض",
"toggleSidebar": "تبديل الشريط الجانبي",
"update": "تحديث",
"upload": "رفع"
"upload": "رفع",
"copyDownloadLinkToClipboard": "نسخ رابط التحميل إلى الحافظة",
"file": "ملف",
"folder": "المجلد",
"submit": "إرسال",
"openFile": "فتح الملف",
"continue": "تابع"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
"downloadSelected": "تنزيل محدد"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "لقد حدث خطأ ما.",
"notFound": "لا يمكن الوصول لهذا المحتوى."
"notFound": "لا يمكن الوصول لهذا المحتوى.",
"connection": "لا يمكن الوصول إلى الخادم"
},
"files": {
"body": "الصفحة",
@@ -59,7 +66,8 @@
"size": "الحجم",
"sortByLastModified": "الترتيب بآخر تعديل",
"sortByName": "الترتيب بالإسم",
"sortBySize": "الترتيب بالحجم"
"sortBySize": "الترتيب بالحجم",
"noPreview": "المعاينة غير متوفرة لهذا الملف."
},
"help": {
"click": "حدد الملف أو المجلد",
@@ -95,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -110,7 +118,8 @@
"submit": "تسجيل دخول",
"username": "إسم المستخدم",
"usernameTaken": "Username already taken",
"wrongCredentials": "بيانات دخول خاطئة"
"wrongCredentials": "بيانات دخول خاطئة",
"or": "أو"
},
"permanent": "دائم",
"prompts": {
@@ -125,7 +134,6 @@
"downloadMessage": "حدد إمتداد الملف المراد تحميله.",
"error": "لقد حدث خطأ ما",
"fileInfo": "معلومات الملف",
"filesSelected": "تم تحديد {count} ملفات.",
"lastModified": "آخر تعديل",
"move": "نقل",
"moveMessage": "إختر مكان جديد للملفات أو المجلدات المراد نقلها:",
@@ -144,8 +152,15 @@
"scheduleMessage": "أختر الوقت والتاريخ لجدولة نشر هذا المقال.",
"show": "عرض",
"size": "الحجم",
"upload": "",
"uploadMessage": ""
"upload": "التحميل",
"uploadMessage": "حدد خياراً للتحميل.",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
"deleteUserMessage": "هل أنت متأكد من رغبتك في حذف هذا المستخدم؟",
"filesSelected": "الملفات المحددة",
"uploadFiles": "Uploading {files} files...",
"optionalPassword": "كلمة مرور اختيارية",
"typeName": "النوع:",
"selected": "مختارة"
},
"search": {
"images": "الصور",
@@ -155,7 +170,12 @@
"search": "البحث...",
"typeToSearch": "Type to search...",
"types": "الأنواع",
"video": "فيديوهات"
"video": "فيديوهات",
"path": "المسار:",
"smallerThan": "أصغر من:",
"largerThan": "أكبر من:",
"helpText1": "يحدث البحث على كل حرف تكتبه (3 أحرف كحد أدنى لمصطلحات البحث).",
"helpText2": "الفهرس: يستخدم البحث الفهرس الذي يتم تحديثه تلقائيًا على الفاصل الزمني الذي تم تكوينه (افتراضيًا: 5 دقائق). قد يؤدي البحث عندما يكون البرنامج قد بدأ للتو إلى نتائج غير مكتملة."
},
"settings": {
"admin": "Admin",
@@ -194,7 +214,7 @@
"newUser": "مستخدم جديد",
"password": "كلمة المرور",
"passwordUpdated": "تم تغيير كلمة المرور",
"path": "",
"path": "المسار",
"permissions": {
"create": "Create files and directories",
"delete": "Delete files and directories",
@@ -202,7 +222,10 @@
"execute": "Execute commands",
"modify": "Edit files",
"rename": "Rename or move files and directories",
"share": "Share files"
"share": "Share files",
"admin": "المدير",
"api": "إنشاء مفاتيح API طويلة الأمد وإدارتها",
"realtime": "تمكين الاتصالات والتحديثات في الوقت الفعلي"
},
"permissions-name": "الصلاحيات",
"permissionsHelp": "يمكنك تعيين المستخدم كـ \"مدير\" أو تحديد الصلاحيات بشكل منفرد.\n إذا قمت بتحديد المستخدم كـ \"مدير\"، باقي الخيارات سيتم تحديدها تلقائياً.\n إدارة المستخدمين تبقى صلاحية فريدة للـ \"مدير\" فقط.\n",
@@ -213,13 +236,13 @@
"rulesHelp": "يمكنك هنا تحديد مجموعة من شروط السماح والمنع لهذا المستخدم. الملفات الممنوعة لن تظهر ضمن القائمة لهذا المستخدم ولن يستطيع الوصول لها. هنا ندعم الـ regex والـ relative path لنطاق المستخدمين.\n",
"scope": "نطاق",
"settingsUpdated": "تم تعديل الإعدادات",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"shareDuration": "مدة المشاركة",
"shareManagement": "إدارة المشاركة",
"singleClick": "استخدام نقرات واحدة لفتح الملفات والدلائل",
"themes": {
"dark": "",
"light": "",
"title": ""
"dark": "داكن",
"light": "خفيف",
"title": "الموضوع"
},
"user": "المستخدم",
"userCommands": "الأوامر",
@@ -230,7 +253,33 @@
"userManagement": "إدارة المستخدمين",
"userUpdated": "تم تعديل المستخدم",
"username": "إسم المستخدم",
"users": "المستخدمين"
"users": "المستخدمين",
"loginMethod": "طريقة تسجيل الدخول",
"modifyOtherUser": "تعديل المستخدم:",
"modifyCurrentUser": "تعديل المستخدم الحالي",
"themeColor": "لون السمة",
"disableOfficePreviewDescription": "قائمة مفصولة بمسافات لامتدادات الملفات لتعطيل عارض OnlyOffice لها. (على سبيل المثال، \".txt .html\").",
"disableOfficePreview": "تعطيل عارض Onlyoffice لامتدادات ملفات معينة",
"filePreviewOptions": "خيارات معاينة الملف",
"listingOptions": "خيارات الإدراج",
"emptyGlobal": "لا توجد خيارات تهيئة عامة حاليًا، يمكنك تحرير yaml التكوين لضبط إعدادات النظام.",
"scopes": "تحديد المصادر المسموح بها ونطاق المستخدم داخلها",
"UserManagement": "إدارة المستخدمين",
"tusUploads": "التحميلات المقطوعة",
"tusUploadsHelp": "يدعم متصفح الملفات عمليات تحميل الملفات المقطعة، مما يسمح بإنشاء عمليات تحميل ملفات فعالة وموثوقة وقابلة للاستئناف ومقطعة حتى على الشبكات غير الموثوقة.",
"tusUploadsChunkSize": "يشير إلى الحد الأقصى لحجم الطلب (سيتم استخدام التحميل المباشر للتحميلات الأصغر حجماً). يمكنك إدخال عدد صحيح عادي يشير إلى إدخال بايت أو سلسلة مثل 10 ميغابايت، 1 غيغابايت، إلخ.",
"tusUploadsRetryCount": "عدد مرات إعادة المحاولة التي يجب إجراؤها في حالة فشل تحميل قطعة ما.",
"userHomeBasePath": "المسار الأساسي للدلائل الرئيسية للمستخدم",
"userScopeGenerationPlaceholder": "سيتم إنشاء النطاق تلقائيًا",
"createUserHomeDirectory": "إنشاء الدليل الرئيسي للمستخدم",
"api": "مفاتيح واجهة برمجة التطبيقات",
"setDateFormat": "تعيين تنسيق التاريخ الدقيق",
"shareDeleted": "تم حذف المشاركة",
"loginMethodDescription": "طريقة تسجيل الدخول المسموح بها للمستخدم.",
"userNotAdmin": "المستخدم ليس مسؤولاً",
"enterPassword": "أدخل كلمة المرور",
"enterPasswordAgain": "أدخل كلمة المرور مرة أخرى",
"passwordsDoNotMatch": "كلمات المرور غير متطابقة"
},
"sidebar": {
"help": "مساعدة",
@@ -243,7 +292,8 @@
"preview": "معاينة",
"settings": "الإعدادات",
"signup": "Signup",
"siteSettings": "إعدادات الموقع"
"siteSettings": "إعدادات الموقع",
"sources": "المصادر"
},
"success": {
"linkCopied": "تم نسخ الملف"
@@ -254,5 +304,51 @@
"minutes": "دقائق",
"seconds": "ثواني",
"unit": "وحدة الوقت"
},
"upload": {
"abortUpload": "هل أنت متأكدة من أنك تريدين الإجهاض؟"
},
"share": {
"notice": "ملاحظة: يمكن لأي شخص لديه حق الوصول إلى الرابط (وكلمة مرور اختيارية) الوصول إلى الملفات المشتركة. لا يوجد شرط لتسجيل الدخول."
},
"api": {
"title": "مفاتيح واجهة برمجة التطبيقات (API):",
"keyName": "اسم مفتاح API:",
"createdAt": "تم الإنشاء في:",
"expiresAt": "تنتهي في:",
"permissions": "الأذونات:",
"createTitle": "إنشاء مفتاح API",
"keyNamePlaceholder": "أدخل اسم مفتاح API فريد من نوعه:",
"tokenDuration": "مدة الرمز المميز:",
"durationNumberPlaceholder": "العدد",
"days": "الأيام",
"months": "الشهور",
"permissionNote": "اختر إذنًا واحدًا على الأقل للمفتاح. يجب أن يكون لدى المستخدم أيضًا الإذن.",
"createKeyFailed": "فشل في إنشاء مفتاح API.",
"createKeySuccess": "تم إنشاء مفتاح API بنجاح.",
"description": "تعتمد مفاتيح API على المستخدم الذي أنشأها. انظر",
"swaggerLinkText": "صفحة التبختر",
"name": "الاسم",
"created": "تم إنشاؤها",
"expires": "تنتهي صلاحيتها",
"actions": "الإجراءات",
"enabled": "ممكّنة",
"disabled": "معاق"
},
"index": {
"status": "الحالة",
"assessment": "التقييم",
"files": "الملفات",
"folders": "المجلدات",
"lastScanned": "آخر مسح ضوئي",
"quickScan": "ثواني المسح السريع",
"fullScan": "كامل ثواني المسح الضوئي",
"settingsHover": "إعدادات المستخدم",
"logout": "تسجيل الخروج",
"toggleClick": "تبديل نقرة واحدة",
"toggleDark": "تبديل الوضع الداكن",
"toggleSticky": "تبديل الوضع اللاصق",
"of": "من",
"used": "مستعمل"
}
}
+88 -8
View File
@@ -36,7 +36,8 @@
"update": "Aktualisieren",
"upload": "Upload",
"openFile": "Datei öffnen",
"continue": "Fortfahren"
"continue": "Fortfahren",
"copyDownloadLinkToClipboard": "Download-Link in die Zwischenablage kopieren"
},
"download": {
"downloadFile": "Download Datei",
@@ -117,7 +118,8 @@
"submit": "Anmelden",
"username": "Benutzername",
"usernameTaken": "Benutzername ist bereits vergeben",
"wrongCredentials": "Falsche Zugangsdaten"
"wrongCredentials": "Falsche Zugangsdaten",
"or": "OR"
},
"permanent": "Permanent",
"prompts": {
@@ -133,7 +135,6 @@
"downloadMessage": "Wählen Sie ein Format zum Herunterladen aus.",
"error": "Etwas ist schief gelaufen",
"fileInfo": "Dateiinformation",
"filesSelected": "{count} Dateien ausgewählt.",
"lastModified": "Zuletzt geändert",
"move": "Verschieben",
"moveMessage": "Wählen Sie einen neuen Ort für ihre Datei(en)/Ordner:",
@@ -155,7 +156,11 @@
"upload": "Upload",
"uploadFiles": "Upload von {files} Dateien...",
"uploadMessage": "Wählen Sie eine Upload-Methode",
"optionalPassword": "Optionales Kennwort"
"optionalPassword": "Optionales Kennwort",
"deleteUserMessage": "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?",
"filesSelected": "ausgewählte Dateien",
"typeName": "Art:",
"selected": "ausgewählt"
},
"search": {
"images": "Bilder",
@@ -165,7 +170,12 @@
"search": "Suche...",
"typeToSearch": "Tippen, um zu suchen...",
"types": "Typen",
"video": "Video"
"video": "Video",
"path": "Pfad:",
"smallerThan": "Kleiner als:",
"largerThan": "Größer als:",
"helpText1": "Die Suche erfolgt bei jedem Zeichen, das Sie eingeben (mindestens 3 Zeichen für Suchbegriffe).",
"helpText2": "Der Index: Die Suche verwendet den Index, der automatisch in dem konfigurierten Intervall aktualisiert wird (Standard: 5 Minuten). Eine Suche, wenn das Programm gerade gestartet wurde, kann zu unvollständigen Ergebnissen führen."
},
"settings": {
"admin": "Admin",
@@ -216,7 +226,10 @@
"execute": "Befehle ausführen",
"modify": "Dateien bearbeiten",
"rename": "Umbenennen oder Verschieben von Dateien oder Ordnern",
"share": "Datei teilen"
"share": "Datei teilen",
"admin": "Verwalter",
"api": "Erstellen und Verwalten von API-Schlüsseln mit langer Lebensdauer",
"realtime": "Verbindungen und Aktualisierungen in Echtzeit ermöglichen"
},
"permissions-name": "Berechtigungen",
"permissionsHelp": "Sie können einem Benutzer Administratorrechte einräumen oder die Berechtigunen individuell festlegen. Wenn Sie \"Administrator\" auswählen, werden alle anderen Rechte automatisch vergeben. Die Nutzerverwaltung kann nur durch einen Administrator erfolgen.\n",
@@ -246,7 +259,27 @@
"userManagement": "Benutzerverwaltung",
"userUpdated": "Benutzer aktualisiert!",
"username": "Benutzername",
"users": "Benutzer"
"users": "Benutzer",
"loginMethod": "Login-Methode",
"modifyOtherUser": "Benutzer ändern:",
"modifyCurrentUser": "Aktuellen Benutzer ändern",
"themeColor": "Thema Farbe",
"disableOfficePreviewDescription": "Eine durch Leerzeichen getrennte Liste von Dateierweiterungen, für die der OnlyOffice-Viewer deaktiviert werden soll. (z. B. '.txt .html').",
"disableOfficePreview": "Onlyoffice-Viewer für bestimmte Dateierweiterungen deaktivieren",
"filePreviewOptions": "Optionen für die Dateivorschau",
"listingOptions": "Auflistung der Optionen",
"emptyGlobal": "Derzeit gibt es keine globalen Konfigurationsoptionen. Sie können die Konfigurations-Yaml bearbeiten, um die Systemeinstellungen anzupassen.",
"scopes": "Definition der zulässigen Quellen und des Benutzerbereichs innerhalb dieser Quellen",
"UserManagement": "Benutzerverwaltung",
"userHomeBasePath": "Basispfad für Benutzer-Home-Verzeichnisse",
"userScopeGenerationPlaceholder": "Der Bereich wird automatisch generiert",
"createUserHomeDirectory": "Benutzer-Home-Verzeichnis erstellen",
"api": "API-Schlüssel",
"loginMethodDescription": "erlaubte Anmeldemethode für den Benutzer.",
"userNotAdmin": "Benutzer ist kein Administrator",
"enterPassword": "Passwort eingeben",
"enterPasswordAgain": "Geben Sie das Passwort erneut ein",
"passwordsDoNotMatch": "Passwörter stimmen nicht überein"
},
"sidebar": {
"help": "Hilfe",
@@ -259,7 +292,8 @@
"preview": "Vorschau",
"settings": "Einstellungen",
"signup": "Registrieren",
"siteSettings": "Seiteneinstellungen"
"siteSettings": "Seiteneinstellungen",
"sources": "Quellen"
},
"success": {
"linkCopied": "Verweis wurde kopiert!"
@@ -270,5 +304,51 @@
"minutes": "Minuten",
"seconds": "Sekunden",
"unit": "Zeiteinheit"
},
"upload": {
"abortUpload": "Sind Sie sicher, dass Sie abtreiben wollen?"
},
"share": {
"notice": "Hinweis: Jeder, der Zugang zum Link (und optional zum Passwort) hat, kann auf die gemeinsamen Dateien zugreifen. Es ist nicht erforderlich, dass Sie eingeloggt sind."
},
"api": {
"title": "API-Schlüssel:",
"keyName": "API-Schlüssel Name:",
"createdAt": "Erstellt am:",
"expiresAt": "Läuft ab bei:",
"permissions": "Erlaubt:",
"createTitle": "API-Schlüssel erstellen",
"keyNamePlaceholder": "Geben Sie einen eindeutigen API-Schlüsselnamen ein:",
"tokenDuration": "Token Dauer:",
"durationNumberPlaceholder": "Nummer",
"days": "Tage",
"months": "Monate",
"permissionNote": "Wählen Sie mindestens eine Berechtigung für den Schlüssel. Ihr Benutzer muss ebenfalls über diese Berechtigung verfügen.",
"createKeyFailed": "API-Schlüssel konnte nicht erstellt werden.",
"createKeySuccess": "API-Schlüssel erfolgreich erstellt.",
"description": "Die API-Schlüssel basieren auf dem Benutzer, der sie erstellt. Siehe",
"swaggerLinkText": "Angeberseite",
"name": "Name",
"created": "Erstellt",
"expires": "Abgelaufen",
"actions": "Aktionen",
"enabled": "Aktiviert",
"disabled": "Behinderte"
},
"index": {
"status": "Status",
"assessment": "Bewertung",
"files": "Dateien",
"folders": "Ordner",
"lastScanned": "Zuletzt gescannt",
"quickScan": "Schnell-Scan-Sekunden",
"fullScan": "Vollständiger Scan in Sekunden",
"settingsHover": "Einstellungen für Benutzer",
"logout": "Abmeldung",
"toggleClick": "Einzelklick umschalten",
"toggleDark": "Dunkler Modus umschalten",
"toggleSticky": "Sticky-Modus umschalten",
"of": "von",
"used": "gebraucht"
}
}
+350 -275
View File
@@ -1,280 +1,355 @@
{
"buttons": {
"cancel": "Ακύρωση",
"close": "Κλείσιμο",
"copy": "Αντιγραφή",
"copyFile": "Αντιγραφή αρχείου",
"copyToClipboard": "Αντιγραφή στο πρόχειρο",
"copyDownloadLinkToClipboard": "Αντιγραφή συνδέσμου λήψης στο πρόχειρο",
"create": "Δημιουργία",
"delete": "Διαγραφή",
"download": "Λήψη",
"file": "Αρχείο",
"folder": "Φάκελος",
"info": "Πληροφορίες",
"more": "Περισσότερα",
"move": "Μετακίνηση",
"moveFile": "Μετακίνηση αρχείου",
"new": "Νέο",
"next": "Επόμενο",
"ok": "Εντάξει",
"permalink": "Λήψη μόνιμου συνδέσμου",
"previous": "Προηγούμενο",
"publish": "Δημοσίευση",
"rename": "Μετονομασία",
"replace": "Αντικατάσταση",
"reportIssue": "Αναφορά προβλήματος",
"save": "Αποθήκευση",
"schedule": "Προγραμματισμός",
"search": "Αναζήτηση",
"select": "Επιλογή",
"selectMultiple": "Επιλογή πολλαπλών",
"share": "Κοινοποίηση",
"submit": "Υποβολή",
"switchView": "Εναλλαγή προβολής",
"toggleSidebar": "(Απ-)ενεργοποίησης της πλευρικής μπάρας",
"update": "Ενημέρωση",
"upload": "Μεταφόρτωση",
"openFile": "Άνοιγμα αρχείου",
"continue": "Συνέχεια"
"buttons": {
"cancel": "Ακύρωση",
"close": "Κλείσιμο",
"copy": "Αντιγραφή",
"copyFile": "Αντιγραφή αρχείου",
"copyToClipboard": "Αντιγραφή στο πρόχειρο",
"copyDownloadLinkToClipboard": "Αντιγραφή συνδέσμου λήψης στο πρόχειρο",
"create": "Δημιουργία",
"delete": "Διαγραφή",
"download": "Λήψη",
"file": "Αρχείο",
"folder": "Φάκελος",
"info": "Πληροφορίες",
"more": "Περισσότερα",
"move": "Μετακίνηση",
"moveFile": "Μετακίνηση αρχείου",
"new": "Νέο",
"next": "Επόμενο",
"ok": "Εντάξει",
"permalink": "Λήψη μόνιμου συνδέσμου",
"previous": "Προηγούμενο",
"publish": "Δημοσίευση",
"rename": "Μετονομασία",
"replace": "Αντικατάσταση",
"reportIssue": "Αναφορά προβλήματος",
"save": "Αποθήκευση",
"schedule": "Προγραμματισμός",
"search": "Αναζήτηση",
"select": "Επιλογή",
"selectMultiple": "Επιλογή πολλαπλών",
"share": "Κοινοποίηση",
"submit": "Υποβολή",
"switchView": "Εναλλαγή προβολής",
"toggleSidebar": "(Απ-)ενεργοποίησης της πλευρικής μπάρας",
"update": "Ενημέρωση",
"upload": "Μεταφόρτωση",
"openFile": "Άνοιγμα αρχείου",
"continue": "Συνέχεια",
"shell": "Εναλλαγή κελύφους"
},
"download": {
"downloadFile": "Λήψη αρχείου",
"downloadFolder": "Λήψη φακέλου",
"downloadSelected": "Λήψη επιλεγμένων"
},
"upload": {
"abortUpload": "Είστε σίγουροι ότι θέλετε να διακόψετε τη μεταφόρτωση;"
},
"errors": {
"forbidden": "Δεν έχετε άδεια πρόσβασης σε αυτό.",
"internal": "Προέκυψε εσωτερικό σφάλμα.",
"notFound": "Αυτή η τοποθεσία δεν μπορεί να βρεθεί.",
"connection": "Ο διακομιστής δεν είναι διαθέσιμος."
},
"files": {
"body": "Περιεχόμενο",
"clear": "Καθαρισμός",
"closePreview": "Κλείσιμο προεπισκόπησης",
"files": "Αρχεία",
"folders": "Φάκελοι",
"home": "Αρχική",
"lastModified": "Τελευταία τροποποίηση",
"loading": "Φορτώνει…",
"lonely": "Δεν υπάρχει τίποτα εδώ (ακόμη)…",
"metadata": "Μεταδεδομένα",
"multipleSelectionEnabled": "Ενεργοποιημένη επιλογή πολλαπλών",
"name": "Όνομα",
"size": "Μέγεθος",
"sortByLastModified": "Ταξινόμηση κατά πρόσφατη τροποποίηση",
"sortByName": "Ταξινόμηση κατά όνομα",
"sortBySize": "Ταξινόμηση κατά μέγεθος",
"noPreview": "Η προεπισκόπηση δεν είναι διαθέσιμη για αυτό το αρχείο."
},
"help": {
"click": "επιλέξτε αρχείο ή φάκελο",
"ctrl": {
"click": "επιλογή πολλαπλών αρχείων ή φακέλων",
"f": "ανοίγει την αναζήτηση",
"s": "αποθηκεύει ένα αρχείο ή εκκινεί λήψη του φακέλου στον οποίο βρίσκεστε"
},
"download": {
"downloadFile": "Λήψη αρχείου",
"downloadFolder": "Λήψη φακέλου",
"downloadSelected": "Λήψη επιλεγμένων"
"del": "διαγραφή επιλεγμένων στοιχείων",
"doubleClick": "ανοίγει ένα αρχείο ή φάκελο",
"esc": "καθαρίζει την επιλογή ή/και κλείνει το παράθυρο",
"f1": "αυτή η πληροφορία",
"f2": "μετονομασία αρχείου",
"help": "Βοήθεια"
},
"languages": {
"he": "עברית",
"hu": "Magyar",
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"el": "Ελληνικά",
"fr": "Français",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": {
"createAnAccount": "Δημιουργία λογαριασμού",
"loginInstead": "Έχετε ήδη λογαριασμό",
"password": "Κωδικός πρόσβασης",
"passwordConfirm": "Επιβεβαίωση κωδικού πρόσβασης",
"passwordsDontMatch": "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
"signup": "Εγγραφή",
"submit": "Είσοδος",
"username": "Όνομα χρήστη",
"usernameTaken": "Το όνομα χρήστη χρησιμοποιείται ήδη",
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης",
"or": "Ή"
},
"permanent": "Μόνιμο",
"prompts": {
"copy": "Αντιγραφή",
"copyMessage": "Επιλέξτε τοποθεσία για αντιγραφή των αρχείων σας:",
"deleteMessageMultiple": "Είστε σίγουροι ότι θέλετε να διαγράψετε {count} αρχεία;",
"deleteMessageSingle": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο/φάκελο;",
"deleteMessageShare": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή την κοινοποίηση ({path});",
"deleteTitle": "Διαγραφή αρχείων",
"displayName": "Εμφάνιση ονόματος:",
"download": "Λήψη αρχείων",
"downloadMessage": "Επιλέξτε τη μορφή που θέλετε να λάβετε.",
"error": "Προέκυψε κάποιο σφάλμα",
"fileInfo": "Πληροφορίες αρχείου",
"lastModified": "Τελευταία τροποποίηση",
"move": "Μετακίνηση",
"moveMessage": "Επιλέξτε νέα τοποθεσία για τα αρχεία / τους φακέλους σας:",
"newArchetype": "Δημιουργία νέας ανάρτησης με βάση έναν αρχέτυπο. Το αρχείο σας θα δημιουργηθεί στο φάκελο περιεχομένου.",
"newDir": "Νέος φάκελος",
"newDirMessage": "Γράψτε το όνομα του νέου φακέλου.",
"newFile": "Νέο αρχείο",
"newFileMessage": "Γράψτε το όνομα του νέου αρχείου.",
"numberDirs": "Αριθμός φακέλων",
"numberFiles": "Αριθμός αρχείων",
"rename": "Μετονομασία",
"renameMessage": "Εισαγάγετε ένα νέο όνομα για το",
"replace": "Αντικατάσταση",
"replaceMessage": "Ένα από τα αρχεία που προσπαθείτε να μεταφορτώσετε δημιουργεί σύγκρουση με υπάρχον αρχείο λόγω του ονόματός του. Θέλετε να συνεχίσετε τη μεταφόρτωση ή να αντικαταστήσετε το υπάρχον;\n",
"schedule": "Προγραμματισμός",
"scheduleMessage": "Επιλέξτε μια ημερομηνία και ώρα για τον προγραμματισμό της δημοσίευσης αυτής της ανάρτησης.",
"show": "Εμφάνιση",
"size": "Μέγεθος",
"upload": "Μεταφόρτωση",
"uploadFiles": "Μεταφόρτωση {files} αρχείων…",
"uploadMessage": "Επιλέξτε μια επιλογή για τη μεταφόρτωση.",
"optionalPassword": "Προαιρετικός κωδικός πρόσβασης",
"currentlyNavigating": "Αυτή τη στιγμή πλοηγείστε στο:",
"deleteUserMessage": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον χρήστη;",
"filesSelected": "επιλεγμένα αρχεία",
"typeName": "Τύπος:",
"selected": "επιλεγμένο"
},
"search": {
"images": "Εικόνες",
"music": "Μουσική",
"pdf": "PDF",
"pressToSearch": "Πατήστε Enter για αναζήτηση…",
"search": "Αναζήτηση…",
"typeToSearch": "Πληκτρολογήστε για αναζήτηση…",
"types": "Τύποι",
"video": "Βίντεο",
"path": "Μονοπάτι:",
"smallerThan": "Μικρότερο από:",
"largerThan": "Μεγαλύτερο από:",
"helpText1": "Η αναζήτηση πραγματοποιείται σε κάθε χαρακτήρα που πληκτρολογείτε (ελάχιστο όριο 3 χαρακτήρων για τους όρους αναζήτησης).",
"helpText2": "Ο δείκτης: Η αναζήτηση χρησιμοποιεί το ευρετήριο το οποίο ενημερώνεται αυτόματα στο ρυθμισμένο διάστημα (προεπιλογή: 5 λεπτά). Η αναζήτηση όταν το πρόγραμμα έχει μόλις ξεκινήσει μπορεί να οδηγήσει σε ελλιπή αποτελέσματα."
},
"settings": {
"admin": "Διαχειριστής",
"administrator": "Διαχειριστής",
"allowCommands": "Εκτέλεση εντολών",
"allowEdit": "Επεξεργασία, μετονομασία και διαγραφή αρχείων ή φακέλων",
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
"branding": "Εξατομίκευση",
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
"brandingHelp": "Μπορείτε να προσαρμόσετε την εμφάνισης της εφαρμογής File Browser αλλάζοντας το όνομά της, αντικαθιστώντας το λογότυπό της, προσθέτοντας προσαρμοσμένα στυλ και ακόμα και απενεργοποιώντας εξωτερικούς συνδέσμους προς το GitHub.\nΓια περισσότερες πληροφορίες σχετικά με αυτές τις προσαρμογές, ελέγξτε το {0}.",
"changePassword": "Αλλαγή κωδικού πρόσβασης",
"commandRunner": "Εκτέλεση εντολών",
"commandRunnerHelp": "Εδώ μπορείτε να ορίσετε εντολές που εκτελούνται στα ονομασμένα γεγονότα και δραστηριότητες. Πρέπει να γράψετε μία εντολή ανά γραμμή. Οι μεταβλητές περιβάλλοντος {0} και {1} θα είναι διαθέσιμες, και θα είναι {0} σχετικές με το {1}. Για περισσότερες πληροφορίες σχετικά με αυτή τη λειτουργία και τις διαθέσιμες μεταβλητές περιβάλλοντος, παρακαλώ διαβάστε το {2}.",
"commandsUpdated": "Οι εντολές ενημερώθηκαν!",
"createUserDir": "Αυτόματη δημιουργία φακέλου χρήστη κατά την προσθήκη νέου χρήστη",
"tusUploads": "Τμηματικές μεταφορές αρχείων",
"tusUploadsHelp": "Η εφαρμογή File Browser υποστηρίζει τμηματικές μεταφορτώσεις αρχείων, επιτρέποντας την αποδοτική, αξιόπιστη και συνεχιζόμενη μεταφόρτωση αρχείων ακόμα και σε ασταθείς συνδέσεις δικτύου.",
"tusUploadsChunkSize": "Υποδεικνύει το μέγιστο μέγεθος ενός αιτήματος μεταφόρτωσης (για μικρότερες μεταφορές αρχείων θα χρησιμοποιηθούν απευθείας και όχι τμηματικές μεταφορτώσεις). Μπορείτε να εισάγετε έναν ακέραιο αριθμό που υποδηλώνει το μέγεθος σε bytes, ή κείμενο με αριθμό και μονάδα μέτρησης μεγέθους δεδομένων, όπως 10MB, 1GB κλπ.",
"tusUploadsRetryCount": "Αριθμός επαναληπτικών δοκιμών που θα πραγματοποιηθούν αν αποτύχει η μεταφόρτωση ενός τμήματος.",
"userHomeBasePath": "Βασική διαδρομή αρχείων για τους φακέλους των χρηστών",
"userScopeGenerationPlaceholder": "Η εμβέλεια εφαρμογής θα δημιουργηθεί αυτόματα",
"createUserHomeDirectory": "Δημιουργία φακέλου χρήστη",
"customStylesheet": "Προσαρμοσμένο στυλ εμφάνισης (stylesheet)",
"defaultUserDescription": "Αυτές είναι οι προεπιλεγμένες ρυθμίσεις για νέους χρήστες.",
"disableExternalLinks": "Απενεργοποίηση εξωτερικών συνδέσμων (εκτός από συνδέσμους προς τις οδηγίες χρήσης)",
"disableUsedDiskPercentage": "Απενεργοποίηση γραφήματος ποσοστού χρήσης χώρου αποθήκευσης",
"documentation": "οδηγίες χρήσης",
"examples": "Παραδείγματα",
"executeOnShell": "Εκτέλεση στο κέλυφος",
"executeOnShellDescription": "Από προεπιλογή, η εφαρμογή File Browser εκτελεί τις εντολές καλώντας τα προγράμματα των εντολών απευθείας. Αν θέλετε να τις εκτελέσετε σε ένα κέλυφος (όπως το Bash ή το PowerShell), μπορείτε να το καθορίσετε εδώ με τις απαιτούμενες παραμέτρους. Εάν οριστεί, η εντολή που εκτελείτε θα προστίθεται ως παράμετρος. Αυτό ισχύει τόσο για τις εντολές χρήστη όσο και για τους αγκίστρους συμβάντων (event hooks).",
"globalRules": "Πρόκειται για ένα γενικό σύνολο κανόνων που επιτρέπουν και απαγορεύουν διάφορες λειτουργίες και ισχύουν για κάθε χρήστη. Μπορείτε να καθορίσετε συγκεκριμένους κανόνες στις ρυθμίσεις κάθε χρήστη για να παρακάμψετε τους γενικούς κανόνες.",
"globalSettings": "Γενικές ρυθμίσεις",
"insertPath": "Εισάγετε διαδρομή",
"insertRegex": "Εισάγετε έκφραση regex",
"instanceName": "Όνομα περιβάλλοντος",
"language": "Γλώσσα",
"lockPassword": "Αποτρέψτε τον χρήστη από την αλλαγή του κωδικού πρόσβασης",
"newPassword": "Νέος κωδικός πρόσβασης",
"newPasswordConfirm": "Επιβεβαιώστε τον νέο κωδικό πρόσβασης",
"newUser": "Νέος χρήστης",
"password": "Κωδικός πρόσβασης",
"passwordUpdated": "Ο κωδικός πρόσβασης ενημερώθηκε!",
"path": "Διαδρομή",
"permissions": {
"create": "Δημιουργία αρχείων και φακέλων",
"delete": "Διαγραφή αρχείων και φακέλων",
"download": "Λήψη",
"execute": "Εκτέλεση εντολών",
"modify": "Επεξεργασία αρχείων",
"rename": "Μετονομασία ή μετακίνηση αρχείων και φακέλων",
"share": "Κοινοποίηση αρχείων",
"admin": "Διαχειριστής",
"api": "Δημιουργία και διαχείριση κλειδιών API μακράς διάρκειας ζωής",
"realtime": "Ενεργοποίηση συνδέσεων και ενημερώσεων σε πραγματικό χρόνο"
},
"upload": {
"abortUpload": "Είστε σίγουροι ότι θέλετε να διακόψετε τη μεταφόρτωση;"
"permissions-name": "Δικαιώματα",
"permissionsHelp": "Μπορείτε να ορίσετε τον χρήστη ως διαχειριστή ή να επιλέξετε τα δικαιώματα μεμονωμένα. Αν επιλέξετε \"Διαχειριστής\", όλες οι υπόλοιπες επιλογές θα είναι αυτόματα επιλεγμένες. Η διαχείριση χρηστών παραμένει προνόμιο ενός χρήστη με τον ρόλο του διαχειριστή.\n",
"profileSettings": "Ρυθμίσεις προφίλ",
"ruleExample1": "αποκλείει την πρόσβαση σε οποιοδήποτε κρυφό αρχείο (όπως .git, .gitignore) σε κάθε φάκελο.\n",
"ruleExample2": "αποκλείει την πρόσβαση στο αρχείο με το όνομα Caddyfile στον ριζικό φάκελο της εμβέλειας του κανόνα.",
"rules": "Κανόνες",
"rulesHelp": "Εδώ μπορείτε να ορίσετε ένα σύνολο κανόνων που επιτρέπουν και απαγορεύουν διάφορες λειτουργίες για τον συγκεκριμένο χρήστη. Τα αποκλεισμένα αρχεία δεν θα εμφανίζονται στα περιεχόμενα των αντίστοιχων φακέλων και δεν θα είναι προσβάσιμα από τον χρήστη. Υποστηρίζονται εκφράσεις regex και διαδρομές σχετικές με την εμβέλεια αρχείων των χρηστών.\n",
"scope": "Εμβέλεια",
"setDateFormat": "Ορισμός ακριβούς μορφής ημερομηνίας",
"settingsUpdated": "Οι ρυθμίσεις ενημερώθηκαν!",
"shareDuration": "Διάρκεια κοινοποίησης",
"shareManagement": "Διαχείριση κοινοποίησης",
"shareDeleted": "Η κοινοποίηση διαγράφηκε!",
"singleClick": "Χρήση μονού κλικ για να ανοίξετε αρχεία και φακέλους",
"themes": {
"dark": "Σκοτεινό",
"light": "Φωτεινό",
"title": "Μοτίβο"
},
"errors": {
"forbidden": "Δεν έχετε άδεια πρόσβασης σε αυτό.",
"internal": "Προέκυψε εσωτερικό σφάλμα.",
"notFound": "Αυτή η τοποθεσία δεν μπορεί να βρεθεί.",
"connection": "Ο διακομιστής δεν είναι διαθέσιμος."
},
"files": {
"body": "Περιεχόμενο",
"clear": "Καθαρισμός",
"closePreview": "Κλείσιμο προεπισκόπησης",
"files": "Αρχεία",
"folders": "Φάκελοι",
"home": "Αρχική",
"lastModified": "Τελευταία τροποποίηση",
"loading": "Φορτώνει…",
"lonely": "Δεν υπάρχει τίποτα εδώ (ακόμη)…",
"metadata": "Μεταδεδομένα",
"multipleSelectionEnabled": "Ενεργοποιημένη επιλογή πολλαπλών",
"name": "Όνομα",
"size": "Μέγεθος",
"sortByLastModified": "Ταξινόμηση κατά πρόσφατη τροποποίηση",
"sortByName": "Ταξινόμηση κατά όνομα",
"sortBySize": "Ταξινόμηση κατά μέγεθος",
"noPreview": "Η προεπισκόπηση δεν είναι διαθέσιμη για αυτό το αρχείο."
},
"help": {
"click": "επιλέξτε αρχείο ή φάκελο",
"ctrl": {
"click": "επιλογή πολλαπλών αρχείων ή φακέλων",
"f": "ανοίγει την αναζήτηση",
"s": "αποθηκεύει ένα αρχείο ή εκκινεί λήψη του φακέλου στον οποίο βρίσκεστε"
},
"del": "διαγραφή επιλεγμένων στοιχείων",
"doubleClick": "ανοίγει ένα αρχείο ή φάκελο",
"esc": "καθαρίζει την επιλογή ή/και κλείνει το παράθυρο",
"f1": "αυτή η πληροφορία",
"f2": "μετονομασία αρχείου",
"help": "Βοήθεια"
},
"languages": {
"he": "עברית",
"hu": "Magyar",
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"el": "Ελληνικά",
"fr": "Français",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": {
"createAnAccount": "Δημιουργία λογαριασμού",
"loginInstead": "Έχετε ήδη λογαριασμό",
"password": "Κωδικός πρόσβασης",
"passwordConfirm": "Επιβεβαίωση κωδικού πρόσβασης",
"passwordsDontMatch": "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
"signup": "Εγγραφή",
"submit": "Είσοδος",
"username": "Όνομα χρήστη",
"usernameTaken": "Το όνομα χρήστη χρησιμοποιείται ήδη",
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης"
},
"permanent": "Μόνιμο",
"prompts": {
"copy": "Αντιγραφή",
"copyMessage": "Επιλέξτε τοποθεσία για αντιγραφή των αρχείων σας:",
"deleteMessageMultiple": "Είστε σίγουροι ότι θέλετε να διαγράψετε {count} αρχεία;",
"deleteMessageSingle": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο/φάκελο;",
"deleteMessageShare": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή την κοινοποίηση ({path});",
"deleteTitle": "Διαγραφή αρχείων",
"displayName": "Εμφάνιση ονόματος:",
"download": "Λήψη αρχείων",
"downloadMessage": "Επιλέξτε τη μορφή που θέλετε να λάβετε.",
"error": "Προέκυψε κάποιο σφάλμα",
"fileInfo": "Πληροφορίες αρχείου",
"filesSelected": "Επιλέχθηκαν {count} αρχεία.",
"lastModified": "Τελευταία τροποποίηση",
"move": "Μετακίνηση",
"moveMessage": "Επιλέξτε νέα τοποθεσία για τα αρχεία / τους φακέλους σας:",
"newArchetype": "Δημιουργία νέας ανάρτησης με βάση έναν αρχέτυπο. Το αρχείο σας θα δημιουργηθεί στο φάκελο περιεχομένου.",
"newDir": "Νέος φάκελος",
"newDirMessage": "Γράψτε το όνομα του νέου φακέλου.",
"newFile": "Νέο αρχείο",
"newFileMessage": "Γράψτε το όνομα του νέου αρχείου.",
"numberDirs": "Αριθμός φακέλων",
"numberFiles": "Αριθμός αρχείων",
"rename": "Μετονομασία",
"renameMessage": "Εισαγάγετε ένα νέο όνομα για το",
"replace": "Αντικατάσταση",
"replaceMessage": "Ένα από τα αρχεία που προσπαθείτε να μεταφορτώσετε δημιουργεί σύγκρουση με υπάρχον αρχείο λόγω του ονόματός του. Θέλετε να συνεχίσετε τη μεταφόρτωση ή να αντικαταστήσετε το υπάρχον;\n",
"schedule": "Προγραμματισμός",
"scheduleMessage": "Επιλέξτε μια ημερομηνία και ώρα για τον προγραμματισμό της δημοσίευσης αυτής της ανάρτησης.",
"show": "Εμφάνιση",
"size": "Μέγεθος",
"upload": "Μεταφόρτωση",
"uploadFiles": "Μεταφόρτωση {files} αρχείων…",
"uploadMessage": "Επιλέξτε μια επιλογή για τη μεταφόρτωση.",
"optionalPassword": "Προαιρετικός κωδικός πρόσβασης"
},
"search": {
"images": "Εικόνες",
"music": "Μουσική",
"pdf": "PDF",
"pressToSearch": "Πατήστε Enter για αναζήτηση…",
"search": "Αναζήτηση…",
"typeToSearch": "Πληκτρολογήστε για αναζήτηση…",
"types": "Τύποι",
"video": "Βίντεο"
},
"settings": {
"admin": "Διαχειριστής",
"administrator": "Διαχειριστής",
"allowCommands": "Εκτέλεση εντολών",
"allowEdit": "Επεξεργασία, μετονομασία και διαγραφή αρχείων ή φακέλων",
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
"branding": "Εξατομίκευση",
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
"brandingHelp": "Μπορείτε να προσαρμόσετε την εμφάνισης της εφαρμογής File Browser αλλάζοντας το όνομά της, αντικαθιστώντας το λογότυπό της, προσθέτοντας προσαρμοσμένα στυλ και ακόμα και απενεργοποιώντας εξωτερικούς συνδέσμους προς το GitHub.\nΓια περισσότερες πληροφορίες σχετικά με αυτές τις προσαρμογές, ελέγξτε το {0}.",
"changePassword": "Αλλαγή κωδικού πρόσβασης",
"commandRunner": "Εκτέλεση εντολών",
"commandRunnerHelp": "Εδώ μπορείτε να ορίσετε εντολές που εκτελούνται στα ονομασμένα γεγονότα και δραστηριότητες. Πρέπει να γράψετε μία εντολή ανά γραμμή. Οι μεταβλητές περιβάλλοντος {0} και {1} θα είναι διαθέσιμες, και θα είναι {0} σχετικές με το {1}. Για περισσότερες πληροφορίες σχετικά με αυτή τη λειτουργία και τις διαθέσιμες μεταβλητές περιβάλλοντος, παρακαλώ διαβάστε το {2}.",
"commandsUpdated": "Οι εντολές ενημερώθηκαν!",
"createUserDir": "Αυτόματη δημιουργία φακέλου χρήστη κατά την προσθήκη νέου χρήστη",
"tusUploads": "Τμηματικές μεταφορές αρχείων",
"tusUploadsHelp": "Η εφαρμογή File Browser υποστηρίζει τμηματικές μεταφορτώσεις αρχείων, επιτρέποντας την αποδοτική, αξιόπιστη και συνεχιζόμενη μεταφόρτωση αρχείων ακόμα και σε ασταθείς συνδέσεις δικτύου.",
"tusUploadsChunkSize": "Υποδεικνύει το μέγιστο μέγεθος ενός αιτήματος μεταφόρτωσης (για μικρότερες μεταφορές αρχείων θα χρησιμοποιηθούν απευθείας και όχι τμηματικές μεταφορτώσεις). Μπορείτε να εισάγετε έναν ακέραιο αριθμό που υποδηλώνει το μέγεθος σε bytes, ή κείμενο με αριθμό και μονάδα μέτρησης μεγέθους δεδομένων, όπως 10MB, 1GB κλπ.",
"tusUploadsRetryCount": "Αριθμός επαναληπτικών δοκιμών που θα πραγματοποιηθούν αν αποτύχει η μεταφόρτωση ενός τμήματος.",
"userHomeBasePath": "Βασική διαδρομή αρχείων για τους φακέλους των χρηστών",
"userScopeGenerationPlaceholder": "Η εμβέλεια εφαρμογής θα δημιουργηθεί αυτόματα",
"createUserHomeDirectory": "Δημιουργία φακέλου χρήστη",
"customStylesheet": "Προσαρμοσμένο στυλ εμφάνισης (stylesheet)",
"defaultUserDescription": "Αυτές είναι οι προεπιλεγμένες ρυθμίσεις για νέους χρήστες.",
"disableExternalLinks": "Απενεργοποίηση εξωτερικών συνδέσμων (εκτός από συνδέσμους προς τις οδηγίες χρήσης)",
"disableUsedDiskPercentage": "Απενεργοποίηση γραφήματος ποσοστού χρήσης χώρου αποθήκευσης",
"documentation": "οδηγίες χρήσης",
"examples": "Παραδείγματα",
"executeOnShell": "Εκτέλεση στο κέλυφος",
"executeOnShellDescription": "Από προεπιλογή, η εφαρμογή File Browser εκτελεί τις εντολές καλώντας τα προγράμματα των εντολών απευθείας. Αν θέλετε να τις εκτελέσετε σε ένα κέλυφος (όπως το Bash ή το PowerShell), μπορείτε να το καθορίσετε εδώ με τις απαιτούμενες παραμέτρους. Εάν οριστεί, η εντολή που εκτελείτε θα προστίθεται ως παράμετρος. Αυτό ισχύει τόσο για τις εντολές χρήστη όσο και για τους αγκίστρους συμβάντων (event hooks).",
"globalRules": "Πρόκειται για ένα γενικό σύνολο κανόνων που επιτρέπουν και απαγορεύουν διάφορες λειτουργίες και ισχύουν για κάθε χρήστη. Μπορείτε να καθορίσετε συγκεκριμένους κανόνες στις ρυθμίσεις κάθε χρήστη για να παρακάμψετε τους γενικούς κανόνες.",
"globalSettings": "Γενικές ρυθμίσεις",
"insertPath": "Εισάγετε διαδρομή",
"insertRegex": "Εισάγετε έκφραση regex",
"instanceName": "Όνομα περιβάλλοντος",
"language": "Γλώσσα",
"lockPassword": "Αποτρέψτε τον χρήστη από την αλλαγή του κωδικού πρόσβασης",
"newPassword": "Νέος κωδικός πρόσβασης",
"newPasswordConfirm": "Επιβεβαιώστε τον νέο κωδικό πρόσβασης",
"newUser": "Νέος χρήστης",
"password": "Κωδικός πρόσβασης",
"passwordUpdated": "Ο κωδικός πρόσβασης ενημερώθηκε!",
"path": "Διαδρομή",
"permissions": {
"create": "Δημιουργία αρχείων και φακέλων",
"delete": "Διαγραφή αρχείων και φακέλων",
"download": "Λήψη",
"execute": "Εκτέλεση εντολών",
"modify": "Επεξεργασία αρχείων",
"rename": "Μετονομασία ή μετακίνηση αρχείων και φακέλων",
"share": "Κοινοποίηση αρχείων"
},
"permissions-name": "Δικαιώματα",
"permissionsHelp": "Μπορείτε να ορίσετε τον χρήστη ως διαχειριστή ή να επιλέξετε τα δικαιώματα μεμονωμένα. Αν επιλέξετε \"Διαχειριστής\", όλες οι υπόλοιπες επιλογές θα είναι αυτόματα επιλεγμένες. Η διαχείριση χρηστών παραμένει προνόμιο ενός χρήστη με τον ρόλο του διαχειριστή.\n",
"profileSettings": "Ρυθμίσεις προφίλ",
"ruleExample1": "αποκλείει την πρόσβαση σε οποιοδήποτε κρυφό αρχείο (όπως .git, .gitignore) σε κάθε φάκελο.\n",
"ruleExample2": "αποκλείει την πρόσβαση στο αρχείο με το όνομα Caddyfile στον ριζικό φάκελο της εμβέλειας του κανόνα.",
"rules": "Κανόνες",
"rulesHelp": "Εδώ μπορείτε να ορίσετε ένα σύνολο κανόνων που επιτρέπουν και απαγορεύουν διάφορες λειτουργίες για τον συγκεκριμένο χρήστη. Τα αποκλεισμένα αρχεία δεν θα εμφανίζονται στα περιεχόμενα των αντίστοιχων φακέλων και δεν θα είναι προσβάσιμα από τον χρήστη. Υποστηρίζονται εκφράσεις regex και διαδρομές σχετικές με την εμβέλεια αρχείων των χρηστών.\n",
"scope": "Εμβέλεια",
"setDateFormat": "Ορισμός ακριβούς μορφής ημερομηνίας",
"settingsUpdated": "Οι ρυθμίσεις ενημερώθηκαν!",
"shareDuration": "Διάρκεια κοινοποίησης",
"shareManagement": "Διαχείριση κοινοποίησης",
"shareDeleted": "Η κοινοποίηση διαγράφηκε!",
"singleClick": "Χρήση μονού κλικ για να ανοίξετε αρχεία και φακέλους",
"themes": {
"dark": "Σκοτεινό",
"light": "Φωτεινό",
"title": "Μοτίβο"
},
"user": "Χρήστης",
"userCommands": "Εντολές χρήστη",
"userCommandsHelp": "Μια λίστα με τις διαθέσιμες εντολές για αυτόν το χρήστη, χωρισμένες μεταξύ τους με κενά. Παράδειγμα:\n",
"userCreated": "Ο χρήστης δημιουργήθηκε!",
"userDefaults": "Προεπιλεγμένες ρυθμίσεις χρήστη",
"userDeleted": "Ο χρήστης διαγράφηκε!",
"userManagement": "Διαχείριση χρηστών",
"userUpdated": "Ο χρήστης ενημερώθηκε!",
"username": "Όνομα χρήστη",
"users": "Χρήστες"
},
"sidebar": {
"help": "Βοήθεια",
"hugoNew": "Νέο Hugo",
"login": "Σύνδεση",
"logout": "Αποσύνδεση",
"myFiles": "Τα αρχεία μου",
"newFile": "Νέο αρχείο",
"newFolder": "Νέος φάκελος",
"preview": "Προεπισκόπηση",
"settings": "Ρυθμίσεις",
"signup": "Εγγραφή",
"siteSettings": "Ρυθμίσεις ιστότοπου"
},
"success": {
"linkCopied": "Ο σύνδεσμος αντιγράφηκε!"
},
"time": {
"days": "Ημέρες",
"hours": "Ώρες",
"minutes": "Λεπτά",
"seconds": "Δευτερόλεπτα",
"unit": "Μονάδα χρόνου"
}
"user": "Χρήστης",
"userCommands": "Εντολές χρήστη",
"userCommandsHelp": "Μια λίστα με τις διαθέσιμες εντολές για αυτόν το χρήστη, χωρισμένες μεταξύ τους με κενά. Παράδειγμα:\n",
"userCreated": "Ο χρήστης δημιουργήθηκε!",
"userDefaults": "Προεπιλεγμένες ρυθμίσεις χρήστη",
"userDeleted": "Ο χρήστης διαγράφηκε!",
"userManagement": "Διαχείριση χρηστών",
"userUpdated": "Ο χρήστης ενημερώθηκε!",
"username": "Όνομα χρήστη",
"users": "Χρήστες",
"loginMethod": "Μέθοδος σύνδεσης",
"modifyOtherUser": "Τροποποίηση χρήστη:",
"modifyCurrentUser": "Τροποποίηση τρέχοντος χρήστη",
"themeColor": "Χρώμα θέματος",
"disableOfficePreviewDescription": "Ένας κατάλογος επεκτάσεων αρχείων με διαχωρισμό διαστήματος για την απενεργοποίηση της προβολής OnlyOffice. (π.χ. '.txt .html').",
"disableOfficePreview": "Απενεργοποίηση της προβολής onlyoffice για ορισμένες επεκτάσεις αρχείων",
"filePreviewOptions": "Επιλογές προεπισκόπησης αρχείων",
"listingOptions": "Επιλογές καταχώρισης",
"emptyGlobal": "Προς το παρόν δεν υπάρχουν καθολικές επιλογές ρυθμίσεων, Μπορείτε να επεξεργαστείτε το yaml ρυθμίσεων για να προσαρμόσετε τις ρυθμίσεις του συστήματος.",
"scopes": "Καθορισμός των επιτρεπόμενων πηγών και του πεδίου εφαρμογής του χρήστη σε αυτές",
"UserManagement": "Διαχείριση χρηστών",
"api": "Κλειδιά API",
"loginMethodDescription": "επιτρεπόμενη μέθοδος σύνδεσης για τον χρήστη.",
"userNotAdmin": "Ο χρήστης δεν είναι διαχειριστής",
"enterPassword": "Εισάγετε τον κωδικό πρόσβασης",
"enterPasswordAgain": "Πληκτρολογήστε ξανά τον κωδικό πρόσβασης",
"passwordsDoNotMatch": "Οι κωδικοί πρόσβασης δεν ταιριάζουν"
},
"sidebar": {
"help": "Βοήθεια",
"hugoNew": "Νέο Hugo",
"login": "Σύνδεση",
"logout": "Αποσύνδεση",
"myFiles": "Τα αρχεία μου",
"newFile": "Νέο αρχείο",
"newFolder": "Νέος φάκελος",
"preview": "Προεπισκόπηση",
"settings": "Ρυθμίσεις",
"signup": "Εγγραφή",
"siteSettings": "Ρυθμίσεις ιστότοπου",
"sources": "Πηγές"
},
"success": {
"linkCopied": "Ο σύνδεσμος αντιγράφηκε!"
},
"time": {
"days": "Ημέρες",
"hours": "Ώρες",
"minutes": "Λεπτά",
"seconds": "Δευτερόλεπτα",
"unit": "Μονάδα χρόνου"
},
"share": {
"notice": "Σημείωση: οποιοσδήποτε έχει πρόσβαση στο σύνδεσμο (και προαιρετικό κωδικό πρόσβασης) μπορεί να έχει πρόσβαση στα κοινόχρηστα αρχεία. Δεν υπάρχει απαίτηση σύνδεσης."
},
"api": {
"title": "Κλειδιά API:",
"keyName": "Όνομα κλειδιού API:",
"createdAt": "Δημιουργήθηκε στο:",
"expiresAt": "Λήγει στις:",
"permissions": "Άδειες:",
"createTitle": "Δημιουργία κλειδιού API",
"keyNamePlaceholder": "Εισάγετε ένα μοναδικό όνομα κλειδιού API:",
"tokenDuration": "Διάρκεια μάρκας:",
"durationNumberPlaceholder": "Αριθμός",
"days": "Ημέρες",
"months": "Μήνες",
"permissionNote": "Επιλέξτε τουλάχιστον μία άδεια για το κλειδί. Ο χρήστης σας πρέπει επίσης να έχει το δικαίωμα.",
"createKeyFailed": "Απέτυχε η δημιουργία κλειδιού API.",
"createKeySuccess": "Το κλειδί API δημιουργήθηκε με επιτυχία.",
"description": "Τα κλειδιά API βασίζονται στον χρήστη που τα δημιουργεί. Βλέπε",
"swaggerLinkText": "σελίδα swagger",
"name": "Όνομα",
"created": "Δημιουργήθηκε",
"expires": "Λήγει",
"actions": "Δράσεις",
"enabled": "Ενεργοποιημένο",
"disabled": "Άτομα με ειδικές ανάγκες"
},
"index": {
"status": "Κατάσταση",
"assessment": "Αξιολόγηση",
"files": "Αρχεία",
"folders": "Φάκελοι",
"lastScanned": "Τελευταία σάρωση",
"quickScan": "Δευτερόλεπτα γρήγορης σάρωσης",
"fullScan": "Δευτερόλεπτα πλήρους σάρωσης",
"settingsHover": "Ρυθμίσεις για τον χρήστη",
"logout": "Αποσύνδεση",
"toggleClick": "Εναλλαγή με ένα κλικ",
"toggleDark": "Εναλλαγή σκοτεινής λειτουργίας",
"toggleSticky": "Εναλλαγή της λειτουργίας Sticky",
"of": "του",
"used": "χρησιμοποιημένο"
}
}
+71 -4
View File
@@ -122,9 +122,13 @@
"submit": "Login",
"username": "Username",
"usernameTaken": "Username already taken",
"wrongCredentials": "Wrong credentials"
"wrongCredentials": "Wrong credentials",
"or": "OR"
},
"permanent": "Permanent",
"share": {
"notice": "Note: anyone who has access to the link (and optional password) can access the shared files. There is no requirement to be logged in."
},
"prompts": {
"copy": "Copy",
"copyMessage": "Choose the place to copy your files:",
@@ -132,13 +136,15 @@
"deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?",
"deleteMessageSingle": "Are you sure you want to delete this file/folder?",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
"deleteUserMessage": "Are you sure you want to delete this user?",
"deleteTitle": "Delete files",
"displayName": "Display Name:",
"download": "Download files",
"downloadMessage": "Choose the format you want to download.",
"error": "Something went wrong",
"fileInfo": "File information",
"filesSelected": "{count} files selected.",
"selected": "selected",
"filesSelected": "files selected",
"lastModified": "Last Modified",
"move": "Move",
"moveMessage": "Choose new house for your file(s)/folder(s):",
@@ -160,7 +166,8 @@
"upload": "Upload",
"uploadFiles": "Uploading {files} files...",
"uploadMessage": "Select an option to upload.",
"optionalPassword": "Optional password"
"optionalPassword": "Optional password",
"typeName": "Type:"
},
"search": {
"images": "Images",
@@ -169,13 +176,32 @@
"pressToSearch": "No results found in indexed search.",
"search": "Search...",
"typeToSearch": "Type to search... (3 character minimum)",
"path": "Path:",
"types": "Types",
"video": "Video"
"video": "Video",
"smallerThan": "Smaller than:",
"largerThan": "Larger than:",
"helpText1": "Search occurs on each character you type (3 character minimum for search terms).",
"helpText2": "The index: Search utilizes the index which automatically gets updated on the configured interval (default: 5 minutes). Searching when the program has just started may result in incomplete results."
},
"settings": {
"enterPassword": "Enter password",
"enterPasswordAgain": "Enter the password again",
"passwordsDoNotMatch": "Passwords do not match",
"loginMethod": "Login method",
"loginMethodDescription": "allowed login method for the user.",
"modifyOtherUser": "Modify user:",
"modifyCurrentUser": "Modify current user",
"themeColor": "Theme Color",
"disableOfficePreviewDescription": "A space-separated list of file extensions to disable the OnlyOffice viewer for. (e.g., '.txt .html').",
"disableOfficePreview": "Disable onlyoffice viewer for certain file extensions",
"filePreviewOptions": "File preview options",
"listingOptions": "Listing options",
"emptyGlobal": "Currently there are no global config options, You can edit the configuration yaml to adjust system settings.",
"scopes": "Define allowed sources and the user scope within them",
"UserManagement": "User Management",
"admin": "Admin",
"userNotAdmin": "User is not an administrator",
"administrator": "Administrator",
"allowCommands": "Execute commands",
"allowEdit": "Edit, rename and delete files or directories",
@@ -267,6 +293,7 @@
"newFolder": "New folder",
"preview": "Preview",
"settings": "Settings",
"sources": "Sources",
"signup": "Signup",
"siteSettings": "Site Settings"
},
@@ -279,5 +306,45 @@
"minutes": "Minutes",
"seconds": "Seconds",
"unit": "Time Unit"
},
"api": {
"title": "API Keys:",
"keyName": "API Key Name:",
"createdAt": "Created At:",
"expiresAt": "Expires At:",
"permissions": "Permissions:",
"createTitle": "Create API Key",
"keyNamePlaceholder": "Enter a unique API key name:",
"tokenDuration": "Token Duration:",
"durationNumberPlaceholder": "Number",
"days": "Days",
"months": "Months",
"permissionNote": "Choose at least one permission for the key. Your user must also have the permission.",
"createKeyFailed": "Failed to create API key.",
"createKeySuccess": "API key created successfully.",
"description": "API keys are based on the user that creates them. See",
"swaggerLinkText": "swagger page",
"name": "Name",
"created": "Created",
"expires": "Expires",
"actions": "Actions",
"enabled": "Enabled",
"disabled": "Disabled"
},
"index": {
"status": "Status",
"assessment": "Assessment",
"files": "Files",
"folders": "Folders",
"lastScanned": "Last Scanned",
"quickScan": "Quick Scan Seconds",
"fullScan": "Full Scan Seconds",
"settingsHover": "Settings For User",
"logout": "Logout",
"toggleClick": "Toggle Single Click",
"toggleDark": "Toggle Dark Mode",
"toggleSticky": "Toggle Sticky Mode",
"of": "of",
"used": "used"
}
}
+90 -9
View File
@@ -35,7 +35,9 @@
"toggleSidebar": "Mostrar/Ocultar menú",
"update": "Actualizar",
"upload": "Subir",
"openFile": "Abrir archivo"
"openFile": "Abrir archivo",
"copyDownloadLinkToClipboard": "Copiar enlace de descarga al portapapeles",
"continue": "Continúe en"
},
"download": {
"downloadFile": "Descargar fichero",
@@ -101,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -116,7 +118,8 @@
"submit": "Iniciar sesión",
"username": "Usuario",
"usernameTaken": "Nombre usuario no disponible",
"wrongCredentials": "Usuario y/o contraseña incorrectos"
"wrongCredentials": "Usuario y/o contraseña incorrectos",
"or": "O"
},
"permanent": "Permanente",
"prompts": {
@@ -132,7 +135,6 @@
"downloadMessage": "Elige el formato de descarga.",
"error": "Algo ha fallado",
"fileInfo": "Información del archivo",
"filesSelected": "{count} archivos seleccionados.",
"lastModified": "Última modificación",
"move": "Mover",
"moveMessage": "Elige una nueva casa para tus archivo(s)/carpeta(s):",
@@ -154,7 +156,11 @@
"upload": "Subir",
"uploadFiles": "Subiendo {files} archivos...",
"uploadMessage": "Seleccione una opción para subir.",
"optionalPassword": "Contraseña opcional"
"optionalPassword": "Contraseña opcional",
"deleteUserMessage": "¿Está seguro de que desea eliminar este usuario?",
"filesSelected": "archivos seleccionados",
"typeName": "Tipo:",
"selected": "seleccionado"
},
"search": {
"images": "Imágenes",
@@ -164,7 +170,12 @@
"search": "Buscar...",
"typeToSearch": "Escribe para realizar una busqueda...",
"types": "Tipos",
"video": "Vídeo"
"video": "Vídeo",
"path": "Senda:",
"smallerThan": "Más pequeño que:",
"largerThan": "Más grande que:",
"helpText1": "La búsqueda se realiza por cada carácter que escriba (mínimo de 3 caracteres para los términos de búsqueda).",
"helpText2": "El índice: La búsqueda utiliza el índice que se actualiza automáticamente en el intervalo configurado (por defecto: 5 minutos). La búsqueda cuando el programa acaba de iniciarse puede dar lugar a resultados incompletos."
},
"settings": {
"scopes": "Definir fuentes permitidas y el alcance del usuario dentro de ellas",
@@ -215,7 +226,10 @@
"execute": "Executar comandos",
"modify": "Editar ficheros",
"rename": "Renombrar o mover ficheros y directorios",
"share": "Compartir ficheros"
"share": "Compartir ficheros",
"admin": "Administrador",
"api": "Crear y gestionar claves API de larga duración",
"realtime": "Permitir conexiones y actualizaciones en tiempo real"
},
"permissions-name": "Permisos",
"permissionsHelp": "Puedes nombrar al usuario como administrador o elegir los permisos individualmente. Si seleccionas \"Administrador\", todas las otras opciones serán activadas automáticamente. La administración de usuarios es un privilegio de administrador.\n",
@@ -245,7 +259,27 @@
"userManagement": "Administración de usuarios",
"userUpdated": "¡Usuario actualizado!",
"username": "Usuario",
"users": "Usuarios"
"users": "Usuarios",
"loginMethod": "Método de inicio de sesión",
"modifyOtherUser": "Modificar usuario:",
"modifyCurrentUser": "Modificar el usuario actual",
"themeColor": "Tema Color",
"disableOfficePreviewDescription": "Una lista separada por espacios de las extensiones de archivo para las que desea desactivar el visor de OnlyOffice. (por ejemplo, '.txt .html').",
"disableOfficePreview": "Desactivar onlyoffice viewer para determinadas extensiones de archivo",
"filePreviewOptions": "Opciones de previsualización de archivos",
"listingOptions": "Opciones de listado",
"emptyGlobal": "Actualmente no hay opciones de configuración global, puede editar el yaml de configuración para ajustar la configuración del sistema.",
"UserManagement": "Gestión de usuarios",
"tusUploads": "Subidas en trozos",
"tusUploadsHelp": "File Browser admite la carga de archivos en trozos, lo que permite crear cargas de archivos eficientes, fiables, reanudables y en trozos, incluso en redes poco fiables.",
"tusUploadsChunkSize": "Indica el tamaño máximo de una solicitud (las cargas directas se utilizarán para cargas más pequeñas). Puede introducir un número entero que indique una entrada en bytes o una cadena como 10 MB, 1 GB, etc.",
"tusUploadsRetryCount": "Número de reintentos a realizar si falla la subida de un chunk.",
"api": "Claves API",
"loginMethodDescription": "método de inicio de sesión permitido para el usuario.",
"userNotAdmin": "El usuario no es administrador",
"enterPassword": "Introduzca la contraseña",
"enterPasswordAgain": "Vuelva a introducir la contraseña",
"passwordsDoNotMatch": "Las contraseñas no coinciden"
},
"sidebar": {
"help": "Ayuda",
@@ -258,7 +292,8 @@
"preview": "Vista previa",
"settings": "Ajustes",
"signup": "Registrate",
"siteSettings": "Ajustes del sitio"
"siteSettings": "Ajustes del sitio",
"sources": "Fuentes"
},
"success": {
"linkCopied": "¡Link copiado!"
@@ -269,5 +304,51 @@
"minutes": "Minutos",
"seconds": "Segundos",
"unit": "Unidad"
},
"upload": {
"abortUpload": "¿Seguro que quieres abortar?"
},
"share": {
"notice": "Nota: cualquiera que tenga acceso al enlace (y contraseña opcional) puede acceder a los archivos compartidos. No es necesario haber iniciado sesión."
},
"api": {
"title": "Claves API:",
"keyName": "Nombre de la clave API:",
"createdAt": "Creado en:",
"expiresAt": "Expira en:",
"permissions": "Permisos:",
"createTitle": "Crear clave API",
"keyNamePlaceholder": "Introduzca un nombre de clave de API único:",
"tokenDuration": "Duración de la ficha:",
"durationNumberPlaceholder": "Número",
"days": "Días",
"months": "Meses",
"permissionNote": "Elija al menos un permiso para la clave. Su usuario también debe tener el permiso.",
"createKeyFailed": "Error al crear la clave API.",
"createKeySuccess": "Clave API creada correctamente.",
"description": "Las claves API se basan en el usuario que las crea. Véase",
"swaggerLinkText": "página swagger",
"name": "Nombre",
"created": "Creado",
"expires": "Expira en",
"actions": "Acciones",
"enabled": "Activado",
"disabled": "Discapacitados"
},
"index": {
"status": "Estado",
"assessment": "Evaluación",
"files": "Archivos",
"folders": "Carpetas",
"lastScanned": "Último escaneado",
"quickScan": "Segundos de exploración rápida",
"fullScan": "Segundos de exploración completa",
"settingsHover": "Ajustes para el usuario",
"logout": "Cierre de sesión",
"toggleClick": "Conmutar un solo clic",
"toggleDark": "Activar el modo oscuro",
"toggleSticky": "Conmutar el modo adhesivo",
"of": "de",
"used": "usado"
}
}
+95 -11
View File
@@ -35,7 +35,9 @@
"toggleSidebar": "Afficher/Masquer la barre latérale",
"update": "Mettre à jour",
"upload": "Importer",
"openFile": "Ouvrir le fichier"
"openFile": "Ouvrir le fichier",
"copyDownloadLinkToClipboard": "Copier le lien de téléchargement dans le presse-papiers",
"continue": "Continuer"
},
"download": {
"downloadFile": "Télécharger le fichier",
@@ -101,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -116,7 +118,8 @@
"submit": "Se connecter",
"username": "Utilisateur",
"usernameTaken": "Le nom d'utilisateur est déjà pris",
"wrongCredentials": "Identifiants incorrects !"
"wrongCredentials": "Identifiants incorrects !",
"or": "OU"
},
"permanent": "Permanent",
"prompts": {
@@ -132,7 +135,6 @@
"downloadMessage": "Choisissez le format de téléchargement :",
"error": "Quelque chose s'est mal passé",
"fileInfo": "Informations",
"filesSelected": "{count} éléments sélectionnés",
"lastModified": "Dernière modification",
"move": "Déplacer",
"moveMessage": "Choisissez l'emplacement où déplacer la sélection :",
@@ -154,7 +156,11 @@
"upload": "Importer",
"uploadFiles": "Importation de {files} fichiers...",
"uploadMessage": "Séléctionnez une option d'import.",
"optionalPassword": "Mot de passe optionnel"
"optionalPassword": "Mot de passe optionnel",
"deleteUserMessage": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?",
"filesSelected": "fichiers sélectionnés",
"typeName": "Type :",
"selected": "sélectionné"
},
"search": {
"images": "Images",
@@ -164,7 +170,12 @@
"search": "Recherche en cours...",
"typeToSearch": "Écrivez pour chercher...",
"types": "Types",
"video": "Video"
"video": "Video",
"path": "Trajectoire :",
"smallerThan": "Plus petit que :",
"largerThan": "Plus grand que :",
"helpText1": "La recherche s'effectue sur chaque caractère saisi (3 caractères minimum pour les termes de recherche).",
"helpText2": "L'index : La recherche utilise l'index qui est automatiquement mis à jour à l'intervalle configuré (par défaut : 5 minutes). Une recherche effectuée alors que le programme vient de démarrer peut donner des résultats incomplets."
},
"settings": {
"scopes": "Définir les sources autorisées et la portée de l'utilisateur au sein de celles-ci",
@@ -181,7 +192,7 @@
"brandingHelp": "Vous pouvez personnaliser l'apparence de votre instance de File Browser en changeant son nom, en remplaçant le logo, en ajoutant des styles personnalisés et même en désactivant les liens externes vers GitHub.\nPour plus d'informations sur la personnalisation de l'image de marque, veuillez consulter la {0}.",
"changePassword": "Modifier le mot de passe",
"commandRunner": "Command runner",
"commandRunnerHelp" : "Ici, vous pouvez définir les commandes qui sont exécutées pour les événements nommés précédemments. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez lire la {2}.",
"commandRunnerHelp": "Ici, vous pouvez définir les commandes qui sont exécutées pour les événements nommés précédemments. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez lire la {2}.",
"commandsUpdated": "Commandes mises à jour !",
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur",
"customStylesheet": "Feuille de style personnalisée",
@@ -204,7 +215,7 @@
"newUser": "Nouvel Utilisateur",
"password": "Mot de passe",
"passwordUpdated": "Mot de passe mis à jour !",
"path": "",
"path": "Trajectoire",
"permissions": {
"create": "Créer des fichiers et des dossiers",
"delete": "Supprimer des fichiers et des dossiers",
@@ -212,7 +223,10 @@
"execute": "Exécuter des commandes",
"modify": "Modifier des fichiers",
"rename": "Renommer ou déplacer des fichiers ou des dossiers",
"share": "Partager des fichiers"
"share": "Partager des fichiers",
"admin": "Administrateur",
"api": "Créer et gérer des clés API à longue durée de vie",
"realtime": "Permettre des connexions et des mises à jour en temps réel"
},
"permissions-name": "Permissions",
"permissionsHelp": "Vous pouvez définir l'utilisateur comme étant un administrateur ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur\", toutes les autres options seront automatiquement activées. La gestion des utilisateurs est un privilège que seul l'administrateur possède.\n",
@@ -242,7 +256,30 @@
"userManagement": "Gestion des utilisateurs",
"userUpdated": "Utilisateur mis à jour !",
"username": "Nom d'utilisateur",
"users": "Utilisateurs"
"users": "Utilisateurs",
"loginMethod": "Méthode de connexion",
"modifyOtherUser": "Modifier l'utilisateur :",
"modifyCurrentUser": "Modifier l'utilisateur actuel",
"themeColor": "Couleur du thème",
"disableOfficePreviewDescription": "Une liste d'extensions de fichiers séparées par des espaces pour lesquelles le visualisateur OnlyOffice doit être désactivé. (par exemple, '.txt .html').",
"disableOfficePreview": "Désactiver la visionneuse d'Office pour certaines extensions de fichiers",
"filePreviewOptions": "Options de prévisualisation des fichiers",
"listingOptions": "Options d'inscription",
"emptyGlobal": "Actuellement, il n'y a pas d'options de configuration globale. Vous pouvez éditer le fichier de configuration yaml pour ajuster les paramètres du système.",
"UserManagement": "Gestion des utilisateurs",
"tusUploads": "Chargements groupés",
"tusUploadsHelp": "File Browser prend en charge les téléchargements de fichiers par morceaux, ce qui permet de créer des téléchargements de fichiers efficaces, fiables, résumables et par morceaux, même sur des réseaux peu fiables.",
"tusUploadsChunkSize": "Indique la taille maximale d'une demande (les téléchargements directs seront utilisés pour les téléchargements plus petits). Vous pouvez saisir un nombre entier simple correspondant à un nombre d'octets ou une chaîne de caractères telle que 10 Mo, 1 Go, etc.",
"tusUploadsRetryCount": "Nombre de tentatives à effectuer si un bloc ne parvient pas à être téléchargé.",
"userHomeBasePath": "Chemin de base pour les répertoires personnels des utilisateurs",
"userScopeGenerationPlaceholder": "Le champ d'application sera généré automatiquement",
"createUserHomeDirectory": "Créer le répertoire personnel de l'utilisateur",
"api": "Clés API",
"loginMethodDescription": "méthode de connexion autorisée pour l'utilisateur.",
"userNotAdmin": "L'utilisateur n'est pas un administrateur",
"enterPassword": "Saisir le mot de passe",
"enterPasswordAgain": "Saisir à nouveau le mot de passe",
"passwordsDoNotMatch": "Les mots de passe ne correspondent pas"
},
"sidebar": {
"help": "Aide",
@@ -255,7 +292,8 @@
"preview": "Prévisualiser",
"settings": "Paramètres",
"signup": "S'inscrire",
"siteSettings": "Paramètres du site"
"siteSettings": "Paramètres du site",
"sources": "Sources d'information"
},
"success": {
"linkCopied": "Lien copié!"
@@ -266,5 +304,51 @@
"minutes": "Minutes",
"seconds": "Secondes",
"unit": "Unité de temps"
},
"upload": {
"abortUpload": "Êtes-vous sûre de vouloir avorter ?"
},
"share": {
"notice": "Remarque : toute personne ayant accès au lien (et au mot de passe facultatif) peut accéder aux fichiers partagés. Il n'est pas nécessaire d'être connecté."
},
"api": {
"title": "Clés API :",
"keyName": "Nom de la clé API :",
"createdAt": "Créé à :",
"expiresAt": "Expire le :",
"permissions": "Permissions :",
"createTitle": "Créer une clé API",
"keyNamePlaceholder": "Saisissez un nom de clé API unique :",
"tokenDuration": "Durée du jeton :",
"durationNumberPlaceholder": "Nombre",
"days": "Jours",
"months": "Mois",
"permissionNote": "Choisissez au moins une autorisation pour la clé. Votre utilisateur doit également disposer de cette autorisation.",
"createKeyFailed": "Échec de la création de la clé API.",
"createKeySuccess": "Clé API créée avec succès.",
"description": "Les clés d'API sont basées sur l'utilisateur qui les a créées. Voir les",
"swaggerLinkText": "page swagger",
"name": "Nom",
"created": "Créée",
"expires": "Expiration",
"actions": "Actions",
"enabled": "Activé",
"disabled": "Handicapés"
},
"index": {
"status": "Statut",
"assessment": "L'évaluation",
"files": "Dossiers",
"folders": "Dossiers",
"lastScanned": "Dernière numérisation",
"quickScan": "Balayage rapide Secondes",
"fullScan": "Balayage complet en secondes",
"settingsHover": "Paramètres pour l'utilisateur",
"logout": "Déconnexion",
"toggleClick": "Toggle Single Click",
"toggleDark": "Basculer en mode sombre",
"toggleSticky": "Basculer le mode collant",
"of": "de",
"used": "utilisé"
}
}
+5 -2
View File
@@ -132,7 +132,6 @@
"downloadMessage": "בחר את הפורמט שברצונך להוריד",
"error": "משהו השתבש",
"fileInfo": "מידע על הקובץ",
"filesSelected": "{count} קבצים נבחרו.",
"lastModified": "שונה לאחרונה",
"move": "העבר",
"moveMessage": "בחר מיקום חדש לקובץ / תקייה:",
@@ -267,5 +266,9 @@
"minutes": "דקות",
"seconds": "שניות",
"unit": "יחידת זמן"
}
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
+91 -8
View File
@@ -35,7 +35,9 @@
"toggleSidebar": "Oldalsáv átváltása",
"update": "Frissítés",
"upload": "Feltöltés",
"openFile": "Fájl megnyitása"
"openFile": "Fájl megnyitása",
"copyDownloadLinkToClipboard": "Letöltési link másolása a vágólapra",
"continue": "Folytatás"
},
"download": {
"downloadFile": "Fájl letöltése",
@@ -116,7 +118,8 @@
"submit": "Belépés",
"username": "Felhasználói név",
"usernameTaken": "A felhasználói név már foglalt",
"wrongCredentials": "Hibás hitelesítő adatok"
"wrongCredentials": "Hibás hitelesítő adatok",
"or": "VAGY"
},
"permanent": "Állandó",
"prompts": {
@@ -132,7 +135,6 @@
"downloadMessage": "Válassza ki a letöltés formátumát.",
"error": "Valami rosszul sült el",
"fileInfo": "Fájlinformáció",
"filesSelected": "{count} fájl van kijelölve.",
"lastModified": "Utolsó módosítás",
"move": "Mozgatás",
"moveMessage": "Válasszon új helyet a fájl(ok)nak/mappá(k)nak:",
@@ -154,7 +156,11 @@
"upload": "Feltöltés",
"uploadFiles": "{files} fájl feltöltése…",
"uploadMessage": "Válasszon egy feltöltési módot.",
"optionalPassword": "Választható jelszó"
"optionalPassword": "Választható jelszó",
"deleteUserMessage": "Biztos, hogy törölni szeretné ezt a felhasználót?",
"filesSelected": "kiválasztott fájlok",
"typeName": "Típus:",
"selected": "kiválasztott"
},
"search": {
"images": "Képek",
@@ -164,7 +170,12 @@
"search": "Keresés…",
"typeToSearch": "Keresés indítása beírással…",
"types": "Típusok",
"video": "Videó"
"video": "Videó",
"path": "Útvonal:",
"smallerThan": "Kisebb, mint:",
"largerThan": "Nagyobb, mint:",
"helpText1": "A keresés minden egyes beírt karakterre történik (minimum 3 karakteres keresőkifejezés).",
"helpText2": "Az index: A keresés az indexet használja, amely automatikusan frissül a beállított időközönként (alapértelmezett: 5 perc). A program indulásakor végzett keresés hiányos eredményeket eredményezhet."
},
"settings": {
"admin": "Admin",
@@ -213,7 +224,10 @@
"execute": "Parancsok futtatása",
"modify": "Fájlok szerkesztése",
"rename": "Fájlok és mappák átnevezése vagy mozgatása",
"share": "Fájlok megosztása"
"share": "Fájlok megosztása",
"admin": "Adminisztrátor",
"api": "Hosszú élettartamú API-kulcsok létrehozása és kezelése",
"realtime": "Valós idejű kapcsolatok és frissítések engedélyezése"
},
"permissions-name": "Jogosultságok",
"permissionsHelp": "A felhasználót beállíthatja rendszergazdának, vagy egyénileg is kiválaszthatja a jogosultságokat. Ha a \"Rendszergazda\" lehetőséget választja, az összes többi opció automatikusan be lesz jelölve. A felhasználók kezelése továbbra is a rendszergazda kiváltsága marad.\n",
@@ -243,7 +257,29 @@
"userManagement": "Felhasználókezelés",
"userUpdated": "Felhasználó frissítve!",
"username": "Felhasználói név",
"users": "Felhasználók"
"users": "Felhasználók",
"loginMethod": "Bejelentkezési módszer",
"modifyOtherUser": "Felhasználó módosítása:",
"modifyCurrentUser": "Jelenlegi felhasználó módosítása",
"themeColor": "Téma színe",
"disableOfficePreviewDescription": "Azon fájlkiterjesztések szóközzel elválasztott listája, amelyek esetében az OnlyOffice megjelenítőjét le kell tiltani. (pl. '.txt .html').",
"disableOfficePreview": "Letiltja a onlyoffice viewer-t bizonyos fájlkiterjesztések esetén",
"filePreviewOptions": "Fájl előnézeti beállítások",
"listingOptions": "Listázási lehetőségek",
"emptyGlobal": "Jelenleg nincsenek globális konfigurációs beállítások, A rendszerbeállítások módosításához szerkesztheti a konfigurációs yaml-t.",
"scopes": "Az engedélyezett források és az azokon belüli felhasználói hatókörök meghatározása",
"UserManagement": "Felhasználó kezelése",
"tusUploads": "Csomagolt feltöltések",
"tusUploadsHelp": "A File Browser támogatja a darabolt fájlfeltöltést, lehetővé téve a hatékony, megbízható, folytatható és darabolt fájlfeltöltést még megbízhatatlan hálózatokon is.",
"tusUploadsChunkSize": "A kérés maximális méretét jelzi (a kisebb feltöltéseknél a közvetlen feltöltést használják). Beadhat egy egyszerű egész számot, amely a bájtos bemenetet jelöli, vagy egy karakterláncot, például 10MB, 1GB stb.",
"tusUploadsRetryCount": "Az újbóli próbálkozások száma, ha egy darabkát nem sikerül feltölteni.",
"disableUsedDiskPercentage": "A használt lemez százalékos grafikonjának kikapcsolása",
"api": "API kulcsok",
"loginMethodDescription": "engedélyezett bejelentkezési módszer a felhasználó számára.",
"userNotAdmin": "A felhasználó nem rendszergazda",
"enterPassword": "Jelszó megadása",
"enterPasswordAgain": "Adja meg újra a jelszót",
"passwordsDoNotMatch": "A jelszavak nem egyeznek"
},
"sidebar": {
"help": "Súgó",
@@ -256,7 +292,8 @@
"preview": "Előnézet",
"settings": "Beállítások",
"signup": "Regisztráció",
"siteSettings": "Oldalbeállítások"
"siteSettings": "Oldalbeállítások",
"sources": "Források"
},
"success": {
"linkCopied": "Link másolva!"
@@ -267,5 +304,51 @@
"minutes": "Perc",
"seconds": "Másodperc",
"unit": "Időegység"
},
"upload": {
"abortUpload": "Biztos, hogy meg akarja szakítani?"
},
"share": {
"notice": "Megjegyzés: bárki, aki hozzáfér a linkhez (és opcionális jelszóval), hozzáférhet a megosztott fájlokhoz. Nem szükséges bejelentkezni."
},
"api": {
"title": "API-kulcsok:",
"keyName": "API-kulcs neve:",
"createdAt": "Létrehozva:",
"expiresAt": "Lejár:",
"permissions": "Engedélyek:",
"createTitle": "API kulcs létrehozása",
"keyNamePlaceholder": "Adjon meg egy egyedi API-kulcs nevet:",
"tokenDuration": "Token időtartama:",
"durationNumberPlaceholder": "Szám",
"days": "Napok",
"months": "Hónapok",
"permissionNote": "Válasszon legalább egy engedélyt a kulcshoz. A felhasználónak is rendelkeznie kell az adott jogosultsággal.",
"createKeyFailed": "Sikertelen API-kulcs létrehozása.",
"createKeySuccess": "Sikeresen létrehozott API-kulcs.",
"description": "Az API-kulcsok a létrehozó felhasználó alapján készülnek. Lásd",
"swaggerLinkText": "swagger oldal",
"name": "Név",
"created": "Létrehozva",
"expires": "Lejárat:",
"actions": "Tevékenységek",
"enabled": "Engedélyezve",
"disabled": "Mozgáskorlátozottak"
},
"index": {
"status": "Állapot",
"assessment": "Értékelés",
"files": "Fájlok",
"folders": "Mappák",
"lastScanned": "Utoljára beolvasott",
"quickScan": "Gyors beolvasás másodpercek",
"fullScan": "Teljes letapogatás másodpercek",
"settingsHover": "Felhasználói beállítások",
"logout": "Kijelentkezés",
"toggleClick": "Egyetlen kattintás kapcsolása",
"toggleDark": "Sötét üzemmód átkapcsolása",
"toggleSticky": "Ragadós mód átkapcsolása",
"of": "a",
"used": "használt"
}
}
+8 -4
View File
@@ -95,7 +95,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -125,7 +125,6 @@
"downloadMessage": "Veldu skrárgerð sem þú vilt sækja.",
"error": "Eitthvað fór úrskeiðis",
"fileInfo": "Upplýsingar um gögn",
"filesSelected": "{count} skjöl valin.",
"lastModified": "Seinast breytt",
"move": "Færa",
"moveMessage": "Velja nýtt hús fyrir skjöl/möppur:",
@@ -145,7 +144,8 @@
"show": "Sýna",
"size": "Stærð",
"upload": "",
"uploadMessage": ""
"uploadMessage": "",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?"
},
"search": {
"images": "Myndir",
@@ -254,5 +254,9 @@
"minutes": "Mínútur",
"seconds": "Sekúndur",
"unit": "Tímastilling"
}
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
+107 -11
View File
@@ -31,7 +31,13 @@
"switchView": "Cambia vista",
"toggleSidebar": "Mostra/nascondi la barra laterale",
"update": "Aggiorna",
"upload": "Carica"
"upload": "Carica",
"copyDownloadLinkToClipboard": "Copiare il link di download negli appunti",
"file": "File",
"folder": "Cartella",
"submit": "Invia",
"openFile": "Aprire il file",
"continue": "Continua"
},
"download": {
"downloadFile": "Scarica file",
@@ -41,7 +47,8 @@
"errors": {
"forbidden": "Non hai i permessi per accedere a questo file.",
"internal": "Qualcosa è andato veramente male.",
"notFound": "Questo percorso non può essere raggiunto."
"notFound": "Questo percorso non può essere raggiunto.",
"connection": "Il server non è raggiungibile."
},
"files": {
"body": "Contenuto",
@@ -59,7 +66,8 @@
"size": "Dimensione",
"sortByLastModified": "Ordina per ultima modifica",
"sortByName": "Ordina per nome",
"sortBySize": "Ordina per dimensione"
"sortBySize": "Ordina per dimensione",
"noPreview": "L'anteprima non è disponibile per questo file."
},
"help": {
"click": "seleziona un file o una cartella",
@@ -95,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -110,7 +118,8 @@
"submit": "Entra",
"username": "Nome utente",
"usernameTaken": "Username già usato",
"wrongCredentials": "Credenziali errate"
"wrongCredentials": "Credenziali errate",
"or": "O"
},
"permanent": "Permanente",
"prompts": {
@@ -125,7 +134,6 @@
"downloadMessage": "Seleziona il formato che vuoi scaricare.",
"error": "Qualcosa è andato per il verso storto",
"fileInfo": "Informazioni sul file",
"filesSelected": "{count} file selezionati.",
"lastModified": "Ultima modifica",
"move": "Sposta",
"moveMessage": "Seleziona la nuova posizione per i tuoi file e/o cartella/e:",
@@ -145,7 +153,14 @@
"show": "Mostra",
"size": "Dimensione",
"upload": "Carica",
"uploadMessage": "Seleziona un'opzione per il caricamento."
"uploadMessage": "Seleziona un'opzione per il caricamento.",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
"deleteUserMessage": "Siete sicuri di voler eliminare questo utente?",
"filesSelected": "file selezionati",
"uploadFiles": "Uploading {files} files...",
"optionalPassword": "Password opzionale",
"typeName": "Tipo:",
"selected": "selezionato"
},
"search": {
"images": "Immagini",
@@ -155,7 +170,12 @@
"search": "Cerca...",
"typeToSearch": "Scrivi per cercare...",
"types": "Tipi",
"video": "Video"
"video": "Video",
"path": "Percorso:",
"smallerThan": "Più piccolo di:",
"largerThan": "Più grande di:",
"helpText1": "La ricerca avviene per ogni carattere digitato (minimo 3 caratteri per i termini di ricerca).",
"helpText2": "L'indice: La ricerca utilizza l'indice che viene aggiornato automaticamente all'intervallo configurato (default: 5 minuti). La ricerca quando il programma è appena stato avviato può dare risultati incompleti."
},
"settings": {
"admin": "Admin",
@@ -202,7 +222,10 @@
"execute": "Eseguire comandi",
"modify": "Modificare file",
"rename": "Rinominare o spostare file e cartelle",
"share": "Condividere file"
"share": "Condividere file",
"admin": "Amministratore",
"api": "Creare e gestire chiavi API a lunga durata",
"realtime": "Consentire connessioni e aggiornamenti in tempo reale"
},
"permissions-name": "Permessi",
"permissionsHelp": "È possibile impostare l'utente come amministratore o scegliere i permessi singolarmente. Se si seleziona \"Amministratore\", tutte le altre opzioni saranno automaticamente assegnate. La gestione degli utenti rimane un privilegio di un amministratore.\n",
@@ -230,7 +253,33 @@
"userManagement": "Gestione degli utenti",
"userUpdated": "Utente aggiornato!",
"username": "Nome utente",
"users": "Utenti"
"users": "Utenti",
"loginMethod": "Metodo di accesso",
"modifyOtherUser": "Modificare l'utente:",
"modifyCurrentUser": "Modificare l'utente corrente",
"themeColor": "Colore del tema",
"disableOfficePreviewDescription": "Un elenco separato da spazi di estensioni di file per cui disabilitare il visualizzatore di OnlyOffice. (ad esempio, '.txt .html').",
"disableOfficePreview": "Disabilitare il visualizzatore onlyoffice per alcune estensioni di file",
"filePreviewOptions": "Opzioni di anteprima dei file",
"listingOptions": "Opzioni di quotazione",
"emptyGlobal": "Attualmente non ci sono opzioni di configurazione globale, ma è possibile modificare lo yaml di configurazione per regolare le impostazioni del sistema.",
"scopes": "Definire le fonti consentite e l'ambito dell'utente all'interno di esse",
"UserManagement": "Gestione degli utenti",
"tusUploads": "Caricamenti spezzettati",
"tusUploadsHelp": "File Browser supporta il caricamento di file a pezzetti, consentendo la creazione di upload di file efficienti, affidabili, riprendibili e a pezzetti anche su reti inaffidabili.",
"tusUploadsChunkSize": "Indica la dimensione massima di una richiesta (i caricamenti diretti saranno utilizzati per i caricamenti più piccoli). È possibile immettere un numero intero semplice che indica un input in byte o una stringa come 10MB, 1GB ecc.",
"tusUploadsRetryCount": "Numero di tentativi da eseguire se un chunk non riesce a essere caricato.",
"userHomeBasePath": "Percorso di base per le directory home degli utenti",
"userScopeGenerationPlaceholder": "L'ambito sarà generato automaticamente",
"createUserHomeDirectory": "Creare la directory home dell'utente",
"api": "Chiavi API",
"setDateFormat": "Impostare il formato esatto della data",
"shareDeleted": "Condividi cancellato!",
"loginMethodDescription": "metodo di accesso consentito per l'utente.",
"userNotAdmin": "L'utente non è un amministratore",
"enterPassword": "Inserire la password",
"enterPasswordAgain": "Immettere nuovamente la password",
"passwordsDoNotMatch": "Le password non corrispondono"
},
"sidebar": {
"help": "Aiuto",
@@ -243,7 +292,8 @@
"preview": "Anteprima",
"settings": "Impostazioni",
"signup": "Registrati",
"siteSettings": "Impostazioni del sito"
"siteSettings": "Impostazioni del sito",
"sources": "Fonti"
},
"success": {
"linkCopied": "Link copiato!"
@@ -254,5 +304,51 @@
"minutes": "Minuti",
"seconds": "Secondi",
"unit": "Unità di tempo"
},
"upload": {
"abortUpload": "Sei sicura di voler abortire?"
},
"share": {
"notice": "Nota: chiunque abbia accesso al link (e alla password opzionale) può accedere ai file condivisi. Non è necessario effettuare il login."
},
"api": {
"title": "Chiavi API:",
"keyName": "Nome della chiave API:",
"createdAt": "Creato a:",
"expiresAt": "Scade a:",
"permissions": "Permessi:",
"createTitle": "Creare la chiave API",
"keyNamePlaceholder": "Inserire un nome univoco per la chiave API:",
"tokenDuration": "Durata del gettone:",
"durationNumberPlaceholder": "Numero",
"days": "Giorni",
"months": "Mesi",
"permissionNote": "Scegliere almeno un'autorizzazione per la chiave. Anche l'utente deve avere l'autorizzazione.",
"createKeyFailed": "Impossibile creare la chiave API.",
"createKeySuccess": "Chiave API creata con successo.",
"description": "Le chiavi API si basano sull'utente che le crea. Vedere",
"swaggerLinkText": "pagina swagger",
"name": "Nome",
"created": "Creato",
"expires": "Scadenza",
"actions": "Azioni",
"enabled": "Abilitato",
"disabled": "Disabili"
},
"index": {
"status": "Stato",
"assessment": "Valutazione",
"files": "File",
"folders": "Cartelle",
"lastScanned": "Ultima scansione",
"quickScan": "Scansione rapida Secondi",
"fullScan": "Secondi di scansione completa",
"settingsHover": "Impostazioni per l'utente",
"logout": "Disconnessione",
"toggleClick": "Alterna il clic singolo",
"toggleDark": "Alterna la modalità scura",
"toggleSticky": "Alterna la modalità appiccicosa",
"of": "di",
"used": "usato"
}
}
+116 -20
View File
@@ -31,17 +31,24 @@
"switchView": "表示を切り替わる",
"toggleSidebar": "サイドバーを表示する",
"update": "更新",
"upload": "アップロード"
"upload": "アップロード",
"copyDownloadLinkToClipboard": "ダウンロードリンクをクリップボードにコピーする",
"file": "ファイル",
"folder": "フォルダ",
"submit": "投稿する",
"openFile": "ファイルを開く",
"continue": "続ける"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder",
"downloadSelected": ""
"downloadSelected": "ダウンロード"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "内部エラーが発生しました。",
"notFound": "リソースが見つからなりませんでした。"
"notFound": "リソースが見つからなりませんでした。",
"connection": "サーバーにアクセスできません。"
},
"files": {
"body": "本文",
@@ -59,7 +66,8 @@
"size": "サイズ",
"sortByLastModified": "最終変更日付によるソート",
"sortByName": "名前によるソート",
"sortBySize": "サイズによるソート"
"sortBySize": "サイズによるソート",
"noPreview": "このファイルではプレビューは利用できません。"
},
"help": {
"click": "ファイルやディレクトリを選択",
@@ -95,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -110,7 +118,8 @@
"submit": "ログイン",
"username": "ユーザ名",
"usernameTaken": "Username already taken",
"wrongCredentials": "ユーザ名またはパスワードが間違っています。"
"wrongCredentials": "ユーザ名またはパスワードが間違っています。",
"or": "または"
},
"permanent": "永久",
"prompts": {
@@ -125,7 +134,6 @@
"downloadMessage": "圧縮形式を選択してください。",
"error": "あるエラーが発生しました。",
"fileInfo": "ファイル情報",
"filesSelected": "{count} つのファイルは選択されました。",
"lastModified": "最終変更",
"move": "移動",
"moveMessage": "移動の目標ディレクトリを選択してください:",
@@ -144,8 +152,15 @@
"scheduleMessage": "このポストの発表日付をスケジュールしてください。",
"show": "表示",
"size": "サイズ",
"upload": "",
"uploadMessage": ""
"upload": "アップロード",
"uploadMessage": "アップロードするオプションを選択します。",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
"deleteUserMessage": "本当にこのユーザーを削除しますか?",
"filesSelected": "選択されたファイル",
"uploadFiles": "Uploading {files} files...",
"optionalPassword": "任意のパスワード",
"typeName": "タイプ",
"selected": "選ばれた"
},
"search": {
"images": "画像",
@@ -155,7 +170,12 @@
"search": "検索...",
"typeToSearch": "Type to search...",
"types": "種類",
"video": "ビデオ"
"video": "ビデオ",
"path": "パス",
"smallerThan": "より小さい:",
"largerThan": "より大きい:",
"helpText1": "検索は入力した文字ごとに行われます(検索語は最低3文字)。",
"helpText2": "インデックス:検索は、設定された間隔(デフォルト:5分)で自動的に更新されるインデックスを利用します。プログラムが開始したばかりの時に検索すると、不完全な結果になることがあります。"
},
"settings": {
"admin": "管理者",
@@ -194,7 +214,7 @@
"newUser": "新しいユーザー",
"password": "パスワード",
"passwordUpdated": "パスワードは更新されました!",
"path": "",
"path": "パス",
"permissions": {
"create": "Create files and directories",
"delete": "Delete files and directories",
@@ -202,7 +222,10 @@
"execute": "Execute commands",
"modify": "Edit files",
"rename": "Rename or move files and directories",
"share": "Share files"
"share": "Share files",
"admin": "管理者",
"api": "長寿命のAPIキーの作成と管理",
"realtime": "リアルタイムの接続と更新を可能にする"
},
"permissions-name": "権限",
"permissionsHelp": "あなたはユーザーを管理者に設定し、または権限を個々に設定しできます。\"管理者\"を選択する場合、その他のすべての選択肢は自動的に設定されます。ユーザーの管理は管理者の権限として保留されました。",
@@ -213,13 +236,13 @@
"rulesHelp": "ここに、あなたはこのユーザーの許可または拒否規則を設定できます。ブロックされたファイルはリストに表示されません、それではアクセスも制限されます。正規表現(regex)のサポートと範囲に相対のパスが提供されています。",
"scope": "範囲",
"settingsUpdated": "設定は更新されました!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"shareDuration": "シェア期間",
"shareManagement": "シェア・マネジメント",
"singleClick": "シングルクリックでファイルやディレクトリを開く",
"themes": {
"dark": "",
"light": "",
"title": ""
"dark": "暗い",
"light": "ライト",
"title": "テーマ"
},
"user": "ユーザー",
"userCommands": "ユーザーのコマンド",
@@ -230,7 +253,33 @@
"userManagement": "ユーザー管理",
"userUpdated": "ユーザーは更新されました!",
"username": "ユーザー名",
"users": "ユーザー"
"users": "ユーザー",
"loginMethod": "ログイン方法",
"modifyOtherUser": "ユーザーを変更する:",
"modifyCurrentUser": "現在のユーザーを変更する",
"themeColor": "テーマカラー",
"disableOfficePreviewDescription": "OnlyOffice ビューアを無効にするファイル拡張子をスペース区切りで指定します。(例: '.txt .html')。",
"disableOfficePreview": "特定のファイル拡張子でonlyofficeビューアを無効にする",
"filePreviewOptions": "ファイルプレビューオプション",
"listingOptions": "リストオプション",
"emptyGlobal": "現在のところ、グローバルな設定オプションはありませんが、コンフィギュレーションyamlを編集してシステム設定を調整することができます。",
"scopes": "許可されるソースとその中のユーザースコープを定義する",
"UserManagement": "ユーザー管理",
"tusUploads": "チャンクアップロード",
"tusUploadsHelp": "File Browserは、チャンクされたファイルアップロードをサポートしており、信頼性の低いネットワーク上でも、効率的で信頼性の高い、再開可能なチャンクされたファイルアップロードを作成することができます。",
"tusUploadsChunkSize": "リクエストの最大サイズを示します(直接アップロードはより小さいアップロードに使用されます)。バイト数を示す整数か、10MB、1GBなどの文字列を入力します。",
"tusUploadsRetryCount": "チャンクのアップロードに失敗した場合の再試行回数。",
"userHomeBasePath": "ユーザーホームディレクトリのベースパス",
"userScopeGenerationPlaceholder": "スコープは自動生成されます。",
"createUserHomeDirectory": "ユーザーホームディレクトリの作成",
"api": "APIキー",
"setDateFormat": "正確な日付書式を設定する",
"shareDeleted": "削除されたシェア",
"loginMethodDescription": "ユーザーに許可されたログイン方法。",
"userNotAdmin": "ユーザーが管理者でない",
"enterPassword": "パスワードを入力",
"enterPasswordAgain": "もう一度パスワードを入力する",
"passwordsDoNotMatch": "パスワードが一致しない"
},
"sidebar": {
"help": "ヘルプ",
@@ -243,7 +292,8 @@
"preview": "プレビュー",
"settings": "設定",
"signup": "Signup",
"siteSettings": "サイト設定"
"siteSettings": "サイト設定",
"sources": "情報源"
},
"success": {
"linkCopied": "リンクがコピーされました!"
@@ -254,5 +304,51 @@
"minutes": "分",
"seconds": "秒",
"unit": "時間単位"
},
"upload": {
"abortUpload": "本当に中絶したいのか?"
},
"share": {
"notice": "注:リンク(およびオプションのパスワード)にアクセスできる人なら、誰でも共有ファイルにアクセスできます。ログインする必要はありません。"
},
"api": {
"title": "APIキー:",
"keyName": "APIキー名",
"createdAt": "で作成された:",
"expiresAt": "有効期限",
"permissions": "許可:",
"createTitle": "APIキーの作成",
"keyNamePlaceholder": "一意のAPIキー名を入力します:",
"tokenDuration": "トークンの有効期間:",
"durationNumberPlaceholder": "番号",
"days": "日数",
"months": "ヶ月",
"permissionNote": "キーに対して少なくとも1つのパーミッションを選択してください。ユーザーもそのパーミッションを持っている必要があります。",
"createKeyFailed": "APIキーの作成に失敗しました。",
"createKeySuccess": "APIキーが正常に作成されました。",
"description": "APIキーは、それを作成したユーザーに基づいています。参照",
"swaggerLinkText": "スワッガーページ",
"name": "名称",
"created": "作成",
"expires": "期限切れ",
"actions": "行動",
"enabled": "有効",
"disabled": "無効"
},
"index": {
"status": "ステータス",
"assessment": "評価",
"files": "ファイル",
"folders": "フォルダ",
"lastScanned": "最終スキャン",
"quickScan": "クイックスキャン秒数",
"fullScan": "フルスキャン秒数",
"settingsHover": "ユーザー設定",
"logout": "ログアウト",
"toggleClick": "トグル・シングルクリック",
"toggleDark": "ダークモードの切り替え",
"toggleSticky": "スティッキーモードの切り替え",
"of": "の",
"used": "中古"
}
}
+116 -20
View File
@@ -31,17 +31,24 @@
"switchView": "보기 전환",
"toggleSidebar": "사이드바 전환",
"update": "업데이트",
"upload": "업로드"
"upload": "업로드",
"copyDownloadLinkToClipboard": "다운로드 링크를 클립보드에 복사",
"file": "파일",
"folder": "폴더",
"submit": "제출하기",
"openFile": "파일 열기",
"continue": "계속하기"
},
"download": {
"downloadFile": "파일 다운로드",
"downloadFolder": "폴더 다운로드",
"downloadSelected": ""
"downloadSelected": "선택된 다운로드"
},
"errors": {
"forbidden": "접근 권한이 없습니다.",
"internal": "오류가 발생하였습니다.",
"notFound": "해당 경로를 찾을 수 없습니다."
"notFound": "해당 경로를 찾을 수 없습니다.",
"connection": "서버에 연결할 수 없습니다."
},
"files": {
"body": "본문",
@@ -59,7 +66,8 @@
"size": "크기",
"sortByLastModified": "수정시간순 정렬",
"sortByName": "이름순",
"sortBySize": "크기순"
"sortBySize": "크기순",
"noPreview": "이 파일에는 미리 보기를 사용할 수 없습니다."
},
"help": {
"click": "파일이나 디렉토리를 선택해주세요.",
@@ -95,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -110,7 +118,8 @@
"submit": "로그인",
"username": "사용자 이름",
"usernameTaken": "사용자 이름이 존재합니다",
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오"
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오",
"or": "또는"
},
"permanent": "영구",
"prompts": {
@@ -125,7 +134,6 @@
"downloadMessage": "다운로드 포맷 설정.",
"error": "에러 발생!",
"fileInfo": "파일 정보",
"filesSelected": "{count} 개의 파일이 선택되었습니다.",
"lastModified": "최종 수정",
"move": "이동",
"moveMessage": "이동할 화일 또는 디렉토리를 선택하세요:",
@@ -144,8 +152,15 @@
"scheduleMessage": "이 글을 공개할 시간을 알려주세요.",
"show": "보기",
"size": "크기",
"upload": "",
"uploadMessage": ""
"upload": "업로드",
"uploadMessage": "업로드할 옵션을 선택합니다.",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
"deleteUserMessage": "이 사용자를 삭제하시겠습니까?",
"filesSelected": "선택한 파일",
"uploadFiles": "Uploading {files} files...",
"optionalPassword": "선택적 비밀번호",
"typeName": "유형:",
"selected": "선택"
},
"search": {
"images": "이미지",
@@ -155,7 +170,12 @@
"search": "검색...",
"typeToSearch": "검색어 입력...",
"types": "Types",
"video": "비디오"
"video": "비디오",
"path": "경로:",
"smallerThan": "보다 작습니다:",
"largerThan": "보다 큽니다:",
"helpText1": "검색은 입력하는 각 문자(검색어의 경우 최소 3자)에 대해 이루어집니다.",
"helpText2": "색인: 검색은 설정된 간격(기본값: 5분)에 따라 자동으로 업데이트되는 색인을 활용합니다. 프로그램이 막 시작되었을 때 검색하면 불완전한 결과가 나올 수 있습니다."
},
"settings": {
"admin": "관리자",
@@ -194,7 +214,7 @@
"newUser": "새로운 사용자",
"password": "비밀번호",
"passwordUpdated": "비밀번호 수정 완료!",
"path": "",
"path": "경로",
"permissions": {
"create": "파일이나 디렉토리 생성하기",
"delete": "화일이나 디렉토리 삭제하기",
@@ -202,7 +222,10 @@
"execute": "명령 실행",
"modify": "파일 편집",
"rename": "파일 이름 변경 또는 디렉토리 이동",
"share": "파일 공유하기"
"share": "파일 공유하기",
"admin": "관리자",
"api": "긴 수명의 API 키 생성 및 관리",
"realtime": "실시간 연결 및 업데이트 사용"
},
"permissions-name": "권한",
"permissionsHelp": "사용자를 관리자로 만들거나 권한을 부여할 수 있습니다. 관리자를 선택하면, 모든 옵션이 자동으로 선택됩니다. 사용자 관리는 현재 관리자만 할 수 있습니다.\n",
@@ -213,13 +236,13 @@
"rulesHelp": "사용자별로 규칙을 허용/방지를 지정할 수 있습니다. 방지된 파일은 보이지 않고 사용자들은 접근할 수 없습니다. 사용자의 접근 허용 범위와 관련해 정규표현식(regex)과 경로를 지원합니다.\n",
"scope": "범위",
"settingsUpdated": "설정 수정됨!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"shareDuration": "공유 기간",
"shareManagement": "공유 관리",
"singleClick": "클릭 한 번으로 파일과 디렉터리 열기",
"themes": {
"dark": "",
"light": "",
"title": ""
"dark": "Dark",
"light": "빛",
"title": "테마"
},
"user": "사용자",
"userCommands": "명령어",
@@ -230,7 +253,33 @@
"userManagement": "사용자 관리",
"userUpdated": "사용자 수정됨!",
"username": "사용자 이름",
"users": "사용자"
"users": "사용자",
"loginMethod": "로그인 방법",
"modifyOtherUser": "사용자를 수정합니다:",
"modifyCurrentUser": "현재 사용자 수정",
"themeColor": "테마 색상",
"disableOfficePreviewDescription": "OnlyOffice 뷰어를 비활성화할 파일 확장명을 공백으로 구분한 목록입니다. (예: '.txt .html').",
"disableOfficePreview": "특정 파일 확장자에 대해서만오피스 뷰어 사용 안 함",
"filePreviewOptions": "파일 미리 보기 옵션",
"listingOptions": "리스팅 옵션",
"emptyGlobal": "현재 글로벌 설정 옵션은 없으며, 구성 yaml을 편집하여 시스템 설정을 조정할 수 있습니다.",
"scopes": "허용된 소스와 그 안에서 사용자 범위 정의하기",
"UserManagement": "사용자 관리",
"tusUploads": "청크 업로드",
"tusUploadsHelp": "파일 브라우저는 청크 파일 업로드를 지원하므로 불안정한 네트워크에서도 효율적이고 안정적이며 재개 가능한 청크 파일 업로드를 생성할 수 있습니다.",
"tusUploadsChunkSize": "요청의 최대 크기를 나타냅니다(더 작은 업로드의 경우 직접 업로드가 사용됩니다). 바이트 입력을 나타내는 일반 정수 또는 10MB, 1GB 등과 같은 문자열을 입력할 수 있습니다.",
"tusUploadsRetryCount": "청크 업로드에 실패한 경우 수행할 재시도 횟수입니다.",
"userHomeBasePath": "사용자 홈 디렉터리의 기본 경로",
"userScopeGenerationPlaceholder": "범위가 자동으로 생성됩니다.",
"createUserHomeDirectory": "사용자 홈 디렉터리 만들기",
"api": "API 키",
"setDateFormat": "정확한 날짜 형식 설정",
"shareDeleted": "공유 삭제됨!",
"loginMethodDescription": "사용자에게 허용된 로그인 방법을 설정합니다.",
"userNotAdmin": "사용자가 관리자가 아닙니다.",
"enterPassword": "비밀번호 입력",
"enterPasswordAgain": "비밀번호를 다시 입력합니다.",
"passwordsDoNotMatch": "비밀번호가 일치하지 않습니다."
},
"sidebar": {
"help": "도움말",
@@ -243,7 +292,8 @@
"preview": "미리보기",
"settings": "설정",
"signup": "가입하기",
"siteSettings": "사이트 설정"
"siteSettings": "사이트 설정",
"sources": "출처"
},
"success": {
"linkCopied": "링크가 복사되었습니다!"
@@ -254,5 +304,51 @@
"minutes": "분",
"seconds": "초",
"unit": "Time Unit"
},
"upload": {
"abortUpload": "취소하시겠습니까?"
},
"share": {
"notice": "참고: 링크(및 선택 사항인 비밀번호)에 액세스할 수 있는 사람은 누구나 공유 파일에 액세스할 수 있습니다. 로그인할 필요는 없습니다."
},
"api": {
"title": "API 키:",
"keyName": "API 키 이름:",
"createdAt": "만든 곳:",
"expiresAt": "만료일:",
"permissions": "권한:",
"createTitle": "API 키 생성",
"keyNamePlaceholder": "고유한 API 키 이름을 입력합니다:",
"tokenDuration": "토큰 기간:",
"durationNumberPlaceholder": "번호",
"days": "일수",
"months": "개월",
"permissionNote": "키에 대한 권한을 하나 이상 선택합니다. 사용자에게도 해당 권한이 있어야 합니다.",
"createKeyFailed": "API 키를 생성하지 못했습니다.",
"createKeySuccess": "API 키가 성공적으로 생성되었습니다.",
"description": "API 키는 키를 생성한 사용자를 기준으로 합니다. 참조",
"swaggerLinkText": "스웨거 페이지",
"name": "이름",
"created": "생성됨",
"expires": "만료",
"actions": "작업",
"enabled": "활성화됨",
"disabled": "장애인"
},
"index": {
"status": "상태",
"assessment": "평가",
"files": "파일",
"folders": "폴더",
"lastScanned": "마지막 스캔",
"quickScan": "빠른 스캔 초",
"fullScan": "전체 스캔 시간(초)",
"settingsHover": "사용자 설정",
"logout": "로그아웃",
"toggleClick": "클릭 한 번으로 토글",
"toggleDark": "다크 모드 전환",
"toggleSticky": "고정 모드 토글",
"of": "의",
"used": "사용됨"
}
}
+8 -4
View File
@@ -95,7 +95,7 @@
"ru": "Russisch",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "Chinees (vereenvoudigd)",
"zhTW": "Chinees (traditioneel)"
@@ -125,7 +125,6 @@
"downloadMessage": "Kies het vermat dat u wilt downloaden. ",
"error": "Er ging iets fout",
"fileInfo": "Bestandinformatie",
"filesSelected": "{count} geselecteerde bestanden",
"lastModified": "Laatst Bewerkt",
"move": "Verplaatsen",
"moveMessage": "Kies een nieuwe locatie voor je bestand(en)/map(pen)",
@@ -145,7 +144,8 @@
"show": "Tonen",
"size": "Grootte",
"upload": "",
"uploadMessage": ""
"uploadMessage": "",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?"
},
"search": {
"images": "Afbeeldingen",
@@ -254,5 +254,9 @@
"minutes": "Minuten",
"seconds": "Seconden",
"unit": "Tijdseenheid"
}
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
+107 -12
View File
@@ -31,7 +31,13 @@
"switchView": "Zmień widok",
"toggleSidebar": "Pokaż/ukryj panel boczny",
"update": "Aktualizuj",
"upload": "Wgraj"
"upload": "Wgraj",
"copyDownloadLinkToClipboard": "Skopiuj łącze pobierania do schowka",
"file": "Plik",
"folder": "Folder",
"submit": "Prześlij",
"openFile": "Otwórz plik",
"continue": "Kontynuuj"
},
"download": {
"downloadFile": "Pobierz plik",
@@ -41,7 +47,8 @@
"errors": {
"forbidden": "Nie posiadasz uprawnień potrzebnych, by uzyskać do tego dostęp.",
"internal": "Pojawił się poważny problem.",
"notFound": "Ten adres nie jest poprawny."
"notFound": "Ten adres nie jest poprawny.",
"connection": "Nie można uzyskać dostępu do serwera."
},
"files": {
"body": "Body",
@@ -59,7 +66,8 @@
"size": "Rozmiar",
"sortByLastModified": "Sortuj wg. daty modyfikacji",
"sortByName": "Sortuj wg. nazwy",
"sortBySize": "Sortuj wg. rozmiaru"
"sortBySize": "Sortuj wg. rozmiaru",
"noPreview": "Podgląd nie jest dostępny dla tego pliku."
},
"help": {
"click": "wybierz plik lub foler",
@@ -95,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Svenska (Sverige)",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -110,7 +118,8 @@
"submit": "Logowanie",
"username": "Nazwa użytkownika",
"usernameTaken": "Nazwa użytkownika już zajęta",
"wrongCredentials": "Błędne dane logowania"
"wrongCredentials": "Błędne dane logowania",
"or": "LUB"
},
"permanent": "Permanentny",
"prompts": {
@@ -125,7 +134,6 @@
"downloadMessage": "Wybierz format, jaki chesz pobrać.",
"error": "Pojawił się nieznany błąd",
"fileInfo": "Informacje o pliku",
"filesSelected": "{count} plików zostało zaznaczonych.",
"lastModified": "Osatnio Zmodyfikowane",
"move": "Przenieś",
"moveMessage": "Wybierz nową lokalizację dla swoich plik(ów)/folder(ów):",
@@ -145,8 +153,14 @@
"show": "Pokaż",
"size": "Rozmiar",
"upload": "Prześlij",
"uploadMessage": "Proszę wybrać metodę przesyłania"
"uploadMessage": "Proszę wybrać metodę przesyłania",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
"deleteUserMessage": "Czy na pewno chcesz usunąć tego użytkownika?",
"filesSelected": "wybrane pliki",
"uploadFiles": "Uploading {files} files...",
"optionalPassword": "Hasło opcjonalne",
"typeName": "Typ:",
"selected": "wybrany"
},
"search": {
"images": "Zdjęcia",
@@ -156,7 +170,12 @@
"search": "Szukaj...",
"typeToSearch": "Zacznij pisać, aby wyszukać...",
"types": "Typy",
"video": "Wideo"
"video": "Wideo",
"path": "Ścieżka:",
"smallerThan": "Mniejszy niż:",
"largerThan": "Większy niż:",
"helpText1": "Wyszukiwanie odbywa się po każdym wpisanym znaku (minimum 3 znaki dla wyszukiwanych haseł).",
"helpText2": "Indeks: Wyszukiwanie wykorzystuje indeks, który jest automatycznie aktualizowany w skonfigurowanych odstępach czasu (domyślnie: 5 minut). Wyszukiwanie po uruchomieniu programu może skutkować niekompletnymi wynikami."
},
"settings": {
"admin": "Admin",
@@ -203,7 +222,10 @@
"execute": "Wykonywanie poleceń",
"modify": "Edycja plików",
"rename": "Zmiana nazw lub przenoszenie plików i katalogów",
"share": "Udostępnianie plików"
"share": "Udostępnianie plików",
"admin": "Administrator",
"api": "Tworzenie i zarządzanie kluczami API o długiej żywotności",
"realtime": "Połączenia i aktualizacje w czasie rzeczywistym"
},
"permissions-name": "Uprawnienia",
"permissionsHelp": "Możesz uczynić użytkownika administratorem, lub wybrać uprawnienia indywidualnie. Jeśli zaznaczysz opcję \"Administrator\", wszystkie pozostałe opcje zostaną automatycznie zaznaczone. Zarządzanie użytkownikami pozostaje przywilejem administratora.\n",
@@ -231,7 +253,33 @@
"userManagement": "Zarządzanie użytkownikami",
"userUpdated": "Użytkownik zapisany!",
"username": "Nazwa użytkownika",
"users": "Użytkownicy"
"users": "Użytkownicy",
"loginMethod": "Metoda logowania",
"modifyOtherUser": "Modyfikacja użytkownika:",
"modifyCurrentUser": "Modyfikacja bieżącego użytkownika",
"themeColor": "Kolor motywu",
"disableOfficePreviewDescription": "Rozdzielana spacjami lista rozszerzeń plików, dla których przeglądarka OnlyOffice ma zostać wyłączona. (np. \".txt .html\").",
"disableOfficePreview": "Wyłącz przeglądarkę onlyoffice dla niektórych rozszerzeń plików",
"filePreviewOptions": "Opcje podglądu plików",
"listingOptions": "Opcje aukcji",
"emptyGlobal": "Obecnie nie ma globalnych opcji konfiguracyjnych, można edytować konfigurację yaml, aby dostosować ustawienia systemu.",
"scopes": "Definiowanie dozwolonych źródeł i zakresu użytkowników w ich obrębie",
"UserManagement": "Zarządzanie użytkownikami",
"tusUploads": "Zbiorcze przesyłanie plików",
"tusUploadsHelp": "File Browser obsługuje przesyłanie plików w postaci fragmentów, umożliwiając tworzenie wydajnych, niezawodnych, wznawianych i podzielonych na fragmenty plików nawet w niewiarygodnych sieciach.",
"tusUploadsChunkSize": "Wskazuje maksymalny rozmiar żądania (bezpośrednie przesyłanie będzie używane do przesyłania mniejszych plików). Możesz wprowadzić zwykłą liczbę całkowitą oznaczającą bajty wejściowe lub ciąg znaków, taki jak 10 MB, 1 GB itp.",
"tusUploadsRetryCount": "Liczba ponawianych prób w przypadku niepowodzenia przesłania fragmentu.",
"userHomeBasePath": "Ścieżka podstawowa dla katalogów domowych użytkownika",
"userScopeGenerationPlaceholder": "Zakres zostanie wygenerowany automatycznie",
"createUserHomeDirectory": "Tworzenie katalogu domowego użytkownika",
"api": "Klucze API",
"setDateFormat": "Ustaw dokładny format daty",
"shareDeleted": "Usunięto udostępnianie!",
"loginMethodDescription": "dozwolona metoda logowania dla użytkownika.",
"userNotAdmin": "Użytkownik nie jest administratorem",
"enterPassword": "Wprowadź hasło",
"enterPasswordAgain": "Wprowadź hasło ponownie",
"passwordsDoNotMatch": "Hasła nie są zgodne"
},
"sidebar": {
"help": "Pomoc",
@@ -244,7 +292,8 @@
"preview": "Podgląd",
"settings": "Ustawienia",
"signup": "Rejestracja",
"siteSettings": "Ustawienia Strony"
"siteSettings": "Ustawienia Strony",
"sources": "Źródła"
},
"success": {
"linkCopied": "Link Skopiowany!"
@@ -255,5 +304,51 @@
"minutes": "Minuty",
"seconds": "Sekundy",
"unit": "Jednostka czasu"
},
"upload": {
"abortUpload": "Czy na pewno chcesz przerwać ciążę?"
},
"share": {
"notice": "Uwaga: każdy, kto ma dostęp do linku (i opcjonalnie hasło), może uzyskać dostęp do udostępnionych plików. Nie ma wymogu bycia zalogowanym."
},
"api": {
"title": "Klucze API:",
"keyName": "Nazwa klucza API:",
"createdAt": "Utworzono w:",
"expiresAt": "Wygasa w:",
"permissions": "Uprawnienia:",
"createTitle": "Tworzenie klucza API",
"keyNamePlaceholder": "Wprowadź unikalną nazwę klucza API:",
"tokenDuration": "Czas trwania tokena:",
"durationNumberPlaceholder": "Liczba",
"days": "Dni",
"months": "Miesiące",
"permissionNote": "Wybierz co najmniej jedno uprawnienie dla klucza. Użytkownik również musi mieć to uprawnienie.",
"createKeyFailed": "Nie udało się utworzyć klucza API.",
"createKeySuccess": "Klucz API został pomyślnie utworzony.",
"description": "Klucze API są oparte na użytkowniku, który je utworzył. Zobacz",
"swaggerLinkText": "strona swagger",
"name": "Nazwa",
"created": "Utworzony",
"expires": "Wygasa",
"actions": "Działania",
"enabled": "Włączone",
"disabled": "Wyłączony"
},
"index": {
"status": "Status",
"assessment": "Ocena",
"files": "Pliki",
"folders": "Foldery",
"lastScanned": "Ostatnio skanowane",
"quickScan": "Sekundy szybkiego skanowania",
"fullScan": "Sekundy pełnego skanowania",
"settingsHover": "Ustawienia dla użytkownika",
"logout": "Wylogowanie",
"toggleClick": "Przełączanie jednym kliknięciem",
"toggleDark": "Przełączanie trybu ciemnego",
"toggleSticky": "Przełączanie trybu samoprzylepnego",
"of": "z",
"used": "używany"
}
}
+92 -10
View File
@@ -35,7 +35,9 @@
"toggleSidebar": "Alternar barra lateral",
"update": "Atualizar",
"upload": "Enviar",
"openFile": "Abrir"
"openFile": "Abrir",
"copyDownloadLinkToClipboard": "Copiar o link de download para a área de transferência",
"continue": "Continuar"
},
"download": {
"downloadFile": "Baixar arquivo",
@@ -101,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -116,7 +118,8 @@
"submit": "Login",
"username": "Nome do usuário",
"usernameTaken": "Nome de usuário já existe",
"wrongCredentials": "Ops! Dados incorretos."
"wrongCredentials": "Ops! Dados incorretos.",
"or": "OU"
},
"permanent": "Permanente",
"prompts": {
@@ -132,7 +135,6 @@
"downloadMessage": "Escolha o formato do arquivo.",
"error": "Algo de errado ocorreu",
"fileInfo": "Informação do arquivo",
"filesSelected": "{count} arquivos selecionados.",
"lastModified": "Última modificação",
"move": "Mover",
"moveMessage": "Escolha uma nova pasta para os seus arquivos:",
@@ -154,7 +156,11 @@
"upload": "Enviar",
"uploadFiles": "Enviando {files} arquivos...",
"uploadMessage": "Selecione uma opção para enviar.",
"optionalPassword": "Senha opcional"
"optionalPassword": "Senha opcional",
"deleteUserMessage": "Tem certeza de que deseja excluir esse usuário?",
"filesSelected": "arquivos selecionados",
"typeName": "Tipo:",
"selected": "selecionado"
},
"search": {
"images": "Imagens",
@@ -164,7 +170,12 @@
"search": "Pesquise...",
"typeToSearch": "Digite para pesquisar...",
"types": "Tipos",
"video": "Vídeos"
"video": "Vídeos",
"path": "Caminho:",
"smallerThan": "Menor que:",
"largerThan": "Maior que:",
"helpText1": "A pesquisa ocorre em cada caractere que você digita (mínimo de 3 caracteres para termos de pesquisa).",
"helpText2": "O índice: A pesquisa utiliza o índice que é atualizado automaticamente no intervalo configurado (padrão: 5 minutos). A pesquisa quando o programa acaba de ser iniciado pode resultar em resultados incompletos."
},
"settings": {
"admin": "Admin",
@@ -206,7 +217,7 @@
"newUser": "Novo usuário",
"password": "Senha",
"passwordUpdated": "Senha atualizada!",
"path": "",
"path": "Caminho",
"permissions": {
"create": "Criar arquivos e diretórios",
"delete": "Apagar arquivos e diretórios",
@@ -214,7 +225,10 @@
"execute": "Executar comandos",
"modify": "Editar arquivos",
"rename": "Renomear ou mover arquivos e diretórios",
"share": "Compartilhar arquivos"
"share": "Compartilhar arquivos",
"admin": "Administrador",
"api": "Criar e gerenciar chaves de API de longa duração",
"realtime": "Permitir conexões e atualizações em tempo real"
},
"permissions-name": "Permissões",
"permissionsHelp": "Pode definir o usuário como administrador ou escolher as permissões manualmente. Se selecionar a opção \"Administrador\", todas as outras opções serão automaticamente selecionadas. A gestão dos usuários é um privilégio restringido aos administradores.\n",
@@ -244,7 +258,28 @@
"userManagement": "Gerenciamento de usuários",
"userUpdated": "Usuário atualizado!",
"username": "Nome do usuário",
"users": "Usuários"
"users": "Usuários",
"loginMethod": "Método de login",
"modifyOtherUser": "Modificar usuário:",
"modifyCurrentUser": "Modificar o usuário atual",
"themeColor": "Cor do tema",
"disableOfficePreviewDescription": "Uma lista de extensões de arquivo separada por espaços para desativar o visualizador do OnlyOffice. (por exemplo, '.txt .html').",
"disableOfficePreview": "Desativar o visualizador do onlyoffice para determinadas extensões de arquivo",
"filePreviewOptions": "Opções de visualização de arquivos",
"listingOptions": "Opções de listagem",
"emptyGlobal": "Atualmente, não há opções de configuração global. Você pode editar o yaml de configuração para ajustar as configurações do sistema.",
"scopes": "Definir fontes permitidas e o escopo do usuário dentro delas",
"UserManagement": "Gerenciamento de usuários",
"tusUploads": "Uploads em pedaços",
"tusUploadsHelp": "O File Browser oferece suporte a uploads de arquivos em pedaços, permitindo a criação de uploads de arquivos eficientes, confiáveis, reestruturáveis e em pedaços, mesmo em redes não confiáveis.",
"tusUploadsChunkSize": "Indica o tamanho máximo de uma solicitação (uploads diretos serão usados para uploads menores). Você pode inserir um número inteiro simples que denota uma entrada de bytes ou uma cadeia de caracteres como 10 MB, 1 GB etc.",
"tusUploadsRetryCount": "Número de novas tentativas a serem executadas se houver falha no upload de um bloco.",
"api": "Chaves de API",
"loginMethodDescription": "método de login permitido para o usuário.",
"userNotAdmin": "O usuário não é um administrador",
"enterPassword": "Digite a senha",
"enterPasswordAgain": "Digite a senha novamente",
"passwordsDoNotMatch": "As senhas não correspondem"
},
"sidebar": {
"help": "Ajuda",
@@ -257,7 +292,8 @@
"preview": "Pré-visualizar",
"settings": "Configurações",
"signup": "Cadastrar",
"siteSettings": "Configurações do site"
"siteSettings": "Configurações do site",
"sources": "Fontes"
},
"success": {
"linkCopied": "Link copiado!"
@@ -268,5 +304,51 @@
"minutes": "Minutos",
"seconds": "Segundos",
"unit": "Unidades de Tempo"
},
"upload": {
"abortUpload": "Tem certeza de que quer abortar?"
},
"share": {
"notice": "Observação: qualquer pessoa que tenha acesso ao link (e à senha opcional) pode acessar os arquivos compartilhados. Não é necessário estar conectado."
},
"api": {
"title": "Chaves de API:",
"keyName": "Nome da chave da API:",
"createdAt": "Criado em:",
"expiresAt": "Expira em:",
"permissions": "Permissões:",
"createTitle": "Criar chave de API",
"keyNamePlaceholder": "Digite um nome de chave de API exclusivo:",
"tokenDuration": "Duração do token:",
"durationNumberPlaceholder": "Número",
"days": "Dias",
"months": "Meses",
"permissionNote": "Escolha pelo menos uma permissão para a chave. Seu usuário também deve ter a permissão.",
"createKeyFailed": "Falha ao criar a chave de API.",
"createKeySuccess": "Chave de API criada com sucesso.",
"description": "As chaves de API são baseadas no usuário que as cria. Veja",
"swaggerLinkText": "página do swagger",
"name": "Nome",
"created": "Criado",
"expires": "Expirações",
"actions": "Ações",
"enabled": "Ativado",
"disabled": "Desativado"
},
"index": {
"status": "Status",
"assessment": "Avaliação",
"files": "Arquivos",
"folders": "Pastas",
"lastScanned": "Última varredura",
"quickScan": "Segundos de varredura rápida",
"fullScan": "Segundos de varredura completa",
"settingsHover": "Configurações para o usuário",
"logout": "Sair",
"toggleClick": "Alternar clique único",
"toggleDark": "Alternar o modo escuro",
"toggleSticky": "Alternar o modo de aderência",
"of": "de",
"used": "usado"
}
}
+8 -4
View File
@@ -95,7 +95,7 @@
"ru": "Russo",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "Chinês simplificado",
"zhTW": "Chinês tradicional"
@@ -125,7 +125,6 @@
"downloadMessage": "Escolha o formato do ficheiro que quer descarregar.",
"error": "Algo correu mal",
"fileInfo": "Informação do ficheiro",
"filesSelected": "{count} ficheiros selecionados.",
"lastModified": "Última alteração",
"move": "Mover",
"moveMessage": "Escolha uma nova casa para os seus ficheiros/pastas:",
@@ -145,7 +144,8 @@
"show": "Mostrar",
"size": "Tamanho",
"upload": "",
"uploadMessage": ""
"uploadMessage": "",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?"
},
"search": {
"images": "Imagens",
@@ -254,5 +254,9 @@
"minutes": "Minutos",
"seconds": "Segundos",
"unit": "Unidades de tempo"
}
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
+116 -20
View File
@@ -31,17 +31,24 @@
"switchView": "Schimba vizualizarea",
"toggleSidebar": "Comută bara laterală",
"update": "Actualizează",
"upload": "Încarcă"
"upload": "Încarcă",
"copyDownloadLinkToClipboard": "Copiați link-ul de descărcare în clipboard",
"file": "Fișier",
"folder": "Dosar",
"submit": "Trimite",
"openFile": "Deschideți fișierul",
"continue": "Continuați"
},
"download": {
"downloadFile": "Descarcă fișier",
"downloadFolder": "Descarcă director",
"downloadSelected": ""
"downloadSelected": "Descărcare selectată"
},
"errors": {
"forbidden": "Nu ai permisiuni sa accesezi asta.",
"internal": "Ceva nu a funcționat corect.",
"notFound": "Aceasta locație nu poate fi accesată."
"notFound": "Aceasta locație nu poate fi accesată.",
"connection": "Serverul nu poate fi accesat."
},
"files": {
"body": "Corp",
@@ -59,7 +66,8 @@
"size": "Dimensiune",
"sortByLastModified": "Ordonează dup ultima modificare",
"sortByName": "Ordonează după nume",
"sortBySize": "Ordonează după dimensiune"
"sortBySize": "Ordonează după dimensiune",
"noPreview": "Previzualizarea nu este disponibilă pentru acest fișier."
},
"help": {
"click": "alege fișier sau director",
@@ -95,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -110,7 +118,8 @@
"submit": "Autentificare",
"username": "Utilizator",
"usernameTaken": "Utilizatorul există",
"wrongCredentials": "Informații greșite"
"wrongCredentials": "Informații greșite",
"or": "SAU"
},
"permanent": "Permanent",
"prompts": {
@@ -125,7 +134,6 @@
"downloadMessage": "Alege formatul în care vrei să descarci.",
"error": "Ceva n-a funcționat cum trebuie",
"fileInfo": "Informații fișier",
"filesSelected": "{count} fișiere selectate.",
"lastModified": "Ultima modificare",
"move": "Mută",
"moveMessage": "Alege destinația:",
@@ -144,8 +152,15 @@
"scheduleMessage": "Alege data si ora pentru a programa publicarea acestei postări.",
"show": "Arată",
"size": "Dimensiune",
"upload": "",
"uploadMessage": ""
"upload": "Încărcare",
"uploadMessage": "Selectați o opțiune pentru încărcare.",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
"deleteUserMessage": "Sunteți sigur că doriți să ștergeți acest utilizator?",
"filesSelected": "fișiere selectate",
"uploadFiles": "Uploading {files} files...",
"optionalPassword": "Parolă opțională",
"typeName": "Tip:",
"selected": "selectat"
},
"search": {
"images": "Imagini",
@@ -155,7 +170,12 @@
"search": "Caută...",
"typeToSearch": "Scrie pentru a căuta...",
"types": "Tipuri",
"video": "Video"
"video": "Video",
"path": "Calea:",
"smallerThan": "Mai mic decât:",
"largerThan": "Mai mare decât:",
"helpText1": "Căutarea are loc pe fiecare caracter tastat (minim 3 caractere pentru termenii de căutare).",
"helpText2": "Indexul: Căutarea utilizează indexul care se actualizează automat la intervalul configurat (implicit: 5 minute). Căutarea atunci când programul tocmai a început poate duce la rezultate incomplete."
},
"settings": {
"admin": "Admin",
@@ -194,7 +214,7 @@
"newUser": "Utilizator nou",
"password": "Parola",
"passwordUpdated": "Parola actualizată!",
"path": "",
"path": "Calea",
"permissions": {
"create": "Crează fișiere și directoare",
"delete": "Șterge fișiere și directoare",
@@ -202,7 +222,10 @@
"execute": "Execută comenzi",
"modify": "Modifică fișiere",
"rename": "Redenumește sau mută fișiere și directoare",
"share": "Distribuie fișiere"
"share": "Distribuie fișiere",
"admin": "Administrator",
"api": "Crearea și gestionarea cheilor API cu durată lungă de viață",
"realtime": "Permite conexiuni și actualizări în timp real"
},
"permissions-name": "Permisiuni",
"permissionsHelp": "Poți alege ca un utilizator să fie administrator sau să-i alegi permisiunile individual. Dacă alegi \"Administrator\", toate celelalte opțiuni vor fi bifate automat. Gestionarea utilizatorilor rămâne un privilegiu exclusiv al administratorilor.\n",
@@ -213,13 +236,13 @@
"rulesHelp": "Aici poți defini un set de reguli pentru acest utilizator. Fișierele blocate nu vor apărea in lista și nici nu vor putea fi accesate de utilizator. Expresiile regulate si căile relative la domeniul utilizatorului sunt permise.\n",
"scope": "Domeniu",
"settingsUpdated": "Setări actualizate!",
"shareDuration": "",
"shareManagement": "",
"singleClick": "",
"shareDuration": "Acțiune Durată",
"shareManagement": "Gestionarea acțiunilor",
"singleClick": "Utilizați un singur clic pentru a deschide fișiere și directoare",
"themes": {
"dark": "",
"light": "",
"title": ""
"dark": "Întuneric",
"light": "Lumină",
"title": "Tema"
},
"user": "Utilizator",
"userCommands": "Comenzi",
@@ -230,7 +253,33 @@
"userManagement": "Gestionare utilizatori",
"userUpdated": "Utilizator actualizat!",
"username": "Utilizator",
"users": "Utilizatori"
"users": "Utilizatori",
"loginMethod": "Metoda de conectare",
"modifyOtherUser": "Modificați utilizatorul:",
"modifyCurrentUser": "Modificarea utilizatorului curent",
"themeColor": "Culoare temă",
"disableOfficePreviewDescription": "O listă de extensii de fișiere separate prin spațiu pentru care se dezactivează vizualizatorul OnlyOffice. (de exemplu, \".txt .html\").",
"disableOfficePreview": "Dezactivați vizualizatorul onlyoffice pentru anumite extensii de fișiere",
"filePreviewOptions": "Opțiuni de previzualizare a fișierelor",
"listingOptions": "Opțiuni de listare",
"emptyGlobal": "În prezent nu există opțiuni de configurare globală, Puteți edita yaml-ul de configurare pentru a ajusta setările sistemului.",
"scopes": "Definirea surselor permise și a domeniului de aplicare al utilizatorului în cadrul acestora",
"UserManagement": "Gestionarea utilizatorilor",
"tusUploads": "Chunked încărcări",
"tusUploadsHelp": "File Browser suportă încărcări de fișiere fragmentate, permițând crearea de încărcări de fișiere eficiente, fiabile, reluabile și fragmentate, chiar și în rețele nesigure.",
"tusUploadsChunkSize": "Indică dimensiunea maximă a unei cereri (încărcările directe vor fi utilizate pentru încărcări mai mici). Puteți introduce un număr întreg simplu care denotă o intrare de octeți sau un șir de caractere precum 10MB, 1GB etc.",
"tusUploadsRetryCount": "Numărul de încercări care trebuie efectuate dacă o bucată nu reușește să fie încărcată.",
"userHomeBasePath": "Calea de bază pentru directoarele home ale utilizatorilor",
"userScopeGenerationPlaceholder": "Domeniul de aplicare va fi generat automat",
"createUserHomeDirectory": "Crearea directorului home al utilizatorului",
"api": "Chei API",
"setDateFormat": "Setați formatul exact al datei",
"shareDeleted": "Share șters!",
"loginMethodDescription": "metoda de conectare permisă pentru utilizator.",
"userNotAdmin": "Utilizatorul nu este administrator",
"enterPassword": "Introduceți parola",
"enterPasswordAgain": "Introduceți din nou parola",
"passwordsDoNotMatch": "Parolele nu se potrivesc"
},
"sidebar": {
"help": "Ajutor",
@@ -243,7 +292,8 @@
"preview": "Previzualizare",
"settings": "Setări",
"signup": "Înregistrare",
"siteSettings": "Setări site"
"siteSettings": "Setări site",
"sources": "Surse"
},
"success": {
"linkCopied": "Legătură copiată!"
@@ -254,5 +304,51 @@
"minutes": "Minute",
"seconds": "Secunde",
"unit": "Unitate de timp"
},
"upload": {
"abortUpload": "Ești sigură că vrei să avortezi?"
},
"share": {
"notice": "Notă: orice persoană care are acces la link (și parola opțională) poate accesa fișierele partajate. Nu este necesar să fiți autentificat."
},
"api": {
"title": "Chei API:",
"keyName": "Nume cheie API:",
"createdAt": "Creat la:",
"expiresAt": "Expiră la:",
"permissions": "Permisiuni:",
"createTitle": "Crearea cheii API",
"keyNamePlaceholder": "Introduceți un nume unic al cheii API:",
"tokenDuration": "Token Durata:",
"durationNumberPlaceholder": "Număr",
"days": "Zile",
"months": "Luni",
"permissionNote": "Alegeți cel puțin o permisiune pentru cheie. Utilizatorul dvs. trebuie să aibă și permisiunea respectivă.",
"createKeyFailed": "A eșuat crearea cheii API.",
"createKeySuccess": "Cheie API creată cu succes.",
"description": "Cheile API sunt bazate pe utilizatorul care le creează. A se vedea",
"swaggerLinkText": "pagina swagger",
"name": "Nume și prenume",
"created": "Creat",
"expires": "Expiră",
"actions": "Acțiuni",
"enabled": "Activat",
"disabled": "Dezactivat"
},
"index": {
"status": "Statut",
"assessment": "Evaluare",
"files": "Fișiere",
"folders": "Dosare",
"lastScanned": "Ultima scanare",
"quickScan": "Scanare rapidă secunde",
"fullScan": "Scanare completă secunde",
"settingsHover": "Setări pentru utilizator",
"logout": "Ieșire",
"toggleClick": "Comutare clic unic",
"toggleDark": "Comutarea modului întunecat",
"toggleSticky": "Schimbarea modului lipicios",
"of": "de",
"used": "utilizate"
}
}
+95 -9
View File
@@ -35,7 +35,9 @@
"toggleSidebar": "Боковая панель",
"update": "Обновить",
"upload": "Загрузить",
"openFile": "Открыть файл"
"openFile": "Открыть файл",
"copyDownloadLinkToClipboard": "Скопируйте ссылку на скачивание в буфер обмена",
"continue": "Продолжить"
},
"download": {
"downloadFile": "Скачать файл",
@@ -101,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -116,7 +118,8 @@
"submit": "Войти",
"username": "Имя пользователя",
"usernameTaken": "Данное имя пользователя уже занято",
"wrongCredentials": "Неверные данные"
"wrongCredentials": "Неверные данные",
"or": "ИЛИ"
},
"permanent": "Постоянный",
"prompts": {
@@ -132,7 +135,6 @@
"downloadMessage": "Выберите формат в котором хотите скачать.",
"error": "Ошибка",
"fileInfo": "Информация о файле",
"filesSelected": "Файлов выбрано: {count}.",
"lastModified": "Последнее изменение",
"move": "Переместить",
"moveMessage": "Переместить в:",
@@ -153,7 +155,12 @@
"size": "Размер",
"upload": "Загрузить",
"uploadMessage": "Выберите вариант для загрузки.",
"optionalPassword": "Необязательный пароль"
"optionalPassword": "Необязательный пароль",
"deleteUserMessage": "Вы уверены, что хотите удалить этого пользователя?",
"filesSelected": "выбранные файлы",
"uploadFiles": "Uploading {files} files...",
"typeName": "Тип:",
"selected": "выбранные"
},
"search": {
"images": "Изображения",
@@ -163,7 +170,12 @@
"search": "Поиск...",
"typeToSearch": "Введите имя файла ...",
"types": "Типы",
"video": "Видео"
"video": "Видео",
"path": "Путь:",
"smallerThan": "Меньше, чем:",
"largerThan": "Больше, чем:",
"helpText1": "Поиск осуществляется по каждому введенному символу (минимум 3 символа для поисковых запросов).",
"helpText2": "Индекс: Для поиска используется индекс, который автоматически обновляется с заданным интервалом (по умолчанию: 5 минут). Поиск, когда программа только что запущена, может привести к неполным результатам."
},
"settings": {
"admin": "Админ",
@@ -210,7 +222,10 @@
"execute": "Выполнять команды",
"modify": "Редактировать файлы",
"rename": "Переименовывать или перемещать файлы и каталоги",
"share": "Делиться файлами"
"share": "Делиться файлами",
"admin": "Администратор",
"api": "Создание и управление долгоживущими ключами API",
"realtime": "Обеспечьте подключение и обновление в режиме реального времени"
},
"permissions-name": "Права доступа",
"permissionsHelp": "Можно настроить пользователя как администратора или выбрать разрешения индивидуально. При выборе \"Администратор\", все остальные параметры будут автоматически выбраны. Управление пользователями - привилегия администратора.\n",
@@ -240,7 +255,31 @@
"userManagement": "Управление пользователями",
"userUpdated": "Пользователь изменен!",
"username": "Имя пользователя",
"users": "Пользователи"
"users": "Пользователи",
"loginMethod": "Метод входа в систему",
"modifyOtherUser": "Измените пользователя:",
"modifyCurrentUser": "Изменить текущего пользователя",
"themeColor": "Цвет темы",
"disableOfficePreviewDescription": "Список расширений файлов, для которых необходимо отключить программу просмотра OnlyOffice, разделенный пробелами. (например, '.txt .html').",
"disableOfficePreview": "Отключите просмотрщик Onlyoffice для определенных расширений файлов",
"filePreviewOptions": "Параметры предварительного просмотра файлов",
"listingOptions": "Варианты листинга",
"emptyGlobal": "В настоящее время нет глобальных опций конфигурации, вы можете редактировать конфигурационный yaml для настройки параметров системы.",
"scopes": "Определите разрешенные источники и область действия пользователей в них",
"UserManagement": "Управление пользователями",
"tusUploads": "Загружаемые файлы",
"tusUploadsHelp": "File Browser поддерживает загрузку файлов в виде фрагментов, что позволяет создавать эффективные, надежные, возобновляемые и фрагментированные загрузки файлов даже в ненадежных сетях.",
"tusUploadsChunkSize": "Указывает максимальный размер запроса (для меньшего размера будет использоваться прямая загрузка). Вы можете ввести целое число, обозначающее байт, или строку, например 10 МБ, 1 ГБ и т. д.",
"tusUploadsRetryCount": "Количество повторных попыток, которые необходимо выполнить, если чанк не удалось загрузить.",
"userHomeBasePath": "Базовый путь к домашним каталогам пользователей",
"userScopeGenerationPlaceholder": "Область действия будет сгенерирована автоматически",
"createUserHomeDirectory": "Создайте домашний каталог пользователя",
"api": "Ключи API",
"loginMethodDescription": "разрешенный метод входа для пользователя.",
"userNotAdmin": "Пользователь не является администратором",
"enterPassword": "Введите пароль",
"enterPasswordAgain": "Введите пароль еще раз",
"passwordsDoNotMatch": "Пароли не совпадают"
},
"sidebar": {
"help": "Помощь",
@@ -253,7 +292,8 @@
"preview": "Предпросмотр",
"settings": "Настройки",
"signup": "Зарегистрироваться",
"siteSettings": "Настройки сайта"
"siteSettings": "Настройки сайта",
"sources": "Источники"
},
"success": {
"linkCopied": "Ссылка скопирована!"
@@ -264,5 +304,51 @@
"minutes": "Минуты",
"seconds": "Секунды",
"unit": "Единица времени"
},
"upload": {
"abortUpload": "Вы уверены, что хотите сделать аборт?"
},
"share": {
"notice": "Примечание: любой, кто имеет доступ к ссылке (и дополнительный пароль), может получить доступ к общим файлам. Вход в систему не требуется."
},
"api": {
"title": "Ключи API:",
"keyName": "Имя ключа API:",
"createdAt": "Создан в:",
"expiresAt": "Истекает в:",
"permissions": "Разрешения:",
"createTitle": "Создайте ключ API",
"keyNamePlaceholder": "Введите уникальное имя ключа API:",
"tokenDuration": "Длительность жетона:",
"durationNumberPlaceholder": "Номер",
"days": "Дни",
"months": "Месяцы",
"permissionNote": "Выберите хотя бы одно разрешение для ключа. Ваш пользователь также должен иметь это разрешение.",
"createKeyFailed": "Не удалось создать ключ API.",
"createKeySuccess": "Ключ API успешно создан.",
"description": "Ключи API зависят от пользователя, который их создает. См.",
"swaggerLinkText": "страница swagger",
"name": "Имя",
"created": "Создано",
"expires": "Срок действия:",
"actions": "Действия",
"enabled": "Включено",
"disabled": "Инвалид"
},
"index": {
"status": "Статус",
"assessment": "Оценка",
"files": "Файлы",
"folders": "Папки",
"lastScanned": "Последнее сканирование",
"quickScan": "Секунды быстрого сканирования",
"fullScan": "Секунды полного сканирования",
"settingsHover": "Настройки для пользователя",
"logout": "Выход из системы",
"toggleClick": "Переключение одним щелчком",
"toggleDark": "Переключение темного режима",
"toggleSticky": "Переключение режима липкости",
"of": "из",
"used": "используется"
}
}
+96 -9
View File
@@ -35,7 +35,9 @@
"toggleSidebar": "Prepnúť sidebar",
"update": "Aktualizovať",
"upload": "Nahrať",
"openFile": "Otvoriť súbor"
"openFile": "Otvoriť súbor",
"copyDownloadLinkToClipboard": "Skopírujte odkaz na stiahnutie do schránky",
"continue": "Pokračovať"
},
"download": {
"downloadFile": "Stiahnuť súbor",
@@ -101,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -116,7 +118,8 @@
"submit": "Prihlásiť",
"username": "Používateľské meno",
"usernameTaken": "Meno je už obsadené",
"wrongCredentials": "Nesprávne prihlasovacie údaje"
"wrongCredentials": "Nesprávne prihlasovacie údaje",
"or": "ALEBO"
},
"permanent": "Trvalé",
"prompts": {
@@ -132,7 +135,6 @@
"downloadMessage": "Vyberte formát, ktorý chcete stiahnuť.",
"error": "Niečo sa pokazilo",
"fileInfo": "Informácie o súbore",
"filesSelected": "{count} súborov vybratých.",
"lastModified": "Dátum zmeny",
"move": "Presunúť",
"moveMessage": "Zvoľte nový domov pre vaše súbory/priečinky:",
@@ -153,7 +155,12 @@
"size": "Veľkosť",
"upload": "Nahrať",
"uploadMessage": "Zvoľte možnosť nahrávania.",
"optionalPassword": "Voliteľné heslo"
"optionalPassword": "Voliteľné heslo",
"deleteUserMessage": "Ste si istí, že chcete odstrániť tohto používateľa?",
"filesSelected": "vybrané súbory",
"uploadFiles": "Uploading {files} files...",
"typeName": "Typ:",
"selected": "vybrané"
},
"search": {
"images": "Obrázky",
@@ -163,7 +170,12 @@
"search": "Hľadať...",
"typeToSearch": "Vyhľadáte písaním...",
"types": "Typy",
"video": "Video"
"video": "Video",
"path": "Cesta:",
"smallerThan": "Menšie ako:",
"largerThan": "Väčšie ako:",
"helpText1": "Vyhľadávanie prebieha na základe každého zadaného znaku (minimálne 3 znaky pre hľadané výrazy).",
"helpText2": "Index: Vyhľadávanie využíva index, ktorý sa automaticky aktualizuje v nastavenom intervale (predvolené: 5 minút). Vyhľadávanie, keď sa program práve spustil, môže viesť k neúplným výsledkom."
},
"settings": {
"admin": "Admin",
@@ -210,7 +222,10 @@
"execute": "Vykonávať príkazy",
"modify": "Upravovať súbory",
"rename": "Premenovať a presúvať súbory a priečinky",
"share": "Zdieľať súbory"
"share": "Zdieľať súbory",
"admin": "Správca",
"api": "Vytváranie a správa kľúčov API s dlhou životnosťou",
"realtime": "Povolenie pripojení a aktualizácií v reálnom čase"
},
"permissions-name": "Práva",
"permissionsHelp": "Môžete nastaviť používateľa, aby bol administrátorom alebo vybrať práva jednotlivo. Ak zvolíte \"Administrator\", všetky ďalšie budú automaticky zaškrtnuté. Manažment používateľov ostáva v správe administrátora.\n",
@@ -239,7 +254,32 @@
"userManagement": "Správa používateľov",
"userUpdated": "Používateľ upravený!",
"username": "Meno používateľa",
"users": "Používatelia"
"users": "Používatelia",
"loginMethod": "Spôsob prihlásenia",
"modifyOtherUser": "Upravte používateľa:",
"modifyCurrentUser": "Úprava aktuálneho používateľa",
"themeColor": "Farba témy",
"disableOfficePreviewDescription": "Medzerou oddelený zoznam prípon súborov, pre ktoré sa má prehliadač OnlyOffice vypnúť. (napr. \".txt .html\").",
"disableOfficePreview": "Zakázať prehliadač OnlyOffice pre určité prípony súborov",
"filePreviewOptions": "Možnosti náhľadu súborov",
"listingOptions": "Možnosti uvedenia v zozname",
"emptyGlobal": "V súčasnosti neexistujú žiadne globálne konfiguračné možnosti, Môžete upraviť konfiguračný yaml upraviť nastavenia systému.",
"scopes": "Definovanie povolených zdrojov a používateľského rozsahu v rámci nich",
"UserManagement": "Správa používateľov",
"tusUploads": "Nahrávanie v častiach",
"tusUploadsHelp": "Prehliadač súborov podporuje sekané odosielanie súborov, čo umožňuje vytvárať efektívne, spoľahlivé, obnoviteľné a sekané odosielanie súborov aj v nespoľahlivých sieťach.",
"tusUploadsChunkSize": "Udáva maximálnu veľkosť požiadavky (pre menšie odoslania sa použije priame odoslanie). Môžete zadať obyčajné celé číslo označujúce vstup v bajtoch alebo reťazec, napríklad 10 MB, 1 GB atď.",
"tusUploadsRetryCount": "Počet opakovaných pokusov, ktoré sa vykonajú, ak sa nepodarí nahrať časť.",
"userHomeBasePath": "Základná cesta pre domovské adresáre používateľov",
"userScopeGenerationPlaceholder": "Rozsah sa vygeneruje automaticky",
"createUserHomeDirectory": "Vytvorenie domovského adresára používateľa",
"api": "Kľúče API",
"setDateFormat": "Nastavenie presného formátu dátumu",
"loginMethodDescription": "povolená metóda prihlásenia pre používateľa.",
"userNotAdmin": "Používateľ nie je správca",
"enterPassword": "Zadajte heslo",
"enterPasswordAgain": "Znovu zadajte heslo",
"passwordsDoNotMatch": "Heslá sa nezhodujú"
},
"sidebar": {
"help": "Pomoc",
@@ -252,7 +292,8 @@
"preview": "Náhľad",
"settings": "Nastavenia",
"signup": "Registrovať",
"siteSettings": "Nastavenia stránky"
"siteSettings": "Nastavenia stránky",
"sources": "Zdroje"
},
"success": {
"linkCopied": "Odkaz skopírovaný!"
@@ -263,5 +304,51 @@
"minutes": "Minúty",
"seconds": "Sekundy",
"unit": "Jednotka času"
},
"upload": {
"abortUpload": "Ste si istí, že chcete prerušiť tehotenstvo?"
},
"share": {
"notice": "Poznámka: prístup k zdieľaným súborom má každý, kto má prístup k odkazu (a voliteľnému heslu). Nie je potrebné byť prihlásený."
},
"api": {
"title": "Kľúče API:",
"keyName": "Názov kľúča API:",
"createdAt": "Vytvorené v:",
"expiresAt": "Platnosť končí na:",
"permissions": "Oprávnenia:",
"createTitle": "Vytvorenie kľúča API",
"keyNamePlaceholder": "Zadajte jedinečný názov kľúča API:",
"tokenDuration": "Trvanie žetónu:",
"durationNumberPlaceholder": "Číslo",
"days": "Dni",
"months": "Mesiace",
"permissionNote": "Vyberte aspoň jedno povolenie pre kľúč. Oprávnenie musí mať aj váš používateľ.",
"createKeyFailed": "Nepodarilo sa vytvoriť kľúč API.",
"createKeySuccess": "Kľúč API bol úspešne vytvorený.",
"description": "Kľúče API sú založené na používateľovi, ktorý ich vytvoril. Pozri .",
"swaggerLinkText": "swagger stránka",
"name": "Názov",
"created": "Vytvorené",
"expires": "Platnosť končí na",
"actions": "Činnosti",
"enabled": "Povolené",
"disabled": "Bezbariérový"
},
"index": {
"status": "Stav",
"assessment": "Hodnotenie",
"files": "Súbory",
"folders": "Priečinky",
"lastScanned": "Posledné skenovanie",
"quickScan": "Sekundy rýchleho skenovania",
"fullScan": "Sekundy úplného skenovania",
"settingsHover": "Nastavenia pre používateľa",
"logout": "Odhlásenie",
"toggleClick": "Prepínanie jedným kliknutím",
"toggleDark": "Prepínanie tmavého režimu",
"toggleSticky": "Prepnutie režimu Sticky",
"of": "z",
"used": "použité"
}
}
+8 -4
View File
@@ -95,7 +95,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -125,7 +125,6 @@
"downloadMessage": "Välj format på det du vill lada ner.",
"error": "Något gick fel",
"fileInfo": "Fil information",
"filesSelected": "{count} filer valda.",
"lastModified": "Senast ändrad",
"move": "Flytta",
"moveMessage": "Välj ny plats för din fil (er)/mapp (ar):",
@@ -145,7 +144,8 @@
"show": "Visa",
"size": "Storlek",
"upload": "",
"uploadMessage": ""
"uploadMessage": "",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?"
},
"search": {
"images": "Bilder",
@@ -254,5 +254,9 @@
"minutes": "Minuter",
"seconds": "Sekunder",
"unit": "Tidsenhet"
}
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
+98 -9
View File
@@ -33,7 +33,11 @@
"toggleSidebar": "Menüyü aç/kapat",
"update": "Güncelle",
"upload": "Yükle",
"openFile": "Dosyayı aç"
"openFile": "Dosyayı aç",
"copyDownloadLinkToClipboard": "İndirme bağlantısını panoya kopyala",
"file": "Dosya",
"folder": "Klasör",
"continue": "Devam et"
},
"download": {
"downloadFile": "Dosyayı indir",
@@ -99,7 +103,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -114,7 +118,8 @@
"submit": "Giriş",
"username": "Kullanıcı adı",
"usernameTaken": "Kullanıcı adı mevcut",
"wrongCredentials": "Yanlış hesap bilgileri"
"wrongCredentials": "Yanlış hesap bilgileri",
"or": "VEYA"
},
"permanent": "Kalıcı",
"prompts": {
@@ -130,7 +135,6 @@
"downloadMessage": "İndirmek istediğiniz formatı seçin.",
"error": "Bir şeyler yanlış gitti",
"fileInfo": "Dosya bilgisi",
"filesSelected": "{count} dosya seçildi.",
"lastModified": "Son güncellenme",
"move": "Taşı",
"moveMessage": "Dosya(lar)ınız/klasör(ler)iniz için yeni ana dizin seçin:",
@@ -151,7 +155,12 @@
"size": "Boyut",
"upload": "Gönder",
"uploadMessage": "Yüklemek için bir seçenek belirleyin.",
"optionalPassword": "İsteğe bağlı şifre"
"optionalPassword": "İsteğe bağlı şifre",
"deleteUserMessage": "Bu kullanıcıyı silmek istediğinizden emin misiniz?",
"filesSelected": "seçilen dosyalar",
"uploadFiles": "Uploading {files} files...",
"typeName": "Tip:",
"selected": "seçilmiş"
},
"search": {
"images": "Görseller",
@@ -161,7 +170,12 @@
"search": "Ara...",
"typeToSearch": "Aramak için yazın...",
"types": "Türler",
"video": "Video"
"video": "Video",
"path": "Yol:",
"smallerThan": "Daha küçük:",
"largerThan": "Daha büyük:",
"helpText1": "Arama, yazdığınız her karakterde gerçekleşir (arama terimleri için minimum 3 karakter).",
"helpText2": "Dizin: Arama, yapılandırılan aralıkta (varsayılan: 5 dakika) otomatik olarak güncellenen dizini kullanır. Program yeni başladığında arama yapmak eksik sonuçlara neden olabilir."
},
"settings": {
"admin": "Yönetim",
@@ -208,7 +222,10 @@
"execute": "Komutları çalıştır",
"modify": "Dosyaları değiştir",
"rename": "Dosyaları ve dizinleri yeniden adlandırın veya taşıyın",
"share": "Dosyaları paylaş"
"share": "Dosyaları paylaş",
"admin": "Yönetici",
"api": "Uzun ömürlü API anahtarları oluşturma ve yönetme",
"realtime": "Gerçek zamanlı bağlantıları ve güncellemeleri etkinleştirin"
},
"permissions-name": "İzinler",
"permissionsHelp": "Kullanıcıyı yönetici olarak ayarlayabilir veya izinleri ayrı ayrı seçebilirsiniz. \"Yönetici\"yi seçerseniz, diğer tüm seçenekler otomatik olarak kontrol edilecektir. Kullanıcıların yönetimi, bir yöneticinin yetkisi olarak kalır.\n",
@@ -237,7 +254,32 @@
"userManagement": "Kullanıcı yönetimi",
"userUpdated": "Kullanıcı güncellendi!",
"username": "Kullanıcı adı",
"users": "Kullanıcılar"
"users": "Kullanıcılar",
"loginMethod": "Giriş yöntemi",
"modifyOtherUser": "Kullanıcıyı değiştir:",
"modifyCurrentUser": "Geçerli kullanıcıyı değiştir",
"themeColor": "Tema Rengi",
"disableOfficePreviewDescription": "OnlyOffice görüntüleyicisinin devre dışı bırakılacağı dosya uzantılarının boşluk bırakılarak ayrılmış bir listesi. (örneğin, '.txt .html').",
"disableOfficePreview": "Belirli dosya uzantıları için onlyoffice görüntüleyiciyi devre dışı bırakma",
"filePreviewOptions": "Dosya önizleme seçenekleri",
"listingOptions": "Listeleme seçenekleri",
"emptyGlobal": "Şu anda global yapılandırma seçenekleri yoktur, sistem ayarlarını yapmak için yapılandırma yaml'ını düzenleyebilirsiniz.",
"scopes": "İzin verilen kaynakları ve bunlar içindeki kullanıcı kapsamını tanımlama",
"UserManagement": "Kullanıcı Yönetimi",
"tusUploads": "Yığınlanmış Yüklemeler",
"tusUploadsHelp": "File Browser, parçalı dosya yüklemelerini destekleyerek güvenilir olmayan ağlarda bile verimli, güvenilir, yeniden başlatılabilir ve parçalı dosya yüklemelerinin oluşturulmasına olanak tanır.",
"tusUploadsChunkSize": "Bir isteğin maksimum boyutunu belirtir (daha küçük yüklemeler için doğrudan yüklemeler kullanılacaktır). Bayt girişini belirten düz bir tamsayı veya 10MB, 1GB vb. gibi bir dize girebilirsiniz.",
"tusUploadsRetryCount": "Bir yığının yüklenememesi durumunda gerçekleştirilecek yeniden deneme sayısı.",
"userHomeBasePath": "Kullanıcı ev dizinleri için temel yol",
"userScopeGenerationPlaceholder": "Kapsam otomatik olarak oluşturulacaktır",
"createUserHomeDirectory": "Kullanıcı ev dizini oluşturma",
"api": "API Anahtarları",
"setDateFormat": "Tam tarih biçimini ayarlama",
"loginMethodDescription": "kullanıcı için izin verilen oturum açma yöntemi.",
"userNotAdmin": "Kullanıcı yönetici değil",
"enterPassword": "Şifre girin",
"enterPasswordAgain": "Şifreyi tekrar girin",
"passwordsDoNotMatch": "Parolalar eşleşmiyor"
},
"sidebar": {
"help": "Yardım",
@@ -250,7 +292,8 @@
"preview": "Önizleme",
"settings": "Ayarlar",
"signup": "Kayıt",
"siteSettings": "Site ayarları!"
"siteSettings": "Site ayarları!",
"sources": "Kaynaklar"
},
"success": {
"linkCopied": "Link kopyalandı!"
@@ -261,5 +304,51 @@
"minutes": "Dakika",
"seconds": "Saniye",
"unit": "Zaman birimi"
},
"upload": {
"abortUpload": "Aldırmak istediğine emin misin?"
},
"share": {
"notice": "Not: Bağlantıya (ve isteğe bağlı şifreye) erişimi olan herkes paylaşılan dosyalara erişebilir. Oturum açma zorunluluğu yoktur."
},
"api": {
"title": "API Anahtarları:",
"keyName": "API Anahtar Adı:",
"createdAt": "Oluşturuldu:",
"expiresAt": "Son kullanma tarihi:",
"permissions": "İzinler:",
"createTitle": "API Anahtarı Oluşturma",
"keyNamePlaceholder": "Benzersiz bir API anahtar adı girin:",
"tokenDuration": "Token Süresi:",
"durationNumberPlaceholder": "Sayı",
"days": "Günler",
"months": "Aylar",
"permissionNote": "Anahtar için en az bir izin seçin. Kullanıcınız da bu izne sahip olmalıdır.",
"createKeyFailed": "API anahtarı oluşturulamadı.",
"createKeySuccess": "API anahtarı başarıyla oluşturuldu.",
"description": "API anahtarları, onları oluşturan kullanıcıyı temel alır. Bkz.",
"swaggerLinkText": "swagger sayfası",
"name": "İsim",
"created": "Oluşturuldu",
"expires": "Sona erdi",
"actions": "Eylemler",
"enabled": "Etkin",
"disabled": "Engelli"
},
"index": {
"status": "Durum",
"assessment": "Değerlendirme",
"files": "Dosyalar",
"folders": "Klasörler",
"lastScanned": "Son Taranan",
"quickScan": "Hızlı Tarama Saniyeleri",
"fullScan": "Tam Tarama Saniyeleri",
"settingsHover": "Kullanıcı Ayarları",
"logout": "Oturum Kapatma",
"toggleClick": "Tek Tıklamayı Değiştir",
"toggleDark": "Karanlık Modu Aç / Kapat",
"toggleSticky": "Yapışkan Modunu Değiştir",
"of": ".",
"used": "kullanılmış"
}
}
+266 -263
View File
@@ -1,268 +1,271 @@
{
"buttons": {
"cancel": "Відмінити",
"close": "Закрити",
"copy": "Копіювати",
"copyFile": "Копіювати файл",
"copyToClipboard": "Копіювати в буфер обміну",
"create": "Створити",
"delete": "Видалити",
"download": "Завантажити",
"file": "Файл",
"folder": "Папка",
"info": "Інфо",
"more": "Більше",
"move": "Перемістити",
"moveFile": "Перемістити файл",
"new": "Новий",
"next": "Далі",
"ok": "ОК",
"permalink": "Отримати постійне посилання",
"previous": "Назад",
"publish": "Опублікувати",
"rename": "Перейменувати",
"replace": "Замінити",
"reportIssue": "Повідомити про помилку",
"save": "Зберегти",
"schedule": "Планування",
"search": "Пошук",
"select": "Вибрати",
"selectMultiple": "Мультивибір",
"share": "Поділитися",
"shell": "Командний рядок",
"submit": "Відправити",
"switchView": "Вид",
"toggleSidebar": "Бічна панель",
"update": "Оновити",
"upload": "Завантажити",
"openFile": "Відкрити файл"
"buttons": {
"cancel": "Відмінити",
"close": "Закрити",
"copy": "Копіювати",
"copyFile": "Копіювати файл",
"copyToClipboard": "Копіювати в буфер обміну",
"create": "Створити",
"delete": "Видалити",
"download": "Завантажити",
"file": "Файл",
"folder": "Папка",
"info": "Інфо",
"more": "Більше",
"move": "Перемістити",
"moveFile": "Перемістити файл",
"new": "Новий",
"next": "Далі",
"ok": "ОК",
"permalink": "Отримати постійне посилання",
"previous": "Назад",
"publish": "Опублікувати",
"rename": "Перейменувати",
"replace": "Замінити",
"reportIssue": "Повідомити про помилку",
"save": "Зберегти",
"schedule": "Планування",
"search": "Пошук",
"select": "Вибрати",
"selectMultiple": "Мультивибір",
"share": "Поділитися",
"shell": "Командний рядок",
"submit": "Відправити",
"switchView": "Вид",
"toggleSidebar": "Бічна панель",
"update": "Оновити",
"upload": "Завантажити",
"openFile": "Відкрити файл"
},
"download": {
"downloadFile": "Завантажити файл",
"downloadFolder": "Завантажити папку",
"downloadSelected": "Завантажити вибране"
},
"errors": {
"forbidden": "У вас немає прав доступу до цього.",
"internal": "Щось пішло не так.",
"notFound": "Неправильне посилання.",
"connection": "Немає підключення до сервера."
},
"files": {
"body": "Тіло",
"clear": "Очистити",
"closePreview": "Закрити",
"files": "Файли",
"folders": "Папки",
"home": "Домівка",
"lastModified": "Останній раз змінено",
"loading": "Завантаження...",
"lonely": "Тут пусто...",
"metadata": "Метадані",
"multipleSelectionEnabled": "Мультивибір включений",
"name": "Ім'я",
"size": "Розмір",
"sortByLastModified": "Сортувати за останнім зміненням",
"sortByName": "Сортувати за іменем",
"sortBySize": "Сортувати за розміром",
"noPreview": "Попередній перегляд для цього файлу недоступний."
},
"help": {
"click": "вибрати файл чи каталог",
"ctrl": {
"click": "вибрати кілька файлів чи каталогів",
"f": "відкрити пошук",
"s": "скачати файл або поточний каталог"
},
"download": {
"downloadFile": "Завантажити файл",
"downloadFolder": "Завантажити папку",
"downloadSelected": "Завантажити вибране"
"del": "видалити вибрані елементи",
"doubleClick": "відкрити файл чи каталог",
"esc": "очистити виділення та/або закрити вікно",
"f1": "допомога",
"f2": "перейменувати файл",
"help": "Допомога"
},
"languages": {
"he": "עברית",
"hu": "Magyar",
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": {
"createAnAccount": "Створити обліковий запис",
"loginInstead": "Вже є обліковий запис",
"password": "Пароль",
"passwordConfirm": "Підтвердження паролю",
"passwordsDontMatch": "Паролі не співпадають",
"signup": "Зареєструватися",
"submit": "Увійти",
"username": "Ім'я користувача",
"usernameTaken": "Ім'я користувача вже використовується",
"wrongCredentials": "Невірне ім'я користувача або пароль"
},
"permanent": "Постійний",
"prompts": {
"copy": "Копіювати",
"copyMessage": "Копіювати в:",
"currentlyNavigating": "Поточний каталог:",
"deleteMessageMultiple": "Видалити ці файли ({count})?",
"deleteMessageSingle": "Видалити цей файл/каталог?",
"deleteMessageShare": "Видалити цей спільний файл/каталог ({path})?",
"deleteTitle": "Видалити файлы",
"displayName": "Відображене ім'я:",
"download": "Завантажити файлы",
"downloadMessage": "Виберіть формат, в якому хочете завантажити.",
"error": "Помилка",
"fileInfo": "Інформація про файл",
"lastModified": "Останній раз змінено",
"move": "Перемістити",
"moveMessage": "Перемістити в:",
"newArchetype": "Створіть новий запис на основі архетипу. Файл буде створено у каталозі.",
"newDir": "Новий каталог",
"newDirMessage": "Ім'я нового каталогу.",
"newFile": "Новий файл",
"newFileMessage": "Ім'я нового файлу.",
"numberDirs": "Кількість каталогів",
"numberFiles": "Кількість файлів",
"rename": "Перейменувати",
"renameMessage": "Нове ім'я",
"replace": "Замінити",
"replaceMessage": "Ім'я одного з файлів, що завантажуються, збігається з вже існуючим файлом. Ви бажаєте замінити існуючий?\n",
"schedule": "Планування",
"scheduleMessage": "Запланувати дату та час публікації.",
"show": "Показати",
"size": "Розмір",
"upload": "Завантажити",
"uploadMessage": "Виберіть варіант для завантаження.",
"optionalPassword": "Необов'язковий пароль"
},
"search": {
"images": "Зображення",
"music": "Музика",
"pdf": "PDF",
"pressToSearch": "Натисніть ENTER для пошуку",
"search": "Пошук...",
"typeToSearch": "Введіть ім'я файлу...",
"types": "Типи",
"video": "Відео"
},
"settings": {
"admin": "Адмін",
"administrator": "Адміністратор",
"allowCommands": "Запуск команд",
"allowEdit": "Редагування, перейменування та видалення файлів чи каталогів",
"allowNew": "Створення нових файлів або каталогів",
"allowPublish": "Публікація нових записів та сторінок",
"allowSignup": "Дозволити користувачам реєструватися",
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
"branding": "Брендинг",
"brandingDirectoryPath": "Шлях до каталогу брендів",
"brandingHelp": "Ви можете налаштувати зовнішній вигляд файлового браузера, змінивши його ім'я, замінивши логотип, додавши власні стилі та навіть відключивши зовнішні посилання на GitHub.\nДодаткову інформацію про персоналізований брендинг можна знайти на сторінці {0}.",
"changePassword": "Зміна пароля",
"commandRunner": "Запуск команд",
"commandRunnerHelp": "Тут ви можете встановити команди, які будуть виконуватися у зазначених подіях. Ви повинні вказати по одній команді в кожному рядку. Змінні середовища {0} та {1} будуть доступні, будучи {0} щодо {1}. Додаткові відомості про цю функцію та доступні змінні середовища див. у {2}.",
"commandsUpdated": "Команди оновлені!",
"createUserDir": "Автоматичне створення домашнього каталогу користувача при додаванні нового користувача",
"customStylesheet": "Свій стиль",
"defaultUserDescription": "Це налаштування за замовчуванням для нових користувачів.",
"disableExternalLinks": "Вимкнути зовнішні посилання (крім документації)",
"disableUsedDiskPercentage": "Disable used disk percentage graph",
"documentation": "документація",
"examples": "Приклади",
"executeOnShell": "Виконати в командному рядку",
"executeOnShellDescription": "За замовчуванням File Browser виконує команди, безпосередньо викликаючи їх бінарні файли. Якщо ви хочете замість цього запускати їх в оболонці (наприклад, Bash або PowerShell), ви можете визначити їх тут з необхідними аргументами та прапорами. Якщо встановлено, виконуєма вами команда буде додана як аргумент. Це стосується як користувацьких команд, так і обробників подій.",
"globalRules": "Це глобальний набір дозволяючих та забороняючих правил. Вони застосовні до кожного користувача. Ви можете визначити певні правила для налаштувань кожного користувача, щоб перевизначити їх.",
"globalSettings": "Глобальні налаштування",
"insertPath": "Вставте шлях",
"insertRegex": "Вставити регулярний вираз",
"instanceName": "Поточна назва програми",
"language": "Мова",
"lockPassword": "Заборонити користувачеві змінювати пароль",
"newPassword": "Новий пароль",
"newPasswordConfirm": "Підтвердження нового пароля",
"newUser": "Новий користувач",
"password": "Пароль",
"passwordUpdated": "Пароль оновлено!",
"path": "Шлях",
"permissions": {
"create": "Створювати файли та каталоги",
"delete": "Видаляти файли та каталоги",
"download": "Завантажувати",
"execute": "Виконувати команди",
"modify": "Редагувати файли",
"rename": "Перейменовувати або переміщувати файли та каталоги",
"share": "Ділітися файлами"
},
"errors": {
"forbidden": "У вас немає прав доступу до цього.",
"internal": "Щось пішло не так.",
"notFound": "Неправильне посилання.",
"connection": "Немає підключення до сервера."
"permissions-name": "Дозволи",
"permissionsHelp": "Можна настроїти користувача як адміністратора або вибрати індивідуальні дозволи. При виборі \"Адміністратор\" всі інші параметри будуть автоматично вибрані. Керування користувачами - привілей адміністратора.\n",
"profileSettings": "Налаштування профілю",
"ruleExample1": "запобігти доступу до будь-якого прихованого файлу (наприклад: .git, .gitignore) у кожній папці.\n",
"ruleExample2": "блокує доступ до файлу з ім'ям Caddyfile у кореневій області.",
"rules": "Права",
"rulesHelp": "Тут ви можете визначити набір дозволяючих та забороняючих правил для цього конкретного користувача. Блоковані файли не відображатимуться у списках, і не будуть доступні для користувача. Є підтримка регулярних виразів та відносних шляхів.\n",
"scope": "Корінь",
"setDateFormat": "Встановити точний формат дати",
"settingsUpdated": "Налаштування застосовані!",
"shareDuration": "Тривалість спільного посилання",
"shareManagement": "Управління спільними посиланнями",
"shareDeleted": "Спільне посилання видалено!",
"singleClick": "Відкриття файлів та каталогів одним кліком",
"themes": {
"dark": "Темна",
"light": "Світла",
"title": "Тема"
},
"files": {
"body": "Тіло",
"clear": "Очистити",
"closePreview": "Закрити",
"files": "Файли",
"folders": "Папки",
"home": "Домівка",
"lastModified": "Останній раз змінено",
"loading": "Завантаження...",
"lonely": "Тут пусто...",
"metadata": "Метадані",
"multipleSelectionEnabled": "Мультивибір включений",
"name": "Ім'я",
"size": "Розмір",
"sortByLastModified": "Сортувати за останнім зміненням",
"sortByName": "Сортувати за іменем",
"sortBySize": "Сортувати за розміром",
"noPreview": "Попередній перегляд для цього файлу недоступний."
},
"help": {
"click": "вибрати файл чи каталог",
"ctrl": {
"click": "вибрати кілька файлів чи каталогів",
"f": "відкрити пошук",
"s": "скачати файл або поточний каталог"
},
"del": "видалити вибрані елементи",
"doubleClick": "відкрити файл чи каталог",
"esc": "очистити виділення та/або закрити вікно",
"f1": "допомога",
"f2": "перейменувати файл",
"help": "Допомога"
},
"languages": {
"he": "עברית",
"hu": "Magyar",
"ar": "العربية",
"de": "Deutsch",
"en": "English",
"es": "Español",
"fr": "Français",
"is": "Icelandic",
"it": "Italiano",
"ja": "日本語",
"ko": "한국어",
"nlBE": "Dutch (Belgium)",
"pl": "Polski",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ro": "Romanian",
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish (Sweden)",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
},
"login": {
"createAnAccount": "Створити обліковий запис",
"loginInstead": "Вже є обліковий запис",
"password": "Пароль",
"passwordConfirm": "Підтвердження паролю",
"passwordsDontMatch": "Паролі не співпадають",
"signup": "Зареєструватися",
"submit": "Увійти",
"username": "Ім'я користувача",
"usernameTaken": "Ім'я користувача вже використовується",
"wrongCredentials": "Невірне ім'я користувача або пароль"
},
"permanent": "Постійний",
"prompts": {
"copy": "Копіювати",
"copyMessage": "Копіювати в:",
"currentlyNavigating": "Поточний каталог:",
"deleteMessageMultiple": "Видалити ці файли ({count})?",
"deleteMessageSingle": "Видалити цей файл/каталог?",
"deleteMessageShare": "Видалити цей спільний файл/каталог ({path})?",
"deleteTitle": "Видалити файлы",
"displayName": "Відображене ім'я:",
"download": "Завантажити файлы",
"downloadMessage": "Виберіть формат, в якому хочете завантажити.",
"error": "Помилка",
"fileInfo": "Інформація про файл",
"filesSelected": "Файлів вибрано: {count}.",
"lastModified": "Останній раз змінено",
"move": "Перемістити",
"moveMessage": "Перемістити в:",
"newArchetype": "Створіть новий запис на основі архетипу. Файл буде створено у каталозі.",
"newDir": "Новий каталог",
"newDirMessage": "Ім'я нового каталогу.",
"newFile": "Новий файл",
"newFileMessage": "Ім'я нового файлу.",
"numberDirs": "Кількість каталогів",
"numberFiles": "Кількість файлів",
"rename": "Перейменувати",
"renameMessage": "Нове ім'я",
"replace": "Замінити",
"replaceMessage": "Ім'я одного з файлів, що завантажуються, збігається з вже існуючим файлом. Ви бажаєте замінити існуючий?\n",
"schedule": "Планування",
"scheduleMessage": "Запланувати дату та час публікації.",
"show": "Показати",
"size": "Розмір",
"upload": "Завантажити",
"uploadMessage": "Виберіть варіант для завантаження.",
"optionalPassword": "Необов'язковий пароль"
},
"search": {
"images": "Зображення",
"music": "Музика",
"pdf": "PDF",
"pressToSearch": "Натисніть ENTER для пошуку",
"search": "Пошук...",
"typeToSearch": "Введіть ім'я файлу...",
"types": "Типи",
"video": "Відео"
},
"settings": {
"admin": "Адмін",
"administrator": "Адміністратор",
"allowCommands": "Запуск команд",
"allowEdit": "Редагування, перейменування та видалення файлів чи каталогів",
"allowNew": "Створення нових файлів або каталогів",
"allowPublish": "Публікація нових записів та сторінок",
"allowSignup": "Дозволити користувачам реєструватися",
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
"branding": "Брендинг",
"brandingDirectoryPath": "Шлях до каталогу брендів",
"brandingHelp": "Ви можете налаштувати зовнішній вигляд файлового браузера, змінивши його ім'я, замінивши логотип, додавши власні стилі та навіть відключивши зовнішні посилання на GitHub.\nДодаткову інформацію про персоналізований брендинг можна знайти на сторінці {0}.",
"changePassword": "Зміна пароля",
"commandRunner": "Запуск команд",
"commandRunnerHelp": "Тут ви можете встановити команди, які будуть виконуватися у зазначених подіях. Ви повинні вказати по одній команді в кожному рядку. Змінні середовища {0} та {1} будуть доступні, будучи {0} щодо {1}. Додаткові відомості про цю функцію та доступні змінні середовища див. у {2}.",
"commandsUpdated": "Команди оновлені!",
"createUserDir": "Автоматичне створення домашнього каталогу користувача при додаванні нового користувача",
"customStylesheet": "Свій стиль",
"defaultUserDescription": "Це налаштування за замовчуванням для нових користувачів.",
"disableExternalLinks": "Вимкнути зовнішні посилання (крім документації)",
"disableUsedDiskPercentage": "Disable used disk percentage graph",
"documentation": "документація",
"examples": "Приклади",
"executeOnShell": "Виконати в командному рядку",
"executeOnShellDescription": "За замовчуванням File Browser виконує команди, безпосередньо викликаючи їх бінарні файли. Якщо ви хочете замість цього запускати їх в оболонці (наприклад, Bash або PowerShell), ви можете визначити їх тут з необхідними аргументами та прапорами. Якщо встановлено, виконуєма вами команда буде додана як аргумент. Це стосується як користувацьких команд, так і обробників подій.",
"globalRules": "Це глобальний набір дозволяючих та забороняючих правил. Вони застосовні до кожного користувача. Ви можете визначити певні правила для налаштувань кожного користувача, щоб перевизначити їх.",
"globalSettings": "Глобальні налаштування",
"insertPath": "Вставте шлях",
"insertRegex": "Вставити регулярний вираз",
"instanceName": "Поточна назва програми",
"language": "Мова",
"lockPassword": "Заборонити користувачеві змінювати пароль",
"newPassword": "Новий пароль",
"newPasswordConfirm": "Підтвердження нового пароля",
"newUser": "Новий користувач",
"password": "Пароль",
"passwordUpdated": "Пароль оновлено!",
"path": "Шлях",
"permissions": {
"create": "Створювати файли та каталоги",
"delete": "Видаляти файли та каталоги",
"download": "Завантажувати",
"execute": "Виконувати команди",
"modify": "Редагувати файли",
"rename": "Перейменовувати або переміщувати файли та каталоги",
"share": "Ділітися файлами"
},
"permissions-name": "Дозволи",
"permissionsHelp": "Можна настроїти користувача як адміністратора або вибрати індивідуальні дозволи. При виборі \"Адміністратор\" всі інші параметри будуть автоматично вибрані. Керування користувачами - привілей адміністратора.\n",
"profileSettings": "Налаштування профілю",
"ruleExample1": "запобігти доступу до будь-якого прихованого файлу (наприклад: .git, .gitignore) у кожній папці.\n",
"ruleExample2": "блокує доступ до файлу з ім'ям Caddyfile у кореневій області.",
"rules": "Права",
"rulesHelp": "Тут ви можете визначити набір дозволяючих та забороняючих правил для цього конкретного користувача. Блоковані файли не відображатимуться у списках, і не будуть доступні для користувача. Є підтримка регулярних виразів та відносних шляхів.\n",
"scope": "Корінь",
"setDateFormat": "Встановити точний формат дати",
"settingsUpdated": "Налаштування застосовані!",
"shareDuration": "Тривалість спільного посилання",
"shareManagement": "Управління спільними посиланнями",
"shareDeleted": "Спільне посилання видалено!",
"singleClick": "Відкриття файлів та каталогів одним кліком",
"themes": {
"dark": "Темна",
"light": "Світла",
"title": "Тема"
},
"user": "Користувач",
"userCommands": "Команди",
"userCommandsHelp": "Список команд, доступних користувачу, розділений пробілами. Приклад:\n",
"userCreated": "Користувач створений!",
"userDefaults": "Налаштування користувача за замовчуванням",
"userDeleted": "Користувач видалений!",
"userManagement": "Керування користувачами",
"userUpdated": "Користувач змінений!",
"username": "Ім'я користувача",
"users": "Користувачі"
},
"sidebar": {
"help": "Допомога",
"hugoNew": "Hugo New",
"login": "Увійти",
"logout": "Вийти",
"myFiles": "Файли",
"newFile": "Новий файл",
"newFolder": "Новий каталог",
"preview": "Перегляд",
"settings": "Налаштування",
"signup": "Зареєструватися",
"siteSettings": "Налаштування сайту"
},
"success": {
"linkCopied": "Посилання скопійоване!"
},
"time": {
"days": "Дні",
"hours": "Години",
"minutes": "Хвилини",
"seconds": "Секунди",
"unit": "Одиниця часу"
}
"user": "Користувач",
"userCommands": "Команди",
"userCommandsHelp": "Список команд, доступних користувачу, розділений пробілами. Приклад:\n",
"userCreated": "Користувач створений!",
"userDefaults": "Налаштування користувача за замовчуванням",
"userDeleted": "Користувач видалений!",
"userManagement": "Керування користувачами",
"userUpdated": "Користувач змінений!",
"username": "Ім'я користувача",
"users": "Користувачі"
},
"sidebar": {
"help": "Допомога",
"hugoNew": "Hugo New",
"login": "Увійти",
"logout": "Вийти",
"myFiles": "Файли",
"newFile": "Новий файл",
"newFolder": "Новий каталог",
"preview": "Перегляд",
"settings": "Налаштування",
"signup": "Зареєструватися",
"siteSettings": "Налаштування сайту"
},
"success": {
"linkCopied": "Посилання скопійоване!"
},
"time": {
"days": "Дні",
"hours": "Години",
"minutes": "Хвилини",
"seconds": "Секунди",
"unit": "Одиниця часу"
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
+6 -3
View File
@@ -101,7 +101,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish(Sweden)",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文(简体)",
"zhTW": "中文(繁體)"
@@ -132,7 +132,6 @@
"downloadMessage": "请选择要下载的压缩格式。",
"error": "出了一点问题...",
"fileInfo": "文件信息",
"filesSelected": "已选择 {count} 个文件。",
"lastModified": "最后修改",
"move": "移动",
"moveMessage": "请选择欲移动至的目录:",
@@ -265,5 +264,9 @@
"minutes": "分钟",
"seconds": "秒",
"unit": "时间单位"
}
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
+8 -4
View File
@@ -95,7 +95,7 @@
"ru": "Русский",
"sk": "Slovenčina",
"svSE": "Swedish(Sweden)",
"tr" : "Türkçe",
"tr": "Türkçe",
"ua": "Українська",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)"
@@ -125,7 +125,6 @@
"downloadMessage": "請選擇要下載的壓縮格式。",
"error": "發出了一點錯誤...",
"fileInfo": "檔案資訊",
"filesSelected": "已選擇 {count} 個檔案。",
"lastModified": "最後修改",
"move": "移動",
"moveMessage": "請選擇欲移動至的目錄:",
@@ -145,7 +144,8 @@
"show": "顯示",
"size": "大小",
"upload": "上傳",
"uploadMessage": "選擇上傳項。"
"uploadMessage": "選擇上傳項。",
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?"
},
"search": {
"images": "影像",
@@ -254,5 +254,9 @@
"minutes": "分鐘",
"seconds": "秒",
"unit": "時間單位"
}
},
"upload": {},
"share": {},
"api": {},
"index": {}
}
-6
View File
@@ -186,10 +186,6 @@ export const mutations = {
}
emitStateChanged();
},
showError: () => {
state.prompts.push("error");
emitStateChanged();
},
setLoading: (loadType, status) => {
if (status === false) {
delete state.loading[loadType];
@@ -235,8 +231,6 @@ export const mutations = {
state.multiple = value;
if (value == true) {
notify.showMultipleSelection()
} else {
notify.closePopUp()
}
emitStateChanged();
},
+7
View File
@@ -96,6 +96,13 @@ export async function signupLogin(username, password) {
}
export function logout() {
if (state.user.loginMethod == "oidc") {
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
mutations.setCurrentUser(null);
let apiPath = getApiPath("api/auth/logout")
window.location.href = apiPath;
return
}
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
mutations.setCurrentUser(null);
router.push({ path: "/login" });
+8
View File
@@ -16,6 +16,7 @@
:class="{
'dark-mode': isDarkMode,
moveWithSidebar: moveWithSidebar,
'remove-padding-top': isOnlyOffice,
'main-padding': showPadding,
scrollable: scrollable,
}"
@@ -70,6 +71,9 @@ export default {
this.updateSourceInfo();
},
computed: {
isOnlyOffice() {
return getters.currentView() === "onlyOfficeEditor";
},
scrollable() {
return getters.isScrollable();
},
@@ -158,6 +162,10 @@ export default {
/* Enable momentum scrolling in iOS */
}
.remove-padding-top {
padding-top: 0 !important;
}
#main {
overflow: unset;
-ms-overflow-style: none;
+2 -2
View File
@@ -42,8 +42,8 @@
</p>
</div>
<div v-if="oidcAvailable" class="password-entry">
<div v-if="passwordAvailable" class="or">OR</div>
<a href="/api/auth/oidc/login" class="button button--block direct-login">
<div v-if="passwordAvailable" class="or">{{ $t('login.or') }}</div>
<a href="/api/auth/oidc/login" class="button button--block direct-login"> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
OpenID Connect
</a>
</div>
+28
View File
@@ -95,17 +95,45 @@ export default {
</script>
<style>
.form-group {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.form-button {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
height: auto !important
}
.invalid-form {
border-color: red !important;
}
.flat-right {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.flat-left {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.dashboard {
display: flex;
flex-direction: column;
height: 100%;
align-items: center;
}
.settings-views {
max-width: 1000px;
width: 100%;
}
.settings-views .card {
border-style: solid;
opacity: 1;
+2 -2
View File
@@ -76,10 +76,10 @@
<strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }}
</div>
<div class="share__box__element" :title="modTime">
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }} <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</div>
<div class="share__box__element">
<strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }}
<strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }} <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
</div>
<div class="share__box__element share__box__center">
<a target="_blank" :href="getLink(false)" class="button button--flat">
+6 -9
View File
@@ -1,13 +1,14 @@
<template>
<header :class="['flexbar', { 'dark-mode-header': isDarkMode }]">
<header v-if="!isOnlyOffice" :class="['flexbar', { 'dark-mode-header': isDarkMode }]">
<action
v-if="!isShare"
icon="close_back"
:label="$t('buttons.close')"
:disabled="isSearchActive"
@action="multiAction"
/>
<search v-if="showSearch" />
<title v-else-if="isSettings" class="topTitle">Settings</title>
<title v-else-if="isSettings" class="topTitle">{{ $t('sidebar.settings') }}</title>
<title v-else class="topTitle">{{ req.name }}</title>
<action
v-if="isListingView"
@@ -45,6 +46,9 @@ export default {
};
},
computed: {
isOnlyOffice() {
return getters.currentView() === "onlyOfficeEditor";
},
isListingView() {
return getters.currentView() == "listingView";
},
@@ -123,13 +127,6 @@ export default {
router.push({path: "/files" });
return;
}
if (listingView === "onlyOfficeEditor") {
const current = window.location.pathname;
const newpath = removeLastDir(current);
window.location = newpath + "#" + state.req.name;
return;
}
mutations.replaceRequest({});
router.go(-1);
}
+9 -7
View File
@@ -8,7 +8,8 @@
import { eventBus } from "@/store/eventBus";
import { state, getters } from "@/store";
import { filesApi } from "@/api";
import ace from "ace-builds/src-min-noconflict/ace.js";
import ace, { version as ace_version } from "ace-builds";
import modelist from "ace-builds/src-noconflict/ext-modelist";
import "ace-builds/src-min-noconflict/theme-chrome";
import "ace-builds/src-min-noconflict/theme-twilight";
@@ -30,10 +31,15 @@ export default {
this.editor.destroy();
},
mounted: function () {
ace.config.set(
"basePath",
`https://cdn.jsdelivr.net/npm/ace-builds@${ace_version}/src-min-noconflict/`
);
// this is empty content string "empty-file-x6OlSil" which is used to represent empty text file
const fileContent =
state.req.content == "empty-file-x6OlSil" ? "" : state.req.content || "";
this.editor = ace.edit("editor", {
this.editor = ace.edit("editor", {
mode: modelist.getModeForPath(state.req.name).mode,
value: fileContent,
showPrintMargin: false,
theme: "ace/theme/chrome",
@@ -41,7 +47,6 @@ export default {
wrap: false,
});
// Set the basePath for Ace Editor
ace.config.set("basePath", "/node_modules/ace-builds/src-min-noconflict");
if (this.isDarkMode) {
this.editor.setTheme("ace/theme/twilight");
}
@@ -51,9 +56,6 @@ export default {
handleEditorValueRequest() {
filesApi.put(state.req.path, state.req.source, this.editor.getValue());
},
back() {
this.$router.push(-1);
},
keyEvent(event) {
const { key, ctrlKey, metaKey } = event;
if (getters.currentPromptName() != null) {
@@ -65,7 +67,7 @@ export default {
switch (key.toLowerCase()) {
case "s":
event.preventDefault();
this.save();
this.handleEditorValueRequest();
break;
default:
+40 -1
View File
@@ -8,7 +8,11 @@
:onLoadComponentError="onLoadComponentError"
/>
<div v-else>
<p>Loading editor...</p>
<p>{{ $t("files.loading") }}</p>
</div>
<div @click="close" class="floating-close button" :class="{ 'float-in': floatIn }"
>
<i class="material-icons">close</i>
</div>
</template>
@@ -19,6 +23,7 @@ import { state } from "@/store";
import { fetchJSON } from "@/api/utils";
import { filesApi } from "@/api";
import { baseURL } from "@/utils/constants";
import { removeLastDir } from "@/utils/url";
export default {
name: "onlyOfficeEditor",
@@ -29,6 +34,7 @@ export default {
return {
ready: false, // Flag to indicate whether the setup is complete
clientConfig: {},
floatIn: false, // Flag for the float-in animation
};
},
computed: {
@@ -53,10 +59,43 @@ export default {
this.clientConfig = configData;
console.log("Client config:", this.clientConfig);
this.ready = true;
// Trigger float-in animation
setTimeout(() => {
this.floatIn = true;
}, 100); // slight delay to allow rendering
} catch (error) {
console.error("Error during setup:", error);
// Handle setup failure if needed
}
},
methods: {
close() {
const current = window.location.pathname;
const newpath = removeLastDir(current);
window.location = newpath + "#" + state.req.name;
},
},
};
</script>
<style scoped>
.floating-close {
position: fixed;
left: 50%;
transform: translate(-50%, -5em); /* Start offscreen */
transition: transform 0.4s ease;
background: var(--surfaceSecondary);
font-size: .5em;
top: 0;
}
.float-in {
transform: translate(-50%, 2.75em); /* Animate to final position */
}
.floating-close i {
font-size: 2em;
padding-right: 1em;
padding-left: 1em;
}
</style>
+11 -10
View File
@@ -2,28 +2,28 @@
<errors v-if="error" :errorCode="error.status" />
<div class="card" :class="{ active: active }">
<div class="card-title">
<h2>{{ $t("settings.api") }}</h2>
<h2>{{ $t("api.title") }}</h2>
<div>
<button @click.prevent="createPrompt" class="button">
{{ $t("buttons.new") }}
</button>
</div>
</div>
<div class="card-content full" v-if="Object.keys(links).length > 0">
<p>
API keys are based on the user that creates them. See
<a class="link" href="swagger/index.html">swagger page</a> for how to use them.
Keys are associated with your user and the user must have access to the permission
level you want to use the key with.
{{ $t("api.description") }}
<a class="link" href="swagger/index.html">{{ $t("api.swaggerLinkText") }}</a>
</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Created</th>
<th>Expires</th>
<th>{{ $t("api.name") }}</th>
<th>{{ $t("api.created") }}</th>
<th>{{ $t("api.expires") }}</th>
<th>{{ $t("settings.permissions-name") }}</th>
<th>Actions</th>
<th>{{ $t("api.actions") }}</th>
</tr>
</thead>
<tbody>
@@ -35,7 +35,7 @@
<span
v-for="(value, permission) in link.Permissions"
:key="permission"
:title="`${permission}: ${value ? 'Enabled' : 'Disabled'}`"
:title="`${permission}: ${value ? $t('api.enabled') : $t('api.disabled')}`"
class="clickable"
@click.prevent="infoPrompt(name, link)"
>
@@ -61,6 +61,7 @@
</tbody>
</table>
</div>
<h2 class="message" v-else>
<i class="material-icons">sentiment_dissatisfied</i>
<span>{{ $t("files.lonely") }}</span>
+1 -4
View File
@@ -5,10 +5,7 @@
<h2>{{ $t("settings.globalSettings") }}</h2>
</div>
<div class="card-content">
Currently there are no global config options, You can edit the configuration yaml to
adjust system settings.
</div>
<div class="card-content"> {{ $t('settings.emptyGlobal') }} </div>
<div class="card-action">
<input class="button button--flat" type="submit" :value="$t('buttons.update')" />
+15 -25
View File
@@ -6,7 +6,7 @@
<div class="card-content">
<form>
<div class="card-content">
<h3>Listing options</h3>
<h3>{{ $t('settings.listingOptions') }}</h3>
<div class="settings-items">
<ToggleSwitch
class="item"
@@ -24,7 +24,7 @@
:name="`Always show download icon for quick access`"
/>
</div>
<h3>File preview options</h3>
<h3> {{ $t('settings.filePreviewOptions') }}</h3>
<div class="settings-items">
<ToggleSwitch
class="item"
@@ -37,6 +37,12 @@
v-model="localuser.preview.video"
name="Preview videos"
/>
<ToggleSwitch
v-if="mediaEnabled"
class="item"
v-model="localuser.preview.motionVideoPreview"
name="Motion previews for videos"
/>
<ToggleSwitch
class="item"
v-model="localuser.preview.highQuality"
@@ -55,27 +61,24 @@
/>
</div>
<div v-if="hasOnlyOfficeEnabled">
<h3>Disable onlyoffice viewer for certain file extensions</h3>
<h3> {{ $t('settings.disableOfficePreview')}} </h3>
<p>
A space-separated list of file extensions to disable the OnlyOffice viewer
for. (e.g., <code>.txt .html</code>)
{{ $t('settings.disableOfficePreviewDescription') }}
</p>
<div class="onlyoffice-group">
<div class="form-group">
<input
class="input input--block onlyoffice-form"
class="input input--block form-form flat-right"
:class="{ 'invalid-form': !formValidation() }"
type="text"
placeholder="enter file extensions"
id="onlyofficeExt"
v-model="formOnlyOfficeExt"
/>
<button type="button" class="button onlyoffice-button" @click="submitOnlyOfficeChange">
save
</button>
<button type="button" class="button form-button" @click="submitOnlyOfficeChange"> {{ $t('buttons.save') }} </button>
</div>
</div>
<h3>Theme Color</h3>
<h3> {{ $t('settings.themeColor') }} </h3>
<ButtonGroup
:buttons="colorChoices"
@button-clicked="setColor"
@@ -210,18 +213,5 @@ export default {
text-align: center;
}
.onlyoffice-group {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.onlyoffice-button {
margin-left: 1em;
}
.onlyoffice-form {
height: 3em;
}
.invalid-form {
border-color: red !important;
}
</style>
+8 -9
View File
@@ -3,21 +3,20 @@
<form @submit="save" class="card active" v-if="loaded">
<div class="card-title">
<h2 v-if="isNew">{{ $t("settings.newUser") }}</h2>
<h2 v-else-if="actor.id == user.id">modify current user ({{ user.username }})</h2>
<h2 v-else>modify user: {{ user.username }}</h2>
<h2 v-else-if="actor.id == user.id"> {{ $t('settings.modifyCurrentUser') }} ({{ user.username }})</h2> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<h2 v-else> {{ $t('settings.modifyOtherUser') }} {{ user.username }}</h2>
</div>
<div class="card-content minimal-card">
<user-form
v-model:user="user"
v-model:updatePassword="updatePassword"
:createUserDir="createUserDir"
:isNew="isNew"
@update:createUserDir="(updatedDir) => (createUserDir = updatedDir)"
/>
</div>
<div class="card-action">
<div v-if="actor.permissions.admin" class="card-action">
<button
v-if="!isNew && actor.permissions.admin"
@click.prevent="deletePrompt"
@@ -53,12 +52,12 @@ export default {
user: {
scopes: [],
username: "",
password: "",
permissions: { admin: false },
},
showDelete: false,
createUserDir: false,
loaded: false,
updatePassword: false,
};
},
created() {
@@ -83,6 +82,7 @@ export default {
if (this.isNew) {
let defaults = await settingsApi.get("userDefaults");
this.user = defaults;
this.user.password = "";
} else {
const id = Array.isArray(state.route.params.id)
? state.route.params.id.join("")
@@ -91,6 +91,7 @@ export default {
return;
}
this.user = { ...(await usersApi.get(id)) };
this.user.password = "";
}
} catch (e) {
notify.showError(e);
@@ -108,16 +109,14 @@ export default {
try {
let fields = ["all"]
if (!state.user.permissions.admin) {
fields = ["password"];
notify.showError(this.$t("settings.userNotAdmin"));
return;
}
if (this.isNew) {
await usersApi.create(this.user); // Use the computed property
this.$router.push({ path: "/settings", hash: "#users-main" });
} else {
await usersApi.update(this.user, fields);
if (this.updatePassword) {
await usersApi.update(this.user, ["password"]);
}
notify.showSuccess(this.$t("settings.userUpdated"));
}
} catch (e) {
+2 -2
View File
@@ -15,9 +15,9 @@
<thead>
<tr>
<th>{{ $t("settings.username") }}</th>
<th>Login Method</th>
<th> {{ $t("settings.loginMethod") }} </th>
<th>{{ $t("settings.admin") }}</th>
<th>Scopes</th>
<th>Scopes</th> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
<th></th>
</tr>
</thead>
@@ -43,10 +43,10 @@ test("open nested file in /files dir from search", async({ page, checkForErrors,
await page.goto("/files/");
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
await page.locator('#search').click()
await page.locator('#main-input').fill('graham');
await page.locator('#main-input').fill('binary');
await expect(page.locator('#result-list')).toHaveCount(1);
await page.locator('li[aria-label="graham.xlsx"]').click();
await expect(page).toHaveTitle("Graham's Filebrowser - Files - graham.xlsx");
await page.locator('li[aria-label="binary.dat"]').click();
await expect(page).toHaveTitle("Graham's Filebrowser - Files - binary.dat");
await expect(page.locator('#previewer')).toContainText('Preview is not available for this file.');
checkForErrors();
})
+3 -3
View File
@@ -28,13 +28,13 @@ run: build-frontend
else \
sed -i '/func init/,+3d' ./swagger/docs/docs.go; \
fi && \
go run ./tools/yaml.go -input ./common/settings/settings.go -output=http/embed/config.generated.yaml && \
cp http/embed/config.generated.yaml http/dist/config.generated.yaml && \
FILEBROWSER_NO_EMBEDED=true go run \
--ldflags="-w -s -X 'github.com/gtsteffaniak/filebrowser/backend/version.CommitSHA=testingCommit' -X 'github.com/gtsteffaniak/filebrowser/backend/version.Version=testing'" . -c test_config.yaml
build-frontend:
cd backend/http && rm -rf dist embed/* && ln -s ../../frontend/dist
cd backend && rm -rf http/dist http/embed/* && \
go run ./tools/yaml.go -input ./common/settings/settings.go -output=../frontend/public/config.generated.yaml
cd backend/http/ && ln -s ../../frontend/dist
if [ "$(OS)" = "Windows_NT" ]; then \
cd frontend && npm run build-windows; \
else \