Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5e1b6dee4 | ||
|
|
1582b8b2cd | ||
|
|
21ad653b7e | ||
|
|
5b7ea9f95a | ||
|
|
607f5708a2 | ||
|
|
d61110e4d7 | ||
|
|
7e758357d1 | ||
|
|
3faec03ed7 | ||
|
|
a7a68f74ae | ||
|
|
6425cc58b4 | ||
|
|
88f1442932 | ||
|
|
545c972214 | ||
|
|
124abc7643 | ||
|
|
b8454bb2e4 | ||
|
|
035084d8e8 | ||
|
|
9072cbce34 | ||
|
|
e6ffb65374 | ||
|
|
5c5942d995 | ||
|
|
1a5c83bcfe | ||
|
|
5a8e7171b1 | ||
|
|
0f27c91eca | ||
|
|
7c716862c1 | ||
|
|
01c814cf98 | ||
|
|
35ca24adb8 | ||
|
|
14b0dfec34 | ||
|
|
528ce92fad | ||
|
|
fbe169b84f | ||
|
|
b4eddf45e4 | ||
|
|
0614dcd89b | ||
|
|
fcb248a5fe | ||
|
|
bf73e4dea3 | ||
|
|
b28952cb25 | ||
|
|
1e96fd9035 | ||
|
|
e423395ef0 | ||
|
|
65bbf44e3c | ||
|
|
200b9a6c26 | ||
|
|
3645b578cd | ||
|
|
cc6db83988 | ||
|
|
046d6193c5 | ||
|
|
244fda2f2c | ||
|
|
e36a9b40a0 | ||
|
|
a756e02142 | ||
|
|
b6394745a3 | ||
|
|
e99e0b3028 | ||
|
|
47b3e218ad | ||
|
|
0c34b79a99 | ||
|
|
04166e81e5 | ||
|
|
fae410ce6e | ||
|
|
9da01be7fc | ||
|
|
e9e7c68557 | ||
|
|
8ef8f2c098 | ||
|
|
3b3df83d64 | ||
|
|
38d0366acf | ||
|
|
4403cd3572 | ||
|
|
8d7522049c | ||
|
|
7b43cfb1dc | ||
|
|
d644744417 | ||
|
|
d1a73a8b18 | ||
|
|
2b5d6cbb99 | ||
|
|
364f391017 | ||
|
|
c13861e13c | ||
|
|
e6b750add5 | ||
|
|
70d59ec03e | ||
|
|
bf37f88c32 | ||
|
|
7354eb6cf9 | ||
|
|
10684e5390 | ||
|
|
58fe817349 | ||
|
|
d8472e767b | ||
|
|
8700cb30ff | ||
|
|
93c4b2e03c | ||
|
|
2d1a82b73f | ||
|
|
5a07291306 | ||
|
|
09f679fae4 | ||
|
|
9e273cd947 | ||
|
|
77d266bc00 | ||
|
|
8861933cf8 | ||
|
|
a5ea2a266b | ||
|
|
f5e531c8ae | ||
|
|
6072540c3e | ||
|
|
464b644adf | ||
|
|
089255997a | ||
|
|
5331969163 | ||
|
|
f32f27383d | ||
|
|
0acd69c537 | ||
|
|
ae4fb0ea25 | ||
|
|
8230eb7ab5 | ||
|
|
8b8fb3343f | ||
|
|
1d494ff315 | ||
|
|
da03728cd7 | ||
|
|
e735491c57 | ||
|
|
4d830f707f | ||
|
|
f84a6db680 |
@@ -1,3 +1,7 @@
|
||||
*
|
||||
!docker/*
|
||||
!filebrowser
|
||||
.venv
|
||||
dist
|
||||
.idea
|
||||
frontend/node_modules
|
||||
frontend/dist
|
||||
filebrowser.db
|
||||
docs/index.md
|
||||
12
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Bug Report
|
||||
description: Report a bug in FileBrowser.
|
||||
labels: [bug, triage]
|
||||
labels: [bug, 'waiting: triage']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
@@ -20,22 +20,32 @@ body:
|
||||
render: Text
|
||||
description: |
|
||||
Enter the version of FileBrowser you are using.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
A clear and concise description of what the issue is about. What are you trying to do?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What actually happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
description: |
|
||||
Tell us how to reproduce this issue. How can someone who is starting from scratch reproduce this behavior as minimally as possible?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Files
|
||||
|
||||
20
.github/workflows/site-pr.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Build Site
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'www'
|
||||
- '*.md'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build site
|
||||
run: make site
|
||||
32
.github/workflows/site-publish.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Build and Deploy Site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build site
|
||||
run: make site
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy www/public --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
1
.gitignore
vendored
@@ -6,6 +6,7 @@ rice-box.go
|
||||
/filebrowser
|
||||
/filebrowser.exe
|
||||
/dist
|
||||
.venv
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
||||
187
.golangci.yml
@@ -1,121 +1,132 @@
|
||||
linters-settings:
|
||||
dupl:
|
||||
threshold: 100
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: false
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 50
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
disabled-checks:
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
- wrapperFunc
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
goimports:
|
||||
local-prefixes: github.com/filebrowser/filebrowser
|
||||
gomnd:
|
||||
# don't include the "operation" and "assign"
|
||||
checks:
|
||||
- argument
|
||||
- case
|
||||
- condition
|
||||
- return
|
||||
ignored-numbers:
|
||||
- '0'
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
ignored-functions:
|
||||
- strings.SplitN
|
||||
govet:
|
||||
enable:
|
||||
- nilness
|
||||
- shadow
|
||||
lll:
|
||||
line-length: 140
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # require an explanation for nolint directives
|
||||
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||
disable-all: true
|
||||
# inverted configuration with `default: all` and `disable` is not scalable during updates of golangci-lint
|
||||
default: none
|
||||
enable:
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- exhaustive
|
||||
- funlen
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godox
|
||||
- goimports
|
||||
- gomnd
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- mnd
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- testifylint
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
settings:
|
||||
dupl:
|
||||
threshold: 100
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: false
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 50
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
- wrapperFunc
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
govet:
|
||||
enable:
|
||||
- nilness
|
||||
- shadow
|
||||
lll:
|
||||
line-length: 140
|
||||
misspell:
|
||||
locale: US
|
||||
mnd:
|
||||
# don't include the "operation" and "assign"
|
||||
checks:
|
||||
- argument
|
||||
- case
|
||||
- condition
|
||||
- return
|
||||
ignored-numbers:
|
||||
- "0"
|
||||
- "1"
|
||||
- "2"
|
||||
- "3"
|
||||
- "0666"
|
||||
- "0700"
|
||||
- "0700"
|
||||
ignored-functions:
|
||||
- strings.SplitN
|
||||
- make
|
||||
nolintlint:
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # require an explanation for nolint directives
|
||||
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
||||
staticcheck:
|
||||
checks:
|
||||
- "all"
|
||||
- "-QF*"
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- gochecknoinits
|
||||
path: cmd/.*.go
|
||||
- linters:
|
||||
- dupl
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- gocyclo
|
||||
- lll
|
||||
- scopelint
|
||||
path: .*_test.go
|
||||
- linters:
|
||||
- misspell
|
||||
text: "[aA]uther"
|
||||
- linters:
|
||||
- mnd
|
||||
text: strconv.Parse
|
||||
paths:
|
||||
- frontend/
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- frontend/
|
||||
exclude-rules:
|
||||
- path: cmd/.*.go
|
||||
linters:
|
||||
- gochecknoinits
|
||||
- path: .*_test.go
|
||||
linters:
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gocyclo
|
||||
- funlen
|
||||
- dupl
|
||||
- scopelint
|
||||
- text: "Auther"
|
||||
linters:
|
||||
- misspell
|
||||
- text: "strconv.Parse"
|
||||
linters:
|
||||
- gomnd
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
formatters:
|
||||
enable:
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/filebrowser/filebrowser
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- frontend/
|
||||
|
||||
@@ -131,7 +131,7 @@ dockers:
|
||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
|
||||
extra_files:
|
||||
- docker
|
||||
- dockerfile: Dockerfile.s6.aarch64
|
||||
- dockerfile: Dockerfile.s6
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
|
||||
187
CHANGELOG.md
@@ -2,6 +2,193 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [2.41.0](https://github.com/filebrowser/filebrowser/compare/v2.40.2...v2.41.0) (2025-07-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow file and directory creation modes to be configured ([21ad653](https://github.com/filebrowser/filebrowser/commit/21ad653b7eb246c0e95ccdc131f8d59267de7818)), closes [#5316](https://github.com/filebrowser/filebrowser/issues/5316) [#5200](https://github.com/filebrowser/filebrowser/issues/5200)
|
||||
* better error handling for sys kill signals ([1582b8b](https://github.com/filebrowser/filebrowser/commit/1582b8b2cd1c62fa93e60ca9b4e740e940b02e84))
|
||||
|
||||
### [2.40.2](https://github.com/filebrowser/filebrowser/compare/v2.40.1...v2.40.2) (2025-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Location header on TUS endpoint ([#5302](https://github.com/filebrowser/filebrowser/issues/5302)) ([607f570](https://github.com/filebrowser/filebrowser/commit/607f5708a2484428ab837781a5ef26b8cc3194f4))
|
||||
|
||||
|
||||
### Build
|
||||
|
||||
* **deps:** bump vue-i18n from 11.1.9 to 11.1.10 in /frontend ([d61110e](https://github.com/filebrowser/filebrowser/commit/d61110e4d7155a5849557adf3b75dc0191f17e80))
|
||||
|
||||
### [2.40.1](https://github.com/filebrowser/filebrowser/compare/v2.40.0...v2.40.1) (2025-07-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* print correct user on setup ([88f1442](https://github.com/filebrowser/filebrowser/commit/88f144293267260fd4d823e3259783309b1a57b3))
|
||||
|
||||
## [2.40.0](https://github.com/filebrowser/filebrowser/compare/v2.39.0...v2.40.0) (2025-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add font size botton to text editor ([#5290](https://github.com/filebrowser/filebrowser/issues/5290)) ([035084d](https://github.com/filebrowser/filebrowser/commit/035084d8e83243065fad69bfac1b69559fbad5fb))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* invalid path when uploading files ([9072cbc](https://github.com/filebrowser/filebrowser/commit/9072cbce340da55477906f5419a4cfb6d6937dc0))
|
||||
* Only left click should drag the image in extended image view ([b8454bb](https://github.com/filebrowser/filebrowser/commit/b8454bb2e41ca2848b926b66354468ba4b1c7ba5))
|
||||
|
||||
## [2.39.0](https://github.com/filebrowser/filebrowser/compare/v2.38.0...v2.39.0) (2025-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve Docker entrypoint and config handling ([01c814c](https://github.com/filebrowser/filebrowser/commit/01c814cf98f81f2bcd622aea75e5b1efe3484940))
|
||||
* rewrite the archiver and added support for zstd and brotli ([#5283](https://github.com/filebrowser/filebrowser/issues/5283)) ([7c71686](https://github.com/filebrowser/filebrowser/commit/7c716862c1bd3cdedd3c02d3a37207293db197ca))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* drop modify permission for uploading new file ([#5270](https://github.com/filebrowser/filebrowser/issues/5270)) ([0f27c91](https://github.com/filebrowser/filebrowser/commit/0f27c91eca581482ce4f82f6429f5dac12f8b64e))
|
||||
* Settings button in the sidebar ([5a8e717](https://github.com/filebrowser/filebrowser/commit/5a8e7171b1b41eff771fe27133c91d2c250896a8))
|
||||
|
||||
|
||||
### Build
|
||||
|
||||
* improve docker image and binary sizes ([35ca24a](https://github.com/filebrowser/filebrowser/commit/35ca24adb886721fc9d5e1a68cfc577e2c5f0230))
|
||||
* lightweight busybox-based container build ([#5285](https://github.com/filebrowser/filebrowser/issues/5285)) ([5c5942d](https://github.com/filebrowser/filebrowser/commit/5c5942d99514b433e09d90624bbe58992eab6be2))
|
||||
* remove upx ([1a5c83b](https://github.com/filebrowser/filebrowser/commit/1a5c83bcfe847f1e41a44cef23fd795b19b6b434))
|
||||
|
||||
## [2.38.0](https://github.com/filebrowser/filebrowser/compare/v2.37.0...v2.38.0) (2025-07-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Show the current users name in the sidebar ([#2821](https://github.com/filebrowser/filebrowser/issues/2821)) ([528ce92](https://github.com/filebrowser/filebrowser/commit/528ce92fad6dcc8e8b7910036bf9175146e27bf7))
|
||||
* Updates for project File Browser ([b4eddf4](https://github.com/filebrowser/filebrowser/commit/b4eddf45e4d7e6f6ccf242e67fe20f89f5e2f9a9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent page change if there are outstanding edits ([#5260](https://github.com/filebrowser/filebrowser/issues/5260)) ([fbe169b](https://github.com/filebrowser/filebrowser/commit/fbe169b84f28cba22ea87f01b52f2420f1ea6814))
|
||||
|
||||
## [2.37.0](https://github.com/filebrowser/filebrowser/compare/v2.36.3...v2.37.0) (2025-07-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Translate frontend/src/i18n/en.json in zh_CN ([65bbf44](https://github.com/filebrowser/filebrowser/commit/65bbf44e3c0bff83e64193d46e9d6ad302952276))
|
||||
* Translate frontend/src/i18n/en.json in zh_TW ([b28952c](https://github.com/filebrowser/filebrowser/commit/b28952cb2582bd4eb44e91d0676e2803c458cf31))
|
||||
* Translate frontend/src/i18n/en.json in zh_TW ([1e96fd9](https://github.com/filebrowser/filebrowser/commit/1e96fd9035d5185dc80970a2826ccb573b5f000e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* long file name overlap ([fcb248a](https://github.com/filebrowser/filebrowser/commit/fcb248a5feb7b7404ca5923aae17f6d3f8d3cc96))
|
||||
* preview PDF is correctly displayed ([bf73e4d](https://github.com/filebrowser/filebrowser/commit/bf73e4dea3b27c01c8f6e60fb2048e1a2122a70e))
|
||||
* Upload progress size calculation ([e423395](https://github.com/filebrowser/filebrowser/commit/e423395ef0bcd106ddc7d460c055b95b5208415e))
|
||||
|
||||
### [2.36.3](https://github.com/filebrowser/filebrowser/compare/v2.36.2...v2.36.3) (2025-07-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* log error if branding file exists but cannot be loaded ([3645b57](https://github.com/filebrowser/filebrowser/commit/3645b578cddb9fc8f25a00e0153fb600ad1b9266))
|
||||
|
||||
### [2.36.2](https://github.com/filebrowser/filebrowser/compare/v2.36.1...v2.36.2) (2025-07-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* lookup directory name if blank when downloading shared directory ([046d619](https://github.com/filebrowser/filebrowser/commit/046d6193c57b4df0e3dc583b6518b43d29d302c9))
|
||||
|
||||
### [2.36.1](https://github.com/filebrowser/filebrowser/compare/v2.36.0...v2.36.1) (2025-07-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove associated shares when deleting file/folder ([e99e0b3](https://github.com/filebrowser/filebrowser/commit/e99e0b3028e1c8a50e1744bb07ecc8e809bdb8e6))
|
||||
|
||||
## [2.36.0](https://github.com/filebrowser/filebrowser/compare/v2.35.0...v2.36.0) (2025-07-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update icons, remove deprecated Microsoft Tiles ([04166e8](https://github.com/filebrowser/filebrowser/commit/04166e81e52d38b1f66ba3313ccb1291c239eea2))
|
||||
|
||||
## [2.35.0](https://github.com/filebrowser/filebrowser/compare/v2.34.2...v2.35.0) (2025-06-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Long press selects item in single click mode ([8d75220](https://github.com/filebrowser/filebrowser/commit/8d7522049ced83f28f0933b55772c32e3ad04627))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* shell value must be joined by blank space ([4403cd3](https://github.com/filebrowser/filebrowser/commit/4403cd35720dbda5a8bb1013b92582accf3317bc))
|
||||
* update documentation links ([38d0366](https://github.com/filebrowser/filebrowser/commit/38d0366acf88352b5a9a97c45837b0f865efae0b))
|
||||
|
||||
### [2.34.2](https://github.com/filebrowser/filebrowser/compare/v2.34.1...v2.34.2) (2025-06-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mitigate unprotected shares ([2b5d6cb](https://github.com/filebrowser/filebrowser/commit/2b5d6cbb996a61a769acc56af0acc12eec2d8d8f))
|
||||
|
||||
### [2.34.1](https://github.com/filebrowser/filebrowser/compare/v2.34.0...v2.34.1) (2025-06-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* exclude to-be-moved folder from move dialog ([#5235](https://github.com/filebrowser/filebrowser/issues/5235)) ([7354eb6](https://github.com/filebrowser/filebrowser/commit/7354eb6cf966244141277c2808988855c004f908))
|
||||
* passthrough the minimum password length ([#5236](https://github.com/filebrowser/filebrowser/issues/5236)) ([bf37f88](https://github.com/filebrowser/filebrowser/commit/bf37f88c32222ad9c186482bb97338a9c9b4a93c))
|
||||
|
||||
## [2.34.0](https://github.com/filebrowser/filebrowser/compare/v2.33.10...v2.34.0) (2025-06-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Translate frontend/src/i18n/en.json in fa ([0acd69c](https://github.com/filebrowser/filebrowser/commit/0acd69c537ce2909ff62c4bb6980982524ece221))
|
||||
* Translate frontend/src/i18n/en.json in fa ([#5233](https://github.com/filebrowser/filebrowser/issues/5233)) ([09f679f](https://github.com/filebrowser/filebrowser/commit/09f679fae43398f5b87d21acc9d974d4d053392f))
|
||||
* update translations for project File Browser ([#5226](https://github.com/filebrowser/filebrowser/issues/5226)) ([a5ea2a2](https://github.com/filebrowser/filebrowser/commit/a5ea2a266bef619d1c4322266d1aa7d397d2c856))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* abort ongoing requests when changing pages ([#3927](https://github.com/filebrowser/filebrowser/issues/3927)) ([93c4b2e](https://github.com/filebrowser/filebrowser/commit/93c4b2e03c5176da01a7e00a03c03ffcce279bc8))
|
||||
* add configurable minimum password length ([#5225](https://github.com/filebrowser/filebrowser/issues/5225)) ([464b644](https://github.com/filebrowser/filebrowser/commit/464b644adf22a2178414a6f1e4fa286276de81d2))
|
||||
* do not expose the name of the root directory ([#5224](https://github.com/filebrowser/filebrowser/issues/5224)) ([0892559](https://github.com/filebrowser/filebrowser/commit/089255997a653c284cd4249990b58bed00086c61))
|
||||
* Graceful shutdown ([8230eb7](https://github.com/filebrowser/filebrowser/commit/8230eb7ab51ccbd00b03f5b9d6964fa4aae331d4))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "docs: change cloudflare environment (#5231)" (#5232) ([9e273cd](https://github.com/filebrowser/filebrowser/commit/9e273cd9475d57b9500034e8b341ff8b620bcab8)), closes [#5231](https://github.com/filebrowser/filebrowser/issues/5231) [#5232](https://github.com/filebrowser/filebrowser/issues/5232)
|
||||
|
||||
|
||||
### Build
|
||||
|
||||
* add an arm64 target for the site image ([#5229](https://github.com/filebrowser/filebrowser/issues/5229)) ([f5e531c](https://github.com/filebrowser/filebrowser/commit/f5e531c8ae0b9b18717e184856ace0ce19beef82))
|
||||
* bump golangci-lint to 2.1.6 ([1d494ff](https://github.com/filebrowser/filebrowser/commit/1d494ff3159ef939cfb4980ccde6f27df3e738b5))
|
||||
* **deps:** bump brace-expansion from 1.1.11 to 1.1.12 in /tools ([#5228](https://github.com/filebrowser/filebrowser/issues/5228)) ([5a07291](https://github.com/filebrowser/filebrowser/commit/5a072913062a6b2b0e5c74a02ca7710218ed3e5e))
|
||||
* **deps:** bump github.com/go-viper/mapstructure/v2 ([f32f273](https://github.com/filebrowser/filebrowser/commit/f32f27383d1fafa074f038cc873bd37b7f20ee27))
|
||||
* **deps:** bump github.com/go-viper/mapstructure/v2 in /tools ([5331969](https://github.com/filebrowser/filebrowser/commit/5331969163f5ae1fd2389f665059fc9e4a98db15))
|
||||
* publish docs to cloudflare pages ([#5230](https://github.com/filebrowser/filebrowser/issues/5230)) ([8861933](https://github.com/filebrowser/filebrowser/commit/8861933cf845b104e072f35e5f37d7c26097c9dc))
|
||||
|
||||
### [2.33.10](https://github.com/filebrowser/filebrowser/compare/v2.33.9...v2.33.10) (2025-06-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correctly check if command is allowed when using shell ([4d830f7](https://github.com/filebrowser/filebrowser/commit/4d830f707fc4314741fd431e70c2ce50cd5a3108))
|
||||
* correctly split shell ([f84a6db](https://github.com/filebrowser/filebrowser/commit/f84a6db680b6df1c7c8f06f1816f7e4c9e963668))
|
||||
* ignore linting error ([e735491](https://github.com/filebrowser/filebrowser/commit/e735491c57b12c3b19dd2e4b570723df78f4eb44))
|
||||
|
||||
### [2.33.9](https://github.com/filebrowser/filebrowser/compare/v2.33.8...v2.33.9) (2025-06-26)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
If you're interested in contributing to this project, this is the best place to start. Before contributing to this project, please take a bit of time to read our [Code of Conduct](./code-of-conduct.md). Also, note that this project is open-source and licensed under [Apache License 2.0](../LICENSE).
|
||||
If you're interested in contributing to this project, this is the best place to start. Before contributing to this project, please take a bit of time to read our [Code of Conduct](code-of-conduct.md). Also, note that this project is open-source and licensed under [Apache License 2.0](LICENSE).
|
||||
|
||||
## Project Structure
|
||||
|
||||
38
Dockerfile
@@ -1,23 +1,37 @@
|
||||
FROM alpine:3.22
|
||||
## Multistage build: First stage fetches dependencies
|
||||
FROM alpine:3.22 AS fetcher
|
||||
|
||||
# install and copy ca-certificates, mailcap, and tini-static; download JSON.sh
|
||||
RUN apk update && \
|
||||
apk --no-cache add ca-certificates mailcap curl jq tini
|
||||
apk --no-cache add ca-certificates mailcap tini-static && \
|
||||
wget -O /JSON.sh https://raw.githubusercontent.com/dominictarr/JSON.sh/0d5e5c77365f63809bf6e77ef44a1f34b0e05840/JSON.sh
|
||||
|
||||
# Make user and create necessary directories
|
||||
## Second stage: Use lightweight BusyBox image for final runtime environment
|
||||
FROM busybox:1.37.0-musl
|
||||
|
||||
# Define non-root user UID and GID
|
||||
ENV UID=1000
|
||||
ENV GID=1000
|
||||
|
||||
# Create user group and user
|
||||
RUN addgroup -g $GID user && \
|
||||
adduser -D -u $UID -G user user && \
|
||||
mkdir -p /config /database /srv && \
|
||||
chown -R user:user /config /database /srv
|
||||
adduser -D -u $UID -G user user
|
||||
|
||||
# Copy files and set permissions
|
||||
COPY filebrowser /bin/filebrowser
|
||||
COPY docker/common/ /
|
||||
COPY docker/alpine/ /
|
||||
# Copy binary, scripts, and configurations into image with proper ownership
|
||||
COPY --chown=user:user filebrowser /bin/filebrowser
|
||||
COPY --chown=user:user docker/common/ /
|
||||
COPY --chown=user:user docker/alpine/ /
|
||||
COPY --chown=user:user --from=fetcher /sbin/tini-static /bin/tini
|
||||
COPY --from=fetcher /JSON.sh /JSON.sh
|
||||
COPY --from=fetcher /etc/ca-certificates.conf /etc/ca-certificates.conf
|
||||
COPY --from=fetcher /etc/ca-certificates /etc/ca-certificates
|
||||
COPY --from=fetcher /etc/mime.types /etc/mime.types
|
||||
COPY --from=fetcher /etc/ssl /etc/ssl
|
||||
|
||||
RUN chown -R user:user /bin/filebrowser /defaults healthcheck.sh init.sh
|
||||
# Create data directories, set ownership, and ensure healthcheck script is executable
|
||||
RUN mkdir -p /config /database /srv && \
|
||||
chown -R user:user /config /database /srv \
|
||||
&& chmod +x /healthcheck.sh
|
||||
|
||||
# Define healthcheck script
|
||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
||||
@@ -29,4 +43,4 @@ VOLUME /srv /config /database
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT [ "tini", "--", "/init.sh", "filebrowser", "--config", "/config/settings.json" ]
|
||||
ENTRYPOINT [ "tini", "--", "/init.sh" ]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM ghcr.io/linuxserver/baseimage-alpine:3.22
|
||||
|
||||
RUN apk update && \
|
||||
apk --no-cache add ca-certificates mailcap curl jq
|
||||
apk --no-cache add ca-certificates mailcap jq
|
||||
|
||||
# Make user and create necessary directories
|
||||
RUN mkdir -p /config /database /srv && \
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.22
|
||||
|
||||
RUN apk update && \
|
||||
apk --no-cache add ca-certificates mailcap curl jq
|
||||
|
||||
# Make user and create necessary directories
|
||||
RUN mkdir -p /config /database /srv && \
|
||||
chown -R abc:abc /config /database /srv
|
||||
|
||||
# Copy files and set permissions
|
||||
COPY filebrowser /bin/filebrowser
|
||||
COPY docker/common/ /
|
||||
COPY docker/s6/ /
|
||||
|
||||
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh
|
||||
|
||||
# Define healthcheck script
|
||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
||||
|
||||
# Set the volumes and exposed ports
|
||||
VOLUME /srv /config /database
|
||||
|
||||
EXPOSE 80
|
||||
2
LICENSE
@@ -187,7 +187,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 File Browser contributors
|
||||
Copyright 2018 File Browser Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
19
Makefile
@@ -3,6 +3,14 @@ include tools.mk
|
||||
|
||||
LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)"
|
||||
|
||||
SITE_DOCKER_FLAGS = \
|
||||
-v $(CURDIR)/www:/docs \
|
||||
-v $(CURDIR)/LICENSE:/docs/docs/LICENSE \
|
||||
-v $(CURDIR)/SECURITY.md:/docs/docs/security.md \
|
||||
-v $(CURDIR)/CHANGELOG.md:/docs/docs/changelog.md \
|
||||
-v $(CURDIR)/CODE-OF-CONDUCT.md:/docs/docs/code-of-conduct.md \
|
||||
-v $(CURDIR)/CONTRIBUTING.md:/docs/docs/contributing.md
|
||||
|
||||
## Build:
|
||||
|
||||
.PHONY: build
|
||||
@@ -53,6 +61,17 @@ clean: clean-tools ## Clean
|
||||
bump-version: $(standard-version) ## Bump app version
|
||||
$Q ./scripts/bump_version.sh
|
||||
|
||||
.PHONY: site
|
||||
site: ## Build site
|
||||
@rm -rf www/public
|
||||
docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
||||
docker run --rm $(SITE_DOCKER_FLAGS) filebrowser.site build -d "public"
|
||||
|
||||
.PHONY: site-serve
|
||||
site-serve: ## Serve site for development
|
||||
docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
||||
docker run --rm -it -p 8000:8000 $(SITE_DOCKER_FLAGS) filebrowser.site
|
||||
|
||||
## Help:
|
||||
help: ## Show this help
|
||||
@echo ''
|
||||
|
||||
49
README.md
@@ -2,15 +2,19 @@
|
||||
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
||||
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
|
||||
[](http://godoc.org/github.com/filebrowser/filebrowser)
|
||||
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
||||
[](http://webchat.freenode.net/?channels=%23filebrowser)
|
||||
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
|
||||
[](http://godoc.org/github.com/filebrowser/filebrowser)
|
||||
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
||||
[](http://webchat.freenode.net/?channels=%23filebrowser)
|
||||
|
||||
filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app.
|
||||
File Browser provides a file managing interface within a specified directory and it can be used to upload, delete, preview and edit your files. It is a **create-your-own-cloud**-kind of software where you can just install it on your server, direct it to a path and access your files through a nice web interface.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation on how to install, configure, and contribute to this project is hosted at [filebrowser.org](https://filebrowser.org).
|
||||
|
||||
## Project Status
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
@@ -25,31 +29,10 @@ filebrowser provides a file managing interface within a specified directory and
|
||||
[issues]: https://github.com/filebrowser/filebrowser/issues
|
||||
[discussions]: https://github.com/filebrowser/filebrowser/discussions
|
||||
|
||||
## Features
|
||||
|
||||
File Browser is a **create-your-own-cloud-kind** of software where you can install it on a server, direct it to a path and then access your files through a nice web interface. You have many available features!
|
||||
|
||||
| Easy Login System | Sleek Interface | User Management |
|
||||
| :----------------------: | :----------------------: | :----------------------: |
|
||||
|  |  |  |
|
||||
|
||||
|
||||
| File Editing | Custom Commands | Customization |
|
||||
| :----------------------: | :----------------------: | :----------------------: |
|
||||
|  |  |  |
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> The **command execution** functionality has been disabled for all existent and new installations by default from version v2.33.8 and onwards, due to continuous and known security vulnerabilities. You should only use this feature if you are aware of all of the security risks involved. For more up to date information, consult issue [#5199](https://github.com/filebrowser/filebrowser/issues/5199).
|
||||
|
||||
## Install
|
||||
|
||||
For information on how to install File Browser, please check [docs/installation.md](./docs/installation.md).
|
||||
|
||||
## Configuration
|
||||
|
||||
For information on how to configure File Browser, please check [docs/configuration.md](./docs/configuration.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
For information on how to contribute to the project, including how translations are managed, please check [docs/contributing.md](./docs/contributing.md).
|
||||
Contributions are always welcome. To start contributing to this project, read our [guidelines](CONTRIBUTING.md) first.
|
||||
|
||||
## License
|
||||
|
||||
[Apache License 2.0](LICENSE) © File Browser Contributors
|
||||
|
||||
@@ -150,7 +150,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
pass, err := users.HashPwd(a.Cred.Password)
|
||||
pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -186,7 +186,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
|
||||
|
||||
// update the password when it doesn't match the current
|
||||
if p {
|
||||
pass, err := users.HashPwd(a.Cred.Password)
|
||||
pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
@@ -29,15 +28,14 @@ func (a ProxyAuth) Auth(r *http.Request, usr users.Store, setting *settings.Sett
|
||||
}
|
||||
|
||||
func (a ProxyAuth) createUser(usr users.Store, setting *settings.Settings, srv *settings.Server, username string) (*users.User, error) {
|
||||
const passwordSize = 32
|
||||
randomPasswordBytes := make([]byte, passwordSize)
|
||||
_, err := rand.Read(randomPasswordBytes)
|
||||
const randomPasswordLength = settings.DefaultMinimumPasswordLength + 10
|
||||
pwd, err := users.RandomPwd(randomPasswordLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hashedRandomPassword string
|
||||
hashedRandomPassword, err = users.HashPwd(string(randomPasswordBytes))
|
||||
hashedRandomPassword, err = users.ValidateAndHashPwd(pwd, setting.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
10
cmd/cmd.go
@@ -1,12 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// Execute executes the commands.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
@@ -15,13 +15,18 @@ var cmdsAddCmd = &cobra.Command{
|
||||
Short: "Add a command to run on a specific event",
|
||||
Long: `Add a command to run on a specific event.`,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
s, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command := strings.Join(args[1:], " ")
|
||||
s.Commands[args[0]] = append(s.Commands[args[0]], command)
|
||||
err = d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printEvents(s.Commands)
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -14,10 +14,15 @@ var cmdsLsCmd = &cobra.Command{
|
||||
Short: "List all commands for each event",
|
||||
Long: `List all commands for each event.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
s, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
evt := mustGetString(cmd.Flags(), "event")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evt, err := getString(cmd.Flags(), "event")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if evt == "" {
|
||||
printEvents(s.Commands)
|
||||
@@ -27,5 +32,6 @@ var cmdsLsCmd = &cobra.Command{
|
||||
show["after_"+evt] = s.Commands["after_"+evt]
|
||||
printEvents(show)
|
||||
}
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -35,22 +35,31 @@ including 'index_end'.`,
|
||||
|
||||
return nil
|
||||
},
|
||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
s, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evt := args[0]
|
||||
|
||||
i, err := strconv.Atoi(args[1])
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f := i
|
||||
if len(args) == 3 {
|
||||
f, err = strconv.Atoi(args[2])
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...)
|
||||
err = d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printEvents(s.Commands)
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
201
cmd/config.go
@@ -32,6 +32,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
||||
addUserFlags(flags)
|
||||
flags.BoolP("signup", "s", false, "allow users to signup")
|
||||
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
|
||||
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
||||
flags.String("shell", "", "shell command to which other commands should be appended")
|
||||
|
||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||
@@ -48,11 +49,18 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
||||
flags.String("branding.files", "", "path to directory with images and custom styles")
|
||||
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
||||
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
|
||||
// NB: these are string so they can be presented as octal in the help text
|
||||
// as that's the conventional representation for modes in Unix.
|
||||
flags.String("file-mode", fmt.Sprintf("%O", settings.DefaultFileMode), "Mode bits that new files are created with")
|
||||
flags.String("dir-mode", fmt.Sprintf("%O", settings.DefaultDirMode), "Mode bits that new directories are created with")
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) {
|
||||
method := settings.AuthMethod(mustGetString(flags, "auth.method"))
|
||||
func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, map[string]interface{}, error) {
|
||||
methodStr, err := getString(flags, "auth.method")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
method := settings.AuthMethod(methodStr)
|
||||
|
||||
var defaultAuther map[string]interface{}
|
||||
if len(defaults) > 0 {
|
||||
@@ -63,87 +71,129 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
|
||||
method = def.AuthMethod
|
||||
case auth.Auther:
|
||||
ms, err := json.Marshal(def)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = json.Unmarshal(ms, &defaultAuther)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var auther auth.Auther
|
||||
if method == auth.MethodProxyAuth {
|
||||
header := mustGetString(flags, "auth.header")
|
||||
|
||||
if header == "" {
|
||||
header = defaultAuther["header"].(string)
|
||||
}
|
||||
|
||||
if header == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.header' for method 'proxy'"))
|
||||
}
|
||||
|
||||
auther = &auth.ProxyAuth{Header: header}
|
||||
}
|
||||
|
||||
if method == auth.MethodNoAuth {
|
||||
auther = &auth.NoAuth{}
|
||||
}
|
||||
|
||||
if method == auth.MethodJSONAuth {
|
||||
jsonAuth := &auth.JSONAuth{}
|
||||
host := mustGetString(flags, "recaptcha.host")
|
||||
key := mustGetString(flags, "recaptcha.key")
|
||||
secret := mustGetString(flags, "recaptcha.secret")
|
||||
|
||||
if key == "" {
|
||||
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||
key = kmap["key"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if secret == "" {
|
||||
if smap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||
secret = smap["secret"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if key != "" && secret != "" {
|
||||
jsonAuth.ReCaptcha = &auth.ReCaptcha{
|
||||
Host: host,
|
||||
Key: key,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
auther = jsonAuth
|
||||
}
|
||||
|
||||
if method == auth.MethodHookAuth {
|
||||
command := mustGetString(flags, "auth.command")
|
||||
|
||||
if command == "" {
|
||||
command = defaultAuther["command"].(string)
|
||||
}
|
||||
|
||||
if command == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.command' for method 'hook'"))
|
||||
}
|
||||
|
||||
auther = &auth.HookAuth{Command: command}
|
||||
}
|
||||
|
||||
if auther == nil {
|
||||
panic(errors.ErrInvalidAuthMethod)
|
||||
}
|
||||
|
||||
return method, auther
|
||||
return method, defaultAuther, nil
|
||||
}
|
||||
|
||||
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
|
||||
func getProxyAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||
header, err := getString(flags, "auth.header")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if header == "" {
|
||||
header = defaultAuther["header"].(string)
|
||||
}
|
||||
|
||||
if header == "" {
|
||||
return nil, nerrors.New("you must set the flag 'auth.header' for method 'proxy'")
|
||||
}
|
||||
|
||||
return &auth.ProxyAuth{Header: header}, nil
|
||||
}
|
||||
|
||||
func getNoAuth() auth.Auther {
|
||||
return &auth.NoAuth{}
|
||||
}
|
||||
|
||||
func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||
jsonAuth := &auth.JSONAuth{}
|
||||
host, err := getString(flags, "recaptcha.host")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := getString(flags, "recaptcha.key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret, err := getString(flags, "recaptcha.secret")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if key == "" {
|
||||
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||
key = kmap["key"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if secret == "" {
|
||||
if smap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||
secret = smap["secret"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if key != "" && secret != "" {
|
||||
jsonAuth.ReCaptcha = &auth.ReCaptcha{
|
||||
Host: host,
|
||||
Key: key,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
return jsonAuth, nil
|
||||
}
|
||||
|
||||
func getHookAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||
command, err := getString(flags, "auth.command")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if command == "" {
|
||||
command = defaultAuther["command"].(string)
|
||||
}
|
||||
|
||||
if command == "" {
|
||||
return nil, nerrors.New("you must set the flag 'auth.command' for method 'hook'")
|
||||
}
|
||||
|
||||
return &auth.HookAuth{Command: command}, nil
|
||||
}
|
||||
|
||||
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther, error) {
|
||||
method, defaultAuther, err := getAuthMethod(flags, defaults...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
var auther auth.Auther
|
||||
switch method {
|
||||
case auth.MethodProxyAuth:
|
||||
auther, err = getProxyAuth(flags, defaultAuther)
|
||||
case auth.MethodNoAuth:
|
||||
auther = getNoAuth()
|
||||
case auth.MethodJSONAuth:
|
||||
auther, err = getJSONAuth(flags, defaultAuther)
|
||||
case auth.MethodHookAuth:
|
||||
auther, err = getHookAuth(flags, defaultAuther)
|
||||
default:
|
||||
return "", nil, errors.ErrInvalidAuthMethod
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return method, auther, nil
|
||||
}
|
||||
|
||||
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) error {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
|
||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
||||
fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
|
||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||
fmt.Fprintln(w, "\nBranding:")
|
||||
@@ -168,6 +218,8 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
||||
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
|
||||
fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick)
|
||||
fmt.Fprintf(w, "\tFile Creation Mode:\t%O\n", set.FileMode)
|
||||
fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode)
|
||||
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
|
||||
fmt.Fprintf(w, "\tSorting:\n")
|
||||
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
|
||||
@@ -184,6 +236,9 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
w.Flush()
|
||||
|
||||
b, err := json.MarshalIndent(auther, "", " ")
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,13 +13,19 @@ var configCatCmd = &cobra.Command{
|
||||
Short: "Prints the configuration",
|
||||
Long: `Prints the configuration.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: python(func(_ *cobra.Command, _ []string, d pythonData) {
|
||||
RunE: python(func(_ *cobra.Command, _ []string, d *pythonData) error {
|
||||
set, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ser, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||
checkErr(err)
|
||||
printSettings(ser, set, auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printSettings(ser, set, auther)
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -15,15 +15,21 @@ var configExportCmd = &cobra.Command{
|
||||
json or yaml file. This exported configuration can be changed,
|
||||
and imported again with 'config import' command.`,
|
||||
Args: jsonYamlArg,
|
||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
settings, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := &settingsFile{
|
||||
Settings: settings,
|
||||
@@ -32,6 +38,9 @@ and imported again with 'config import' command.`,
|
||||
}
|
||||
|
||||
err = marshal(args[0], data)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -34,61 +34,89 @@ database.
|
||||
|
||||
The path must be for a json or yaml file.`,
|
||||
Args: jsonYamlArg,
|
||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
var key []byte
|
||||
var err error
|
||||
if d.hadDB {
|
||||
settings, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
settings, settingErr := d.store.Settings.Get()
|
||||
if settingErr != nil {
|
||||
return settingErr
|
||||
}
|
||||
key = settings.Key
|
||||
} else {
|
||||
key = generateKey()
|
||||
}
|
||||
|
||||
file := settingsFile{}
|
||||
err := unmarshal(args[0], &file)
|
||||
checkErr(err)
|
||||
err = unmarshal(args[0], &file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file.Settings.Key = key
|
||||
err = d.store.Settings.Save(file.Settings)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(file.Server)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rawAuther interface{}
|
||||
if filepath.Ext(args[0]) != ".json" { //nolint:goconst
|
||||
if filepath.Ext(args[0]) != ".json" {
|
||||
rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
|
||||
} else {
|
||||
rawAuther = file.Auther
|
||||
}
|
||||
|
||||
var auther auth.Auther
|
||||
var autherErr error
|
||||
switch file.Settings.AuthMethod {
|
||||
case auth.MethodJSONAuth:
|
||||
auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth)
|
||||
var a interface{}
|
||||
a, autherErr = getAuther(auth.JSONAuth{}, rawAuther)
|
||||
auther = a.(*auth.JSONAuth)
|
||||
case auth.MethodNoAuth:
|
||||
auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth)
|
||||
var a interface{}
|
||||
a, autherErr = getAuther(auth.NoAuth{}, rawAuther)
|
||||
auther = a.(*auth.NoAuth)
|
||||
case auth.MethodProxyAuth:
|
||||
auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth)
|
||||
var a interface{}
|
||||
a, autherErr = getAuther(auth.ProxyAuth{}, rawAuther)
|
||||
auther = a.(*auth.ProxyAuth)
|
||||
case auth.MethodHookAuth:
|
||||
auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth)
|
||||
var a interface{}
|
||||
a, autherErr = getAuther(&auth.HookAuth{}, rawAuther)
|
||||
auther = a.(*auth.HookAuth)
|
||||
default:
|
||||
checkErr(errors.New("invalid auth method"))
|
||||
return errors.New("invalid auth method")
|
||||
}
|
||||
|
||||
if autherErr != nil {
|
||||
return autherErr
|
||||
}
|
||||
|
||||
err = d.store.Auth.Save(auther)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printSettings(file.Server, file.Settings, auther)
|
||||
return printSettings(file.Server, file.Settings, auther)
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}
|
||||
|
||||
func getAuther(sample auth.Auther, data interface{}) interface{} {
|
||||
func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
|
||||
authType := reflect.TypeOf(sample)
|
||||
auther := reflect.New(authType).Interface()
|
||||
bytes, err := json.Marshal(data)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(bytes, &auther)
|
||||
checkErr(err)
|
||||
return auther
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auther, nil
|
||||
}
|
||||
|
||||
@@ -22,51 +22,161 @@ this options can be changed in the future with the command
|
||||
to the defaults when creating new users and you don't
|
||||
override the options.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
defaults := settings.UserDefaults{}
|
||||
flags := cmd.Flags()
|
||||
getUserDefaults(flags, &defaults, true)
|
||||
authMethod, auther := getAuthentication(flags)
|
||||
err := getUserDefaults(flags, &defaults, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authMethod, auther, err := getAuthentication(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := generateKey()
|
||||
|
||||
signup, err := getBool(flags, "signup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createUserDir, err := getBool(flags, "create-user-dir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minLength, err := getUint(flags, "minimum-password-length")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shell, err := getString(flags, "shell")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingName, err := getString(flags, "branding.name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingDisableExternal, err := getBool(flags, "branding.disableExternal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingDisableUsedPercentage, err := getBool(flags, "branding.disableUsedPercentage")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingTheme, err := getString(flags, "branding.theme")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingFiles, err := getString(flags, "branding.files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s := &settings.Settings{
|
||||
Key: generateKey(),
|
||||
Signup: mustGetBool(flags, "signup"),
|
||||
CreateUserDir: mustGetBool(flags, "create-user-dir"),
|
||||
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
|
||||
AuthMethod: authMethod,
|
||||
Defaults: defaults,
|
||||
Key: key,
|
||||
Signup: signup,
|
||||
CreateUserDir: createUserDir,
|
||||
MinimumPasswordLength: minLength,
|
||||
Shell: convertCmdStrToCmdArray(shell),
|
||||
AuthMethod: authMethod,
|
||||
Defaults: defaults,
|
||||
Branding: settings.Branding{
|
||||
Name: mustGetString(flags, "branding.name"),
|
||||
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
||||
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"),
|
||||
Theme: mustGetString(flags, "branding.theme"),
|
||||
Files: mustGetString(flags, "branding.files"),
|
||||
Name: brandingName,
|
||||
DisableExternal: brandingDisableExternal,
|
||||
DisableUsedPercentage: brandingDisableUsedPercentage,
|
||||
Theme: brandingTheme,
|
||||
Files: brandingFiles,
|
||||
},
|
||||
}
|
||||
|
||||
ser := &settings.Server{
|
||||
Address: mustGetString(flags, "address"),
|
||||
Socket: mustGetString(flags, "socket"),
|
||||
Root: mustGetString(flags, "root"),
|
||||
BaseURL: mustGetString(flags, "baseurl"),
|
||||
TLSKey: mustGetString(flags, "key"),
|
||||
TLSCert: mustGetString(flags, "cert"),
|
||||
Port: mustGetString(flags, "port"),
|
||||
Log: mustGetString(flags, "log"),
|
||||
s.FileMode, err = getMode(flags, "file-mode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
s.DirMode, err = getMode(flags, "file-mode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
address, err := getString(flags, "address")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
socket, err := getString(flags, "socket")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err := getString(flags, "root")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseURL, err := getString(flags, "baseurl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsKey, err := getString(flags, "key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cert, err := getString(flags, "cert")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := getString(flags, "port")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log, err := getString(flags, "log")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser := &settings.Server{
|
||||
Address: address,
|
||||
Socket: socket,
|
||||
Root: root,
|
||||
BaseURL: baseURL,
|
||||
TLSKey: tlsKey,
|
||||
TLSCert: cert,
|
||||
Port: port,
|
||||
Log: log,
|
||||
}
|
||||
|
||||
err = d.store.Settings.Save(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.store.Auth.Save(auther)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf(`
|
||||
Congratulations! You've set up your database to use with File Browser.
|
||||
Now add your first user via 'filebrowser users add' and then you just
|
||||
need to call the main command to boot up the server.
|
||||
`)
|
||||
printSettings(ser, s, auther)
|
||||
return printSettings(ser, s, auther)
|
||||
}, pythonConfig{noDB: true}),
|
||||
}
|
||||
|
||||
@@ -16,71 +16,105 @@ var configSetCmd = &cobra.Command{
|
||||
Long: `Updates the configuration. Set the flags for the options
|
||||
you want to change. Other options will remain unchanged.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
flags := cmd.Flags()
|
||||
set, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasAuth := false
|
||||
flags.Visit(func(flag *pflag.Flag) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch flag.Name {
|
||||
case "baseurl":
|
||||
ser.BaseURL = mustGetString(flags, flag.Name)
|
||||
ser.BaseURL, err = getString(flags, flag.Name)
|
||||
case "root":
|
||||
ser.Root = mustGetString(flags, flag.Name)
|
||||
ser.Root, err = getString(flags, flag.Name)
|
||||
case "socket":
|
||||
ser.Socket = mustGetString(flags, flag.Name)
|
||||
ser.Socket, err = getString(flags, flag.Name)
|
||||
case "cert":
|
||||
ser.TLSCert = mustGetString(flags, flag.Name)
|
||||
ser.TLSCert, err = getString(flags, flag.Name)
|
||||
case "key":
|
||||
ser.TLSKey = mustGetString(flags, flag.Name)
|
||||
ser.TLSKey, err = getString(flags, flag.Name)
|
||||
case "address":
|
||||
ser.Address = mustGetString(flags, flag.Name)
|
||||
ser.Address, err = getString(flags, flag.Name)
|
||||
case "port":
|
||||
ser.Port = mustGetString(flags, flag.Name)
|
||||
ser.Port, err = getString(flags, flag.Name)
|
||||
case "log":
|
||||
ser.Log = mustGetString(flags, flag.Name)
|
||||
ser.Log, err = getString(flags, flag.Name)
|
||||
case "signup":
|
||||
set.Signup = mustGetBool(flags, flag.Name)
|
||||
set.Signup, err = getBool(flags, flag.Name)
|
||||
case "auth.method":
|
||||
hasAuth = true
|
||||
case "shell":
|
||||
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
||||
var shell string
|
||||
shell, err = getString(flags, flag.Name)
|
||||
set.Shell = convertCmdStrToCmdArray(shell)
|
||||
case "create-user-dir":
|
||||
set.CreateUserDir = mustGetBool(flags, flag.Name)
|
||||
set.CreateUserDir, err = getBool(flags, flag.Name)
|
||||
case "minimum-password-length":
|
||||
set.MinimumPasswordLength, err = getUint(flags, flag.Name)
|
||||
case "branding.name":
|
||||
set.Branding.Name = mustGetString(flags, flag.Name)
|
||||
set.Branding.Name, err = getString(flags, flag.Name)
|
||||
case "branding.color":
|
||||
set.Branding.Color = mustGetString(flags, flag.Name)
|
||||
set.Branding.Color, err = getString(flags, flag.Name)
|
||||
case "branding.theme":
|
||||
set.Branding.Theme = mustGetString(flags, flag.Name)
|
||||
set.Branding.Theme, err = getString(flags, flag.Name)
|
||||
case "branding.disableExternal":
|
||||
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
|
||||
set.Branding.DisableExternal, err = getBool(flags, flag.Name)
|
||||
case "branding.disableUsedPercentage":
|
||||
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name)
|
||||
set.Branding.DisableUsedPercentage, err = getBool(flags, flag.Name)
|
||||
case "branding.files":
|
||||
set.Branding.Files = mustGetString(flags, flag.Name)
|
||||
set.Branding.Files, err = getString(flags, flag.Name)
|
||||
case "file-mode":
|
||||
set.FileMode, err = getMode(flags, flag.Name)
|
||||
case "dir-mode":
|
||||
set.DirMode, err = getMode(flags, flag.Name)
|
||||
}
|
||||
})
|
||||
|
||||
getUserDefaults(flags, &set.Defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = getUserDefaults(flags, &set.Defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read the defaults
|
||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if there are new flags for existing auth method
|
||||
set.AuthMethod, auther = getAuthentication(flags, hasAuth, set, auther)
|
||||
set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Auth.Save(auther)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.store.Settings.Save(set)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
checkErr(err)
|
||||
printSettings(ser, set, auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return printSettings(ser, set, auther)
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
37
cmd/docs.go
@@ -39,12 +39,19 @@ var docsCmd = &cobra.Command{
|
||||
Use: "docs",
|
||||
Hidden: true,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
dir := mustGetString(cmd.Flags(), "path")
|
||||
generateDocs(rootCmd, dir)
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
dir, err := getString(cmd.Flags(), "path")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = generateDocs(rootCmd, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
names := []string{}
|
||||
|
||||
err := filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
||||
err = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return err
|
||||
}
|
||||
@@ -56,30 +63,38 @@ var docsCmd = &cobra.Command{
|
||||
names = append(names, info.Name())
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
printToc(names)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func generateDocs(cmd *cobra.Command, dir string) {
|
||||
func generateDocs(cmd *cobra.Command, dir string) error {
|
||||
for _, c := range cmd.Commands() {
|
||||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
||||
continue
|
||||
}
|
||||
|
||||
generateDocs(c, dir)
|
||||
err := generateDocs(c, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
||||
filename := filepath.Join(dir, basename)
|
||||
f, err := os.Create(filename)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
generateMarkdown(cmd, f)
|
||||
return generateMarkdown(cmd, f)
|
||||
}
|
||||
|
||||
func generateMarkdown(cmd *cobra.Command, w io.Writer) {
|
||||
func generateMarkdown(cmd *cobra.Command, w io.Writer) error {
|
||||
cmd.InitDefaultHelpCmd()
|
||||
cmd.InitDefaultHelpFlag()
|
||||
|
||||
@@ -108,7 +123,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
|
||||
|
||||
printOptions(buf, cmd)
|
||||
_, err := buf.WriteTo(w)
|
||||
checkErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
||||
|
||||
@@ -17,9 +17,12 @@ var hashCmd = &cobra.Command{
|
||||
Short: "Hashes a password",
|
||||
Long: `Hashes a password using bcrypt algorithm.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
pwd, err := users.HashPwd(args[0])
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(pwd)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
183
cmd/root.go
@@ -1,8 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
@@ -23,6 +26,7 @@ import (
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/auth"
|
||||
"github.com/filebrowser/filebrowser/v2/diskcache"
|
||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||
"github.com/filebrowser/filebrowser/v2/frontend"
|
||||
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
||||
"github.com/filebrowser/filebrowser/v2/img"
|
||||
@@ -37,6 +41,7 @@ var (
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
rootCmd.SilenceUsage = true
|
||||
cobra.MousetrapHelpText = ""
|
||||
|
||||
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
||||
@@ -61,11 +66,11 @@ func addServerFlags(flags *pflag.FlagSet) {
|
||||
flags.StringP("key", "k", "", "tls key")
|
||||
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
||||
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
||||
flags.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd
|
||||
flags.Uint32("socket-perm", 0666, "unix socket file permissions")
|
||||
flags.StringP("baseurl", "b", "", "base url")
|
||||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||
flags.String("token-expiration-time", "2h", "user session timeout")
|
||||
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
|
||||
flags.Int("img-processors", 4, "image processors count") //nolint:mnd
|
||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||
flags.Bool("disable-exec", true, "disables Command Runner feature")
|
||||
@@ -110,36 +115,48 @@ set FB_DATABASE.
|
||||
Also, if the database path doesn't exist, File Browser will enter into
|
||||
the quick setup mode and a new database will be bootstrapped and a new
|
||||
user created with the credentials from options "username" and "password".`,
|
||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
log.Println(cfgFile)
|
||||
|
||||
if !d.hadDB {
|
||||
quickSetup(cmd.Flags(), d)
|
||||
err := quickSetup(cmd.Flags(), *d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// build img service
|
||||
workersCount, err := cmd.Flags().GetInt("img-processors")
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if workersCount < 1 {
|
||||
log.Fatal("Image resize workers count could not be < 1")
|
||||
return errors.New("image resize workers count could not be < 1")
|
||||
}
|
||||
imgSvc := img.New(workersCount)
|
||||
|
||||
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cacheDir != "" {
|
||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
|
||||
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
||||
return fmt.Errorf("can't make directory %s: %w", cacheDir, err)
|
||||
}
|
||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||
}
|
||||
|
||||
server := getRunParams(cmd.Flags(), d.store)
|
||||
server, err := getRunParams(cmd.Flags(), d.store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setupLog(server.Log)
|
||||
|
||||
root, err := filepath.Abs(server.Root)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.Root = root
|
||||
|
||||
adr := server.Address + ":" + server.Port
|
||||
@@ -149,57 +166,102 @@ user created with the credentials from options "username" and "password".`,
|
||||
switch {
|
||||
case server.Socket != "":
|
||||
listener, err = net.Listen("unix", server.Socket)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case server.TLSKey != "" && server.TLSCert != "":
|
||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listener, err = tls.Listen("tcp", adr, &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{cer}},
|
||||
)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
listener, err = net.Listen("tcp", adr)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||
go cleanupHandler(listener, sigc)
|
||||
|
||||
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer listener.Close()
|
||||
|
||||
log.Println("Listening on", listener.Addr().String())
|
||||
//nolint: gosec
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
log.Fatal(err)
|
||||
srv := &http.Server{
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := srv.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("HTTP server error: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Stopped serving new connections.")
|
||||
}()
|
||||
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc,
|
||||
os.Interrupt,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT,
|
||||
)
|
||||
sig := <-sigc
|
||||
log.Println("Got signal:", sig)
|
||||
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd
|
||||
defer shutdownRelease()
|
||||
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
log.Fatalf("HTTP shutdown error: %v", err)
|
||||
}
|
||||
log.Println("Graceful shutdown complete.")
|
||||
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
d.err = fbErrors.ErrSighup
|
||||
case syscall.SIGINT:
|
||||
d.err = fbErrors.ErrSigint
|
||||
case syscall.SIGQUIT:
|
||||
d.err = fbErrors.ErrSigquit
|
||||
case syscall.SIGTERM:
|
||||
d.err = fbErrors.ErrSigTerm
|
||||
}
|
||||
|
||||
return d.err
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}
|
||||
|
||||
func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer
|
||||
sig := <-c
|
||||
log.Printf("Caught signal %s: shutting down.", sig)
|
||||
listener.Close()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, error) {
|
||||
server, err := st.Settings.GetServer()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "root"); set {
|
||||
server.Root = val
|
||||
@@ -242,7 +304,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||
}
|
||||
|
||||
if isAddrSet && isSocketSet {
|
||||
checkErr(errors.New("--socket flag cannot be used with --address, --port, --key nor --cert"))
|
||||
return nil, errors.New("--socket flag cannot be used with --address, --port, --key nor --cert")
|
||||
}
|
||||
|
||||
// Do not use saved Socket if address was manually set.
|
||||
@@ -273,7 +335,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||
server.TokenExpirationTime = val
|
||||
}
|
||||
|
||||
return server
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
||||
@@ -352,12 +414,15 @@ func setupLog(logMethod string) {
|
||||
}
|
||||
}
|
||||
|
||||
func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||
func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||
log.Println("Performing quick setup")
|
||||
|
||||
set := &settings.Settings{
|
||||
Key: generateKey(),
|
||||
Signup: false,
|
||||
CreateUserDir: false,
|
||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
||||
Key: generateKey(),
|
||||
Signup: false,
|
||||
CreateUserDir: false,
|
||||
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
||||
Defaults: settings.UserDefaults{
|
||||
Scope: ".",
|
||||
Locale: "en",
|
||||
@@ -392,10 +457,14 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||
set.AuthMethod = auth.MethodJSONAuth
|
||||
err = d.store.Auth.Save(&auth.JSONAuth{})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
err = d.store.Settings.Save(set)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser := &settings.Server{
|
||||
BaseURL: getStringParam(flags, "baseurl"),
|
||||
@@ -408,20 +477,27 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := getStringParam(flags, "username")
|
||||
password := getStringParam(flags, "password")
|
||||
|
||||
if password == "" {
|
||||
var pwd string
|
||||
pwd, err = users.RandomPwd()
|
||||
checkErr(err)
|
||||
pwd, err = users.RandomPwd(set.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("Randomly generated password for user 'admin':", pwd)
|
||||
|
||||
password, err = users.HashPwd(pwd)
|
||||
checkErr(err)
|
||||
log.Printf("User '%s' initialized with randomly generated password: %s\n", username, pwd)
|
||||
password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("User '%s' initialize wth user-provided password\n", username)
|
||||
}
|
||||
|
||||
if username == "" || password == "" {
|
||||
@@ -437,14 +513,15 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||
set.Defaults.Apply(user)
|
||||
user.Perm.Admin = true
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr(err)
|
||||
return d.store.Users.Save(user)
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
if cfgFile == "" {
|
||||
home, err := homedir.Dir()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath(home)
|
||||
v.AddConfigPath("/etc/filebrowser/")
|
||||
|
||||
@@ -40,27 +40,29 @@ including 'index_end'.`,
|
||||
|
||||
return nil
|
||||
},
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
i, err := strconv.Atoi(args[0])
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f := i
|
||||
if len(args) == 2 {
|
||||
f, err = strconv.Atoi(args[1])
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
user := func(u *users.User) {
|
||||
user := func(u *users.User) error {
|
||||
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
||||
err := d.store.Users.Save(u)
|
||||
checkErr(err)
|
||||
return d.store.Users.Save(u)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) {
|
||||
global := func(s *settings.Settings) error {
|
||||
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
return d.store.Settings.Save(s)
|
||||
}
|
||||
|
||||
runRules(d.store, cmd, user, global)
|
||||
return runRules(d.store, cmd, user, global)
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
55
cmd/rules.go
@@ -29,41 +29,62 @@ rules.`,
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
|
||||
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) {
|
||||
id := getUserIdentifier(cmd.Flags())
|
||||
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User) error, globalFn func(*settings.Settings) error) error {
|
||||
id, err := getUserIdentifier(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if id != nil {
|
||||
user, err := st.Users.Get("", id)
|
||||
checkErr(err)
|
||||
var user *users.User
|
||||
user, err = st.Users.Get("", id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if usersFn != nil {
|
||||
usersFn(user)
|
||||
err = usersFn(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
printRules(user.Rules, id)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
s, err := st.Settings.Get()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if globalFn != nil {
|
||||
globalFn(s)
|
||||
err = globalFn(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
printRules(s.Rules, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserIdentifier(flags *pflag.FlagSet) interface{} {
|
||||
id := mustGetUint(flags, "id")
|
||||
username := mustGetString(flags, "username")
|
||||
|
||||
if id != 0 {
|
||||
return id
|
||||
} else if username != "" {
|
||||
return username
|
||||
func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
|
||||
id, err := getUint(flags, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
username, err := getString(flags, "username")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
if id != 0 {
|
||||
return id, nil
|
||||
} else if username != "" {
|
||||
return username, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func printRules(rulez []rules.Rule, id interface{}) {
|
||||
|
||||
@@ -21,9 +21,15 @@ var rulesAddCmd = &cobra.Command{
|
||||
Short: "Add a global rule or user rule",
|
||||
Long: `Add a global rule or user rule.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
allow := mustGetBool(cmd.Flags(), "allow")
|
||||
regex := mustGetBool(cmd.Flags(), "regex")
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
allow, err := getBool(cmd.Flags(), "allow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regex, err := getBool(cmd.Flags(), "regex")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exp := args[0]
|
||||
|
||||
if regex {
|
||||
@@ -41,18 +47,16 @@ var rulesAddCmd = &cobra.Command{
|
||||
rule.Path = exp
|
||||
}
|
||||
|
||||
user := func(u *users.User) {
|
||||
user := func(u *users.User) error {
|
||||
u.Rules = append(u.Rules, rule)
|
||||
err := d.store.Users.Save(u)
|
||||
checkErr(err)
|
||||
return d.store.Users.Save(u)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) {
|
||||
global := func(s *settings.Settings) error {
|
||||
s.Rules = append(s.Rules, rule)
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
return d.store.Settings.Save(s)
|
||||
}
|
||||
|
||||
runRules(d.store, cmd, user, global)
|
||||
return runRules(d.store, cmd, user, global)
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var rulesLsCommand = &cobra.Command{
|
||||
Short: "List global rules or user specific rules",
|
||||
Long: `List global rules or user specific rules.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||
runRules(d.store, cmd, nil, nil)
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
return runRules(d.store, cmd, nil, nil)
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -21,11 +21,20 @@ var upgradeCmd = &cobra.Command{
|
||||
import share links because they are incompatible with
|
||||
this version.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
flags := cmd.Flags()
|
||||
oldDB := mustGetString(flags, "old.database")
|
||||
oldConf := mustGetString(flags, "old.config")
|
||||
err := importer.Import(oldDB, oldConf, getStringParam(flags, "database"))
|
||||
checkErr(err)
|
||||
oldDB, err := getString(flags, "old.database")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldConf, err := getString(flags, "old.config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := getString(flags, "database")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return importer.Import(oldDB, oldConf, db)
|
||||
},
|
||||
}
|
||||
|
||||
57
cmd/users.go
@@ -79,50 +79,60 @@ func addUserFlags(flags *pflag.FlagSet) {
|
||||
flags.Bool("singleClick", false, "use single clicks only")
|
||||
}
|
||||
|
||||
func getViewMode(flags *pflag.FlagSet) users.ViewMode {
|
||||
viewMode := users.ViewMode(mustGetString(flags, "viewMode"))
|
||||
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode {
|
||||
checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\""))
|
||||
func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
|
||||
viewModeStr, err := getString(flags, "viewMode")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return viewMode
|
||||
viewMode := users.ViewMode(viewModeStr)
|
||||
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode {
|
||||
return "", errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")
|
||||
}
|
||||
return viewMode, nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) {
|
||||
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error {
|
||||
var visitErr error
|
||||
visit := func(flag *pflag.Flag) {
|
||||
if visitErr != nil {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
switch flag.Name {
|
||||
case "scope":
|
||||
defaults.Scope = mustGetString(flags, flag.Name)
|
||||
defaults.Scope, err = getString(flags, flag.Name)
|
||||
case "locale":
|
||||
defaults.Locale = mustGetString(flags, flag.Name)
|
||||
defaults.Locale, err = getString(flags, flag.Name)
|
||||
case "viewMode":
|
||||
defaults.ViewMode = getViewMode(flags)
|
||||
defaults.ViewMode, err = getViewMode(flags)
|
||||
case "singleClick":
|
||||
defaults.SingleClick = mustGetBool(flags, flag.Name)
|
||||
defaults.SingleClick, err = getBool(flags, flag.Name)
|
||||
case "perm.admin":
|
||||
defaults.Perm.Admin = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Admin, err = getBool(flags, flag.Name)
|
||||
case "perm.execute":
|
||||
defaults.Perm.Execute = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Execute, err = getBool(flags, flag.Name)
|
||||
case "perm.create":
|
||||
defaults.Perm.Create = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Create, err = getBool(flags, flag.Name)
|
||||
case "perm.rename":
|
||||
defaults.Perm.Rename = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Rename, err = getBool(flags, flag.Name)
|
||||
case "perm.modify":
|
||||
defaults.Perm.Modify = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Modify, err = getBool(flags, flag.Name)
|
||||
case "perm.delete":
|
||||
defaults.Perm.Delete = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Delete, err = getBool(flags, flag.Name)
|
||||
case "perm.share":
|
||||
defaults.Perm.Share = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Share, err = getBool(flags, flag.Name)
|
||||
case "perm.download":
|
||||
defaults.Perm.Download = mustGetBool(flags, flag.Name)
|
||||
defaults.Perm.Download, err = getBool(flags, flag.Name)
|
||||
case "commands":
|
||||
commands, err := flags.GetStringSlice(flag.Name)
|
||||
checkErr(err)
|
||||
defaults.Commands = commands
|
||||
defaults.Commands, err = flags.GetStringSlice(flag.Name)
|
||||
case "sorting.by":
|
||||
defaults.Sorting.By = mustGetString(flags, flag.Name)
|
||||
defaults.Sorting.By, err = getString(flags, flag.Name)
|
||||
case "sorting.asc":
|
||||
defaults.Sorting.Asc = mustGetBool(flags, flag.Name)
|
||||
defaults.Sorting.Asc, err = getBool(flags, flag.Name)
|
||||
}
|
||||
if err != nil {
|
||||
visitErr = err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +141,5 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
||||
} else {
|
||||
flags.Visit(visit)
|
||||
}
|
||||
return visitErr
|
||||
}
|
||||
|
||||
@@ -16,36 +16,57 @@ var usersAddCmd = &cobra.Command{
|
||||
Short: "Create a new user",
|
||||
Long: `Create a new user and add it to the database.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
s, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password, err := users.HashPwd(args[1])
|
||||
checkErr(err)
|
||||
password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lockPassword, err := getBool(cmd.Flags(), "lockPassword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &users.User{
|
||||
Username: args[0],
|
||||
Password: password,
|
||||
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
|
||||
LockPassword: lockPassword,
|
||||
}
|
||||
|
||||
s.Defaults.Apply(user)
|
||||
|
||||
servSettings, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// since getUserDefaults() polluted s.Defaults.Scope
|
||||
// which makes the Scope not the one saved in the db
|
||||
// we need the right s.Defaults.Scope here
|
||||
s2, err := d.store.Settings.Get()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Scope = userHome
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printUsers([]*users.User{user})
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -14,11 +14,16 @@ var usersExportCmd = &cobra.Command{
|
||||
Long: `Export all users to a json or yaml file. Please indicate the
|
||||
path to the file where you want to write the users.`,
|
||||
Args: jsonYamlArg,
|
||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
list, err := d.store.Users.Gets("")
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = marshal(args[0], list)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@ var usersFindCmd = &cobra.Command{
|
||||
Short: "Find a user by username or id",
|
||||
Long: `Find a user by username or id. If no flag is set, all users will be printed.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: findUsers,
|
||||
RunE: findUsers,
|
||||
}
|
||||
|
||||
var usersLsCmd = &cobra.Command{
|
||||
Use: "ls",
|
||||
Short: "List all users.",
|
||||
Args: cobra.NoArgs,
|
||||
Run: findUsers,
|
||||
RunE: findUsers,
|
||||
}
|
||||
|
||||
var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
var (
|
||||
list []*users.User
|
||||
user *users.User
|
||||
@@ -46,6 +46,9 @@ var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
list, err = d.store.Users.Gets("")
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printUsers(list)
|
||||
return nil
|
||||
}, pythonConfig{})
|
||||
|
||||
@@ -25,34 +25,54 @@ file. You can use this command to import new users to your
|
||||
installation. For that, just don't place their ID on the files
|
||||
list or set it to 0.`,
|
||||
Args: jsonYamlArg,
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
fd, err := os.Open(args[0])
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
list := []*users.User{}
|
||||
err = unmarshal(args[0], &list)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range list {
|
||||
err = user.Clean("")
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
if mustGetBool(cmd.Flags(), "replace") {
|
||||
oldUsers, err := d.store.Users.Gets("")
|
||||
checkErr(err)
|
||||
|
||||
err = marshal("users.backup.json", list)
|
||||
checkErr(err)
|
||||
|
||||
for _, user := range oldUsers {
|
||||
err = d.store.Users.Delete(user.ID)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
overwrite := mustGetBool(cmd.Flags(), "overwrite")
|
||||
replace, err := getBool(cmd.Flags(), "replace")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if replace {
|
||||
oldUsers, userImportErr := d.store.Users.Gets("")
|
||||
if userImportErr != nil {
|
||||
return userImportErr
|
||||
}
|
||||
|
||||
err = marshal("users.backup.json", list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range oldUsers {
|
||||
err = d.store.Users.Delete(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overwrite, err := getBool(cmd.Flags(), "overwrite")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range list {
|
||||
onDB, err := d.store.Users.Get("", user.ID)
|
||||
@@ -60,7 +80,7 @@ list or set it to 0.`,
|
||||
// User exists in DB.
|
||||
if err == nil {
|
||||
if !overwrite {
|
||||
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered"))
|
||||
return errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered")
|
||||
}
|
||||
|
||||
// If the usernames mismatch, check if there is another one in the DB
|
||||
@@ -68,7 +88,7 @@ list or set it to 0.`,
|
||||
// operation
|
||||
if user.Username != onDB.Username {
|
||||
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
|
||||
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
|
||||
return usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -78,8 +98,11 @@ list or set it to 0.`,
|
||||
}
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ var usersRmCmd = &cobra.Command{
|
||||
Short: "Delete a user by username or id",
|
||||
Long: `Delete a user by username or id`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
username, id := parseUsernameOrID(args[0])
|
||||
var err error
|
||||
|
||||
@@ -25,7 +25,10 @@ var usersRmCmd = &cobra.Command{
|
||||
err = d.store.Users.Delete(id)
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("user deleted successfully")
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -21,14 +21,24 @@ var usersUpdateCmd = &cobra.Command{
|
||||
Long: `Updates an existing user. Set the flags for the
|
||||
options you want to change.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
username, id := parseUsernameOrID(args[0])
|
||||
flags := cmd.Flags()
|
||||
password := mustGetString(flags, "password")
|
||||
newUsername := mustGetString(flags, "username")
|
||||
password, err := getString(flags, "password")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newUsername, err := getString(flags, "username")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := d.store.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
user *users.User
|
||||
)
|
||||
|
||||
@@ -38,7 +48,9 @@ options you want to change.`,
|
||||
user, err = d.store.Users.Get("", username)
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaults := settings.UserDefaults{
|
||||
Scope: user.Scope,
|
||||
@@ -49,7 +61,10 @@ options you want to change.`,
|
||||
Sorting: user.Sorting,
|
||||
Commands: user.Commands,
|
||||
}
|
||||
getUserDefaults(flags, &defaults, false)
|
||||
err = getUserDefaults(flags, &defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Scope = defaults.Scope
|
||||
user.Locale = defaults.Locale
|
||||
user.ViewMode = defaults.ViewMode
|
||||
@@ -57,19 +72,27 @@ options you want to change.`,
|
||||
user.Perm = defaults.Perm
|
||||
user.Commands = defaults.Commands
|
||||
user.Sorting = defaults.Sorting
|
||||
user.LockPassword = mustGetBool(flags, "lockPassword")
|
||||
user.LockPassword, err = getBool(flags, "lockPassword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if newUsername != "" {
|
||||
user.Username = newUsername
|
||||
}
|
||||
|
||||
if password != "" {
|
||||
user.Password, err = users.HashPwd(password)
|
||||
checkErr(err)
|
||||
user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = d.store.Users.Update(user)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printUsers([]*users.User{user})
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
74
cmd/utils.go
@@ -4,9 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/asdine/storm/v3"
|
||||
@@ -14,44 +16,57 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/files"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/storage"
|
||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||
)
|
||||
|
||||
func checkErr(err error) {
|
||||
const dbPerms = 0640
|
||||
|
||||
func returnErr(err error) error {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
||||
func getString(flags *pflag.FlagSet, flag string) (string, error) {
|
||||
s, err := flags.GetString(flag)
|
||||
checkErr(err)
|
||||
return s
|
||||
return s, returnErr(err)
|
||||
}
|
||||
|
||||
func mustGetBool(flags *pflag.FlagSet, flag string) bool {
|
||||
func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
|
||||
s, err := getString(flags, flag)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b, err := strconv.ParseUint(s, 0, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fs.FileMode(b), nil
|
||||
}
|
||||
|
||||
func getBool(flags *pflag.FlagSet, flag string) (bool, error) {
|
||||
b, err := flags.GetBool(flag)
|
||||
checkErr(err)
|
||||
return b
|
||||
return b, returnErr(err)
|
||||
}
|
||||
|
||||
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
|
||||
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
||||
b, err := flags.GetUint(flag)
|
||||
checkErr(err)
|
||||
return b
|
||||
return b, returnErr(err)
|
||||
}
|
||||
|
||||
func generateKey() []byte {
|
||||
k, err := settings.GenerateKey()
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
type cobraFunc func(cmd *cobra.Command, args []string)
|
||||
type pythonFunc func(cmd *cobra.Command, args []string, data pythonData)
|
||||
type cobraFunc func(cmd *cobra.Command, args []string) error
|
||||
type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error
|
||||
|
||||
type pythonConfig struct {
|
||||
noDB bool
|
||||
@@ -61,6 +76,7 @@ type pythonConfig struct {
|
||||
type pythonData struct {
|
||||
hadDB bool
|
||||
store *storage.Storage
|
||||
err error
|
||||
}
|
||||
|
||||
func dbExists(path string) (bool, error) {
|
||||
@@ -73,7 +89,7 @@ func dbExists(path string) (bool, error) {
|
||||
d := filepath.Dir(path)
|
||||
_, err = os.Stat(d)
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet,gomnd
|
||||
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
@@ -84,8 +100,8 @@ func dbExists(path string) (bool, error) {
|
||||
}
|
||||
|
||||
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
data := pythonData{hadDB: true}
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
data := &pythonData{hadDB: true}
|
||||
|
||||
path := getStringParam(cmd.Flags(), "database")
|
||||
absPath, err := filepath.Abs(path)
|
||||
@@ -106,18 +122,24 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||
|
||||
log.Println("Using database: " + absPath)
|
||||
data.hadDB = exists
|
||||
db, err := storm.Open(path, storm.BoltOptions(files.PermFile, nil))
|
||||
checkErr(err)
|
||||
db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
data.store, err = bolt.NewStorage(db)
|
||||
checkErr(err)
|
||||
fn(cmd, args, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(cmd, args, data)
|
||||
}
|
||||
}
|
||||
|
||||
func marshal(filename string, data interface{}) error {
|
||||
fd, err := os.Create(filename)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
switch ext := filepath.Ext(filename); ext {
|
||||
@@ -125,7 +147,7 @@ func marshal(filename string, data interface{}) error {
|
||||
encoder := json.NewEncoder(fd)
|
||||
encoder.SetIndent("", " ")
|
||||
return encoder.Encode(data)
|
||||
case ".yml", ".yaml": //nolint:goconst
|
||||
case ".yml", ".yaml":
|
||||
encoder := yaml.NewEncoder(fd)
|
||||
return encoder.Encode(data)
|
||||
default:
|
||||
@@ -135,7 +157,9 @@ func marshal(filename string, data interface{}) error {
|
||||
|
||||
func unmarshal(filename string, data interface{}) error {
|
||||
fd, err := os.Open(filename)
|
||||
checkErr(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
switch ext := filepath.Ext(filename); ext {
|
||||
|
||||
@@ -37,11 +37,11 @@ func (f *FileCache) Store(_ context.Context, key string, value []byte) error {
|
||||
defer mu.Unlock()
|
||||
|
||||
fileName := f.getFileName(key)
|
||||
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil { //nolint:gomnd
|
||||
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil { //nolint:gomnd
|
||||
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
9
docker/alpine/healthcheck.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
PORT=${FB_PORT:-$(cat /config/settings.json | sh /JSON.sh | grep '\["port"\]' | awk '{print $2}')}
|
||||
ADDRESS=${FB_ADDRESS:-$(cat /config/settings.json | sh /JSON.sh | grep '\["address"\]' | awk '{print $2}' | sed 's/"//g')}
|
||||
ADDRESS=${ADDRESS:-localhost}
|
||||
|
||||
wget -q --spider http://$ADDRESS:$PORT/health || exit 1
|
||||
@@ -2,40 +2,34 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Backwards compatibility for old Docker image
|
||||
if [ -f "/.filebrowser.json" ]; then
|
||||
ln -s /.filebrowser.json /config/settings.json
|
||||
|
||||
echo ""
|
||||
echo "!!!!!!!!!!!!!!!!!!!!! IMPORTANT INFORMATION !!!!!!!!!!!!!!!!!!!!!"
|
||||
echo "Symlinking /.filebrowser.json to /config/settings.json for backwards compatibility."
|
||||
echo ""
|
||||
echo "The volume mount configuration has changed in the latest release."
|
||||
echo "Please rename .filebrowser.json to settings.json and mount the parent directory to /config".
|
||||
echo "Read more on https://github.com/filebrowser/filebrowser/blob/master/docs/installation.md#docker"
|
||||
echo ""
|
||||
echo "This workaround will be removed in a future release."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Backwards compatibility for old Docker image
|
||||
if [ -f "/database.db" ]; then
|
||||
ln -s /database.db /database/filebrowser.db
|
||||
|
||||
echo ""
|
||||
echo "!!!!!!!!!!!!!!!!!!!!! IMPORTANT INFORMATION !!!!!!!!!!!!!!!!!!!!!"
|
||||
echo ""
|
||||
echo "The volume mount configuration has changed in the latest release."
|
||||
echo "Please rename database.db to filebrowser.db and mount the parent directory to /database".
|
||||
echo "Read more on https://github.com/filebrowser/filebrowser/blob/master/docs/installation.md#docker"
|
||||
echo ""
|
||||
echo "This workaround will be removed in a future release."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Ensure configuration exists
|
||||
if [ ! -f "/config/settings.json" ]; then
|
||||
cp -a /defaults/settings.json /config/settings.json
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
# Extract config file path from arguments
|
||||
config_file=""
|
||||
next_is_config=0
|
||||
for arg in "$@"; do
|
||||
if [ "$next_is_config" -eq 1 ]; then
|
||||
config_file="$arg"
|
||||
break
|
||||
fi
|
||||
case "$arg" in
|
||||
-c|--config)
|
||||
next_is_config=1
|
||||
;;
|
||||
-c=*|--config=*)
|
||||
config_file="${arg#*=}"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If no config argument is provided, set the default and add it to the args
|
||||
if [ -z "$config_file" ]; then
|
||||
config_file="/config/settings.json"
|
||||
set -- --config=/config/settings.json "$@"
|
||||
fi
|
||||
|
||||
exec filebrowser "$@"
|
||||
|
||||
@@ -6,4 +6,4 @@ PORT=${FB_PORT:-$(jq -r .port /config/settings.json)}
|
||||
ADDRESS=${FB_ADDRESS:-$(jq -r .address /config/settings.json)}
|
||||
ADDRESS=${ADDRESS:-localhost}
|
||||
|
||||
curl -f http://$ADDRESS:$PORT/health || exit 1
|
||||
wget -q --spider http://$ADDRESS:$PORT/health || exit 1
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
# Installation
|
||||
|
||||
File Browser is a single binary and can be used as a standalone executable. Although, some might prefer to use it with [Docker](https://www.docker.com) or [Caddy](https://caddyserver.com), which is a fantastic web server that enables HTTPS by default. Its installation is quite straightforward independently on which system you want to use.
|
||||
|
||||
## Quick Setup
|
||||
|
||||
The quickest way for beginners to start using File Browser is by opening your terminal and executing the following commands:
|
||||
|
||||
### Brew
|
||||
|
||||
```sh
|
||||
brew tap filebrowser/tap
|
||||
brew install filebrowser
|
||||
filebrowser -r /path/to/your/files
|
||||
```
|
||||
|
||||
### Unix
|
||||
|
||||
```sh
|
||||
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
|
||||
filebrowser -r /path/to/your/files
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
```sh
|
||||
iwr -useb https://raw.githubusercontent.com/filebrowser/get/master/get.ps1 | iex
|
||||
filebrowser -r /path/to/your/files
|
||||
```
|
||||
|
||||
### Configuring
|
||||
|
||||
Done! It will bootstrap a database in which all the configurations and users are stored. Now, you can see on your command line the address in which your instance is running. You just need to go to that URL and use the following credentials:
|
||||
|
||||
* Username: `admin`
|
||||
* Password: (printed in your console)
|
||||
|
||||
Although this is the fastest way to bootstrap an instance, we recommend you to take a look at other possible options, by checking `config init --help` and `config set --help`, to make the installation as safe and customized as it can be.
|
||||
|
||||
## Docker
|
||||
|
||||
File Browser is available as two different Docker images, which can be found on [Docker Hub](https://hub.docker.com/r/filebrowser/filebrowser).
|
||||
|
||||
### Alpine
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
-v /path/to/srv:/srv \
|
||||
-v /path/to/database:/database \
|
||||
-v /path/to/config:/config \
|
||||
-p 8080:80 \
|
||||
filebrowser/filebrowser
|
||||
```
|
||||
|
||||
The default user has PID 1000 and GID 1000. Please make sure that this user has access to the different mounted volumes. To change the user running inside the Docker image, you need to use the [`--user` flag](https://docs.docker.com/engine/containers/run/#user).
|
||||
|
||||
### s6 overlay
|
||||
|
||||
The `s6` image is based on LinuxServer and leverages the [s6-overlay](https://github.com/just-containers/s6-overlay) system for a standard, highly customizable image. It should be used as follows:
|
||||
|
||||
```shell
|
||||
docker run \
|
||||
-v /path/to/srv:/srv \
|
||||
-v /path/to/database:/database \
|
||||
-v /path/to/config:/config \
|
||||
-e PUID=$(id -u) \
|
||||
-e PGID=$(id -g) \
|
||||
-p 8080:80 \
|
||||
filebrowser/filebrowser:s6
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
Where:
|
||||
|
||||
- `/path/to/srv` contains the files root directory for File Browser
|
||||
- `/path/to/config` contains a `settings.json` file
|
||||
- `/path/to/database` contains a `filebrowser.db` file
|
||||
|
||||
Both `settings.json` and `filebrowser.db` will automatically be initialized if they don't exist.
|
||||
@@ -1,12 +1,25 @@
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ExitCodeSigTerm = 128 + int(syscall.SIGTERM)
|
||||
ExitCodeSighup = 128 + int(syscall.SIGHUP)
|
||||
ExitCodeSigint = 128 + int(syscall.SIGINT)
|
||||
ExitCodeSigquit = 128 + int(syscall.SIGQUIT)
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyKey = errors.New("empty key")
|
||||
ErrExist = errors.New("the resource already exists")
|
||||
ErrNotExist = errors.New("the resource does not exist")
|
||||
ErrEmptyPassword = errors.New("password is empty")
|
||||
ErrEasyPassword = errors.New("password is too easy")
|
||||
ErrEmptyUsername = errors.New("username is empty")
|
||||
ErrEmptyRequest = errors.New("empty request")
|
||||
ErrScopeIsRelative = errors.New("scope is a relative path")
|
||||
@@ -18,4 +31,57 @@ var (
|
||||
ErrInvalidRequestParams = errors.New("invalid request params")
|
||||
ErrSourceIsParent = errors.New("source is parent")
|
||||
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
|
||||
ErrSigTerm = errors.New("exit on signal: sigterm")
|
||||
ErrSighup = errors.New("exit on signal: sighup")
|
||||
ErrSigint = errors.New("exit on signal: sigint")
|
||||
ErrSigquit = errors.New("exit on signal: sigquit")
|
||||
)
|
||||
|
||||
type ErrShortPassword struct {
|
||||
MinimumLength uint
|
||||
}
|
||||
|
||||
func (e ErrShortPassword) Error() string {
|
||||
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength)
|
||||
}
|
||||
|
||||
// GetExitCode returns the exit code for a given error.
|
||||
func GetExitCode(err error) int {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
exitCodeMap := map[error]int{
|
||||
ErrSigTerm: ExitCodeSigTerm,
|
||||
ErrSighup: ExitCodeSighup,
|
||||
ErrSigint: ExitCodeSigint,
|
||||
ErrSigquit: ExitCodeSigquit,
|
||||
}
|
||||
|
||||
for e, code := range exitCodeMap {
|
||||
if errors.Is(err, e) {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(interface{ ExitCode() int }); ok {
|
||||
return exitErr.ExitCode()
|
||||
}
|
||||
|
||||
var pathErr *os.PathError
|
||||
if errors.As(err, &pathErr) {
|
||||
return 1
|
||||
}
|
||||
|
||||
var syscallErr *os.SyscallError
|
||||
if errors.As(err, &syscallErr) {
|
||||
return 1
|
||||
}
|
||||
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -27,9 +27,6 @@ import (
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
)
|
||||
|
||||
const PermFile = 0640
|
||||
const PermDir = 0750
|
||||
|
||||
var (
|
||||
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
||||
reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
|
||||
@@ -86,6 +83,11 @@ func NewFileInfo(opts *FileOptions) (*FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Do not expose the name of root directory.
|
||||
if file.Path == "/" {
|
||||
file.Name = ""
|
||||
}
|
||||
|
||||
if opts.Expand {
|
||||
if file.IsDir {
|
||||
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
|
||||
@@ -217,7 +219,6 @@ func (i *FileInfo) RealPath() string {
|
||||
return i.Path
|
||||
}
|
||||
|
||||
//nolint:goconst
|
||||
func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
||||
if IsNamedPipe(i.Mode) {
|
||||
i.Type = "blob"
|
||||
@@ -314,7 +315,7 @@ func (i *FileInfo) readFirstBytes() []byte {
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
buffer := make([]byte, 512) //nolint:gomnd
|
||||
buffer := make([]byte, 512)
|
||||
n, err := reader.Read(buffer)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
log.Print(err)
|
||||
|
||||
@@ -16,8 +16,6 @@ type Listing struct {
|
||||
}
|
||||
|
||||
// ApplySort applies the sort order using .Order and .Sort
|
||||
//
|
||||
//nolint:goconst
|
||||
func (l Listing) ApplySort() {
|
||||
// Check '.Order' to know how to sort
|
||||
if !l.Sorting.Asc {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
@@ -8,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// Copy copies a file or folder from one place to another.
|
||||
func Copy(fs afero.Fs, src, dst string) error {
|
||||
func Copy(afs afero.Fs, src, dst string, fileMode, dirMode fs.FileMode) error {
|
||||
if src = path.Clean("/" + src); src == "" {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
@@ -26,14 +27,14 @@ func Copy(fs afero.Fs, src, dst string) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
info, err := fs.Stat(src)
|
||||
info, err := afs.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return CopyDir(fs, src, dst)
|
||||
return CopyDir(afs, src, dst, fileMode, dirMode)
|
||||
}
|
||||
|
||||
return CopyFile(fs, src, dst)
|
||||
return CopyFile(afs, src, dst, fileMode, dirMode)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package fileutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@@ -9,20 +10,20 @@ import (
|
||||
// CopyDir copies a directory from source to dest and all
|
||||
// of its sub-directories. It doesn't stop if it finds an error
|
||||
// during the copy. Returns an error if any.
|
||||
func CopyDir(fs afero.Fs, source, dest string) error {
|
||||
func CopyDir(afs afero.Fs, source, dest string, fileMode, dirMode fs.FileMode) error {
|
||||
// Get properties of source.
|
||||
srcinfo, err := fs.Stat(source)
|
||||
srcinfo, err := afs.Stat(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the destination directory.
|
||||
err = fs.MkdirAll(dest, srcinfo.Mode())
|
||||
err = afs.MkdirAll(dest, srcinfo.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir, _ := fs.Open(source)
|
||||
dir, _ := afs.Open(source)
|
||||
obs, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -36,13 +37,13 @@ func CopyDir(fs afero.Fs, source, dest string) error {
|
||||
|
||||
if obj.IsDir() {
|
||||
// Create sub-directories, recursively.
|
||||
err = CopyDir(fs, fsource, fdest)
|
||||
err = CopyDir(afs, fsource, fdest, fileMode, dirMode)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
} else {
|
||||
// Perform the file copy.
|
||||
err = CopyFile(fs, fsource, fdest)
|
||||
err = CopyFile(afs, fsource, fdest, fileMode, dirMode)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
@@ -2,29 +2,28 @@ package fileutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/files"
|
||||
)
|
||||
|
||||
// MoveFile moves file from src to dst.
|
||||
// By default the rename filesystem system call is used. If src and dst point to different volumes
|
||||
// the file copy is used as a fallback
|
||||
func MoveFile(fs afero.Fs, src, dst string) error {
|
||||
if fs.Rename(src, dst) == nil {
|
||||
func MoveFile(afs afero.Fs, src, dst string, fileMode, dirMode fs.FileMode) error {
|
||||
if afs.Rename(src, dst) == nil {
|
||||
return nil
|
||||
}
|
||||
// fallback
|
||||
err := Copy(fs, src, dst)
|
||||
err := Copy(afs, src, dst, fileMode, dirMode)
|
||||
if err != nil {
|
||||
_ = fs.Remove(dst)
|
||||
_ = afs.Remove(dst)
|
||||
return err
|
||||
}
|
||||
if err := fs.RemoveAll(src); err != nil {
|
||||
if err := afs.RemoveAll(src); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -32,9 +31,9 @@ func MoveFile(fs afero.Fs, src, dst string) error {
|
||||
|
||||
// CopyFile copies a file from source to dest and returns
|
||||
// an error if any.
|
||||
func CopyFile(fs afero.Fs, source, dest string) error {
|
||||
func CopyFile(afs afero.Fs, source, dest string, fileMode, dirMode fs.FileMode) error {
|
||||
// Open the source file.
|
||||
src, err := fs.Open(source)
|
||||
src, err := afs.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,13 +41,13 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
||||
|
||||
// Makes the directory needed to create the dst
|
||||
// file.
|
||||
err = fs.MkdirAll(filepath.Dir(dest), files.PermDir)
|
||||
err = afs.MkdirAll(filepath.Dir(dest), dirMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the destination file.
|
||||
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, files.PermFile)
|
||||
dst, err := afs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,11 +60,11 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
||||
}
|
||||
|
||||
// Copy the mode
|
||||
info, err := fs.Stat(source)
|
||||
info, err := afs.Stat(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fs.Chmod(dest, info.Mode())
|
||||
err = afs.Chmod(dest, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import pluginVue from "eslint-plugin-vue";
|
||||
import vueTsEslintConfig from "@vue/eslint-config-typescript";
|
||||
import {
|
||||
defineConfigWithVueTs,
|
||||
vueTsConfigs,
|
||||
} from "@vue/eslint-config-typescript";
|
||||
import prettierConfig from "@vue/eslint-config-prettier";
|
||||
|
||||
export default [
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
name: "app/files-to-lint",
|
||||
files: ["**/*.{ts,mts,tsx,vue}"],
|
||||
},
|
||||
|
||||
{
|
||||
name: "app/files-to-ignore",
|
||||
ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
|
||||
},
|
||||
|
||||
...pluginVue.configs["flat/essential"],
|
||||
...vueTsEslintConfig(),
|
||||
pluginVue.configs["flat/essential"],
|
||||
vueTsConfigs.recommended,
|
||||
prettierConfig,
|
||||
|
||||
{
|
||||
rules: {
|
||||
// Note: you must disable the base rule as it can report incorrect errors
|
||||
"no-unused-expressions": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "off",
|
||||
// TODO: theres too many of these from before ts
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
@@ -34,5 +33,5 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
@@ -10,18 +10,10 @@
|
||||
|
||||
<title>File Browser</title>
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/img/icons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/img/icons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/img/icons/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/icons/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
||||
|
||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||
<link
|
||||
@@ -31,19 +23,6 @@
|
||||
/>
|
||||
<meta name="theme-color" content="#2979ff" />
|
||||
|
||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-title" content="assets" />
|
||||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon.png" />
|
||||
|
||||
<!-- Add to home screen for Windows -->
|
||||
<meta
|
||||
name="msapplication-TileImage"
|
||||
content="/img/icons/mstile-144x144.png"
|
||||
/>
|
||||
<meta name="msapplication-TileColor" content="#2979ff" />
|
||||
|
||||
<!-- Inject Some Variables and generate the manifest json -->
|
||||
<script>
|
||||
// We can assign JSON directly
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
"@chenfengyuan/vue-number-input": "^2.0.1",
|
||||
"@vueuse/core": "^12.5.0",
|
||||
"@vueuse/integrations": "^12.5.0",
|
||||
"ace-builds": "^1.37.5",
|
||||
"core-js": "^3.40.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"ace-builds": "^1.43.2",
|
||||
"core-js": "^3.44.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dompurify": "^3.2.6",
|
||||
"epubjs": "^0.3.93",
|
||||
"filesize": "^10.1.1",
|
||||
@@ -31,45 +31,46 @@
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^15.0.6",
|
||||
"material-icons": "^1.13.13",
|
||||
"material-icons": "^1.13.14",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pinia": "^2.3.1",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"tus-js-client": "^4.3.1",
|
||||
"utif": "^3.1.0",
|
||||
"video.js": "^8.21.0",
|
||||
"video.js": "^8.23.3",
|
||||
"videojs-hotkeys": "^0.2.28",
|
||||
"videojs-mobile-ui": "^1.1.1",
|
||||
"vue": "^3.4.21",
|
||||
"vue-final-modal": "^4.5.4",
|
||||
"vue-i18n": "^11.1.2",
|
||||
"vue": "^3.5.17",
|
||||
"vue-final-modal": "^4.5.5",
|
||||
"vue-i18n": "^11.1.10",
|
||||
"vue-lazyload": "^3.0.0",
|
||||
"vue-reader": "^1.2.17",
|
||||
"vue-router": "^4.3.0",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue-toastification": "^2.0.0-rc.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.10.10",
|
||||
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@vitejs/plugin-legacy": "^6.0.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.3.0",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"concurrently": "^9.2.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-vue": "^9.24.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"postcss": "^8.5.1",
|
||||
"prettier": "^3.4.2",
|
||||
"terser": "^5.37.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"terser": "^5.43.1",
|
||||
"vite": "^6.1.6",
|
||||
"vite-plugin-compression2": "^1.0.0",
|
||||
"vue-tsc": "^2.2.0"
|
||||
|
||||
1082
frontend/pnpm-lock.yaml
generated
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 8.3 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#455a64</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 843 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
3
frontend/public/img/icons/favicon.svg
Normal file
|
After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M3245 6989 c-522 -39 -1042 -197 -1480 -449 -849 -488 -1459 -1308
|
||||
-1673 -2250 -177 -776 -89 -1582 250 -2301 368 -778 1052 -1418 1857 -1739
|
||||
903 -359 1927 -325 2812 92 778 368 1418 1052 1739 1857 359 903 325 1927 -92
|
||||
2812 -296 627 -806 1175 -1423 1529 -587 338 -1308 500 -1990 449z m555 -580
|
||||
c519 -51 1018 -245 1446 -565 788 -588 1229 -1526 1174 -2496 -16 -277 -58
|
||||
-500 -145 -763 -144 -440 -378 -819 -710 -1150 -452 -452 -1005 -730 -1655
|
||||
-832 -91 -14 -175 -18 -405 -18 -304 0 -369 6 -595 51 -1105 223 -1999 1092
|
||||
-2259 2197 -52 221 -73 412 -73 667 0 397 64 732 204 1080 304 752 886 1334
|
||||
1638 1638 431 174 895 238 1380 191z"/>
|
||||
<path d="M2670 5215 c0 -13 -44 -15 -335 -15 -352 0 -383 -3 -399 -45 -3 -9
|
||||
-6 -758 -6 -1663 0 -1168 -3 -1643 -11 -1632 -8 11 -9 8 -4 -15 3 -16 17 -41
|
||||
31 -55 l24 -25 1530 0 1530 0 24 25 c14 14 26 36 27 50 1 14 1 711 1 1550 l-2
|
||||
1526 -228 142 -229 142 -136 0 -137 0 0 -600 0 -600 -705 0 -705 0 0 615 0
|
||||
615 -135 0 c-113 0 -135 -2 -135 -15z m-264 -190 c57 -29 89 -71 103 -137 35
|
||||
-154 -98 -282 -258 -247 -55 12 -122 62 -148 113 -36 69 -12 186 49 243 62 58
|
||||
170 70 254 28z m2316 -1702 c17 -15 18 -49 18 -670 l0 -653 -1245 0 -1245 0 0
|
||||
654 c0 582 2 656 16 670 14 14 139 16 1226 16 1113 0 1213 -1 1230 -17z
|
||||
m-2602 -1363 c40 -40 13 -100 -43 -100 -60 0 -88 59 -47 100 11 11 31 20 45
|
||||
20 14 0 34 -9 45 -20z m2840 0 c41 -41 11 -100 -52 -100 -35 0 -58 24 -58 60
|
||||
0 54 71 79 110 40z"/>
|
||||
<path d="M2431 3091 c-7 -13 -7 -23 2 -35 11 -15 97 -16 1067 -14 l1055 3 0
|
||||
30 0 30 -1057 3 c-1023 2 -1058 1 -1067 -17z"/>
|
||||
<path d="M2436 2675 c-19 -19 -11 -41 17 -49 41 -11 2067 -7 2088 4 23 13 25
|
||||
46 3 54 -9 3 -483 6 -1054 6 -919 0 -1040 -2 -1054 -15z"/>
|
||||
<path d="M2447 2273 c-14 -4 -17 -13 -15 -36 l3 -32 1049 -3 c767 -1 1052 1
|
||||
1062 9 20 16 17 47 -5 59 -20 10 -2055 13 -2094 3z"/>
|
||||
<path d="M3822 5027 c-21 -23 -22 -30 -22 -293 0 -258 1 -271 20 -292 27 -29
|
||||
56 -35 140 -30 56 3 75 8 93 26 22 22 22 26 22 298 l0 276 -24 19 c-19 16 -40
|
||||
19 -115 19 -84 0 -95 -2 -114 -23z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -18,18 +18,10 @@
|
||||
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="[{[ .StaticURL ]}]/img/icons/favicon.svg" />
|
||||
<link rel="shortcut icon" href="[{[ .StaticURL ]}]/img/icons/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
||||
|
||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||
<link
|
||||
@@ -42,25 +34,6 @@
|
||||
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
||||
/>
|
||||
|
||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-title" content="assets" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
|
||||
/>
|
||||
|
||||
<!-- Add to home screen for Windows -->
|
||||
<meta
|
||||
name="msapplication-TileImage"
|
||||
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"
|
||||
/>
|
||||
<meta
|
||||
name="msapplication-TileColor"
|
||||
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
||||
/>
|
||||
|
||||
<!-- Inject Some Variables and generate the manifest json -->
|
||||
<script>
|
||||
// We can assign JSON directly
|
||||
|
||||
@@ -2,14 +2,22 @@ import { useAuthStore } from "@/stores/auth";
|
||||
import { useLayoutStore } from "@/stores/layout";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import { upload as postTus, useTus } from "./tus";
|
||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||
import { createURL, fetchURL, removePrefix, StatusError } from "./utils";
|
||||
|
||||
export async function fetch(url: string) {
|
||||
export async function fetch(url: string, signal?: AbortSignal) {
|
||||
url = removePrefix(url);
|
||||
const res = await fetchURL(`/api/resources${url}`, { signal });
|
||||
|
||||
const res = await fetchURL(`/api/resources${url}`, {});
|
||||
|
||||
const data = (await res.json()) as Resource;
|
||||
let data: Resource;
|
||||
try {
|
||||
data = (await res.json()) as Resource;
|
||||
} catch (e) {
|
||||
// Check if the error is an intentional cancellation
|
||||
if (e instanceof Error && e.name === "AbortError") {
|
||||
throw new StatusError("000 No connection", 0, true);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
data.url = `/files${url}`;
|
||||
|
||||
if (data.isDir) {
|
||||
@@ -205,10 +213,18 @@ export function getSubtitlesURL(file: ResourceItem) {
|
||||
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
|
||||
}
|
||||
|
||||
export async function usage(url: string) {
|
||||
export async function usage(url: string, signal: AbortSignal) {
|
||||
url = removePrefix(url);
|
||||
|
||||
const res = await fetchURL(`/api/usage${url}`, {});
|
||||
const res = await fetchURL(`/api/usage${url}`, { signal });
|
||||
|
||||
return await res.json();
|
||||
try {
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
// Check if the error is an intentional cancellation
|
||||
if (e instanceof Error && e.name == "AbortError") {
|
||||
throw new StatusError("000 No connection", 0, true);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as tus from "tus-js-client";
|
||||
import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
|
||||
import { baseURL, tusEndpoint, tusSettings, origin } from "@/utils/constants";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useUploadStore } from "@/stores/upload";
|
||||
import { removePrefix } from "@/api/utils";
|
||||
import { fetchURL } from "./utils";
|
||||
|
||||
const RETRY_BASE_DELAY = 1000;
|
||||
const RETRY_MAX_DELAY = 20000;
|
||||
@@ -28,8 +27,6 @@ export async function upload(
|
||||
filePath = removePrefix(filePath);
|
||||
const resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`;
|
||||
|
||||
await createUpload(resourcePath);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// Exit early because of typescript, tus content can't be a string
|
||||
@@ -38,7 +35,7 @@ export async function upload(
|
||||
}
|
||||
return new Promise<void | string>((resolve, reject) => {
|
||||
const upload = new tus.Upload(content, {
|
||||
uploadUrl: `${baseURL}${resourcePath}`,
|
||||
endpoint: `${origin}${baseURL}${resourcePath}`,
|
||||
chunkSize: tusSettings.chunkSize,
|
||||
retryDelays: computeRetryDelays(tusSettings),
|
||||
parallelUploads: 1,
|
||||
@@ -46,6 +43,18 @@ export async function upload(
|
||||
headers: {
|
||||
"X-Auth": authStore.jwt,
|
||||
},
|
||||
onShouldRetry: function (err) {
|
||||
const status = err.originalResponse
|
||||
? err.originalResponse.getStatus()
|
||||
: 0;
|
||||
|
||||
// Do not retry for file conflict.
|
||||
if (status === 409) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
onError: function (error) {
|
||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||
@@ -92,17 +101,6 @@ export async function upload(
|
||||
});
|
||||
}
|
||||
|
||||
async function createUpload(resourcePath: string) {
|
||||
const headResp = await fetchURL(resourcePath, {
|
||||
method: "POST",
|
||||
});
|
||||
if (headResp.status !== 201) {
|
||||
throw new Error(
|
||||
`Failed to create an upload: ${headResp.status} ${headResp.statusText}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function computeRetryDelays(tusSettings: TusSettings): number[] | undefined {
|
||||
if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
|
||||
// Disable retries altogether
|
||||
@@ -130,7 +128,8 @@ function isTusSupported() {
|
||||
return tus.isSupported === true;
|
||||
}
|
||||
|
||||
function computeETA(state: ETAState, speed?: number) {
|
||||
function computeETA(speed?: number) {
|
||||
const state = useUploadStore();
|
||||
if (state.speedMbyte === 0) {
|
||||
return Infinity;
|
||||
}
|
||||
@@ -138,22 +137,13 @@ function computeETA(state: ETAState, speed?: number) {
|
||||
(acc: number, size: number) => acc + size,
|
||||
0
|
||||
);
|
||||
const uploadedSize = state.progress.reduce(
|
||||
(acc: number, progress: Progress) => {
|
||||
if (typeof progress === "number") {
|
||||
return acc + progress;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
0
|
||||
);
|
||||
const uploadedSize = state.progress.reduce((a, b) => a + b, 0);
|
||||
const remainingSize = totalSize - uploadedSize;
|
||||
const speedBytesPerSecond = (speed ?? state.speedMbyte) * 1024 * 1024;
|
||||
return remainingSize / speedBytesPerSecond;
|
||||
}
|
||||
|
||||
function computeGlobalSpeedAndETA() {
|
||||
const uploadStore = useUploadStore();
|
||||
let totalSpeed = 0;
|
||||
let totalCount = 0;
|
||||
|
||||
@@ -165,7 +155,7 @@ function computeGlobalSpeedAndETA() {
|
||||
if (totalCount === 0) return { speed: 0, eta: Infinity };
|
||||
|
||||
const averageSpeed = totalSpeed / totalCount;
|
||||
const averageETA = computeETA(uploadStore, averageSpeed);
|
||||
const averageETA = computeETA(averageSpeed);
|
||||
|
||||
return { speed: averageSpeed, eta: averageETA };
|
||||
}
|
||||
@@ -207,6 +197,9 @@ export function abortAllUploads() {
|
||||
}
|
||||
if (CURRENT_UPLOAD_LIST[filePath].upload) {
|
||||
CURRENT_UPLOAD_LIST[filePath].upload.abort(true);
|
||||
CURRENT_UPLOAD_LIST[filePath].upload.options!.onError!(
|
||||
new Error("Upload aborted")
|
||||
);
|
||||
}
|
||||
delete CURRENT_UPLOAD_LIST[filePath];
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import { encodePath } from "@/utils/url";
|
||||
export class StatusError extends Error {
|
||||
constructor(
|
||||
message: any,
|
||||
public status?: number
|
||||
public status?: number,
|
||||
public is_canceled?: boolean
|
||||
) {
|
||||
super(message);
|
||||
this.name = "StatusError";
|
||||
@@ -33,7 +34,11 @@ export async function fetchURL(
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
} catch {
|
||||
} catch (e) {
|
||||
// Check if the error is an intentional cancellation
|
||||
if (e instanceof Error && e.name === "AbortError") {
|
||||
throw new StatusError("000 No connection", 0, true);
|
||||
}
|
||||
throw new StatusError("000 No connection", 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,8 @@ export default {
|
||||
style["position"] = "absolute";
|
||||
style["top"] = "0";
|
||||
style["height"] = "100%";
|
||||
(style["min-height"] = this.size_px + "px"), (style["z-index"] = "-1");
|
||||
((style["min-height"] = this.size_px + "px"),
|
||||
(style["z-index"] = "-1"));
|
||||
}
|
||||
|
||||
return style;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<div v-show="active" @click="closeHovers" class="overlay"></div>
|
||||
<nav :class="{ active }">
|
||||
<template v-if="isLoggedIn">
|
||||
<button @click="toAccountSettings" class="action">
|
||||
<i class="material-icons">person</i>
|
||||
<span>{{ user.username }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="action"
|
||||
@click="toRoot"
|
||||
@@ -34,29 +38,28 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div v-if="user.perm.admin">
|
||||
<button
|
||||
class="action"
|
||||
@click="toSettings"
|
||||
@click="toGlobalSettings"
|
||||
:aria-label="$t('sidebar.settings')"
|
||||
:title="$t('sidebar.settings')"
|
||||
>
|
||||
<i class="material-icons">settings_applications</i>
|
||||
<span>{{ $t("sidebar.settings") }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="canLogout"
|
||||
@click="logout"
|
||||
class="action"
|
||||
id="logout"
|
||||
:aria-label="$t('sidebar.logout')"
|
||||
:title="$t('sidebar.logout')"
|
||||
>
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
<span>{{ $t("sidebar.logout") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="canLogout"
|
||||
@click="logout"
|
||||
class="action"
|
||||
id="logout"
|
||||
:aria-label="$t('sidebar.logout')"
|
||||
:title="$t('sidebar.logout')"
|
||||
>
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
<span>{{ $t("sidebar.logout") }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link
|
||||
@@ -129,6 +132,7 @@ import {
|
||||
import { files as api } from "@/api";
|
||||
import ProgressBar from "@/components/ProgressBar.vue";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import { StatusError } from "@/api/utils.js";
|
||||
|
||||
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||
|
||||
@@ -136,7 +140,7 @@ export default {
|
||||
name: "sidebar",
|
||||
setup() {
|
||||
const usage = reactive(USAGE_DEFAULT);
|
||||
return { usage };
|
||||
return { usage, usageAbortController: new AbortController() };
|
||||
},
|
||||
components: {
|
||||
ProgressBar,
|
||||
@@ -157,6 +161,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
||||
abortOngoingFetchUsage() {
|
||||
this.usageAbortController.abort();
|
||||
},
|
||||
async fetchUsage() {
|
||||
const path = this.$route.path.endsWith("/")
|
||||
? this.$route.path
|
||||
@@ -166,13 +173,18 @@ export default {
|
||||
return Object.assign(this.usage, usageStats);
|
||||
}
|
||||
try {
|
||||
const usage = await api.usage(path);
|
||||
this.abortOngoingFetchUsage();
|
||||
this.usageAbortController = new AbortController();
|
||||
const usage = await api.usage(path, this.usageAbortController.signal);
|
||||
usageStats = {
|
||||
used: prettyBytes(usage.used, { binary: true }),
|
||||
total: prettyBytes(usage.total, { binary: true }),
|
||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof StatusError && error.is_canceled) {
|
||||
return;
|
||||
}
|
||||
this.$showError(error);
|
||||
}
|
||||
return Object.assign(this.usage, usageStats);
|
||||
@@ -181,8 +193,12 @@ export default {
|
||||
this.$router.push({ path: "/files" });
|
||||
this.closeHovers();
|
||||
},
|
||||
toSettings() {
|
||||
this.$router.push({ path: "/settings" });
|
||||
toAccountSettings() {
|
||||
this.$router.push({ path: "/settings/profile" });
|
||||
this.closeHovers();
|
||||
},
|
||||
toGlobalSettings() {
|
||||
this.$router.push({ path: "/settings/global" });
|
||||
this.closeHovers();
|
||||
},
|
||||
help() {
|
||||
@@ -200,5 +216,8 @@ export default {
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
unmounted() {
|
||||
this.abortOngoingFetchUsage();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -172,7 +172,8 @@ const setCenter = () => {
|
||||
imgex.value.style.top = position.value.center.y + "px";
|
||||
};
|
||||
|
||||
const mousedownStart = (event: Event) => {
|
||||
const mousedownStart = (event: MouseEvent) => {
|
||||
if (event.button !== 0) return;
|
||||
lastX.value = null;
|
||||
lastY.value = null;
|
||||
inDrag.value = true;
|
||||
@@ -184,8 +185,10 @@ const mouseMove = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
const mouseUp = (event: Event) => {
|
||||
if (inDrag.value) {
|
||||
event.preventDefault();
|
||||
}
|
||||
inDrag.value = false;
|
||||
event.preventDefault();
|
||||
};
|
||||
const touchStart = (event: TouchEvent) => {
|
||||
lastX.value = null;
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
@dragover="dragOver"
|
||||
@drop="drop"
|
||||
@click="itemClick"
|
||||
@mousedown="handleMouseDown"
|
||||
@mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseLeave"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd"
|
||||
@touchcancel="handleTouchCancel"
|
||||
@touchmove="handleTouchMove"
|
||||
:data-dir="isDir"
|
||||
:data-type="type"
|
||||
:aria-label="name"
|
||||
@@ -50,6 +57,12 @@ import { useRouter } from "vue-router";
|
||||
|
||||
const touches = ref<number>(0);
|
||||
|
||||
const longPressTimer = ref<number | null>(null);
|
||||
const longPressTriggered = ref<boolean>(false);
|
||||
const longPressDelay = ref<number>(500);
|
||||
const startPosition = ref<{ x: number; y: number } | null>(null);
|
||||
const moveThreshold = ref<number>(10);
|
||||
|
||||
const $showError = inject<IToastError>("$showError")!;
|
||||
const router = useRouter();
|
||||
|
||||
@@ -209,6 +222,12 @@ const drop = async (event: Event) => {
|
||||
};
|
||||
|
||||
const itemClick = (event: Event | KeyboardEvent) => {
|
||||
// If long press was triggered, prevent normal click behavior
|
||||
if (longPressTriggered.value) {
|
||||
longPressTriggered.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
singleClick.value &&
|
||||
!(event as KeyboardEvent).ctrlKey &&
|
||||
@@ -281,4 +300,76 @@ const getExtension = (fileName: string): string => {
|
||||
}
|
||||
return fileName.substring(lastDotIndex);
|
||||
};
|
||||
|
||||
// Long-press helper functions
|
||||
const startLongPress = (clientX: number, clientY: number) => {
|
||||
startPosition.value = { x: clientX, y: clientY };
|
||||
longPressTimer.value = window.setTimeout(() => {
|
||||
handleLongPress();
|
||||
}, longPressDelay.value);
|
||||
};
|
||||
|
||||
const cancelLongPress = () => {
|
||||
if (longPressTimer.value !== null) {
|
||||
window.clearTimeout(longPressTimer.value);
|
||||
longPressTimer.value = null;
|
||||
}
|
||||
startPosition.value = null;
|
||||
};
|
||||
|
||||
const handleLongPress = () => {
|
||||
if (singleClick.value) {
|
||||
longPressTriggered.value = true;
|
||||
click(new Event("longpress"));
|
||||
}
|
||||
cancelLongPress();
|
||||
};
|
||||
|
||||
const checkMovement = (clientX: number, clientY: number): boolean => {
|
||||
if (!startPosition.value) return false;
|
||||
|
||||
const deltaX = Math.abs(clientX - startPosition.value.x);
|
||||
const deltaY = Math.abs(clientY - startPosition.value.y);
|
||||
|
||||
return deltaX > moveThreshold.value || deltaY > moveThreshold.value;
|
||||
};
|
||||
|
||||
// Event handlers
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
if (event.button === 0) {
|
||||
startLongPress(event.clientX, event.clientY);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
cancelLongPress();
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
cancelLongPress();
|
||||
};
|
||||
|
||||
const handleTouchStart = (event: TouchEvent) => {
|
||||
if (event.touches.length === 1) {
|
||||
const touch = event.touches[0];
|
||||
startLongPress(touch.clientX, touch.clientY);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
cancelLongPress();
|
||||
};
|
||||
|
||||
const handleTouchCancel = () => {
|
||||
cancelLongPress();
|
||||
};
|
||||
|
||||
const handleTouchMove = (event: TouchEvent) => {
|
||||
if (event.touches.length === 1 && startPosition.value) {
|
||||
const touch = event.touches[0];
|
||||
if (checkMovement(touch.clientX, touch.clientY)) {
|
||||
cancelLongPress();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -36,5 +36,7 @@ const formats = {
|
||||
tarxz: "tar.xz",
|
||||
tarlz4: "tar.lz4",
|
||||
tarsz: "tar.sz",
|
||||
tarbr: "tar.br",
|
||||
tarzst: "tar.zst",
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -31,9 +31,16 @@ import { useFileStore } from "@/stores/file";
|
||||
|
||||
import url from "@/utils/url";
|
||||
import { files } from "@/api";
|
||||
import { StatusError } from "@/api/utils.js";
|
||||
|
||||
export default {
|
||||
name: "file-list",
|
||||
props: {
|
||||
exclude: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
items: [],
|
||||
@@ -43,6 +50,7 @@ export default {
|
||||
},
|
||||
selected: null,
|
||||
current: window.location.pathname,
|
||||
nextAbortController: new AbortController(),
|
||||
};
|
||||
},
|
||||
inject: ["$showError"],
|
||||
@@ -56,7 +64,13 @@ export default {
|
||||
mounted() {
|
||||
this.fillOptions(this.req);
|
||||
},
|
||||
unmounted() {
|
||||
this.abortOngoingNext();
|
||||
},
|
||||
methods: {
|
||||
abortOngoingNext() {
|
||||
this.nextAbortController.abort();
|
||||
},
|
||||
fillOptions(req) {
|
||||
// Sets the current path and resets
|
||||
// the current items.
|
||||
@@ -82,6 +96,7 @@ export default {
|
||||
// move options.
|
||||
for (const item of req.items) {
|
||||
if (!item.isDir) continue;
|
||||
if (this.exclude?.includes(item.url)) continue;
|
||||
|
||||
this.items.push({
|
||||
name: item.name,
|
||||
@@ -94,8 +109,17 @@ export default {
|
||||
// just clicked in and fill the options with its
|
||||
// content.
|
||||
const uri = event.currentTarget.dataset.url;
|
||||
|
||||
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
|
||||
this.abortOngoingNext();
|
||||
this.nextAbortController = new AbortController();
|
||||
files
|
||||
.fetch(uri, this.nextAbortController.signal)
|
||||
.then(this.fillOptions)
|
||||
.catch((e) => {
|
||||
if (e instanceof StatusError && e.is_canceled) {
|
||||
return;
|
||||
}
|
||||
this.$showError(e);
|
||||
});
|
||||
},
|
||||
touchstart(event) {
|
||||
const url = event.currentTarget.dataset.url;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<file-list
|
||||
ref="fileList"
|
||||
@update:selected="(val) => (dest = val)"
|
||||
:exclude="excludedFolders"
|
||||
tabindex="1"
|
||||
/>
|
||||
</div>
|
||||
@@ -76,6 +77,11 @@ export default {
|
||||
computed: {
|
||||
...mapState(useFileStore, ["req", "selected"]),
|
||||
...mapState(useAuthStore, ["user"]),
|
||||
excludedFolders() {
|
||||
return this.selected
|
||||
.filter((idx) => this.req.items[idx].isDir)
|
||||
.map((idx) => this.req.items[idx].url);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
||||
|
||||
@@ -32,16 +32,6 @@
|
||||
<i class="material-icons">content_paste</i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="small" v-if="hasDownloadLink()">
|
||||
<button
|
||||
class="action copy-clipboard"
|
||||
:aria-label="$t('buttons.copyDownloadLinkToClipboard')"
|
||||
:title="$t('buttons.copyDownloadLinkToClipboard')"
|
||||
@click="copyToClipboard(buildDownloadLink(link))"
|
||||
>
|
||||
<i class="material-icons">content_paste_go</i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button
|
||||
class="action"
|
||||
@@ -142,7 +132,7 @@
|
||||
<script>
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useFileStore } from "@/stores/file";
|
||||
import { share as api, pub as pub_api } from "@/api";
|
||||
import { share as api } from "@/api";
|
||||
import dayjs from "dayjs";
|
||||
import { useLayoutStore } from "@/stores/layout";
|
||||
import { copy } from "@/utils/clipboard";
|
||||
@@ -257,14 +247,6 @@ export default {
|
||||
buildLink(share) {
|
||||
return api.getShareURL(share);
|
||||
},
|
||||
hasDownloadLink() {
|
||||
return (
|
||||
this.selected.length === 1 && !this.req.items[this.selected[0]].isDir
|
||||
);
|
||||
},
|
||||
buildDownloadLink(share) {
|
||||
return pub_api.getDownloadURL(share);
|
||||
},
|
||||
sort() {
|
||||
this.links = this.links.sort((a, b) => {
|
||||
if (a.expire === 0) return -1;
|
||||
|
||||
@@ -195,10 +195,6 @@ html[dir="rtl"] #listing {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#listing.list .item p.name:not(#listing.list .item.header .name) {
|
||||
margin-right: -3em;
|
||||
}
|
||||
|
||||
#listing.list .item .name {
|
||||
width: 50%;
|
||||
}
|
||||
@@ -227,18 +223,18 @@ html[dir="rtl"] #listing {
|
||||
border-bottom: 1px solid var(--borderPrimary);
|
||||
}
|
||||
|
||||
#listing.list .item.header > div:first-child {
|
||||
width: 0;
|
||||
#listing.list .item.header > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#listing.list .item.header .name {
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
#listing.list .header a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#listing.list .item.header > div:first-child {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#listing.list .name {
|
||||
font-weight: normal;
|
||||
word-wrap: break-word;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"ok": "موافق",
|
||||
"permalink": "الحصول على رابط دائم",
|
||||
"previous": "السابق",
|
||||
"preview": "Preview",
|
||||
"publish": "نشر",
|
||||
"rename": "إعادة تسمية",
|
||||
"replace": "استبدال",
|
||||
@@ -169,6 +170,7 @@
|
||||
"commandRunnerHelp": "هنا بإمكانك تعيين اﻷوامر التي سيتم تنفيذها في اﻷحداث المسماة. يجب كتابة أمر واحد في كل سطر. ستكون المتغيرات البيئية (env) {0} و {1} متاحة، حيث {0} نسبي لـ {1}. لمزيد من المعلومات حول هذه الميزة و المتغيرات البيئية المتاحة، يرجى قراءة {2}.",
|
||||
"commandsUpdated": "تم تحديث اﻷوامر",
|
||||
"createUserDir": "إنشاء مجلد المستخدم (home) تلقائياً عند إنشاء مستخدم جديد",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "التحميلات المتقطعة",
|
||||
"tusUploadsHelp": "يدعم متصفح الملفات تحميل الملفات المتقطعة، مما يسمح بتحميلات الملفات بشكل فعال و موثوق و قابلة للمتابغة و متقطعة حتى على الشبكات غير الموثوقة.",
|
||||
"tusUploadsChunkSize": "يشير إلى الحد اﻷقصى لحجم الطلب (سيتم استخدام التحميل المباشر للتحميلات صغيرة الخحم). يمكنك إدخال عدد صحيح عادي يدل على الحجم بوحدة البايت أو نمظ مثل10MB, 1GB, إلخ.",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"ok": "D'acord",
|
||||
"permalink": "Enllaç permanent",
|
||||
"previous": "Anterior",
|
||||
"preview": "Preview",
|
||||
"publish": "Publicar",
|
||||
"rename": "Reanomenar",
|
||||
"replace": "Substituir",
|
||||
@@ -169,6 +170,7 @@
|
||||
"commandRunnerHelp": "Aquí pots establir les comandes que s'executen en els esdeveniments anomenats. Has d'escriure'n una per línia. Les variables d'entorn {0} i {1} estaran disponibles, sent {0} relativa a {1}. Per a més informació sobre aquesta característica i les variables d'entorn disponibles, si us plau llegeix el {2}.",
|
||||
"commandsUpdated": "Comandes actualitzades!",
|
||||
"createUserDir": "Crea automàticament una carpeta d'inici quan s'afegeix un usuari",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Càrregues a trossos",
|
||||
"tusUploadsHelp": "El File Browser suporta càrregues de fitxers a trossos, permetent la creació de càrregues de fitxers eficients, fiables, reanudables i a trossos fins i tot en xarxes poc fiables.",
|
||||
"tusUploadsChunkSize": "Indica la mida màxima d'una sol·licitud (s'utilitzaran càrregues directes per a càrregues més petites). Podeu introduir un enter pla que indiqui la mida en bytes o una cadena com 10MB, 1GB, etc.",
|
||||
|
||||
@@ -170,6 +170,7 @@
|
||||
"commandRunnerHelp": "Zde můžete nastavit příkazy, které se spustí při určených událostech. Každý příkaz musí být na samostatném řádku. Budou k dispozici proměnné prostředí {0} a {1}, přičemž {0} se vztahuje na {1}. Pro více informací o této funkci a dostupných proměnných prostředí si přečtěte {2}.",
|
||||
"commandsUpdated": "Příkazy aktualizovány!",
|
||||
"createUserDir": "Automaticky vytvořit domovskou složku uživatele při přidání nového uživatele",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Nahrávání po částech",
|
||||
"tusUploadsHelp": "File Browser podporuje nahrávání souborů po částech, což umožňuje vytváření efektivních, spolehlivých, obnovitelných a rozdělených nahrávek souborů i na nespolehlivých sítích.",
|
||||
"tusUploadsChunkSize": "Maximální velikost požadavku (přímé nahrávání bude použito pro menší nahrávky). Můžete zadat prosté číslo označující velikost v bajtech nebo řetězec jako 10MB, 1GB atd.",
|
||||
|
||||
@@ -170,6 +170,7 @@
|
||||
"commandRunnerHelp": "Hier könne Sie Befehle eintragen, welche bei den benannten Aktionen ausgeführt werden. Sie müssen pro Zeile jeweils einen Befehl eingeben. Die Umgebungsvariable {0} und {1} sind verfügbar, wobei {0} relative zu {1} ist. Für mehr Informationen über diese Funktion und die verfügbaren Umgebungsvariablen lesen Sie bitte die {2}.",
|
||||
"commandsUpdated": "Befehle aktualisiert!",
|
||||
"createUserDir": "Automatisches Erstellen des Home-Verzeichnisses beim Anlegen neuer Benutzer",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Gestückelter Upload",
|
||||
"tusUploadsHelp": "File Browser unterstützt das Hochladen von gestückelten Dateien und ermöglicht so einen effizienten, zuverlässigen, fortsetzbaren und gestückelten Datei-Upload auch in unzuverlässigen Netzwerken.",
|
||||
"tusUploadsChunkSize": "Gibt die maximale Größe pro Anfrage an (direkte Uploads werden für kleinere Uploads verwendet). Bitte geben Sie eine Byte-Angabe oder eine Zeichenfolge wie 10 MB, 1 GB usw. an",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"cancel": "Ακύρωση",
|
||||
"clear": "Καθαρισμός",
|
||||
"close": "Κλείσιμο",
|
||||
"continue": "Continue",
|
||||
"copy": "Αντιγραφή",
|
||||
"copyFile": "Αντιγραφή αρχείου",
|
||||
"copyToClipboard": "Αντιγραφή στο πρόχειρο",
|
||||
@@ -12,6 +13,7 @@
|
||||
"download": "Λήψη",
|
||||
"file": "Αρχείο",
|
||||
"folder": "Φάκελος",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "Απόκρυψη κρυφών αρχείων",
|
||||
"info": "Πληροφορίες",
|
||||
"more": "Περισσότερα",
|
||||
@@ -22,6 +24,7 @@
|
||||
"ok": "Εντάξει",
|
||||
"permalink": "Λήψη μόνιμου συνδέσμου",
|
||||
"previous": "Προηγούμενο",
|
||||
"preview": "Preview",
|
||||
"publish": "Δημοσίευση",
|
||||
"rename": "Μετονομασία",
|
||||
"replace": "Αντικατάσταση",
|
||||
@@ -32,12 +35,14 @@
|
||||
"select": "Επιλογή",
|
||||
"selectMultiple": "Επιλογή πολλαπλών",
|
||||
"share": "Κοινοποίηση",
|
||||
"shell": "Toggle shell",
|
||||
"submit": "Υποβολή",
|
||||
"switchView": "Εναλλαγή προβολής",
|
||||
"toggleSidebar": "(Απ-)ενεργοποίησης της πλευρικής μπάρας",
|
||||
"update": "Ενημέρωση",
|
||||
"upload": "Μεταφόρτωση",
|
||||
"openFile": "Άνοιγμα αρχείου"
|
||||
"openFile": "Άνοιγμα αρχείου",
|
||||
"discardChanges": "Discard"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Λήψη αρχείου",
|
||||
@@ -101,9 +106,11 @@
|
||||
"prompts": {
|
||||
"copy": "Αντιγραφή",
|
||||
"copyMessage": "Επιλέξτε τοποθεσία για αντιγραφή των αρχείων σας:",
|
||||
"currentlyNavigating": "Currently navigating on:",
|
||||
"deleteMessageMultiple": "Είστε σίγουροι ότι θέλετε να διαγράψετε {count} αρχεία;",
|
||||
"deleteMessageSingle": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο/φάκελο;",
|
||||
"deleteMessageShare": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή την κοινοποίηση ({path});",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "Διαγραφή αρχείων",
|
||||
"displayName": "Εμφάνιση ονόματος:",
|
||||
"download": "Λήψη αρχείων",
|
||||
@@ -132,7 +139,9 @@
|
||||
"upload": "Μεταφόρτωση",
|
||||
"uploadFiles": "Μεταφόρτωση {files} αρχείων…",
|
||||
"uploadMessage": "Επιλέξτε μια επιλογή για τη μεταφόρτωση.",
|
||||
"optionalPassword": "Προαιρετικός κωδικός πρόσβασης"
|
||||
"optionalPassword": "Προαιρετικός κωδικός πρόσβασης",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||
},
|
||||
"search": {
|
||||
"images": "Εικόνες",
|
||||
@@ -161,6 +170,7 @@
|
||||
"commandRunnerHelp": "Εδώ μπορείτε να ορίσετε εντολές που εκτελούνται στα ονομασμένα γεγονότα και δραστηριότητες. Πρέπει να γράψετε μία εντολή ανά γραμμή. Οι μεταβλητές περιβάλλοντος {0} και {1} θα είναι διαθέσιμες, και θα είναι {0} σχετικές με το {1}. Για περισσότερες πληροφορίες σχετικά με αυτή τη λειτουργία και τις διαθέσιμες μεταβλητές περιβάλλοντος, παρακαλώ διαβάστε το {2}.",
|
||||
"commandsUpdated": "Οι εντολές ενημερώθηκαν!",
|
||||
"createUserDir": "Αυτόματη δημιουργία φακέλου χρήστη κατά την προσθήκη νέου χρήστη",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Τμηματικές μεταφορές αρχείων",
|
||||
"tusUploadsHelp": "Η εφαρμογή File Browser υποστηρίζει τμηματικές μεταφορτώσεις αρχείων, επιτρέποντας την αποδοτική, αξιόπιστη και συνεχιζόμενη μεταφόρτωση αρχείων ακόμα και σε ασταθείς συνδέσεις δικτύου.",
|
||||
"tusUploadsChunkSize": "Υποδεικνύει το μέγιστο μέγεθος ενός αιτήματος μεταφόρτωσης (για μικρότερες μεταφορές αρχείων θα χρησιμοποιηθούν απευθείας και όχι τμηματικές μεταφορτώσεις). Μπορείτε να εισάγετε έναν ακέραιο αριθμό που υποδηλώνει το μέγεθος σε bytes, ή κείμενο με αριθμό και μονάδα μέτρησης μεγέθους δεδομένων, όπως 10MB, 1GB κλπ.",
|
||||
@@ -214,6 +224,7 @@
|
||||
"shareDeleted": "Η κοινοποίηση διαγράφηκε!",
|
||||
"singleClick": "Χρήση μονού κλικ για να ανοίξετε αρχεία και φακέλους",
|
||||
"themes": {
|
||||
"default": "System default",
|
||||
"dark": "Σκοτεινό",
|
||||
"light": "Φωτεινό",
|
||||
"title": "Μοτίβο"
|
||||
|
||||
@@ -170,6 +170,7 @@
|
||||
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
|
||||
"commandsUpdated": "Commands updated!",
|
||||
"createUserDir": "Auto create user home dir while adding new user",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Chunked Uploads",
|
||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
"copy": "Copiar",
|
||||
"copyFile": "Copiar archivo",
|
||||
"copyToClipboard": "Copiar al portapapeles",
|
||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
||||
"create": "Crear",
|
||||
"delete": "Borrar",
|
||||
"download": "Descargar",
|
||||
"file": "Archivo",
|
||||
"folder": "Carpeta",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "Ocultar archivos empezados por punto",
|
||||
"info": "Info",
|
||||
"more": "Más",
|
||||
@@ -22,6 +24,7 @@
|
||||
"ok": "OK",
|
||||
"permalink": "Link permanente",
|
||||
"previous": "Anterior",
|
||||
"preview": "Preview",
|
||||
"publish": "Publicar",
|
||||
"rename": "Renombrar",
|
||||
"replace": "Reemplazar",
|
||||
@@ -38,13 +41,17 @@
|
||||
"toggleSidebar": "Mostrar/Ocultar menú",
|
||||
"update": "Actualizar",
|
||||
"upload": "Subir",
|
||||
"openFile": "Abrir archivo"
|
||||
"openFile": "Abrir archivo",
|
||||
"discardChanges": "Discard"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descargar fichero",
|
||||
"downloadFolder": "Descargar directorio",
|
||||
"downloadSelected": "Descargar seleccionados"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Are you sure you wish to abort?"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "No tienes los permisos necesarios para acceder.",
|
||||
"internal": "La verdad es que algo ha ido mal.",
|
||||
@@ -103,6 +110,7 @@
|
||||
"deleteMessageMultiple": "¿Estás seguro que quieres eliminar {count} archivo(s)?",
|
||||
"deleteMessageSingle": "¿Estás seguro que quieres eliminar este archivo/carpeta?",
|
||||
"deleteMessageShare": "¿Está seguro de que quiere eliminar este recurso compartido({path})?",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "Borrar archivos",
|
||||
"displayName": "Nombre:",
|
||||
"download": "Descargar archivos",
|
||||
@@ -131,7 +139,9 @@
|
||||
"upload": "Subir",
|
||||
"uploadFiles": "Subiendo {files} archivos...",
|
||||
"uploadMessage": "Seleccione una opción para subir.",
|
||||
"optionalPassword": "Contraseña opcional"
|
||||
"optionalPassword": "Contraseña opcional",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||
},
|
||||
"search": {
|
||||
"images": "Imágenes",
|
||||
@@ -160,6 +170,11 @@
|
||||
"commandRunnerHelp": "Aquí puede establecer los comandos que se ejecutan en los eventos nombrados. Debe escribir uno por línea. Las variables de entorno {0} y {1} estarán disponibles, siendo {0} relativa a {1}. Para más información sobre esta característica y las variables de entorno disponibles, por favor lea el {2}.",
|
||||
"commandsUpdated": "¡Comandos actualizados!",
|
||||
"createUserDir": "Crea automaticamente una carpeta de inicio cuando se agrega un usuario",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Chunked Uploads",
|
||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
||||
"userHomeBasePath": "Ruta base para los directorios personales de los usuarios",
|
||||
"userScopeGenerationPlaceholder": "El ámbito se generará automáticamente",
|
||||
"createUserHomeDirectory": "Crear el directorio principal del usuario",
|
||||
@@ -209,6 +224,7 @@
|
||||
"shareDeleted": "¡Recurso compartido eliminado!",
|
||||
"singleClick": "Utilice un solo clic para abrir archivos y directorios",
|
||||
"themes": {
|
||||
"default": "System default",
|
||||
"dark": "Oscuro",
|
||||
"light": "Claro",
|
||||
"title": "Tema"
|
||||
|
||||
266
frontend/src/i18n/fa.json
Normal file
@@ -0,0 +1,266 @@
|
||||
{
|
||||
"buttons": {
|
||||
"cancel": "لغو",
|
||||
"clear": "پاک کردن",
|
||||
"close": "بستن",
|
||||
"continue": "ادامه",
|
||||
"copy": "کپی",
|
||||
"copyFile": "کپی فایل",
|
||||
"copyToClipboard": "کپی به حافظه",
|
||||
"copyDownloadLinkToClipboard": "کپی آدرس به حافظه",
|
||||
"create": "ایجاد",
|
||||
"delete": "حذف",
|
||||
"download": "دانلود",
|
||||
"file": "فایل",
|
||||
"folder": "پوشه",
|
||||
"fullScreen": "تمام صفحه ",
|
||||
"hideDotfiles": "مخفی کردن دات فایلها",
|
||||
"info": "اطلاعات",
|
||||
"more": "بیشتر",
|
||||
"move": "انتقال",
|
||||
"moveFile": "انتقال فایل",
|
||||
"new": "جدید",
|
||||
"next": "بعدی",
|
||||
"ok": "تایید",
|
||||
"permalink": "دریافت لینک دائمی",
|
||||
"previous": "قبلی",
|
||||
"preview": "نمایش",
|
||||
"publish": "انتشار",
|
||||
"rename": "تغییر نام",
|
||||
"replace": "جایگزین",
|
||||
"reportIssue": "گزارش مشکل",
|
||||
"save": "ذخیره",
|
||||
"schedule": "زمان بندی",
|
||||
"search": "جستجو",
|
||||
"select": "انتخاب",
|
||||
"selectMultiple": "انتخاب چندتایی",
|
||||
"share": "اشتراک گذاری",
|
||||
"shell": "تغییر پوسته",
|
||||
"submit": "ثبت",
|
||||
"switchView": "تغییر نمایش",
|
||||
"toggleSidebar": "تغییر نوارکناری",
|
||||
"update": "به روز سانی",
|
||||
"upload": "آپلود",
|
||||
"openFile": "باز کردن فایل",
|
||||
"discardChanges": "لغو کردن"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "دانلود فایل",
|
||||
"downloadFolder": "دانلود پوشه",
|
||||
"downloadSelected": "دانلود انتخاب شده ها"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "آیا مطمئن هستید که میخواهید لغو کنید؟"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "شما مجوز دسترسی به این را ندارید.",
|
||||
"internal": "اشتباهی اتفاق افتاده است",
|
||||
"notFound": "این محل قابل دسترسی نیست",
|
||||
"connection": "سرور قابل دسترسی نیست"
|
||||
},
|
||||
"files": {
|
||||
"body": "بدنه",
|
||||
"closePreview": "بستن نمایش",
|
||||
"files": "فایل ها",
|
||||
"folders": "پوشه ها",
|
||||
"home": "صفحه اصلی",
|
||||
"lastModified": "آخرین ویرایش",
|
||||
"loading": "در حال بارگذاری...",
|
||||
"lonely": "It feels lonely here...",
|
||||
"metadata": "فراداده",
|
||||
"multipleSelectionEnabled": "فعال بودن چند گزینه ای",
|
||||
"name": "نام",
|
||||
"size": "اندازه",
|
||||
"sortByLastModified": "مرتب سازی آخرین ویرایش",
|
||||
"sortByName": "مرتب سازی نام",
|
||||
"sortBySize": "مرتب سازی اندازه",
|
||||
"noPreview": "این فایل قابل نمایش نیست"
|
||||
},
|
||||
"help": {
|
||||
"click": "انتخاب فایل یا پوشه",
|
||||
"ctrl": {
|
||||
"click": "انتخاب چند فایل یا پوشه",
|
||||
"f": "باز کردن جستجو",
|
||||
"s": "ذخیره یک فایل یا دانلود پوشه جاری"
|
||||
},
|
||||
"del": "حذف گزینه انتخابی ",
|
||||
"doubleClick": "باز کردن فایل یا پوشه",
|
||||
"esc": "لغو انتخاب و/یا بستن پیغام",
|
||||
"f1": "این اطلاعات",
|
||||
"f2": "تغییر نام فایل",
|
||||
"help": "راهنما"
|
||||
},
|
||||
"login": {
|
||||
"createAnAccount": "ایجاد کاربر",
|
||||
"loginInstead": "کاربر تکراری",
|
||||
"password": "رمز عبور",
|
||||
"passwordConfirm": "تایید رمز",
|
||||
"passwordsDontMatch": "عدم تطابق رمزها",
|
||||
"signup": "ثبت نام",
|
||||
"submit": "ورود",
|
||||
"username": "نام کاربری",
|
||||
"usernameTaken": "نام کاربری تکراری",
|
||||
"wrongCredentials": "خطا در اعتبارسنجی"
|
||||
},
|
||||
"permanent": "دائمی",
|
||||
"prompts": {
|
||||
"copy": "کپی",
|
||||
"copyMessage": "انتخاب محل برای کپی فایل به آنجا ",
|
||||
"currentlyNavigating": "در حال پیمایش",
|
||||
"deleteMessageMultiple": "آیا مطمئنید که میخواهید {count} فایل را حذف کنید؟",
|
||||
"deleteMessageSingle": "آیا مطمئنید که میخواهید فایل/پوشه را حذف کنید؟",
|
||||
"deleteMessageShare": "آیا مطمئن هستید که میخواهید این اشتراکگذاری ({path}) را حذف کنید؟",
|
||||
"deleteUser": "آیا مطمئنید که میخواهید این کاربر را حذف کنید؟",
|
||||
"deleteTitle": "حذف فایل ها",
|
||||
"displayName": "نمایش نام:",
|
||||
"download": "دانلود فایل ها",
|
||||
"downloadMessage": "نوع فایلی که میخواهید دانلود کنید را انتخاب کنید ",
|
||||
"error": "اشتباهی رخ داده",
|
||||
"fileInfo": "اطلاعات فایل ",
|
||||
"filesSelected": "{count} فایل انتخاب شد.",
|
||||
"lastModified": "آخرین ویرایش",
|
||||
"move": "انتقال",
|
||||
"moveMessage": "محل جدیدی برای فایل(ها)/پوشه(های) خود انتخاب کنید:",
|
||||
"newArchetype": "یک پست جدید بر اساس یک آرکتایپ ایجاد کنید. فایل شما در پوشه محتوا ایجاد خواهد شد.",
|
||||
"newDir": "پوشه جدید",
|
||||
"newDirMessage": "نام پوشه جدید",
|
||||
"newFile": "فایل جدید",
|
||||
"newFileMessage": "نام فایل جدید",
|
||||
"numberDirs": "تعداد پوشه ها",
|
||||
"numberFiles": "تعداد فایل ها",
|
||||
"rename": "تغییر نام",
|
||||
"renameMessage": "ورود نام جدید برای",
|
||||
"replace": "جایگزین کردن",
|
||||
"replaceMessage": "یکی از فایلهایی که میخواهید آپلود کنید، نام متفاوتی دارد. آیا میخواهید از این فایل صرف نظر کنید و به آپلود ادامه دهید یا فایل موجود را جایگزین کنید؟",
|
||||
"schedule": "زمان بندی",
|
||||
"scheduleMessage": "تاریخ و زمانی را برای برنامهریزی انتشار این پست انتخاب کنید",
|
||||
"show": "نمایش",
|
||||
"size": "اندازه",
|
||||
"upload": "آپلود",
|
||||
"uploadFiles": "در حال آپلود {files} فایلها...",
|
||||
"uploadMessage": "یک گزینه برای آپلود انتخاب کنید.",
|
||||
"optionalPassword": "رمز عبور اختیاری",
|
||||
"resolution": "وضوح تصویر",
|
||||
"discardEditorChanges": "آیا مطمئن هستید که میخواهید تغییراتی را که ایجاد کردهاید، لغو کنید؟"
|
||||
},
|
||||
"search": {
|
||||
"images": "تصاویر",
|
||||
"music": "موسیقی",
|
||||
"pdf": "پی دی اف",
|
||||
"pressToSearch": "برای جستجو enter را بزنید...",
|
||||
"search": "جستجو...",
|
||||
"typeToSearch": "تایپ برای جستجو",
|
||||
"types": "انواع",
|
||||
"video": "ویدئو "
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
"administrator": "Administrator",
|
||||
"allowCommands": "اجرای دستورات",
|
||||
"allowEdit": "ویرایش، تغییر نام، و حذف فایل ها و پوشه ها",
|
||||
"allowNew": "ایجاد فایلها و پوشه های جدید",
|
||||
"allowPublish": "انتشار پست ها و صفحات جدید",
|
||||
"allowSignup": "اجاره دادن به کاربران برای ثبت نام",
|
||||
"avoidChanges": "(خالی بگذارید تا تغییر ایجاد نشود)",
|
||||
"branding": "برندسازی",
|
||||
"brandingDirectoryPath": "مسیر پوشه برند",
|
||||
"brandingHelp": "شما میتوانید ظاهر و حس نمونهی مرورگر فایل خود را با تغییر نام، جایگزینی لوگو، اضافه کردن سبکهای سفارشی و حتی غیرفعال کردن لینکهای خارجی به گیتهاب، سفارشی کنید.\nبرای اطلاعات بیشتر در مورد برندسازی سفارشی، لطفاً به {0} مراجعه کنید.",
|
||||
"changePassword": "تعبیر رمز",
|
||||
"commandRunner": "اجرا کننده دستورات",
|
||||
"commandRunnerHelp": "در اینجا میتوانید دستوراتی را که در رویدادهای نامگذاری شده اجرا میشوند، تنظیم کنید. باید در هر خط یکی را بنویسید. متغیرهای محیطی {0} و {1} در دسترس خواهند بود و {0} نسبت به {1} هستند. برای اطلاعات بیشتر در مورد این ویژگی و متغیرهای محیطی موجود، لطفاً {2} را مطالعه کنید.",
|
||||
"commandsUpdated": "دستورات ویرایش شد!",
|
||||
"createUserDir": "ایجاد خودکار پوشه برای هر کاربر هنگام اضافه کردن کاربر جدید",
|
||||
"minimumPasswordLength": "حداقل طول رمز عبور",
|
||||
"tusUploads": "آپلودهای بخش بخش شده",
|
||||
"tusUploadsHelp": "مرورگر فایل از آپلود فایل بخش بخش شده پشتیبانی میکند و امکان ایجاد آپلودهای فایل کارآمد، قابل اعتماد، قابل از سرگیری و بخش بخش شده را حتی در شبکههای غیرقابل اعتماد فراهم میکند.",
|
||||
"tusUploadsChunkSize": "حداکثر اندازه یک درخواست را نشان میدهد (برای آپلودهای کوچکتر از آپلود مستقیم استفاده میشود). میتوانید یک عدد صحیح ساده که نشاندهنده اندازه بایت است یا یک رشته مانند ۱۰ مگابایت، ۱ گیگابایت و غیره وارد کنید.",
|
||||
"tusUploadsRetryCount": "تعداد تلاشهای مجدد برای انجام در صورت عدم موفقیت در آپلود یک قطعه داده.",
|
||||
"userHomeBasePath": "مسیر پایه برای پوشه های کاربر",
|
||||
"userScopeGenerationPlaceholder": "محدوده به صورت خودکار تولید خواهد شد",
|
||||
"createUserHomeDirectory": "ایجاد پوشه ناحیه کاربری",
|
||||
"customStylesheet": "Stylesheet سفارشی",
|
||||
"defaultUserDescription": "این تنظیمات پیشفرض برای کاربران جدید است.",
|
||||
"disableExternalLinks": "غیرفعال کردن لینکهای خارجی (به جز مستندات)",
|
||||
"disableUsedDiskPercentage": "نمودار درصد دیسک استفاده شده را غیرفعال کنید",
|
||||
"documentation": "مستندسازی",
|
||||
"examples": "مثال ها",
|
||||
"executeOnShell": "اجرا روی shell",
|
||||
"executeOnShellDescription": "به طور پیشفرض، مرورگر فایل، دستورات را با فراخوانی مستقیم فایلهای باینری آنها اجرا میکند. اگر میخواهید آنها را روی یک پوسته (مانند Bash یا PowerShell) اجرا کنید، میتوانید آن را در اینجا با آرگومانها و پرچمهای مورد نیاز تعریف کنید. در صورت تنظیم، دستوری که اجرا میکنید به عنوان یک آرگومان پیوست میشود. این موضوع هم در مورد دستورات کاربر و هم در مورد هوک ها صدق میکند.",
|
||||
"globalRules": "این یک مجموعه جهانی از قوانین مجاز و غیرمجاز است. آنها برای هر کاربر اعمال میشوند. شما میتوانید قوانین خاصی را در تنظیمات هر کاربر تعریف کنید تا این قوانین را لغو کنید.",
|
||||
"globalSettings": "تنظیمات سراسری",
|
||||
"hideDotfiles": "مخفی کردن دات فایل ها",
|
||||
"insertPath": "وارد کردن مسیر",
|
||||
"insertRegex": "وارد کردن عبارات باقاعده",
|
||||
"instanceName": "نام نمونه",
|
||||
"language": "زبان",
|
||||
"lockPassword": "جلوگیری از تغییر رمز توسط کاربر",
|
||||
"newPassword": "رمز جدید شما",
|
||||
"newPasswordConfirm": "تایید رمز جدید شما",
|
||||
"newUser": "کاربر جدید ",
|
||||
"password": " رمز عبور",
|
||||
"passwordUpdated": "رمز عبور ویرایش شد!",
|
||||
"path": "مسیر",
|
||||
"perm": {
|
||||
"create": "استاد فایل ها و پوشه ها",
|
||||
"delete": "حذف فایل ها و پوشه ها",
|
||||
"download": "دانلود",
|
||||
"execute": "اجرای دستورات",
|
||||
"modify": "ویرایش فایل ها",
|
||||
"rename": "تغییر نام یا انتقال فایل ها و پوشه ها",
|
||||
"share": "به اشتراک گذاری فایل ها"
|
||||
},
|
||||
"permissions": "دسترسی ها",
|
||||
"permissionsHelp": "شما میتوانید کاربر را به عنوان مدیر تنظیم کنید یا دسترسیها را به صورت جداگانه انتخاب کنید. اگر \"مدیر\" را انتخاب کنید، تمام گزینههای دیگر به طور خودکار اعمال میشوند. مدیریت کاربران همچنان از اختیارات مدیر است.",
|
||||
"profileSettings": "تنظیمات ناحیه کاربری",
|
||||
"ruleExample1": "از دسترسی به هرگونه فایل نقطهای (مانند .git، .gitignore) در هر پوشه جلوگیری میکند.",
|
||||
"ruleExample2": "دسترسی به فایلی به نام Caddyfile را در ریشه دامنه مسدود میکند.",
|
||||
"rules": "قواعد",
|
||||
"rulesHelp": "در اینجا میتوانید مجموعهای از قوانین مجاز و غیرمجاز را برای این کاربر خاص تعریف کنید. فایلهای مسدود شده در لیستها نمایش داده نمیشوند و کاربر به آنها دسترسی نخواهد داشت. ما از regex و مسیرهای مربوط به محدوده کاربر پشتیبانی میکنیم.",
|
||||
"scope": "محدوده",
|
||||
"setDateFormat": "تنظیم قالب دقیق تاریخ",
|
||||
"settingsUpdated": "تنظیمات به روز شد!",
|
||||
"shareDuration": "زمان به اشتراک گذاری",
|
||||
"shareManagement": "مدیریت به اشتراک گذاری",
|
||||
"shareDeleted": "به اشتراک گذاری حذف شد!",
|
||||
"singleClick": "استفاده از یک کلیک برای باز کردن فایل ها و پوشه ها",
|
||||
"themes": {
|
||||
"default": "تنظیمات پیش فرض سیستم",
|
||||
"dark": "تاریک ",
|
||||
"light": "روشن",
|
||||
"title": "تم یا زمینه"
|
||||
},
|
||||
"user": "کاربر",
|
||||
"userCommands": "دستورات",
|
||||
"userCommandsHelp": "فهرستی از دستورات موجود برای این کاربر که با فاصله از هم جدا شدهاند. مثال:",
|
||||
"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": "واحد زمان"
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,7 @@
|
||||
"commandRunnerHelp": "Ici, vous pouvez définir les commandes qui seront exécutées lors des é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",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Uploads segmentés",
|
||||
"tusUploadsHelp": "File Browser prend en charge les uploads segmentés afin de permettre une gestion efficace, fiable et reprenable sur des réseaux instables.",
|
||||
"tusUploadsChunkSize": "Taille maximale autorisée par segment (les uploads directs seront utilisés pour les fichiers plus petits). Vous pouvez entrer un entier en octets ou une chaîne telle que 10MB, 1GB, etc.",
|
||||
@@ -223,6 +224,7 @@
|
||||
"shareDeleted": "Partage supprimé !",
|
||||
"singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers",
|
||||
"themes": {
|
||||
"default": "System default",
|
||||
"dark": "Sombre",
|
||||
"light": "Clair",
|
||||
"title": "Thème"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"cancel": "ביטול",
|
||||
"clear": "נקה",
|
||||
"close": "סגירה",
|
||||
"continue": "המשך",
|
||||
"copy": "העתקה",
|
||||
"copyFile": "העתק קובץ",
|
||||
"copyToClipboard": "העתק ללוח",
|
||||
@@ -12,6 +13,7 @@
|
||||
"download": "הורדה",
|
||||
"file": "קובץ",
|
||||
"folder": "תיקייה",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "הסתר קבצים/תיקיות ששמם מתחיל בנקודה",
|
||||
"info": "מידע",
|
||||
"more": "עוד",
|
||||
@@ -22,6 +24,7 @@
|
||||
"ok": "אישור",
|
||||
"permalink": "יצירת קישור קבוע",
|
||||
"previous": "הקודם",
|
||||
"preview": "Preview",
|
||||
"publish": "פרסום",
|
||||
"rename": "שינוי שם",
|
||||
"replace": "החלפה",
|
||||
@@ -39,7 +42,6 @@
|
||||
"update": "עדכון",
|
||||
"upload": "העלאה",
|
||||
"openFile": "פתח קובץ",
|
||||
"continue": "המשך",
|
||||
"discardChanges": "זריקת השינויים"
|
||||
},
|
||||
"download": {
|
||||
@@ -58,7 +60,6 @@
|
||||
},
|
||||
"files": {
|
||||
"body": "גוף",
|
||||
"clear": "ניקוי",
|
||||
"closePreview": "סגירת תצוגה מקדימה",
|
||||
"files": "קבצים",
|
||||
"folders": "תיקיות",
|
||||
@@ -109,6 +110,7 @@
|
||||
"deleteMessageMultiple": "האם אתה בטוח שברצונך למחוק {count} קבצים?",
|
||||
"deleteMessageSingle": "האם אתה בטוח שברצונך למחוק את הקובץ/התיקייה?",
|
||||
"deleteMessageShare": "האם אתה בטוח שברצונך למחוק את השיתוף הזה ({path})?",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "מחיקת קבצים",
|
||||
"displayName": "שם:",
|
||||
"download": "הורדת קבצים",
|
||||
@@ -138,6 +140,7 @@
|
||||
"uploadFiles": "מעלה {files} קבצים...",
|
||||
"uploadMessage": "בחר אפשרות העלאה.",
|
||||
"optionalPassword": "סיסמא אופציונלית",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "האם אתה בטוח שברצונך לבטל את השינויים שביצעת?"
|
||||
},
|
||||
"search": {
|
||||
@@ -167,6 +170,11 @@
|
||||
"commandRunnerHelp": "אתה יכול להגדיר פקודות שיבוצעו באירועים שונים. עליך לכתוב אחד בכל שורה. משתני הסביבה {0} ו-{1} יהיו זמינים, בהיותם {0} ביחס ל-{1}. למידע נוסף על תכונה זו ועל משתני הסביבה הזמינים, עיין ב {2}.",
|
||||
"commandsUpdated": "הפקודות עודכנו!",
|
||||
"createUserDir": "צור אוטומטית תיקיית בית בעת הוספת משתמש חדש",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Chunked Uploads",
|
||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
||||
"userHomeBasePath": "נתיב ראשי לתיקיות הבית של משתמשים",
|
||||
"userScopeGenerationPlaceholder": "ההיקף יווצר אוטומטית",
|
||||
"createUserHomeDirectory": "צור תיקיית בית למשתמש",
|
||||
@@ -216,6 +224,7 @@
|
||||
"shareDeleted": "השיתוף נמחק!",
|
||||
"singleClick": "השתמש בלחיצה בודדת כדי לפתוח קובץ/תיקייה",
|
||||
"themes": {
|
||||
"default": "System default",
|
||||
"dark": "כהה",
|
||||
"light": "בהיר",
|
||||
"title": "ערכת נושא"
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
"cancel": "Mégse",
|
||||
"clear": "Törlése",
|
||||
"close": "Bezárás",
|
||||
"continue": "Continue",
|
||||
"copy": "Másolás",
|
||||
"copyFile": "Fájl másolása",
|
||||
"copyToClipboard": "Másolás vágólapra",
|
||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
||||
"create": "Létrehozás",
|
||||
"delete": "Törlése",
|
||||
"download": "Letöltés",
|
||||
"file": "Fájl",
|
||||
"folder": "Mappa",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "Rejtett fájlok elrejtése",
|
||||
"info": "Infó",
|
||||
"more": "További",
|
||||
@@ -21,6 +24,7 @@
|
||||
"ok": "OK",
|
||||
"permalink": "Állandó link lekérése",
|
||||
"previous": "Előző",
|
||||
"preview": "Preview",
|
||||
"publish": "Publikálása",
|
||||
"rename": "Átnevezés",
|
||||
"replace": "Csere",
|
||||
@@ -37,13 +41,17 @@
|
||||
"toggleSidebar": "Oldalsáv átváltása",
|
||||
"update": "Frissítés",
|
||||
"upload": "Feltöltés",
|
||||
"openFile": "Fájl megnyitása"
|
||||
"openFile": "Fájl megnyitása",
|
||||
"discardChanges": "Discard"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Fájl letöltése",
|
||||
"downloadFolder": "Mappa letöltése",
|
||||
"downloadSelected": "Kijelölés letöltése"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Are you sure you wish to abort?"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Nincs jogosultsága a hozzáféréshez.",
|
||||
"internal": "Valami nagyon elromlott.",
|
||||
@@ -102,6 +110,7 @@
|
||||
"deleteMessageMultiple": "Biztosan törölni szeretne {count} fájlt?",
|
||||
"deleteMessageSingle": "Biztosan törölni szeretné ezt a fájl vagy mappát?",
|
||||
"deleteMessageShare": "Biztosan törölni szeretné ezt a megosztást ({path})?",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "Fájlok törlése",
|
||||
"displayName": "Megjelenített név:",
|
||||
"download": "Fájlok letöltése",
|
||||
@@ -130,7 +139,9 @@
|
||||
"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ó",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||
},
|
||||
"search": {
|
||||
"images": "Képek",
|
||||
@@ -159,12 +170,18 @@
|
||||
"commandRunnerHelp": "Beállíthatja azokat a parancsokat, amelyek a megnevezett események során végrehajtásra kerülnek. Soronként egyet kell megadni. A {0} és a {1} környezeti változók lesznek elérhetőek, ahol a {0} relatív a {1}-hez. A funkcióról és a rendelkezésre álló környezeti változókról további információ: {2}.",
|
||||
"commandsUpdated": "Parancsok frissítve!",
|
||||
"createUserDir": "Felhasználók saját mappáinak automatikus létrehozása új felhasználók hozzáadásakor",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Chunked Uploads",
|
||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
||||
"userHomeBasePath": "Alap elérési útvonal a felhasználók saját mappáihoz",
|
||||
"userScopeGenerationPlaceholder": "A környezet automatikus lesz létrehozva",
|
||||
"createUserHomeDirectory": "Felhasználói saját mappák létrehozása",
|
||||
"customStylesheet": "Egyéni stíluslap",
|
||||
"defaultUserDescription": "Ezek az alapértelmezett beállítások az új felhasználók számára.",
|
||||
"disableExternalLinks": "Külső linkek letiltása (kivéve a dokumentáció)",
|
||||
"disableUsedDiskPercentage": "Disable used disk percentage graph",
|
||||
"documentation": "dokumentáció",
|
||||
"examples": "Példák",
|
||||
"executeOnShell": "Futtatás parancsértelmezőben",
|
||||
@@ -207,6 +224,7 @@
|
||||
"shareDeleted": "Megosztás törölve!",
|
||||
"singleClick": "Fájlok és könyvtárak megnyitása egyetlen kattintással",
|
||||
"themes": {
|
||||
"default": "System default",
|
||||
"dark": "Sötét",
|
||||
"light": "Világos",
|
||||
"title": "Téma"
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
"cancel": "Hætta við",
|
||||
"clear": "Hreinsa",
|
||||
"close": "Loka",
|
||||
"continue": "Continue",
|
||||
"copy": "Afrita",
|
||||
"copyFile": "Afrita skjal",
|
||||
"copyToClipboard": "Afrita",
|
||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
||||
"create": "Búa til",
|
||||
"delete": "Eyða",
|
||||
"download": "Sækja",
|
||||
"hideDotfiles": "",
|
||||
"file": "File",
|
||||
"folder": "Folder",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "Hide dotfiles",
|
||||
"info": "Upplýsingar",
|
||||
"more": "Meira",
|
||||
"move": "Færa",
|
||||
@@ -19,6 +24,7 @@
|
||||
"ok": "OK",
|
||||
"permalink": "Sækja fastan hlekk",
|
||||
"previous": "Fyrri",
|
||||
"preview": "Preview",
|
||||
"publish": "Gefa út",
|
||||
"rename": "Endurnefna",
|
||||
"replace": "Skipta út",
|
||||
@@ -30,20 +36,27 @@
|
||||
"selectMultiple": "Velja mörg",
|
||||
"share": "Deila",
|
||||
"shell": "Sýna skipanaglugga",
|
||||
"submit": "Submit",
|
||||
"switchView": "Skipta um útlit",
|
||||
"toggleSidebar": "Sýna hliðarstiku",
|
||||
"update": "Vista",
|
||||
"upload": "Hlaða upp"
|
||||
"upload": "Hlaða upp",
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Discard"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Sækja skjal",
|
||||
"downloadFolder": "Sækja möppu",
|
||||
"downloadSelected": ""
|
||||
"downloadSelected": "Download Selected"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Are you sure you wish to abort?"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Þú hefur ekki aðgang að þessari síðu.",
|
||||
"internal": "Eitthvað fór úrskeiðis.",
|
||||
"notFound": "Ekki er hægt að opna þessa síðu."
|
||||
"notFound": "Ekki er hægt að opna þessa síðu.",
|
||||
"connection": "The server can't be reached."
|
||||
},
|
||||
"files": {
|
||||
"body": "Meginmál",
|
||||
@@ -60,7 +73,8 @@
|
||||
"size": "Stærð",
|
||||
"sortByLastModified": "Flokka eftir Seinast breytt",
|
||||
"sortByName": "Flokka eftir nafni",
|
||||
"sortBySize": "Flokka eftir stærð"
|
||||
"sortBySize": "Flokka eftir stærð",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
},
|
||||
"help": {
|
||||
"click": "velja skjal eða möppu",
|
||||
@@ -95,6 +109,8 @@
|
||||
"currentlyNavigating": "Núverandi staðsetning:",
|
||||
"deleteMessageMultiple": "Ertu viss um að þú viljir eyða {count} skjölum?",
|
||||
"deleteMessageSingle": "Ertu viss um að þú viljir eyða þessu skjali/möppu?",
|
||||
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "Eyða skjölum",
|
||||
"displayName": "Nafn: ",
|
||||
"download": "Sækja skjöl",
|
||||
@@ -120,8 +136,12 @@
|
||||
"scheduleMessage": "Veldu dagsetningu og tíma fyrir áætlaða útgáfu. ",
|
||||
"show": "Sýna",
|
||||
"size": "Stærð",
|
||||
"upload": "",
|
||||
"uploadMessage": ""
|
||||
"upload": "Upload",
|
||||
"uploadFiles": "Uploading {files} files...",
|
||||
"uploadMessage": "Select an option to upload.",
|
||||
"optionalPassword": "Optional password",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||
},
|
||||
"search": {
|
||||
"images": "Myndir",
|
||||
@@ -150,6 +170,14 @@
|
||||
"commandRunnerHelp": "Hér geturðu sett inn skipanir sem eru keyrðar eftir því sem þú tilgreinir. Skrifaðu eina skipun í hverja línu. Umhverfisbreyturnar {0} og {1} verða aðgengilegar ({0} miðast við {1}). Til að lesa meira og sjá lista yfir þær skipanir sem eru í boði, vinsamlegast lestu {2}. ",
|
||||
"commandsUpdated": "Skipanastillingar vistaðar!",
|
||||
"createUserDir": "Auto create user home dir while adding new user",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Chunked Uploads",
|
||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
||||
"userHomeBasePath": "Base path for user home directories",
|
||||
"userScopeGenerationPlaceholder": "The scope will be auto generated",
|
||||
"createUserHomeDirectory": "Create user home directory",
|
||||
"customStylesheet": "Custom Stylesheet",
|
||||
"defaultUserDescription": "Þetta eru sjálfgefnar stillingar fyrir nýja notendur.",
|
||||
"disableExternalLinks": "Sýna ytri-hlekki (fyrir utan leiðbeiningar)",
|
||||
@@ -160,7 +188,7 @@
|
||||
"executeOnShellDescription": "Sjálfgefnar stillingar File Browser eru að keyra skipanir beint með því að sækja binaries. Ef þú villt keyra skipanir í skel (t.d. í Bash eða PowerShell), þá geturðu skilgreint það hér með nauðsynlegum arguments og flags. Ef þetta er stillt, þá verður skipuninni bætt fyrir aftan sem argument. Þetta gildir bæði um skipanir notenda og event hooks.",
|
||||
"globalRules": "Þetta eru sjálfgegnar aðgangsreglur. Þær gilda um alla notendur. Þú getur tilgreint sérstakar reglur í stillingum fyrir hvern notenda til að ógilda þessar reglur. ",
|
||||
"globalSettings": "Global stillingar",
|
||||
"hideDotfiles": "",
|
||||
"hideDotfiles": "Hide dotfiles",
|
||||
"insertPath": "Settu inn slóð",
|
||||
"insertRegex": "Setja inn reglulega segð",
|
||||
"instanceName": "Nafn tilviks",
|
||||
@@ -171,7 +199,7 @@
|
||||
"newUser": "Nýr notandi",
|
||||
"password": "Lykilorð",
|
||||
"passwordUpdated": "Lykilorð vistað!",
|
||||
"path": "",
|
||||
"path": "Path",
|
||||
"perm": {
|
||||
"create": "Búa til sköl og möppur",
|
||||
"delete": "Eyða skjölum og möppum",
|
||||
@@ -189,14 +217,17 @@
|
||||
"rules": "Reglur",
|
||||
"rulesHelp": "Hér getur þú skilgreint hvaða reglur gilda um notandann. Skjölin sem hann hefur ekki aðgang að eru óaðgengileg og hann sér þau ekki. Stuðst er við reglulegar segðir og slóðir sem miðast við sýn notandans. ",
|
||||
"scope": "Sýn notandans",
|
||||
"setDateFormat": "Set exact date format",
|
||||
"settingsUpdated": "Stillingar vistaðar!",
|
||||
"shareDuration": "",
|
||||
"shareManagement": "",
|
||||
"singleClick": "",
|
||||
"shareDuration": "Share Duration",
|
||||
"shareManagement": "Share Management",
|
||||
"shareDeleted": "Share deleted!",
|
||||
"singleClick": "Use single clicks to open files and directories",
|
||||
"themes": {
|
||||
"dark": "",
|
||||
"light": "",
|
||||
"title": ""
|
||||
"default": "System default",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"title": "Theme"
|
||||
},
|
||||
"user": "Notandi",
|
||||
"userCommands": "Skipanir",
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
"copy": "Copia",
|
||||
"copyFile": "Copia file",
|
||||
"copyToClipboard": "Copia negli appunti",
|
||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
||||
"create": "Crea",
|
||||
"delete": "Elimina",
|
||||
"download": "Scarica",
|
||||
"file": "File",
|
||||
"folder": "Folder",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "Nascondi dotfile",
|
||||
"info": "Informazioni",
|
||||
"more": "Altro",
|
||||
@@ -20,6 +24,7 @@
|
||||
"ok": "OK",
|
||||
"permalink": "Ottieni link permanente",
|
||||
"previous": "Precedente",
|
||||
"preview": "Preview",
|
||||
"publish": "Publica",
|
||||
"rename": "Rinomina",
|
||||
"replace": "Sostituisci",
|
||||
@@ -31,20 +36,27 @@
|
||||
"selectMultiple": "Seleziona molteplici",
|
||||
"share": "Condividi",
|
||||
"shell": "Mostra/nascondi shell",
|
||||
"submit": "Submit",
|
||||
"switchView": "Cambia vista",
|
||||
"toggleSidebar": "Mostra/nascondi la barra laterale",
|
||||
"update": "Aggiorna",
|
||||
"upload": "Carica"
|
||||
"upload": "Carica",
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Discard"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Scarica file",
|
||||
"downloadFolder": "Scarica cartella",
|
||||
"downloadSelected": "Scarica selezionati"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Are you sure you wish to abort?"
|
||||
},
|
||||
"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": "The server can't be reached."
|
||||
},
|
||||
"files": {
|
||||
"body": "Contenuto",
|
||||
@@ -61,7 +73,8 @@
|
||||
"size": "Dimensione",
|
||||
"sortByLastModified": "Ordina per ultima modifica",
|
||||
"sortByName": "Ordina per nome",
|
||||
"sortBySize": "Ordina per dimensione"
|
||||
"sortBySize": "Ordina per dimensione",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
},
|
||||
"help": {
|
||||
"click": "seleziona un file o una cartella",
|
||||
@@ -96,6 +109,8 @@
|
||||
"currentlyNavigating": "Attualmente navigando su:",
|
||||
"deleteMessageMultiple": "Sei sicuro di voler eliminare {count} file?",
|
||||
"deleteMessageSingle": "Sei sicuro di voler eliminare questo file/cartella?",
|
||||
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "Elimina",
|
||||
"displayName": "Nome visualizzato:",
|
||||
"download": "Scarica files",
|
||||
@@ -122,7 +137,11 @@
|
||||
"show": "Mostra",
|
||||
"size": "Dimensione",
|
||||
"upload": "Carica",
|
||||
"uploadMessage": "Seleziona un'opzione per il caricamento."
|
||||
"uploadFiles": "Uploading {files} files...",
|
||||
"uploadMessage": "Seleziona un'opzione per il caricamento.",
|
||||
"optionalPassword": "Optional password",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||
},
|
||||
"search": {
|
||||
"images": "Immagini",
|
||||
@@ -151,6 +170,14 @@
|
||||
"commandRunnerHelp": "Qui puoi impostare i comandi da eseguire negli eventi nominati. Ne devi scrivere uno per riga. Le variabili d'ambiente {0} e {1} sono disponibili, essendo {0} relativo a {1}. Per altre informazioni su questa funzionalità e sulle variabili d'ambiente utilizzabili, leggi la {2}.",
|
||||
"commandsUpdated": "Comandi aggiornati!",
|
||||
"createUserDir": "Crea automaticamente la home directory dell'utente quando lo aggiungi",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Chunked Uploads",
|
||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
||||
"userHomeBasePath": "Base path for user home directories",
|
||||
"userScopeGenerationPlaceholder": "The scope will be auto generated",
|
||||
"createUserHomeDirectory": "Create user home directory",
|
||||
"customStylesheet": "Foglio di stile personalizzato",
|
||||
"defaultUserDescription": "Queste sono le impostazioni predefinite per i nuovi utenti.",
|
||||
"disableExternalLinks": "Disabilita link esterni (tranne per la documentazione)",
|
||||
@@ -190,11 +217,14 @@
|
||||
"rules": "Regole",
|
||||
"rulesHelp": "Qui è possibile definire una serie di regole e permessi per questo specifico utente. I file bloccati non appariranno negli elenchi e non saranno accessibili dagli utenti. all'utente. Sia regex che i percorsi relativi all'ambito di applicazione degli utenti sono supportati.\n",
|
||||
"scope": "Scope",
|
||||
"setDateFormat": "Set exact date format",
|
||||
"settingsUpdated": "Impostazioni aggiornate!",
|
||||
"shareDuration": "Durata della condivisione",
|
||||
"shareManagement": "Gestione delle condivisioni",
|
||||
"shareDeleted": "Share deleted!",
|
||||
"singleClick": "Usa un singolo click per aprire file e cartelle",
|
||||
"themes": {
|
||||
"default": "System default",
|
||||
"dark": "Scuro",
|
||||
"light": "Chiaro",
|
||||
"title": "Tema"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"cancel": "キャンセル",
|
||||
"clear": "クリアー",
|
||||
"close": "閉じる",
|
||||
"continue": "続行",
|
||||
"copy": "コピー",
|
||||
"copyFile": "ファイルのコピー",
|
||||
"copyToClipboard": "共有リンクをコピー",
|
||||
@@ -12,6 +13,7 @@
|
||||
"download": "ダウンロード",
|
||||
"file": "ファイル",
|
||||
"folder": "フォルダー",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "ドットで始まるファイルを表示しない",
|
||||
"info": "情報",
|
||||
"more": "さらに",
|
||||
@@ -22,6 +24,7 @@
|
||||
"ok": "OK",
|
||||
"permalink": "パーマリンクを取得",
|
||||
"previous": "前へ",
|
||||
"preview": "Preview",
|
||||
"publish": "公開",
|
||||
"rename": "名前の変更",
|
||||
"replace": "置換する",
|
||||
@@ -39,7 +42,7 @@
|
||||
"update": "更新",
|
||||
"upload": "アップロード",
|
||||
"openFile": "ファイルを開く",
|
||||
"continue": "続行"
|
||||
"discardChanges": "Discard"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "ファイルのダウンロード",
|
||||
@@ -57,7 +60,6 @@
|
||||
},
|
||||
"files": {
|
||||
"body": "本文",
|
||||
"clear": "消去",
|
||||
"closePreview": "プレビューを閉じる",
|
||||
"files": "ファイル",
|
||||
"folders": "フォルダー",
|
||||
@@ -108,6 +110,7 @@
|
||||
"deleteMessageMultiple": "{count} 個のファイルを削除してもよろしいですか?",
|
||||
"deleteMessageSingle": "このファイル/フォルダーを削除してもよろしいですか?",
|
||||
"deleteMessageShare": "共有中のファイル({path})を削除してもよろしいですか?",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "ファイルの削除",
|
||||
"displayName": "表示名:",
|
||||
"download": "ファイルのダウンロード",
|
||||
@@ -136,7 +139,9 @@
|
||||
"upload": "アップロード",
|
||||
"uploadFiles": "{files} 個のファイルをアップロードしています…",
|
||||
"uploadMessage": "アップロードするオプションを選択してください。",
|
||||
"optionalPassword": "パスワード(オプション)"
|
||||
"optionalPassword": "パスワード(オプション)",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||
},
|
||||
"search": {
|
||||
"images": "画像",
|
||||
@@ -165,6 +170,7 @@
|
||||
"commandRunnerHelp": "ここでは、指定したイベントの際に実行されるコマンドを設定することができます。1行に1つずつ書く必要があります。環境変数として {0} や {1} が使用可能で、{0} は {1} に関連した変数として扱われます。この機能と使用可能な環境変数の詳細については、{2}をお読みください。",
|
||||
"commandsUpdated": "コマンドを更新しました!",
|
||||
"createUserDir": "新規ユーザー追加時にユーザーのホームディレクトリを自動生成する",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "チャンクされたファイルアップロード",
|
||||
"tusUploadsHelp": "File Browser はチャンクされたファイルアップロードをサポートしており、信頼性の低いネットワーク上でも、効率的で信頼性の高い、再開可能なチャンクされたファイルアップロードを作成することができます。",
|
||||
"tusUploadsChunkSize": "1チャンクあたりのリクエストの最大サイズ。バイト数を示す整数か、10MB、1GBなどの文字列を入力できます。",
|
||||
@@ -218,6 +224,7 @@
|
||||
"shareDeleted": "ファイルの共有を削除しました!",
|
||||
"singleClick": "ダブルクリックの代わりにクリックでファイルやフォルダーを開く",
|
||||
"themes": {
|
||||
"default": "System default",
|
||||
"dark": "ダーク",
|
||||
"light": "ライト",
|
||||
"title": "テーマ"
|
||||
|
||||
@@ -3,46 +3,46 @@
|
||||
"cancel": "취소",
|
||||
"clear": "지우기",
|
||||
"close": "닫기",
|
||||
"continue": "계속",
|
||||
"continue": "Continue",
|
||||
"copy": "복사",
|
||||
"copyFile": "파일 복사",
|
||||
"copyToClipboard": "클립보드에 복사",
|
||||
"copyDownloadLinkToClipboard": "다운로드 링크 클립보드에 복사",
|
||||
"copyToClipboard": "클립보드 복사",
|
||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
||||
"create": "생성",
|
||||
"delete": "삭제",
|
||||
"download": "다운로드",
|
||||
"file": "파일",
|
||||
"folder": "폴더",
|
||||
"fullScreen": "전체 화면 전환",
|
||||
"hideDotfiles": "숨김 파일 숨기기",
|
||||
"file": "File",
|
||||
"folder": "Folder",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hideDotfiles": "숨김파일(dotfile)을 표시 안함",
|
||||
"info": "정보",
|
||||
"more": "더 보기",
|
||||
"more": "더보기",
|
||||
"move": "이동",
|
||||
"moveFile": "파일 이동",
|
||||
"new": "새로 만들기",
|
||||
"new": "신규",
|
||||
"next": "다음",
|
||||
"ok": "확인",
|
||||
"permalink": "영구 링크 받기",
|
||||
"permalink": "링크 얻기",
|
||||
"previous": "이전",
|
||||
"preview": "미리보기",
|
||||
"preview": "Preview",
|
||||
"publish": "게시",
|
||||
"rename": "이름 바꾸기",
|
||||
"replace": "바꾸기",
|
||||
"reportIssue": "문제 보고",
|
||||
"replace": "대체",
|
||||
"reportIssue": "이슈 보내기",
|
||||
"save": "저장",
|
||||
"schedule": "예약",
|
||||
"schedule": "일정",
|
||||
"search": "검색",
|
||||
"select": "선택",
|
||||
"selectMultiple": "다중 선택",
|
||||
"share": "공유",
|
||||
"shell": "셸 전환",
|
||||
"submit": "제출",
|
||||
"shell": "쉘 전환",
|
||||
"submit": "Submit",
|
||||
"switchView": "보기 전환",
|
||||
"toggleSidebar": "사이드바 전환",
|
||||
"update": "업데이트",
|
||||
"upload": "업로드",
|
||||
"openFile": "파일 열기",
|
||||
"discardChanges": "변경 사항 취소"
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Discard"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "파일 다운로드",
|
||||
@@ -50,13 +50,13 @@
|
||||
"downloadSelected": "선택 항목 다운로드"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "업로드를 중단하시겠습니까?"
|
||||
"abortUpload": "Are you sure you wish to abort?"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "이곳에 접근할 권한이 없습니다.",
|
||||
"internal": "문제가 발생했습니다.",
|
||||
"notFound": "이 위치에 접근할 수 없습니다.",
|
||||
"connection": "서버에 연결할 수 없습니다."
|
||||
"forbidden": "접근 권한이 없습니다.",
|
||||
"internal": "오류가 발생하였습니다.",
|
||||
"notFound": "해당 경로를 찾을 수 없습니다.",
|
||||
"connection": "The server can't be reached."
|
||||
},
|
||||
"files": {
|
||||
"body": "본문",
|
||||
@@ -64,192 +64,193 @@
|
||||
"files": "파일",
|
||||
"folders": "폴더",
|
||||
"home": "홈",
|
||||
"lastModified": "마지막 수정일",
|
||||
"loading": "로딩 중...",
|
||||
"lonely": "여기에 아무것도 없네요...",
|
||||
"lastModified": "최종 수정",
|
||||
"loading": "로딩중...",
|
||||
"lonely": "폴더가 비어 있습니다...",
|
||||
"metadata": "메타데이터",
|
||||
"multipleSelectionEnabled": "다중 선택 활성화됨",
|
||||
"multipleSelectionEnabled": "다중 선택 켜짐",
|
||||
"name": "이름",
|
||||
"size": "크기",
|
||||
"sortByLastModified": "마지막 수정일 순 정렬",
|
||||
"sortByName": "이름 순 정렬",
|
||||
"sortBySize": "크기 순 정렬",
|
||||
"noPreview": "이 파일은 미리보기를 사용할 수 없습니다."
|
||||
"sortByLastModified": "수정시간순 정렬",
|
||||
"sortByName": "이름순",
|
||||
"sortBySize": "크기순",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
},
|
||||
"help": {
|
||||
"click": "파일 또는 디렉터리 선택",
|
||||
"click": "파일이나 디렉토리를 선택해주세요.",
|
||||
"ctrl": {
|
||||
"click": "여러 파일 또는 디렉터리 선택",
|
||||
"f": "검색 열기",
|
||||
"s": "파일 저장 또는 현재 디렉터리 다운로드"
|
||||
"click": "여러 개의 파일이나 디렉토리를 선택해주세요.",
|
||||
"f": "검색창 열기",
|
||||
"s": "파일 또는 디렉토리 다운로드"
|
||||
},
|
||||
"del": "선택한 항목 삭제",
|
||||
"doubleClick": "파일 또는 디렉터리 열기",
|
||||
"esc": "선택 취소 및/또는 프롬프트 닫기",
|
||||
"f1": "이 정보",
|
||||
"f2": "파일 이름 바꾸기",
|
||||
"del": "선택된 파일 삭제",
|
||||
"doubleClick": "파일 또는 디렉토리 열기",
|
||||
"esc": "선택 취소/프롬프트 닫기",
|
||||
"f1": "정보",
|
||||
"f2": "파일 이름 변경",
|
||||
"help": "도움말"
|
||||
},
|
||||
"login": {
|
||||
"createAnAccount": "계정 만들기",
|
||||
"createAnAccount": "계정 생성",
|
||||
"loginInstead": "이미 계정이 있습니다",
|
||||
"password": "비밀번호",
|
||||
"passwordConfirm": "비밀번호 확인",
|
||||
"passwordsDontMatch": "비밀번호가 일치하지 않습니다",
|
||||
"signup": "가입",
|
||||
"signup": "가입하기",
|
||||
"submit": "로그인",
|
||||
"username": "사용자 이름",
|
||||
"usernameTaken": "이미 사용 중인 사용자 이름입니다",
|
||||
"wrongCredentials": "잘못된 자격 증명"
|
||||
"usernameTaken": "사용자 이름이 존재합니다",
|
||||
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오"
|
||||
},
|
||||
"permanent": "영구",
|
||||
"prompts": {
|
||||
"copy": "복사",
|
||||
"copyMessage": "파일을 복사할 위치를 선택하세요:",
|
||||
"currentlyNavigating": "현재 탐색 중:",
|
||||
"deleteMessageMultiple": "{count}개의 파일을 삭제하시겠습니까?",
|
||||
"deleteMessageSingle": "이 파일/폴더를 삭제하시겠습니까?",
|
||||
"deleteMessageShare": "이 공유({path})를 삭제하시겠습니까?",
|
||||
"deleteUser": "이 사용자를 삭제하시겠습니까?",
|
||||
"copyMessage": "복사할 디렉토리:",
|
||||
"currentlyNavigating": "현재 위치:",
|
||||
"deleteMessageMultiple": "{count} 개의 파일을 삭제하시겠습니까?",
|
||||
"deleteMessageSingle": "파일 혹은 디렉토리를 삭제하시겠습니까?",
|
||||
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
|
||||
"deleteUser": "Are you sure you want to delete this user?",
|
||||
"deleteTitle": "파일 삭제",
|
||||
"displayName": "표시 이름:",
|
||||
"displayName": "게시 이름:",
|
||||
"download": "파일 다운로드",
|
||||
"downloadMessage": "다운로드할 형식을 선택하세요.",
|
||||
"error": "문제가 발생했습니다",
|
||||
"downloadMessage": "다운로드 포맷 설정.",
|
||||
"error": "에러 발생!",
|
||||
"fileInfo": "파일 정보",
|
||||
"filesSelected": "{count}개의 파일 선택됨.",
|
||||
"lastModified": "마지막 수정일",
|
||||
"filesSelected": "{count} 개의 파일이 선택되었습니다.",
|
||||
"lastModified": "최종 수정",
|
||||
"move": "이동",
|
||||
"moveMessage": "파일/폴더의 새 위치를 선택하세요:",
|
||||
"newArchetype": "아키타입을 기반으로 새 게시물을 만듭니다. 파일은 content 폴더에 생성됩니다.",
|
||||
"newDir": "새 디렉터리",
|
||||
"newDirMessage": "새 디렉터리 이름을 지정하세요.",
|
||||
"moveMessage": "이동할 화일 또는 디렉토리를 선택하세요:",
|
||||
"newArchetype": "원형을 유지하는 포스트를 생성합니다. 파일은 컨텐트 폴더에 생성됩니다.",
|
||||
"newDir": "새 디렉토리",
|
||||
"newDirMessage": "새 디렉토리 이름을 입력해주세요.",
|
||||
"newFile": "새 파일",
|
||||
"newFileMessage": "새 파일 이름을 지정하세요.",
|
||||
"numberDirs": "디렉터리 수",
|
||||
"newFileMessage": "새 파일 이름을 입력해주세요.",
|
||||
"numberDirs": "디렉토리 수",
|
||||
"numberFiles": "파일 수",
|
||||
"rename": "이름 바꾸기",
|
||||
"renameMessage": "새 이름을 입력하세요:",
|
||||
"replace": "바꾸기",
|
||||
"replaceMessage": "업로드하려는 파일 중 이름이 충돌하는 파일이 있습니다. 이 파일을 건너뛰고 업로드를 계속하거나 기존 파일을 바꾸시겠습니까?\n",
|
||||
"schedule": "예약",
|
||||
"scheduleMessage": "이 게시물의 게시를 예약할 날짜와 시간을 선택하세요.",
|
||||
"show": "표시",
|
||||
"rename": "이름 변경",
|
||||
"renameMessage": "새로운 이름을 입력하세요.",
|
||||
"replace": "대체하기",
|
||||
"replaceMessage": "동일한 파일 이름이 존재합니다. 현재 파일을 덮어쓸까요?\n",
|
||||
"schedule": "일정",
|
||||
"scheduleMessage": "이 글을 공개할 시간을 알려주세요.",
|
||||
"show": "보기",
|
||||
"size": "크기",
|
||||
"upload": "업로드",
|
||||
"uploadFiles": "{files}개의 파일 업로드 중...",
|
||||
"uploadMessage": "업로드할 옵션을 선택하세요.",
|
||||
"optionalPassword": "선택적 비밀번호",
|
||||
"resolution": "해상도",
|
||||
"discardEditorChanges": "변경 사항을 취소하시겠습니까?"
|
||||
"uploadFiles": "Uploading {files} files...",
|
||||
"uploadMessage": "업로드 옵션을 선택하세요.",
|
||||
"optionalPassword": "Optional password",
|
||||
"resolution": "Resolution",
|
||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||
},
|
||||
"search": {
|
||||
"images": "이미지",
|
||||
"music": "음악",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Enter 키를 눌러 검색...",
|
||||
"pressToSearch": "검색하려면 엔터를 입력하세요",
|
||||
"search": "검색...",
|
||||
"typeToSearch": "검색어 입력...",
|
||||
"types": "유형",
|
||||
"types": "Types",
|
||||
"video": "비디오"
|
||||
},
|
||||
"settings": {
|
||||
"admin": "관리자",
|
||||
"administrator": "관리자",
|
||||
"allowCommands": "명령 실행 허용",
|
||||
"allowEdit": "파일 또는 디렉터리 편집, 이름 바꾸기, 삭제 허용",
|
||||
"allowNew": "새 파일 및 디렉터리 생성 허용",
|
||||
"allowPublish": "새 게시물 및 페이지 게시 허용",
|
||||
"allowCommands": "명령 실행",
|
||||
"allowEdit": "파일/디렉토리의 수정/변경/삭제 허용",
|
||||
"allowNew": "파일/디렉토리 생성 허용",
|
||||
"allowPublish": "새 포스트/페이지 생성 허용",
|
||||
"allowSignup": "사용자 가입 허용",
|
||||
"avoidChanges": "(변경하지 않으려면 비워두세요)",
|
||||
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
||||
"branding": "브랜딩",
|
||||
"brandingDirectoryPath": "브랜딩 디렉터리 경로",
|
||||
"brandingHelp": "File Browser 인스턴스의 이름 변경, 로고 교체, 사용자 정의 스타일 추가, GitHub 외부 링크 비활성화를 통해 모양과 느낌을 사용자 지정할 수 있습니다.\n사용자 정의 브랜딩에 대한 자세한 내용은 {0}을(를) 확인하세요.",
|
||||
"brandingDirectoryPath": "브랜드 디렉토리 경로",
|
||||
"brandingHelp": "File Browser 인스턴스는 이름, 로고, 스타일 등을 변경할 수 있습니다. 자세한 사항은 여기{0}에서 확인하세요.",
|
||||
"changePassword": "비밀번호 변경",
|
||||
"commandRunner": "명령어 실행기",
|
||||
"commandRunnerHelp": "여기서 지정된 이벤트에서 실행될 명령어를 설정할 수 있습니다. 한 줄에 하나씩 작성해야 합니다. 환경 변수 {0} 및 {1}을(를) 사용할 수 있으며, {0}은(는) {1}에 상대적입니다. 이 기능과 사용 가능한 환경 변수에 대한 자세한 내용은 {2}을(를) 읽어보세요.",
|
||||
"commandsUpdated": "명령어가 업데이트되었습니다!",
|
||||
"createUserDir": "새 사용자 추가 시 사용자 홈 디렉터리 자동 생성",
|
||||
"tusUploads": "청크 업로드",
|
||||
"tusUploadsHelp": "File Browser는 청크 파일 업로드를 지원하여 불안정한 네트워크에서도 효율적이고 안정적이며 재개 가능하고 분할된 파일 업로드를 가능하게 합니다.",
|
||||
"tusUploadsChunkSize": "요청의 최대 크기를 나타냅니다 (더 작은 업로드에는 직접 업로드가 사용됩니다). 바이트 크기를 나타내는 일반 정수 또는 10MB, 1GB 등과 같은 문자열을 입력할 수 있습니다.",
|
||||
"tusUploadsRetryCount": "청크 업로드 실패 시 재시도 횟수.",
|
||||
"userHomeBasePath": "사용자 홈 디렉터리의 기본 경로",
|
||||
"userScopeGenerationPlaceholder": "범위가 자동으로 생성됩니다",
|
||||
"createUserHomeDirectory": "사용자 홈 디렉터리 생성",
|
||||
"customStylesheet": "사용자 정의 스타일시트",
|
||||
"defaultUserDescription": "새 사용자의 기본 설정입니다.",
|
||||
"disableExternalLinks": "외부 링크 비활성화 (문서 제외)",
|
||||
"disableUsedDiskPercentage": "사용된 디스크 비율 그래프 비활성화",
|
||||
"commandRunner": "명령 실행기",
|
||||
"commandRunnerHelp": "이벤트에 해당하는 명령을 설정하세요. 줄당 1개의 명령을 적으세요. 환경 변수{0} 와 {1}이 사용가능하며, {0} 은 {1}에 상대 경로 입니다. 자세한 사항은 {2} 를 참조하세요.",
|
||||
"commandsUpdated": "명령 수정됨!",
|
||||
"createUserDir": "Auto create user home dir while adding new user",
|
||||
"minimumPasswordLength": "Minimum password length",
|
||||
"tusUploads": "Chunked Uploads",
|
||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
||||
"userHomeBasePath": "Base path for user home directories",
|
||||
"userScopeGenerationPlaceholder": "The scope will be auto generated",
|
||||
"createUserHomeDirectory": "Create user home directory",
|
||||
"customStylesheet": "커스텀 스타일시트",
|
||||
"defaultUserDescription": "아래 사항은 신규 사용자들에 대한 기본 설정입니다.",
|
||||
"disableExternalLinks": "외부 링크 감추기",
|
||||
"disableUsedDiskPercentage": "Disable used disk percentage graph",
|
||||
"documentation": "문서",
|
||||
"examples": "예시",
|
||||
"executeOnShell": "셸에서 실행",
|
||||
"executeOnShellDescription": "기본적으로 File Browser는 바이너리를 직접 호출하여 명령을 실행합니다. 대신 셸(예: Bash 또는 PowerShell)에서 실행하려면 필요한 인수 및 플래그와 함께 여기에 정의할 수 있습니다. 설정된 경우 실행하는 명령이 인수로 추가됩니다. 이는 사용자 명령과 이벤트 후크 모두에 적용됩니다.",
|
||||
"globalRules": "이것은 전역 허용 및 차단 규칙 세트입니다. 모든 사용자에게 적용됩니다. 각 사용자 설정에서 특정 규칙을 정의하여 이 규칙을 재정의할 수 있습니다.",
|
||||
"examples": "예",
|
||||
"executeOnShell": "쉘에서 실행",
|
||||
"executeOnShellDescription": "기본적으로 File Browser 는 바이너리를 명령어로 호출하여 실행합니다. 쉘을 통해 실행하기를 원한다면, Bash 또는 PowerShell 에 필요한 인수와 플래그를 설정하세요. 사용자 명령어와 이벤트 훅에 모두 적용됩니다.",
|
||||
"globalRules": "규칙에 대한 전역설정으로 모든 사용자에게 적용됩니다. 지정된 규칙은 사용자 설정을 덮어쓰기 합니다.",
|
||||
"globalSettings": "전역 설정",
|
||||
"hideDotfiles": "숨김 파일 숨기기",
|
||||
"insertPath": "경로 삽입",
|
||||
"insertRegex": "정규식 표현 삽입",
|
||||
"hideDotfiles": "숨김파일(dotfile)을 표시하지 않습니다.",
|
||||
"insertPath": "경로 입력",
|
||||
"insertRegex": "정규식 입력",
|
||||
"instanceName": "인스턴스 이름",
|
||||
"language": "언어",
|
||||
"lockPassword": "사용자가 비밀번호를 변경하지 못하도록 잠금",
|
||||
"newPassword": "새 비밀번호",
|
||||
"newPasswordConfirm": "새 비밀번호 확인",
|
||||
"newUser": "새 사용자",
|
||||
"lockPassword": "사용자에 의한 비밀번호 변경을 허용하지 않음",
|
||||
"newPassword": "새로운 비밀번호",
|
||||
"newPasswordConfirm": "새로운 비밀번호 확인",
|
||||
"newUser": "새로운 사용자",
|
||||
"password": "비밀번호",
|
||||
"passwordUpdated": "비밀번호가 업데이트되었습니다!",
|
||||
"passwordUpdated": "비밀번호 수정 완료!",
|
||||
"path": "경로",
|
||||
"perm": {
|
||||
"create": "파일 및 디렉터리 생성",
|
||||
"delete": "파일 및 디렉터리 삭제",
|
||||
"create": "파일이나 디렉토리 생성하기",
|
||||
"delete": "화일이나 디렉토리 삭제하기",
|
||||
"download": "다운로드",
|
||||
"execute": "명령 실행",
|
||||
"modify": "파일 편집",
|
||||
"rename": "파일 및 디렉터리 이름 바꾸기 또는 이동",
|
||||
"share": "파일 공유"
|
||||
"rename": "파일 이름 변경 또는 디렉토리 이동",
|
||||
"share": "파일 공유하기"
|
||||
},
|
||||
"permissions": "권한",
|
||||
"permissionsHelp": "사용자를 관리자로 설정하거나 개별적으로 권한을 선택할 수 있습니다. \"관리자\"를 선택하면 다른 모든 옵션이 자동으로 선택됩니다. 사용자 관리는 관리자의 권한으로 유지됩니다.\n",
|
||||
"permissionsHelp": "사용자를 관리자로 만들거나 권한을 부여할 수 있습니다. 관리자를 선택하면, 모든 옵션이 자동으로 선택됩니다. 사용자 관리는 현재 관리자만 할 수 있습니다.\n",
|
||||
"profileSettings": "프로필 설정",
|
||||
"ruleExample1": "모든 폴더에서 모든 숨김 파일(예: .git, .gitignore)에 대한 액세스를 방지합니다.\n",
|
||||
"ruleExample2": "범위의 루트에 있는 Caddyfile이라는 파일에 대한 액세스를 차단합니다.",
|
||||
"rules": "규칙",
|
||||
"rulesHelp": "여기서 이 특정 사용자에 대한 허용 및 차단 규칙 세트를 정의할 수 있습니다. 차단된 파일은 목록에 표시되지 않으며 사용자가 액세스할 수 없습니다. 사용자의 범위에 상대적인 정규식 및 경로를 지원합니다.\n",
|
||||
"ruleExample1": "점(.)으로 시작하는 모든 파일의 접근을 방지합니다.(예 .git, .gitignore)\n",
|
||||
"ruleExample2": "Caddyfile파일의 접근을 방지합니다.",
|
||||
"rules": "룰",
|
||||
"rulesHelp": "사용자별로 규칙을 허용/방지를 지정할 수 있습니다. 방지된 파일은 보이지 않고 사용자들은 접근할 수 없습니다. 사용자의 접근 허용 범위와 관련해 정규표현식(regex)과 경로를 지원합니다.\n",
|
||||
"scope": "범위",
|
||||
"setDateFormat": "정확한 날짜 형식 설정",
|
||||
"settingsUpdated": "설정이 업데이트되었습니다!",
|
||||
"setDateFormat": "Set exact date format",
|
||||
"settingsUpdated": "설정 수정됨!",
|
||||
"shareDuration": "공유 기간",
|
||||
"shareManagement": "공유 관리",
|
||||
"shareDeleted": "공유가 삭제되었습니다!",
|
||||
"singleClick": "파일 및 디렉터리를 열 때 한 번 클릭 사용",
|
||||
"shareManagement": "공유 내역 관리",
|
||||
"shareDeleted": "Share deleted!",
|
||||
"singleClick": "한번 클릭으로 파일과 폴더를 열도록 합니다.",
|
||||
"themes": {
|
||||
"default": "시스템 기본값",
|
||||
"dark": "어둡게",
|
||||
"light": "밝게",
|
||||
"default": "System default",
|
||||
"dark": "다크테마",
|
||||
"light": "라이트테마",
|
||||
"title": "테마"
|
||||
},
|
||||
"user": "사용자",
|
||||
"userCommands": "명령어",
|
||||
"userCommandsHelp": "이 사용자가 사용할 수 있는 명령어 목록 (공백으로 구분). 예:\n",
|
||||
"userCreated": "사용자가 생성되었습니다!",
|
||||
"userCommandsHelp": "사용에게 허용할 명령어를 공백으로 구분하여 입력하세요. 예:\n",
|
||||
"userCreated": "사용자 생성됨!",
|
||||
"userDefaults": "사용자 기본 설정",
|
||||
"userDeleted": "사용자가 삭제되었습니다!",
|
||||
"userDeleted": "사용자 삭제됨!",
|
||||
"userManagement": "사용자 관리",
|
||||
"userUpdated": "사용자가 업데이트되었습니다!",
|
||||
"userUpdated": "사용자 수정됨!",
|
||||
"username": "사용자 이름",
|
||||
"users": "사용자"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "도움말",
|
||||
"hugoNew": "Hugo 새로 만들기",
|
||||
"hugoNew": "Hugo New",
|
||||
"login": "로그인",
|
||||
"logout": "로그아웃",
|
||||
"myFiles": "내 파일",
|
||||
"newFile": "새 파일",
|
||||
"newFolder": "새 폴더",
|
||||
"newFile": "새로운 파일",
|
||||
"newFolder": "새로운 폴더",
|
||||
"preview": "미리보기",
|
||||
"settings": "설정",
|
||||
"signup": "가입",
|
||||
"signup": "가입하기",
|
||||
"siteSettings": "사이트 설정"
|
||||
},
|
||||
"success": {
|
||||
@@ -257,9 +258,9 @@
|
||||
},
|
||||
"time": {
|
||||
"days": "일",
|
||||
"hours": "시간",
|
||||
"hours": "시",
|
||||
"minutes": "분",
|
||||
"seconds": "초",
|
||||
"unit": "시간 단위"
|
||||
"unit": "Time Unit"
|
||||
}
|
||||
}
|
||||
|
||||